Working with XML

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