edifact.lua

Verified
Added by iNTERFACEWARE

Convert EDI messages to HL7 format so you can process them like an HL7 message (and convert them back to EDI afterwards)

Source Code
 ------------------------------------------------------------------
--                                                              --
--  Utilities for EDIFACT Messages                              --
--                                                              --
--  Copyright (c) 2012 iNTERFACEWARE Inc.  All Rights Reserved. --
--                                                              --
------------------------------------------------------------------

local edifact = {}

-- Define the header patterns in one place.
local DEL = '([^%w ])'  -- A delimiter.
local UNA = '^UNA'..DEL:rep(3)..'(%W)(%W)'..DEL
local MSH = '^MSH'..DEL:rep(5)..'%1'  -- HL7

-- Some functions take a EDIFACT header as an
-- argument; this is used when one is not given.
local DefaultHeader = "UNA:+.?*'"

-- Convert EDIFACT to HL7 with standard EDIFACT
-- delimiters, HL7 escapes but no MSH header.
--
function edifact.toHl7(Data)
   -- These are the delimiters we want.  These will
   -- replace the input delimiters in the same order.
   local Output = {  '^',     '|',   '.',  '',     '~',     '\r'   }
   local Escape = {'\\S\\', '\\F\\', '.', '\\E\\', '\\R\\', '\\X0D\\'}
   local Header = 'MSH|^~\\&|||||||EDIFACT\r'
   
   -- These are the delimiters in the message.
   local Input = { select(3, Data:find(UNA)) }
   if #Input == 0 then  -- Panic!
      error('No UNA segment found.', 2)
   end
   
   -- We start with a simple map from the input
   -- delimiters to what we want for output.
   local BasicMap = {}
   for I,Get in ipairs(Input) do
      local Want = Output[I]
      Escape[Want] = Escape[I]
      BasicMap[Get] = Want
   end
   
   -- We expand this map to include checks for
   -- output delimiters in the input, and create
   -- a pattern to match all special characters.
   local Map, Pattern = { ['&']='\\T\\' }, '[%&'
   for Get,Want in pairs(BasicMap) do
      Map[Get] = Want
      Pattern = Pattern..'%'..Get
      -- Any output delimiters found in the
      -- input must be escaped in the output.
      if not BasicMap[Want] then
         Map[Want] = Escape[Want]
         Pattern = Pattern..'%'..Want
      end
   end
   
   -- If the input contains a release character,
   -- we expand the map and pattern further to
   -- handle escaped delimiters in the input.
   local Release = Input[4]
   if Release ~= ' ' then
      -- If a release character occurs before
      -- a non-delimiter, we leave it as-is.
      Map[Release] = Escape[Release] or Release
      -- Otherwise, we replace the pair with the
      -- second delimiter, escaped if necessary. 
      Pattern = '%'..Release..'?'..Pattern
      BasicMap, Map = Map, {}
      for Get,Want in pairs(BasicMap) do
         Map[Get] = Want
         Map[Release..Get] = Escape[Get] or Get
      end
   end
   
   Map[' '] = nil
   Pattern = Pattern:gsub('%% ','') .. ']'
   
   local Out = Data:sub(10):gsub(Pattern, Map)
   return Header..Out
end

-- Convert HL7 formatted EDIFACT to real EDIFACT
-- with the removal of HL7 escapes and replace
-- the MSH header with an EDIFACT UNA header.
--
function edifact.fromHl7(Data, NewHeader)
   local Dec, Seg = '.', '\r'
   local Ok,_,Fld,Com,Rep,Esc,Sub = Data:find(MSH)
   assert(Ok, 'Invalid/missing MSH segment.')
   Data = Data:gsub('MSH.-\r','')
   local Header = table.concat{
      'UNA',Com,Fld,Dec,Esc,Rep,Seg}
   local Escape = {
      ['\\'] = '\\',  -- Special in HL7 only.
      [Com] = Esc..Com, [Fld] = Esc..Fld,
      [Esc] = Esc..Esc, [Seg] = Esc..Seg,
      [Rep] = (Rep ~= ' ') and Esc..Rep
   }
   -- First we escape every occurance of the
   -- release character in the input.
   local Out = Data
   if #Esc > 0 then  -- Assuming we need to.
      Data:gsub('%'..Esc, Escape[Esc])
   end
   -- Then we replace all HL7 escapes with
   -- EDIFACT escapes or regular characters.
   Out = Out:gsub('(\\%u%x?%x?\\)',
      setmetatable({
            ['\\S\\'] = Escape[Com],
            ['\\F\\'] = Escape[Fld],
            ['\\E\\'] = Escape['\\'],
            ['\\R\\'] = Escape[Rep],
            ['\\T\\'] = Sub,  -- Not special.
            -- ['\\X27\\'] = Escape[Seg],
         }, {
            -- This handles other \X..\ escapes
            -- which could be present in HL7.
            __index = function(self, s)
               local Out = s:gsub('\\X(%x%x)\\',
                  function(hex)
                     local i = tonumber(hex,16)
                     return string.char(i)
                  end)
               Out = Escape[Out] or Out
               self[s] = Out  -- Memoize.
               return Out
            end
         }))
   Out = edifact.clean(Header..Out, NewHeader)
   return Out
end

-- Replace the delimiters of an EDIFACT message
-- with those in the header provided or use the
-- default EDIFACT delimiters.
--
function edifact.clean(Data, Header)
   Header = Header or DefaultHeader
   local Input  = { select(3, Data  :find(UNA)) }
   local Output = { select(3, Header:find(UNA..'$')) }
   local InpRelease, OutRelease = Input[4], Output[4]
   if not InpRelease then error('Bad UNA header in message.', 2) end
   if not OutRelease then error('Invalid output header.',     2) end
   local Escape, Map, Pattern = {}, {}, '['
   if OutRelease ~= ' ' then
      for _,Want in ipairs(Output) do
         Escape[Want] = OutRelease..Want
         Map[Want] = OutRelease..Want
         Pattern = Pattern..'%'..Want
      end
   end
   for I,Get in ipairs(Input) do
      local Want = Output[I]
      Map[Get] = Want
      Pattern = Pattern..'%'..Get
   end
   if InpRelease ~= ' ' then
      Pattern = '%'..InpRelease..'?'..Pattern
      for I,Get in ipairs(Input) do
         Map[InpRelease..Get] = Escape[Get] or Get
      end
   end
   Map[' '] = nil
   Pattern = Pattern:gsub('%% ','') .. ']'
   local Out = Data:sub(10):gsub(Pattern,Map)
   return Header..Out
end

return edifact
Description
Convert EDI messages to HL7 format so you can process them like an HL7 message (and convert them back to EDI afterwards)
Usage Details

EDIFACT and HL7 are very similar.  The main difference is how delimiters are escaped when they occur in a message. For instance, in the following EDIFACT snippet, the question-mark (?) is used to escape delimiters, or “release” them in EDIFACT parlance.

This module allows you to convert messages back an forth between EDI and HL7. This means you can convert an incoming EDI message to HL7, then process it as an HL7 message (hl7.parse{}, map fields etc), then convert it back to EDI and forward it.

How to use edifact.lua:

  • Use a Filter or To Translator component
  • Create the edifact module and paste in the code
    Note: You can also load the attached project edifact.zip
  • Add some sample HL7 messages from SampleData.txt
    Note: The project already contains the sample data
  • Load sample EDI messages if you have some (useful but not required)
  • Convert HL7 to EDI with edifact.fromHl7(), then convert it back with edifact.toHl7()
  • Play with the code and compare annotations to see how it works

Here is some sample code for main():

local edifact = require 'edifact'

function main(Data)
   local edi = edifact.fromHl7(Data) -- convert to EDI
   local hl7 = edifact.toHl7(edi)    -- convert back to HL7
   
   trace(edi)
   trace(hl7)
end