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

...