Using node.setNodeValue() to manipulate node values directly

The node.setNodeValue() method was added to the node API in Iguana 5.6.1. It allows you to directly set the value of a node without needing to reference it from it’s parent node.

So what does this mean, and when do we need to use it? Well that is actually two questions, so lets answer them separately.

What does this mean and how does it work?

Let’s say we want to set the Time value in an HL7 ADT message:

Screen Shot 2014-05-20 at 17.58.35

Before 5.6.1 the only way to set the value for a node was to reference it from it’s parent node MSH[7]:

Screen Shot 2014-05-20 at 18.37.35

From 5.6.1 on you can also use the node.setNodeValue() method:

Screen Shot 2014-05-20 at 18.52.17

This just looks like a more complicated way to do same thing… so where is the added value?

Well, the advantage is that it still works when you cannot access the parent node:

Screen Shot 2014-05-20 at 19.14.47

So this was an an artificial example just to demonstrate how it works, next we will explain when it is actually needed.

When do we need to use setNodeValue, when is it useful?

One of our users identified that it was not possible to directly change the value of a node. He came across this when he created a node.clearNode() function, and he was not able to clear the value for a leaf node passed to the function. See the original discussion on LinkedIn.

The requirements for node:clearNode() are very simple:

  1. For a non-leaf node remove all the child nodes
  2. For a leaf node remove the data (by setting it to an empty string)

The issue is with second requirement, as the only way to set a leaf node was from its parent. So lets demonstrate the problem and the solution.

Issue: Cannot clear a leaf node

This is the original function:

Screen Shot 2014-05-22 at 10.47.12

As you can see it works when called with a non-leaf node:

Screen Shot 2014-05-22 at 10.20.23

However it fails when called using a leaf node:

Screen Shot 2014-05-22 at 10.57.14

Solution: Clear leaf nodes using setNodeValue()

The solution is to add a condition to use setNodeValue() to clear leaf nodes.

Here is the updated function:

Screen Shot 2014-05-22 at 10.47.46

As you can see it now works correctly with leaf nodes:

Screen Shot 2014-05-22 at 10.50.56

Code for you to try

function main(Data)
   
   local Msg = hl7.message{vmd='demo.vmd', name='ADT'}
   
   -- assign the parent node to a variable for demonstration purposes
   local ParentNode = Msg.MSH[7]
   trace(ParentNode)
   
   -- set the Time field by referencing it from the parent node
   ParentNode[1]   = os.date()  -- default auto-completion syntax
   ParentNode.Time = os.date()  -- alternative syntax same result
   trace(ParentNode)
   
   -- NOTE: normally we would use this syntax - "ParentNode" was just for emphasis!
   Msg.MSH[7][1] = os.date()
   
   -- set the Time field using setNodeValue()
   ParentNode[1]:setNodeValue(os.date())   -- default syntax
   ParentNode.Time:setNodeValue(os.date()) -- alternative syntax
   trace(ParentNode)
      
   -- NOTE: as before we would use this syntax - rather than "ParentNode"
   Msg.MSH[7][1]:setNodeValue(os.date())

   -- assign the Time node (reference) to a variable
   local Time = Msg.MSH[7][1]
   -- set the Time node (reference) using setNodeValue()
   Time:setNodeValue(os.date())
   trace(Time)   

   -- assigning the date directly just overwrites the node (reference)
   Time = os.date()
   trace(Time)

   -- NOTE: The date we previously assigned to the Time node is unaffected
   trace(Msg.MSH[7][1])
   
   -- clearing a non-leaf node works correctly
   trace(Msg.MSH)
   --Msg.MSH:clearNode() -- commented out so code below works
   trace(Msg.MSH)
   
   -- assign the Time leaf node to a variable
   local Time = Msg.MSH[7][1]
  
   -- attempting to clear the leaf node fails
   -- clearing the leaf node now succeeds - using clearNode_new()
   trace(Msg.MSH[7])
   Time:clearNode()
   trace(Msg.MSH[7])
   
end

-- original function - does not work on leaf nodes
function node:clearNode()
   for nChild = self:childCount(), 1 , -1 do
      self:remove(nChild)
   end
end

-- updated function works on leaf nodes
function node:clearNode_new()
   if self:isLeaf() then
      self:setNodeValue('')
   else
      for nChild = self:childCount(), 1 , -1 do
         self:remove(nChild)
      end
   end
end

-- you could also create a (recursive) function to empty all the nodes
-- NOTE: this is a lot slower than clearNode()
function node:emptyNode()
   if self:isLeaf() then
      self:setNodeValue('')
   else
      for nChild = self:childCount(), 1 , -1 do
         self[nChild]:emptyNode()
      end
   end
end