HL7 to HL7

The zsegment module

We’re going to create a new shared module called zsegment. From the project files pane of the Translator click on Add…

This dialog will come up. Enter zsegment in the Create a new module textbox:

Then click on the Create button.

The new module will be appear in the Project Files pane:

Click on zsegment and then copy-paste the following code into the module:

zsegment = {}

local function trace(a,b,c,d) return end

function zsegment.extractZSegment(Msg)
   local Segments = Msg:split('\r')
   local ZSegments = {}
   for i = 1, #Segments do
      if Segments[i]:sub(1,1) == 'Z' then
         trace('Found '..Segments[i])
         ZSegments[i] = Segments[i]
      end
   end
   return ZSegments
end

function zsegment.copyZSegments(Orig, Copy)
   -- enforce correct return character
   Copy = Copy:gsub('\r?\n','\r')
   Orig = Orig:gsub('\r?\n','\r')
   -- and strip return(s) from end of string
   -- '$' anchors matching to end of string
   Copy = Copy:gsub('\r+$','') 
   local ZSegments = zsegment.extractZSegment(Orig)
   local SegmentList = Copy:split('\r')
   trace(SegmentList)
   for K,V in pairs(ZSegments) do
      table.insert(SegmentList, V)
   end
   trace(SegmentList)
   local Result = ''
   for i = 1, #SegmentList do
      Result = Result..SegmentList[i]..'\r'
   end
   return Result
end

The beauty of the Translator is that:

  • One can implement sophisticated logic
  • Other people in your team can easily see what you have done

Do take the time to step through and look through the annotations of the zsegment module to understand it. It’s simple. This is how it works:

  • The extractZSegment()function:
    • Slices the segments apart by splitting on the carriage return ‘\r’ character.
    • Creates an array of those segments which start with Z, i.e., Z-segments.
  • The main copyZSegments() routine takes the HL7 message without Z-segments and does the folowing:
    • Ensures the correct return character is used.
    • Removes any returns from the end of the message.
    • Slices it into segments also by splitting on the \r character.
    • Then appends the Z-segments.

The Translator makes writing reusable modules like this easy. It’s one of the many things that makes Iguana so fun and productive.

Now go back to the main module and insert two require statements at the top:

require 'split'
require 'zsegment'

Then add these lines to the main module:

Notice how we still have our error. If we look at the two extra trace lines we added earlier in the ‘Escaped Text’ mode we immediately see the culprit: there is a trailing carriage return in the copied data.

The new version of the message has a trailing \r. We can trim that \r using a helpful string extension we can write:

function string.StripLastReturns(S)
   -- strip return(s) "\r" & "\n" from the end of a string
   local i = #S
   while S:byte(i) == 10 or S:byte(i) == 13 do
      i = i - 1
   end
   return S:sub(1,i)
end

That means we can apply :StripLastReturns() to any string and it will strip one or more \r or \n off the end.

This function uses

  • # operator – returns the number of characters in the string
  • byte(N) – returns the byte at the Nth place.
  • sub(Start, End) – returns the substring at the indexes given.

These are part of the standard Lua string library.

So we make things work by altering the code to the following:

Notice how I used Data:StripLastReturns() as well to ensure there are no unwanted returns on the Data or the Copy.

You can really start to appreciate the visibility and power the Translator environment gives.

If you need to ensure your code is up-to-date then copy-paste this in:

require 'split'
require 'zsegment'

local function trace(a,b,c,d) return end

function main(Data)
   local Orig = hl7.parse  {vmd = 'transform.vmd', data = Data}
   local Out  = hl7.message{vmd = 'transform.vmd', name = Orig:nodeName()}

   Out:mapTree(Orig)
   local Copy = zsegment.copyZSegments(Data, Out:S())
   CheckTransform(Data, Copy:StripLastReturns())

   Out.MSH[3][1] = 'Acme'
   Out.MSH[4][1] = 'Lab'
   trace(Out)
   local DataOut = Out:S()
   DataOut = zsegment.copyZSegments(Data, DataOut)
   DataOut = DataOut:StripLastReturns()
   trace(DataOut)
   queue.push{data = DataOut}
end

function CheckTransform(Orig, Copy)
   if Orig ~= Copy then
      trace(Orig)
      trace(Copy)
      error('Copy of HL7 message does not match the original')
   end   
end

function string.StripLastReturns(S)
   -- strip return(s) "\r" & "\n" from the end of a string
   local i = #S
   while S:byte(i) == 10 or S:byte(i) == 13 do
      i = i - 1
   end
   return S:sub(1,i)
end

We still have one major difference to overcome with how we are handling ORM messages, but we will use that as an opportunity to show how Iguana can handle runtime errors.