local function init() local obj = { root = {}, options = {noreduce = {}} } obj._stack = {obj.root} return obj end --- @module XML Tree Handler. -- Generates a lua table from an XML content string. -- It is a simplified handler which attempts -- to generate a more 'natural' table based structure which -- supports many common XML formats. -- -- The XML tree structure is mapped directly into a recursive -- table structure with node names as keys and child elements -- as either a table of values or directly as a string value -- for text. Where there is only a single child element this -- is inserted as a named key - if there are multiple -- elements these are inserted as a vector (in some cases it -- may be preferable to always insert elements as a vector -- which can be specified on a per element basis in the -- options). Attributes are inserted as a child element with -- a key of '_attr'. -- -- Only Tag/Text & CDATA elements are processed - all others -- are ignored. -- -- This format has some limitations - primarily -- -- * Mixed-Content behaves unpredictably - the relationship -- between text elements and embedded tags is lost and -- multiple levels of mixed content does not work -- * If a leaf element has both a text element and attributes -- then the text must be accessed through a vector (to -- provide a container for the attribute) -- -- In general however this format is relatively useful. -- -- It is much easier to understand by running some test -- data through 'testxml.lua -simpletree' than to read this) -- -- Options -- ======= -- options.noreduce = { = bool,.. } -- - Nodes not to reduce children vector even if only -- one child -- -- License: -- ======== -- -- This code is freely distributable under the terms of the [MIT license](LICENSE). -- --@author Paul Chakravarti (paulc@passtheaardvark.com) --@author Manoel Campos da Silva Filho local tree = init() ---Instantiates a new handler object. --Each instance can handle a single XML. --By using such a constructor, you can parse --multiple XML files in the same application. --@return the handler instance function tree:new() local obj = init() obj.__index = self setmetatable(obj, self) return obj end --- Recursively removes redundant vectors for nodes -- with single child elements function tree:reduce(node, key, parent) for k,v in pairs(node) do if type(v) == 'table' then self:reduce(v,k,node) end end if #node == 1 and not self.options.noreduce[key] and node._attr == nil then parent[key] = node[1] end end --- If an object is not an array, -- creates an empty array and insert that object as the 1st element. -- -- It's a workaround for duplicated XML tags outside an inner tag. Check issue #55 for details. -- It checks if a given tag already exists on the parsing stack. -- In such a case, if that tag is represented as a single element, -- an array is created and that element is inserted on it. -- The existing tag is then replaced by the created array. -- For instance, if we have a tag x = {attr1=1, attr2=2} -- and another x tag is found, the previous entry will be changed to an array -- x = {{attr1=1, attr2=2}}. This way, the duplicated tag will be -- inserted into this array as x = {{attr1=1, attr2=2}, {attr1=3, attr2=4}} -- https://github.com/manoelcampos/xml2lua/issues/55 -- -- @param obj the object to try to convert to an array -- @return the same object if it's already an array or a new array with the object -- as the 1st element. local function convertObjectToArray(obj) --#obj == 0 verifies if the field is not an array if #obj == 0 then local array = {} table.insert(array, obj) return array end return obj end ---Parses a start tag. -- @param tag a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function tree:starttag(tag) local node = {} if self.parseAttributes == true then node._attr=tag.attrs end --Table in the stack representing the tag being processed local current = self._stack[#self._stack] if current[tag.name] then local array = convertObjectToArray(current[tag.name]) table.insert(array, node) current[tag.name] = array else current[tag.name] = {node} end table.insert(self._stack, node) end ---Parses an end tag. -- @param tag a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function tree:endtag(tag, s) --Table in the stack representing the tag being processed --Table in the stack representing the containing tag of the current tag local prev = self._stack[#self._stack-1] if not prev[tag.name] then error("XML Error - Unmatched Tag ["..s..":"..tag.name.."]\n") end if prev == self.root then -- Once parsing complete, recursively reduce tree self:reduce(prev, nil, nil) end table.remove(self._stack) end ---Parses a tag content. -- @param t text to process function tree:text(text) local current = self._stack[#self._stack] table.insert(current, text) end ---Parses CDATA tag content. tree.cdata = tree.text tree.__index = tree return tree