edifact.lua

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)