Working with XML

Verified
Added by iNTERFACEWARE

Shows the correct way to add and update TEXT (and other) fields, as well as what not to do

Source Code
myXML = [[
<myXml>
   <elem1>
      <sub1></sub1>
   </elem1>
   <elem2></elem2>
   <elem3></elem3>
   <elem4></elem4>
   <elem5></elem5>
   <elem6>
      <sub1>valuable data</sub1>
      <sub2>more valuable data</sub2>
   </elem6>
</myXml>
]]

function main()
   local X = xml.parse(myXML)
     
   --------------------------------------------
   -- Use append to add a TEXT field
   --------------------------------------------

   -- RECOMMENDED 
   X.myXml.elem1:append(xml.TEXT, 'encodes <&> correctly')
   
   -- or can create an empty TEXT field and assign a value later
   X.myXml.elem2:append(xml.TEXT, '')
   X.myXml.elem2[1] = 'encodes <&> correctly'
   
   -- NOT RECOMMENDED 
   --  - use setInner() to set the TEXT value
   --  - works because setInner() does escaping when 
   --    applied to an XML TEXT field
   -- ISSUE: you can accidentally apply it to an ELEMENT
   --        and then it will "work sometimes" see below:
   --        "Incorrect use of setInner() to add a TEXT field" 
   X.myXml.elem3:append(xml.TEXT, '')
   -- apply setInner() to the TEXT field escapes correctly
   X.myXml.elem3[1]:setInner('encodes <&> correctly')  

   -------------------------------------------------------------
   -- Incorrect use of setInner() to add a TEXT field
   -------------------------------------------------------------
   -- NOTE: this is a easy mistake to make (see point XXX below)
   -------------------------------------------------------------

   -- THIS DOES NOT WORK!
   -- FAILS BECAUSE: setInner() parses XML when loading an ELEMENT
   -- ISSUE: fails when you use XML special characters <&>

   -- plain text parses successfully - SEEMS to work
   X.myXml.elem4:setInner('DECEPTIVE: works with no special characters')

   -- until you add special characters - then the XML parse fails
   -- uncommenting the line below will produce an ERROR
   --X.myXml.elem4:setInner('fails when you add <&>') 

   ---------------------------------------
   -- How to update an existing text field
   ---------------------------------------
   
   -- it is simple find and update an existing TEXT field
   local x = X.myXml.elem1
   for i=1,#x do
      if x[i]:nodeType() == 'text' then
         x[i] = x[i]..' - and I changed the text'
      end
   end
  
   ------------------------------------------------------------
   -- Correct use of setInner() to set an ELEMENT field
   ------------------------------------------------------------
   -- NOTE: this is the **only** recommended use of setInnner()
   ------------------------------------------------------------

   -- add sub elements to elem6
   X.myXml.elem5:setInner('<sub1></sub1><sub2></sub2>')
   
   -- WARNING: setInner() will overwrite all data in the ELEMENT
   -- be careful in case this is NOT what you want...
   X.myXml.elem6:setInner('OOPs overwrote valuable data...')
   trace(X:S())
   
   --------------------------------------------------------------
   -- Using append() or setInner() to create multiple TEXT fields
   --------------------------------------------------------------
   -- NOTE: both methods are valid, using setInner() is a bit 
   --       more concise
   --------------------------------------------------------------
   -- NOTE: creating 3 ELEMENTS each with a TEXT sub-field
   --       saves 5 lines of codes, the more ELEMENTS
   --       the more lines of code you save
   --------------------------------------------------------------
    
   -- ###### first we do "long-hand" using append() ######
   
   -- first we add a new elem7 with append
   X.myXml:append(xml.ELEMENT, 'elem7')
   local x = X.myXml.elem7
   -- then we create three ELEMENT sub-fields with append
   x:append(xml.ELEMENT, 'sub1')
   x:append(xml.ELEMENT, 'sub2')
   x:append(xml.ELEMENT, 'sub3') 
   -- then we create a TEXT an empty TEXT field in each ELEMENT
   x.sub1:append(xml.TEXT, '')
   x.sub2:append(xml.TEXT, '')
   x.sub3:append(xml.TEXT, '')   
   trace(x)
   -- then we can assign values later as we need to
   for i=1,#x do
      x[i][1] = 'hello '..i
   end   
   trace(x)
   
   -- ###### now the shorter way using setInner () ######
   
    -- first we add a new elem8 with append
   X.myXml:append(xml.ELEMENT, 'elem8')
   x = X.myXml.elem8
   -- then we create three ELEMENT sub-fields with append
   -- the placeholder "#" symbols create the TEXT sub fields
   x:setInner([[<sub1>#</sub1><sub2>#</sub2><sub3>#</sub3>]])
   trace(x)
   -- then we can assign values later as we need to
   for i=1,#x do
      x[i][1] = 'hello '..i
   end   
   -- or even make the text fields empty to directly mimic
   -- the behaviour of the "append() code" above
   for i=1,#x do
      x[i][1] = ''
   end      
   trace(x)
   
   -- NOTE: there is no way to directly create empty TEXT fields using setInner()
   -- removing the "#" symbols = empty ELEMENTS (no TEXT sub fields)
   x:setInner([[<sub1></sub1><sub2></sub2><sub3></sub3>]])
   trace(x)
   -- if we attempt to assign values directly now it will fail
   
   -- WARNING! this is when/where we might tempted to wrongly use setInner()
   -- to create our TEXT subfields DO NOT DO IT!!! (see point XXX above)
   for i=1,#x do
      x[i]:setInner('UNSAFE: will fail with XML special characters')
      --x[i]:setInner('this will fail <&> try it!')
   end   
  
   ----------------------------------------------
   -- Adding other fields is very similar to TEXT
   ----------------------------------------------

   -- add a CDATA
   X.myXml.elem1:append(xml.CDATA, 'CDATA does not encode <&>')
   
   -- add an ELEMENT
   X.myXml.elem1:append(xml.ELEMENT, 'myElement')

   -- add an ATTRIBUTE
   X.myXml.elem1.myElement:append(xml.ATTRIBUTE, 'myAttribute')
   X.myXml.elem1.myElement['myAttribute'] = 'hello'   
   trace(X:S())
end
Description
Shows the correct way to add and update TEXT (and other) fields, as well as what not to do
Usage Details

Demonstrates the correct way to add and update TEXT (and other) fields. It also shows some issues that can occur if you try other methods.

How to use the snippet:

  • Paste the code into your script
  • Inspect the annotations and read the comment to see how it works