1- --- @module Module providing a non-validating XML stream parser in Lua.
2- --
1+ --- @module Module providing a non-validating XML stream parser in Lua.
2+ --
33-- Features:
44-- =========
5- --
5+ --
66-- * Tokenises well-formed XML (relatively robustly)
77-- * Flexible handler based event API (see below)
88-- * Parses all XML Infoset elements - ie.
1313-- - XML Decl
1414-- - Processing Instructions
1515-- - DOCTYPE declarations
16- -- * Provides limited well-formedness checking
16+ -- * Provides limited well-formedness checking
1717-- (checks for basic syntax & balanced tags only)
1818-- * Flexible whitespace handling (selectable)
1919-- * Entity Handling (selectable)
20- --
20+ --
2121-- Limitations:
2222-- ============
23- --
23+ --
2424-- * Non-validating
25- -- * No charset handling
26- -- * No namespace support
25+ -- * No charset handling
26+ -- * No namespace support
2727-- * Shallow well-formedness checking only (fails
2828-- to detect most semantic errors)
29- --
29+ --
3030-- API:
3131-- ====
3232--
33- -- The parser provides a partially object-oriented API with
33+ -- The parser provides a partially object-oriented API with
3434-- functionality split into tokeniser and handler components.
35- --
35+ --
3636-- The handler instance is passed to the tokeniser and receives
3737-- callbacks for each XML element processed (if a suitable handler
38- -- function is defined). The API is conceptually similar to the
38+ -- function is defined). The API is conceptually similar to the
3939-- SAX API but implemented differently.
4040--
4141-- XML data is passed to the parser instance through the 'parse'
4949--
5050-- @author Paul Chakravarti ([email protected] )5151-- @author Manoel Campos da Silva Filho
52- local xml2lua = {_VERSION = " 1.5-2 " }
52+ local xml2lua = { _VERSION = " 1.6-1 " }
5353local XmlParser = require (" XmlParser" )
5454
5555--- Recursivelly prints a table in an easy-to-ready format
@@ -59,7 +59,7 @@ local function printableInternal(tb, level)
5959 if tb == nil then
6060 return
6161 end
62-
62+
6363 level = level or 1
6464 local spaces = string.rep (' ' , level * 2 )
6565 for k ,v in pairs (tb ) do
@@ -69,7 +69,7 @@ local function printableInternal(tb, level)
6969 else
7070 print (spaces .. k .. ' =' .. v )
7171 end
72- end
72+ end
7373end
7474
7575--- Instantiates a XmlParser object to parse a XML string
7979-- local handler = require("xmlhandler/tree").
8080-- @return a XmlParser object used to parse the XML
8181-- @see XmlParser
82- function xml2lua .parser (handler )
82+ function xml2lua .parser (handler )
8383 if handler == xml2lua then
8484 error (" You must call xml2lua.parse(handler) instead of xml2lua:parse(handler)" )
8585 end
8686
87- local options = {
87+ local options = {
8888 -- Indicates if whitespaces should be striped or not
89- stripWS = 1 ,
89+ stripWS = 1 ,
9090 expandEntities = 1 ,
91- errorHandler = function (errMsg , pos )
91+ errorHandler = function (errMsg , pos )
9292 error (string.format (" %s [char=%d]\n " , errMsg or " Parse Error" , pos ))
9393 end
9494 }
@@ -114,10 +114,10 @@ function xml2lua.toString(t)
114114 end
115115
116116 for k ,v in pairs (t ) do
117- if type (v ) == ' table' then
117+ if type (v ) == ' table' then
118118 v = xml2lua .toString (v )
119119 end
120- res = res .. sep .. string.format (" %s=%s" , k , v )
120+ res = res .. sep .. string.format (" %s=%s" , k , v )
121121 sep = ' ,'
122122 end
123123 res = ' {' .. res .. ' }'
@@ -136,7 +136,7 @@ function xml2lua.loadFile(xmlFilePath)
136136 f :close ()
137137 return content
138138 end
139-
139+
140140 error (e )
141141end
142142
@@ -149,51 +149,114 @@ end
149149local function attrToXml (attrTable )
150150 local s = " "
151151 attrTable = attrTable or {}
152-
152+
153153 for k , v in pairs (attrTable ) do
154154 s = s .. " " .. k .. " =" .. ' "' .. v .. ' "'
155155 end
156156 return s
157157end
158158
159159--- Gets the first key of a given table
160- local function getFirstKey (tb )
161- if type (tb ) == " table" then
160+ local function getSingleChild (tb )
161+ local count = 0
162+ for _ in pairs (tb ) do
163+ count = count + 1
164+ end
165+ if (count == 1 ) then
162166 for k , _ in pairs (tb ) do
163167 return k
164168 end
169+ end
170+ return nil
171+ end
172+
173+ --- Gets the first value of a given table
174+ local function getFirstValue (tb )
175+ if type (tb ) == " table" then
176+ for _ , v in pairs (tb ) do
177+ return v
178+ end
165179 return nil
166180 end
167181
168182 return tb
169183end
170184
171- --- Parses a given entry in a lua table
172- -- and inserts it as a XML string into a destination table.
173- -- Entries in such a destination table will be concatenated to generated
174- -- the final XML string from the origin table.
175- -- @param xmltb the destination table where the XML string from the parsed key will be inserted
176- -- @param tagName the name of the table field that will be used as XML tag name
177- -- @param fieldValue a field from the lua table to be recursively parsed to XML or a primitive value that will be enclosed in a tag name
178- -- @param level a int value used to include indentation in the generated XML from the table key
179- local function parseTableKeyToXml (xmltb , tagName , fieldValue , level )
180- local spaces = string.rep (' ' , level * 2 )
181-
182- local strValue , attrsStr = " " , " "
183- if type (fieldValue ) == " table" then
184- attrsStr = attrToXml (fieldValue ._attr )
185- fieldValue ._attr = nil
186- -- If after removing the _attr field there is just one element inside it,
187- -- the tag was enclosing a single primitive value instead of other inner tags.
188- strValue = # fieldValue == 1 and spaces .. tostring (fieldValue [1 ]) or xml2lua .toXml (fieldValue , tagName , level + 1 )
189- strValue = ' \n ' .. strValue .. ' \n ' .. spaces
190- else
191- strValue = tostring (fieldValue )
185+ xml2lua .pretty = true
186+
187+ function xml2lua .getSpaces (level )
188+ local spaces = ' '
189+ if (xml2lua .pretty ) then
190+ spaces = string.rep (' ' , level * 2 )
191+ end
192+ return spaces
193+ end
194+
195+ function xml2lua .addTagValueAttr (tagName , tagValue , attrTable , level )
196+ local attrStr = attrToXml (attrTable )
197+ local spaces = xml2lua .getSpaces (level )
198+ if (tagValue == ' ' ) then
199+ table.insert (xml2lua .xmltb , spaces .. ' <' .. tagName .. attrStr .. ' />' )
200+ else
201+ table.insert (xml2lua .xmltb , spaces .. ' <' .. tagName .. attrStr .. ' >' .. tostring (tagValue ) .. ' </' .. tagName .. ' >' )
202+ end
203+ end
204+
205+ function xml2lua .startTag (tagName , attrTable , level )
206+ local attrStr = attrToXml (attrTable )
207+ local spaces = xml2lua .getSpaces (level )
208+ if (tagName ~= nil ) then
209+ table.insert (xml2lua .xmltb , spaces .. ' <' .. tagName .. attrStr .. ' >' )
210+ end
211+ end
212+
213+ function xml2lua .endTag (tagName , level )
214+ local spaces = xml2lua .getSpaces (level )
215+ if (tagName ~= nil ) then
216+ table.insert (xml2lua .xmltb , spaces .. ' </' .. tagName .. ' >' )
217+ end
218+ end
219+
220+ function xml2lua .isChildArray (obj )
221+ for tag , _ in pairs (obj ) do
222+ if (type (tag ) == ' number' ) then
223+ return true
192224 end
225+ end
226+ return false
227+ end
193228
194- table.insert (xmltb , spaces .. ' <' .. tagName .. attrsStr .. ' >' .. strValue .. ' </' .. tagName .. ' >' )
229+ function xml2lua .isTableEmpty (obj )
230+ for k , _ in pairs (obj ) do
231+ if (k ~= ' _attr' ) then
232+ return false
233+ end
234+ end
235+ return true
195236end
196237
238+ function xml2lua .parseTableToXml (obj , tagName , level )
239+ if (tagName ~= ' _attr' ) then
240+ if (type (obj ) == ' table' ) then
241+ if (xml2lua .isChildArray (obj )) then
242+ for _ , value in pairs (obj ) do
243+ xml2lua .parseTableToXml (value , tagName , level )
244+ end
245+ elseif xml2lua .isTableEmpty (obj ) then
246+ xml2lua .addTagValueAttr (tagName , " " , obj ._attr , level )
247+ else
248+ xml2lua .startTag (tagName , obj ._attr , level )
249+ for tag , value in pairs (obj ) do
250+ xml2lua .parseTableToXml (value , tag , level + 1 )
251+ end
252+ xml2lua .endTag (tagName , level )
253+ end
254+ else
255+ xml2lua .addTagValueAttr (tagName , obj , nil , level )
256+ end
257+ end
258+ end
259+
197260--- Converts a Lua table to a XML String representation.
198261-- @param tb Table to be converted to XML
199262-- @param tableName Name of the table variable given to this function,
@@ -203,52 +266,21 @@ end
203266--
204267-- @return a String representing the table content in XML
205268function xml2lua .toXml (tb , tableName , level )
206- level = level or 1
207- local firstLevel = level
208- tableName = tableName or ' '
209- local xmltb = (tableName ~= ' ' and level == 1 ) and {' <' .. tableName .. attrToXml (tb ._attr ).. ' >' } or {}
210- tb ._attr = nil
211-
212- for k , v in pairs (tb ) do
213- if type (v ) == ' table' then
214- -- If the key is a number, the given table is an array and the value is an element inside that array.
215- -- In this case, the name of the array is used as tag name for each element.
216- -- So, we are parsing an array of objects, not an array of primitives.
217- if type (k ) == ' number' then
218- parseTableKeyToXml (xmltb , tableName , v , level )
219- else
220- level = level + 1
221- -- If the type of the first key of the value inside the table
222- -- is a number, it means we have a HashTable-like structure,
223- -- in this case with keys as strings and values as arrays.
224- if type (getFirstKey (v )) == ' number' then
225- for sub_k , sub_v in pairs (v ) do
226- if sub_k ~= ' _attr' then
227- local sub_v_with_attr = type (v ._attr ) == ' table' and { sub_v , _attr = v ._attr } or sub_v
228- parseTableKeyToXml (xmltb , k , sub_v_with_attr , level )
229- end
230- end
269+ xml2lua .xmltb = {}
270+ level = level or 0
271+ local singleChild = getSingleChild (tb )
272+ tableName = tableName or singleChild
273+
274+ if (singleChild ) then
275+ xml2lua .parseTableToXml (getFirstValue (tb ), tableName , level )
231276 else
232- -- Otherwise, the "HashTable" values are objects
233- parseTableKeyToXml (xmltb , k , v , level )
234- end
235- end
236- else
237- -- When values are primitives:
238- -- If the type of the key is number, the value is an element from an array.
239- -- In this case, uses the array name as the tag name.
240- if type (k ) == ' number' then
241- k = tableName
242- end
243- parseTableKeyToXml (xmltb , k , v , level )
244- end
277+ xml2lua .parseTableToXml (tb , tableName , level )
245278 end
246279
247- if tableName ~= ' ' and firstLevel == 1 then
248- table.insert ( xmltb , ' </ ' .. tableName .. ' > \n ' )
280+ if ( xml2lua . pretty ) then
281+ return table.concat ( xml2lua . xmltb , ' \n ' )
249282 end
250-
251- return table.concat (xmltb , ' \n ' )
283+ return table.concat (xml2lua .xmltb )
252284end
253285
254286return xml2lua
0 commit comments