· 2 years ago · Dec 31, 2022, 12:30 AM
1--CREDIT: https://github.com/manoelcampos/xml2lua
2
3execute=[[
4--- @module Module providing a non-validating XML stream parser in Lua.
5--
6-- Features:
7-- =========
8--
9-- * Tokenises well-formed XML (relatively robustly)
10-- * Flexible handler based event API (see below)
11-- * Parses all XML Infoset elements - ie.
12-- - Tags
13-- - Text
14-- - Comments
15-- - CDATA
16-- - XML Decl
17-- - Processing Instructions
18-- - DOCTYPE declarations
19-- * Provides limited well-formedness checking
20-- (checks for basic syntax & balanced tags only)
21-- * Flexible whitespace handling (selectable)
22-- * Entity Handling (selectable)
23--
24-- Limitations:
25-- ============
26--
27-- * Non-validating
28-- * No charset handling
29-- * No namespace support
30-- * Shallow well-formedness checking only (fails
31-- to detect most semantic errors)
32--
33-- API:
34-- ====
35--
36-- The parser provides a partially object-oriented API with
37-- functionality split into tokeniser and handler components.
38--
39-- The handler instance is passed to the tokeniser and receives
40-- callbacks for each XML element processed (if a suitable handler
41-- function is defined). The API is conceptually similar to the
42-- SAX API but implemented differently.
43--
44-- XML data is passed to the parser instance through the 'parse'
45-- method (Note: must be passed a single string currently)
46--
47-- License:
48-- ========
49--
50-- This code is freely distributable under the terms of the [MIT license](LICENSE).
51--
52--
53--@author Paul Chakravarti (paulc@passtheaardvark.com)
54--@author Manoel Campos da Silva Filho
55local xml2lua = {_VERSION = "1.5-2"}
56local ParserPath = gg.EXT_CACHE_DIR.."/xmlparser"
57if io.open(ParserPath) == nil then
58 XmlParser = gg.makeRequest("https://github.com/manoelcampos/xml2lua/raw/master/XmlParser.lua")["content"]
59 io.output(ParserPath, "w"):write(XmlParser)
60 io.close()
61else
62 XmlParser = io.input(ParserPath):read("*all")
63end
64local XmlParser = load(XmlParser)()
65
66---Recursivelly prints a table in an easy-to-ready format
67--@param tb The table to be printed
68--@param level the indentation level to start with
69local function printableInternal(tb, level)
70 if tb == nil then
71 return
72 end
73
74 level = level or 1
75 local spaces = string.rep(' ', level*2)
76 for k,v in pairs(tb) do
77 if type(v) == "table" then
78 print(spaces .. k)
79 printableInternal(v, level+1)
80 else
81 print(spaces .. k..'='..v)
82 end
83 end
84end
85
86---Instantiates a XmlParser object to parse a XML string
87--@param handler Handler module to be used to convert the XML string
88--to another formats. See the available handlers at the handler directory.
89-- Usually you get an instance to a handler module using, for instance:
90-- local handler = require("xmlhandler/tree").
91--@return a XmlParser object used to parse the XML
92--@see XmlParser
93function xml2lua.parser()
94 local handlerPath = gg.EXT_CACHE_DIR.."/xmlhandler"
95 if io.open(handlerPath) == nil then
96 XmlHandler = gg.makeRequest("https://github.com/manoelcampos/xml2lua/raw/master/xmlhandler/tree.lua")["content"]
97 io.output(handlerPath):write(XmlHandler)
98 io.close()
99 else
100 XmlHandler = io.input(handlerPath):read("*all")
101 end
102 local handler = load(XmlHandler)()
103 local options = {
104 --Indicates if whitespaces should be striped or not
105 stripWS = 1,
106 expandEntities = 1,
107 errorHandler = function(errMsg, pos)
108 error(string.format("%s [char=%d]\n", errMsg or "Parse Error", pos))
109 end
110 }
111
112 return XmlParser.new(handler, options)
113end
114
115---Recursivelly prints a table in an easy-to-ready format
116--@param tb The table to be printed
117function xml2lua.printable(tb)
118 printableInternal(tb)
119end
120
121---Handler to generate a string prepresentation of a table
122--Convenience function for printHandler (Does not support recursive tables).
123--@param t Table to be parsed
124--@return a string representation of the table
125function xml2lua.toString(t)
126 local sep = ''
127 local res = ''
128 if type(t) ~= 'table' then
129 return t
130 end
131
132 for k,v in pairs(t) do
133 if type(v) == 'table' then
134 v = xml2lua.toString(v)
135 end
136 res = res .. sep .. string.format("%s=%s", k, v)
137 sep = ','
138 end
139 res = '{'..res..'}'
140
141 return res
142end
143
144--- Loads an XML file from a specified path
145-- @param xmlFilePath the path for the XML file to load
146-- @return the XML loaded file content
147function xml2lua.loadFile(xmlFilePath)
148 local f, e = io.open(xmlFilePath, "r")
149 if f then
150 --Gets the entire file content and stores into a string
151 local content = f:read("*a")
152 f:close()
153 return content
154 end
155
156 error(e)
157end
158
159---Gets an _attr element from a table that represents the attributes of an XML tag,
160--and generates a XML String representing the attibutes to be inserted
161--into the openning tag of the XML
162--
163--@param attrTable table from where the _attr field will be got
164--@return a XML String representation of the tag attributes
165local function attrToXml(attrTable)
166 local s = ""
167 attrTable = attrTable or {}
168
169 for k, v in pairs(attrTable) do
170 s = s .. " " .. k .. "=" .. '"' .. v .. '"'
171 end
172 return s
173end
174
175---Gets the first key of a given table
176local function getFirstKey(tb)
177 if type(tb) == "table" then
178 for k, _ in pairs(tb) do
179 return k
180 end
181 return nil
182 end
183
184 return tb
185end
186
187--- Parses a given entry in a lua table
188-- and inserts it as a XML string into a destination table.
189-- Entries in such a destination table will be concatenated to generated
190-- the final XML string from the origin table.
191-- @param xmltb the destination table where the XML string from the parsed key will be inserted
192-- @param tagName the name of the table field that will be used as XML tag name
193-- @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
194-- @param level a int value used to include indentation in the generated XML from the table key
195local function parseTableKeyToXml(xmltb, tagName, fieldValue, level)
196 local spaces = string.rep(' ', level*2)
197
198 local strValue, attrsStr = "", ""
199 if type(fieldValue) == "table" then
200 attrsStr = attrToXml(fieldValue._attr)
201 fieldValue._attr = nil
202 --If after removing the _attr field there is just one element inside it,
203 --the tag was enclosing a single primitive value instead of other inner tags.
204 strValue = #fieldValue == 1 and spaces..tostring(fieldValue[1]) or xml2lua.toXml(fieldValue, tagName, level+1)
205 strValue = '\n'..strValue..'\n'..spaces
206 else
207 strValue = tostring(fieldValue)
208 end
209
210 table.insert(xmltb, spaces..'<'..tagName.. attrsStr ..'>'..strValue..'</'..tagName..'>')
211end
212
213---Converts a Lua table to a XML String representation.
214--@param tb Table to be converted to XML
215--@param tableName Name of the table variable given to this function,
216-- to be used as the root tag. If a value is not provided
217-- no root tag will be created.
218--@param level Only used internally, when the function is called recursively to print indentation
219--
220--@return a String representing the table content in XML
221function xml2lua.toXml(tb, tableName, level)
222 level = level or 1
223 local firstLevel = level
224 tableName = tableName or ''
225 local xmltb = (tableName ~= '' and level == 1) and {'<'..tableName..attrToXml(tb._attr)..'>'} or {}
226 tb._attr = nil
227
228 for k, v in pairs(tb) do
229 if type(v) == 'table' then
230 -- If the key is a number, the given table is an array and the value is an element inside that array.
231 -- In this case, the name of the array is used as tag name for each element.
232 -- So, we are parsing an array of objects, not an array of primitives.
233 if type(k) == 'number' then
234 parseTableKeyToXml(xmltb, tableName, v, level)
235 else
236 level = level + 1
237 -- If the type of the first key of the value inside the table
238 -- is a number, it means we have a HashTable-like structure,
239 -- in this case with keys as strings and values as arrays.
240 if type(getFirstKey(v)) == 'number' then
241 for sub_k, sub_v in pairs(v) do
242 if sub_k ~= '_attr' then
243 local sub_v_with_attr = type(v._attr) == 'table' and { sub_v, _attr = v._attr } or sub_v
244 parseTableKeyToXml(xmltb, k, sub_v_with_attr, level)
245 end
246 end
247 else
248 -- Otherwise, the "HashTable" values are objects
249 parseTableKeyToXml(xmltb, k, v, level)
250 end
251 end
252 else
253 -- When values are primitives:
254 -- If the type of the key is number, the value is an element from an array.
255 -- In this case, uses the array name as the tag name.
256 if type(k) == 'number' then
257 k = tableName
258 end
259 parseTableKeyToXml(xmltb, k, v, level)
260 end
261 end
262
263 if tableName ~= '' and firstLevel == 1 then
264 table.insert(xmltb, '</'..tableName..'>\n')
265 end
266
267 return table.concat(xmltb, '\n')
268end
269
270return xml2lua
271]]
272
273function run(xml)
274 temp = load(execute)().parser(); temp:parse(xml)
275 return temp.handler.root
276end
277
278return run