Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Info

The default xml node and xml module provide limited functionalities to either parse or create XML node. In additional to https://help.interfaceware.com/v6/xml-channel. The XMLUtils.lua module extends further to the following capabilities:

XMLUtils.lua

Expand
titleSource Code
Code Block
languagelua
function node.select(X, path, required)
   local elements = path:split('/')
   if not elements or #elements == 0 then
      return
   end
   
   local thisNode = X
   for i, v in ipairs(elements) do
      local nodeType = xml.ELEMENT
      local _, _, nodeName, predicate = v:find("^([^@]%w+)%[(.+)%]$")
      if not nodeName then
         nodeName = v
         if nodeName:sub(1, 1) == '@' then
            nodeType = xml.ATTRIBUTE
            nodeName = nodeName:sub(2)
         end
      end
      
      local nextNode
      local count = thisNode:childCount(nodeName)
      if count > 0 then
         for j = 1, count do
            local child = thisNode:child(nodeName, j)
            if child:nodeType() == nodeType then
               if predicate then
                  -- predicate = child index
                  local index = tonumber(predicate)
                  if index then
                     if index == j then
                        nextNode = child
                        break
                     end
                  end
                  
                  -- predicate = child expression
                  local parts = predicate:split("=")
                  if parts then
                     local nodeValue = child:text(parts[1])
                     local compare = parts[2]:gsub("^['](%w+)[']$", "%1")
                     if nodeValue == compare then
                        nextNode = child
                        break
                     end
                  else
                     error('invalid predicate: ' .. predicate, 2)
                  end
               else
                  nextNode = child
                  break
               end
            end
         end
      end
      trace(nextNode)
      
      -- set thisNode for next loop iteration
      thisNode = nextNode
      if not thisNode then
         if required then
            error('required path not found: ' .. v, 2)
         end
         return
      end
   end
   
   return thisNode
end

function node.number(X, path, required, default)
   if path then
      X = X:select(path, required)
   end
   
   local nodeValue
   if X then
      if X:nodeType() == xml.ATTRIBUTE then
         nodeValue = X:nodeValue()
      else
         for i = 1, #X do
            if X[i]:nodeType() == xml.TEXT then
               nodeValue = X[i]:nodeValue()
               break
            end
         end
      end
   end
   
   nodeValue = tonumber(nodeValue)
   if nodeValue then
      return nodeValue
   elseif default then
      return default
   else
      return 0
   end
end

function node.text(X, path, required, default)
   if path then
      X = X:select(path, required)
   end
   
   local nodeValue
   if X then
      if X:nodeType() == xml.ATTRIBUTE then
         nodeValue = X:nodeValue()
      else
         for i = 1, #X do
            if X[i]:nodeType() == xml.TEXT then
               nodeValue = X[i]:nodeValue()
               break
            end
         end
      end
   end
   
   if nodeValue then
      return nodeValue
   elseif default then
      return default
   else
      return ''
   end
end

function node.children(X, name, matchPath, matchValue)
   local list = {}
   for i = 1, #X do
      if X[i]:nodeType() == xml.ELEMENT then
         if X[i]:nodeName() == name then
            if matchPath then
               if X[i]:text(matchPath) == matchValue then
                  table.insert(list, X[i])
               end
            else
               table.insert(list, X[i])
            end
         end
      end
   end
   return list
end

function node.firstChild(X, name, matchPath, matchValue)
   for i = 1, #X do
      if X[i]:nodeType() == xml.ELEMENT then
         if X[i]:nodeName() == name then
            if matchPath then
               if X[i]:text(matchPath) == matchValue then
                  return X[i]
               end
            else
               return X[i]
            end
         end
      end
   end
end

function node.appendText(X, name, value)
   local element = X:append(xml.ELEMENT, name)
   element:append(xml.TEXT, value)
   return element
end

function node.removeText(X)
   for i = #X, 1, -1 do
      if X[i]:nodeType() == xml.TEXT then
         X[i] = nil
      end
   end
end

-- validate required parameters (List) are present in table (ignore arg1..argN parameters)
function checkParam(T, List)
   if type(T) ~= 'table' then
      error('invalid arguments, expected table', 3)
   end
   for k,v in pairs(List) do
      for w,x in pairs(T) do
         if w:find('arg') then
            if w == 'arg' then error('Unknown parameter "'..w..'"', 3) end
         else
            if not List[w] then error('Unknown parameter "'..w..'"', 3) end
         end
      end
   end
end

-- convert arg1..argN parameters into table array
function getArgs(P)
   local args = {}
   for k,v in pairs(P) do
      if k:find('arg')==1 then
         args[tonumber(k:sub(4))] = P[k]
      end
   end
   return args
end

-- safely retry function
function retry(P)
   checkParam(P, {func=0, retry=0, pause=0, funcname=0, errorfunc=0})
   if type(P.func) ~= 'function' then
      error('invalid func specified, expected pointer to function', 2)
   end 
   
   local RetryCount = P.retry or -1
   local Delay = P.pause or 10
   local Fname = P.funcname
   local Func = P.func
   local ErrorFunc = P.errorfunc
   local Success, ErrMsgOrReturnCode
   local Args = getArgs(P)
   
   local i = 0
   if iguana.isTest() then RetryCount = 2 end
   while true do
      local R = {pcall(Func, unpack(Args))}
      Success = R[1]
      ErrMsgOrReturnCode = R[2]
      if ErrorFunc then
         Success = ErrorFunc(unpack(R))
      end
      if Success then
         if i > 0 then
            status('green', 'Operation succeeded on retry after ' .. i .. ' attempts.')
         else
            status('green')
         end
         return unpack(R,2)
      else
         local msg = 'Error executing operation '
         if Fname then
            msg = msg .. Fname .. '() '
         end
         if i == 0 then
            msg = msg .. 'on first attempt.'
         else
            msg = msg .. 'on retry attempt ' .. i .. '.'
         end
         if RetryCount == -1 or i < RetryCount then
            msg = msg .. '\nSleeping for ' .. Delay .. ' seconds before next retry.'
            msg = msg .. '\nError: ' .. ErrMsgOrReturnCode
            status('yellow', msg)
            util.sleep(Delay * 1000)
            i = i + 1
         else
            msg = msg .. '\nMax retries reached, stopping retries.'
            status('yellow', msg)
            return false, msg
         end
      end
   end
end

function iguana.getChannelStatus(channelName)
   if not channelName then
      channelName = iguana.channelName()
   end
   
   local iguanaStatus = iguana.status()
   
   local root = xml.parse(iguanaStatus)
   for i = 1, root.IguanaStatus:childCount('Channel') do
      local channel = root.IguanaStatus:child('Channel', i)
      if channel.Name:nodeValue() == channelName then
         return channel
      end
   end
end

function iguana.throttleChannel(queueSize, channelName)
   local channelStatus = iguana.getChannelStatus(channelName)
   trace(channelStatus)
   
   if not channelStatus then
      return true, 'unable to determine destination channel status'
   end
   
   local destChannelStatus = channelStatus.Status:nodeValue()
   if not iguana.isTest() and destChannelStatus == 'off' then
      return true, 'destination channel is currently off'
   end
   
   local destQueueSize = tonumber(channelStatus.MessagesQueued:nodeValue())
   if not iguana.isTest() and destQueueSize > queueSize then
      return true, 'destination queue size exceeds ' .. queueSize .. ' (currently ' .. destQueueSize .. ')'
   end
   
   -- do not throttle channel
   return false
end

function status(color, text)
   if text then
      text = text .. '\n' .. os.date()
   end
   iguana.setChannelStatus{color = color, text = text}
end

function warning(text)
   iguana.logWarning(text)
   text = text .. '\n' .. os.date()
   iguana.setChannelStatus{color = 'yellow', text = text}
end

...

Select child node from the root XML node based on the XML path. Unlike traditional xml node drill down with nil validation, you can easily use select function to find the sub child of xml node.

REQUIRED PARAMETERS

...

  • path: xml child node path

  • required: If 'required' is true, the path must be exist otherwise throw “required path not found“ error.

...

Select child node from the root XML node based on the XML path.

REQUIRED PARAMETERS

  • XMLNode: the root of XML node

  • path: xml child node path

  • required: If 'required' is false and 'thisNode' is nil, the function returns nothing and exits.

...

Code Block
local patientXML = recordXMLchargeTransaction:selectnumber("chargeTransaction/patient", truebatchId") .. '-' .. chargeTransaction:number("syncId")

Node.text()

USAGE

Select child node from Keep the root XML node based on XML element but empty the XML pathelement Text value

REQUIRED PARAMETERS

  • XMLNode: the root of XML node

  • path: xml child node path

  • required: If 'required' is false and 'thisNode' is nil, the function returns nothing and exits.

...

  • The child node tree

SAMPLE CODE

Code Block
local patientXMLPID[3][1]  = recordXMLpatient:selecttext("chargeTransaction/patient", truemedicalRecordNumber")

Node.children()

USAGE

Select child node from the root XML node based on the XML path

...

Code Block
local patientXML = recordXML:select("chargeTransaction/patient", true)

Node.firstChild()

USAGE

Select child node from the root XML node based on the XML pathSearch for the first XML element that meets to criteria passed into the function.

REQUIRED PARAMETERS

  • XMLNodeElement Name: The name of the root of XML nodeelement.

  • path: xml child node path

  • required: If 'required' is false and 'thisNode' is nil, the function returns nothing and exitsAttribute Name: The attribute name that you want to be found. Make sure to include the “@” prefix.

  • Attribute Value: The attribute value attached to the attribute name to be found.

RETURNS

  • The child node treefirst match for the XML element.

SAMPLE CODE

Code Block
local patientXMLfirstElement = recordXMLXMLRecord:selectfirstChild("chargeTransaction/patientElement", "@AttributeName", true"AttributeValue")

Node.removeText()

USAGE

Select child node from Keep the root XML node based on XML element but empty the XML pathelement Text value

REQUIRED PARAMETERS

  • XMLNode: the root of XML node

  • path: xml child node path

  • required: If 'required' is false and 'thisNode' is nil, the function returns nothing and exits.None

RETURNS

  • The child node treeEmpty

SAMPLE CODE

Code Block
local patientXML = recordXML:select("chargeTransaction/patient", truerecordXML.element:removeText()