diff --git a/README.md b/README.md index e0f76db..a7e5fca 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ The kit also includes: * `url` - a module with functions for encoding / decoding a string in a URL from the Lua Penlight library; * [luaDate](https://github.com/Tieske/date) - functions for working with time; * [json.lua](https://github.com/rxi/json.lua) - JSON parser; -* [SLAXDOM](https://github.com/Phrogz/SLAXML) - XML parser. +* [Lua-Simple-XML-Parser](https://github.com/Cluain/Lua-Simple-XML-Parser) - XML parser (see example `xml-test.lua`). # Metadata diff --git a/README_ru.md b/README_ru.md index 3ff6c3f..5b999cb 100644 --- a/README_ru.md +++ b/README_ru.md @@ -193,7 +193,7 @@ AIO Launcher включает в себя интерпретатор LuaJ 3.0.1 * `url` - модуль с функциями для кодирования/декодирования строки в URL из библиотеки Lua Penlight; * [luaDate](https://github.com/Tieske/date) - функции для работы со временем; * [json.lua](https://github.com/rxi/json.lua) - парзер JSON; -* [SLAXDOM](https://github.com/Phrogz/SLAXML) - парзер XML; +* [Lua-Simple-XML-Parser](https://github.com/Cluain/Lua-Simple-XML-Parser) - парзер XML (см. пример `xml-test.lua`). # Метаданные diff --git a/lib/xml.lua b/lib/xml.lua new file mode 100644 index 0000000..8186f08 --- /dev/null +++ b/lib/xml.lua @@ -0,0 +1,134 @@ +XmlParser = {}; + +local function newNode(name) + local node = {} + node.___value = nil + node.___name = name + node.___children = {} + node.___props = {} + + function node:value() return self.___value end + function node:setValue(val) self.___value = val end + function node:name() return self.___name end + function node:setName(name) self.___name = name end + function node:children() return self.___children end + function node:numChildren() return #self.___children end + function node:addChild(child) + if self[child:name()] ~= nil then + if type(self[child:name()].name) == "function" then + local tempTable = {} + table.insert(tempTable, self[child:name()]) + self[child:name()] = tempTable + end + table.insert(self[child:name()], child) + else + self[child:name()] = child + end + table.insert(self.___children, child) + end + + function node:properties() return self.___props end + function node:numProperties() return #self.___props end + function node:addProperty(name, value) + local lName = "@" .. name + if self[lName] ~= nil then + if type(self[lName]) == "string" then + local tempTable = {} + table.insert(tempTable, self[lName]) + self[lName] = tempTable + end + table.insert(self[lName], value) + else + self[lName] = value + end + table.insert(self.___props, { name = name, value = self[name] }) + end + + return node +end + +function XmlParser:ToXmlString(value) + value = string.gsub(value, "&", "&"); -- '&' -> "&" + value = string.gsub(value, "<", "<"); -- '<' -> "<" + value = string.gsub(value, ">", ">"); -- '>' -> ">" + value = string.gsub(value, "\"", """); -- '"' -> """ + value = string.gsub(value, "([^%w%&%;%p%\t% ])", + function(c) + return string.format("&#x%X;", string.byte(c)) + end); + return value; +end + +function XmlParser:FromXmlString(value) + value = string.gsub(value, "&#x([%x]+)%;", + function(h) + return string.char(tonumber(h, 16)) + end); + value = string.gsub(value, "&#([0-9]+)%;", + function(h) + return string.char(tonumber(h, 10)) + end); + value = string.gsub(value, """, "\""); + value = string.gsub(value, "'", "'"); + value = string.gsub(value, ">", ">"); + value = string.gsub(value, "<", "<"); + value = string.gsub(value, "&", "&"); + return value; +end + +function XmlParser:ParseArgs(node, s) + string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a) + node:addProperty(w, self:FromXmlString(a)) + end) +end + +function XmlParser:ParseXmlText(xmlText) + local stack = {} + local top = newNode() + table.insert(stack, top) + local ni, c, label, xarg, empty + local i, j = 1, 1 + + while true do + ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w_:]+)(.-)(%/?)>", i) + if not ni then break end + local text = string.sub(xmlText, i, ni - 1); + if not string.find(text, "^%s*$") then + local lVal = (top:value() or "") .. self:FromXmlString(text) + stack[#stack]:setValue(lVal) + end + if empty == "/" then -- empty element tag + local lNode = newNode(label) + self:ParseArgs(lNode, xarg) + top:addChild(lNode) + elseif c == "" then -- start tag + local lNode = newNode(label) + self:ParseArgs(lNode, xarg) + table.insert(stack, lNode) + top = lNode + else -- end tag + local toclose = table.remove(stack) -- remove top + + top = stack[#stack] + if #stack < 1 then + error("XmlParser: nothing to close with " .. label) + end + if toclose:name() ~= label then + error("XmlParser: trying to close " .. toclose.name .. " with " .. label) + end + top:addChild(toclose) + end + i = j + 1 + end + local text = string.sub(xmlText, i); + if #stack > 1 then + error("XmlParser: unclosed " .. stack[#stack]:name()) + end + return top +end + +function XmlParser:parse(text) + return ParseXmlText(text) +end + +return XmlParser diff --git a/samples/xml-test.lua b/samples/xml-test.lua new file mode 100644 index 0000000..3188fd0 --- /dev/null +++ b/samples/xml-test.lua @@ -0,0 +1,16 @@ +local xml = require "xml" + +local test_xml = +[[ + + + eight + twelve + +]] + +function on_resume() + local parsed = xml:parse(test_xml) + + ui:show_text(parsed.test["@one"]) +end