├── README ├── README.md ├── xmlSimple.lua └── xmlTest.lua /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cluain/Lua-Simple-XML-Parser/2c318f3b01121dad61dee34b8c80c970ff938192/README -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | 1. Copy the xmlSimple.lua file to your project. 4 | 2. Create a local variable `local xml = require("xmlSimple.lua").newParser()` 5 | 3. Read xml using `xml:ParseXmlText(xmlString)` or `xml:loadFile(xmlFilename, base)` 6 | 7 | # Parsing XML 8 | 9 | ``` xml 10 | 11 | 12 | eight 13 | twelve 14 | 15 | ``` 16 | You can access values in two ways: 17 | 18 | Using the simple method: 19 | 20 | ``` lua 21 | xml.test["@one"] == "two" 22 | xml.test.nine["@ten"] == "eleven" 23 | xml.test.nine:value() == "twelve" 24 | xml.test.three[1]["@four"][1] == "five" 25 | xml.test.three[1]["@four"][2] == "six" 26 | xml.test.three[2]:value() == "eight" 27 | ``` 28 | 29 | or if your XML is a little bit more complicated you can do it like this: 30 | 31 | ``` lua 32 | xml:children()[1]:name() == "test" 33 | xml:children()[1]:children()[2]:value() == "eight" 34 | xml:properties()[1] == {name = "one", value = "two"} 35 | ``` 36 | 37 | # Limitations 38 | 39 | There's no support for namespaces. When I see namespaces I immediately start to remember days when I worked at corporate. We had to use namespaces only because XML was so convoluted we would not be able to handle it without them. In the end XML parsing took longer for some APIs then actual logic of the API. 40 | If you're in this situation it is better to step back and do something about it rather than asking for namespace support. 41 | I am using this module to read fairly simple XML. Even if it is a large XML string, the structure is still simple, so I was not able to test it properly. Please create a new Issue if you spot a problem. 42 | Please take a loook at xmlTest.lua for an example of use. 43 | 44 | # Final notes 45 | 46 | This is a modified version of [Corona-XML-Module](https://github.com/jonbeebe/Corona-XML-Module) by Jonathan Beebe which in turn is based on Alexander Makeev's Lua-only XML parser found [here](http://lua-users.org/wiki/LuaXml) 47 | -------------------------------------------------------------------------------- /xmlSimple.lua: -------------------------------------------------------------------------------- 1 | module(..., package.seeall) 2 | 3 | --------------------------------------------------------------------------------- 4 | --------------------------------------------------------------------------------- 5 | -- 6 | -- xml.lua - XML parser for use with the Corona SDK. 7 | -- 8 | -- version: 1.2 9 | -- 10 | -- CHANGELOG: 11 | -- 12 | -- 1.2 - Created new structure for returned table 13 | -- 1.1 - Fixed base directory issue with the loadFile() function. 14 | -- 15 | -- NOTE: This is a modified version of Alexander Makeev's Lua-only XML parser 16 | -- found here: http://lua-users.org/wiki/LuaXml 17 | -- 18 | --------------------------------------------------------------------------------- 19 | --------------------------------------------------------------------------------- 20 | function newParser() 21 | 22 | XmlParser = {}; 23 | 24 | function XmlParser:ToXmlString(value) 25 | value = string.gsub(value, "&", "&"); -- '&' -> "&" 26 | value = string.gsub(value, "<", "<"); -- '<' -> "<" 27 | value = string.gsub(value, ">", ">"); -- '>' -> ">" 28 | value = string.gsub(value, "\"", """); -- '"' -> """ 29 | value = string.gsub(value, "([^%w%&%;%p%\t% ])", 30 | function(c) 31 | return string.format("&#x%X;", string.byte(c)) 32 | end); 33 | return value; 34 | end 35 | 36 | function XmlParser:FromXmlString(value) 37 | value = string.gsub(value, "&#x([%x]+)%;", 38 | function(h) 39 | return string.char(tonumber(h, 16)) 40 | end); 41 | value = string.gsub(value, "&#([0-9]+)%;", 42 | function(h) 43 | return string.char(tonumber(h, 10)) 44 | end); 45 | value = string.gsub(value, """, "\""); 46 | value = string.gsub(value, "'", "'"); 47 | value = string.gsub(value, ">", ">"); 48 | value = string.gsub(value, "<", "<"); 49 | value = string.gsub(value, "&", "&"); 50 | return value; 51 | end 52 | 53 | function XmlParser:ParseArgs(node, s) 54 | string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a) 55 | node:addProperty(w, self:FromXmlString(a)) 56 | end) 57 | end 58 | 59 | function XmlParser:ParseXmlText(xmlText) 60 | local stack = {} 61 | local top = newNode() 62 | table.insert(stack, top) 63 | local ni, c, label, xarg, empty 64 | local i, j = 1, 1 65 | while true do 66 | ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w_:]+)(.-)(%/?)>", i) 67 | if not ni then break end 68 | local text = string.sub(xmlText, i, ni - 1); 69 | if not string.find(text, "^%s*$") then 70 | local lVal = (top:value() or "") .. self:FromXmlString(text) 71 | stack[#stack]:setValue(lVal) 72 | end 73 | if empty == "/" then -- empty element tag 74 | local lNode = newNode(label) 75 | self:ParseArgs(lNode, xarg) 76 | top:addChild(lNode) 77 | elseif c == "" then -- start tag 78 | local lNode = newNode(label) 79 | self:ParseArgs(lNode, xarg) 80 | table.insert(stack, lNode) 81 | top = lNode 82 | else -- end tag 83 | local toclose = table.remove(stack) -- remove top 84 | 85 | top = stack[#stack] 86 | if #stack < 1 then 87 | error("XmlParser: nothing to close with " .. label) 88 | end 89 | if toclose:name() ~= label then 90 | error("XmlParser: trying to close " .. toclose.name .. " with " .. label) 91 | end 92 | top:addChild(toclose) 93 | end 94 | i = j + 1 95 | end 96 | local text = string.sub(xmlText, i); 97 | if #stack > 1 then 98 | error("XmlParser: unclosed " .. stack[#stack]:name()) 99 | end 100 | return top 101 | end 102 | 103 | function XmlParser:loadFile(xmlFilename, base) 104 | if not base then 105 | base = system.ResourceDirectory 106 | end 107 | 108 | local path = system.pathForFile(xmlFilename, base) 109 | local hFile, err = io.open(path, "r"); 110 | 111 | if hFile and not err then 112 | local xmlText = hFile:read("*a"); -- read file content 113 | io.close(hFile); 114 | return self:ParseXmlText(xmlText), nil; 115 | else 116 | print(err) 117 | return nil 118 | end 119 | end 120 | 121 | return XmlParser 122 | end 123 | 124 | function newNode(name) 125 | local node = {} 126 | node.___value = nil 127 | node.___name = name 128 | node.___children = {} 129 | node.___props = {} 130 | 131 | function node:value() return self.___value end 132 | function node:setValue(val) self.___value = val end 133 | function node:name() return self.___name end 134 | function node:setName(name) self.___name = name end 135 | function node:children() return self.___children end 136 | function node:numChildren() return #self.___children end 137 | function node:addChild(child) 138 | if self[child:name()] ~= nil then 139 | if type(self[child:name()].name) == "function" then 140 | local tempTable = {} 141 | table.insert(tempTable, self[child:name()]) 142 | self[child:name()] = tempTable 143 | end 144 | table.insert(self[child:name()], child) 145 | else 146 | self[child:name()] = child 147 | end 148 | table.insert(self.___children, child) 149 | end 150 | 151 | function node:properties() return self.___props end 152 | function node:numProperties() return #self.___props end 153 | function node:addProperty(name, value) 154 | local lName = "@" .. name 155 | if self[lName] ~= nil then 156 | if type(self[lName]) == "string" then 157 | local tempTable = {} 158 | table.insert(tempTable, self[lName]) 159 | self[lName] = tempTable 160 | end 161 | table.insert(self[lName], value) 162 | else 163 | self[lName] = value 164 | end 165 | table.insert(self.___props, { name = name, value = self[name] }) 166 | end 167 | 168 | return node 169 | end 170 | -------------------------------------------------------------------------------- /xmlTest.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- User: krystian 3 | -- Company: Cluain Krystian Szczęsny 4 | -- Date: 12/21/11 5 | -- Time: 7:40 PM 6 | -- 7 | 8 | local xml = require("xmlSimple").newParser() 9 | 10 | local testXml = '' 11 | testXml = testXml .. '' 12 | testXml = testXml .. '' 13 | testXml = testXml .. 'testThreeValue' 14 | testXml = testXml .. '' 15 | testXml = testXml .. '' 16 | testXml = testXml .. 'testThreeValueTwo' 17 | testXml = testXml .. '' 18 | testXml = testXml .. '' 19 | testXml = testXml .. 'testFourValue' 20 | testXml = testXml .. '' 21 | testXml = testXml .. '' 22 | testXml = testXml .. '' 23 | testXml = testXml .. '' 24 | testXml = testXml .. '' 25 | testXml = testXml .. '' 26 | testXml = testXml .. '' 27 | testXml = testXml .. '' 28 | testXml = testXml .. 'testTwoValue' 29 | testXml = testXml .. '' 30 | testXml = testXml .. '' 31 | 32 | 33 | local parsedXml = xml:ParseXmlText(testXml) 34 | 35 | 36 | if parsedXml.testOne == nil then error("Node not created") end 37 | if parsedXml.testOne:name() ~= "testOne" then error("Node name not set") end 38 | if parsedXml.testOne.testTwo == nil then error("Child node not created") end 39 | if parsedXml.testOne.testTwo:name() ~= "testTwo" then error("Child node name not set") end 40 | if parsedXml.testOne.testTwo:value() ~= "testTwoValue" then error("Node value not set") end 41 | if parsedXml.testOne.testTwo.test_Four:value() ~= "testFourValue" then error("Second child node value not set") end 42 | if parsedXml.testOne["@param"] ~= "param1value" then error("Parameter not set") end 43 | if parsedXml.testOne.testTwo["@paramTwo"] ~= "param2value" then error("Second child node parameter not set") end 44 | if parsedXml.testOne.testTwo.test_Four["@something"] ~= "else" then error("Deepest node parameter not set") end 45 | 46 | -- duplicate names tests 47 | if parsedXml.testOne.testTwo.testThree[1]:value() ~= "testThreeValue" then error("First of duplicate nodes value not set") end 48 | if parsedXml.testOne.testTwo.testThree[2]:value() ~= "testThreeValueTwo" then error("Second of duplicate nodes value not set") end 49 | if parsedXml.testOne.testTwo.testThree[2]["@duplicate"][1] ~= "one" then error("First of duplicate parameters not set") end 50 | if parsedXml.testOne.testTwo.testThree[2]["@duplicate"][2] ~= "two" then error("Second of duplicate parameters not set") end 51 | 52 | -- deep element test 53 | 54 | if parsedXml.testOne.testTwo.testFive.testFiveDeep.testFiveEvenDeeper.testSix['@someParam'] ~= "someValue" then error("Deep test error") end 55 | 56 | -- node functions test 57 | local node = require("xmlSimple").newNode("testName") 58 | 59 | if node:name() ~= "testName" then error("Node creation failed") end 60 | 61 | node:setName("nameTest") 62 | if node:name() ~= "nameTest" then error("Name function test failed") end 63 | 64 | node:setValue("valueTest") 65 | if node:value() ~= "valueTest" then error("Value function test failed") end 66 | 67 | local childNode = require("xmlSimple").newNode("parent") 68 | 69 | node:addChild(childNode) 70 | 71 | if type(node:children()) ~= "table" then error("children function test failed") end 72 | if #node:children() ~= 1 then error("AddChild function test failed") end 73 | if node:numChildren() ~= 1 then error("numChildren function test failed") end 74 | 75 | 76 | node:addProperty("name", "value") 77 | 78 | if type(node:properties()) ~= "table" then error("properties function test failed") end 79 | if #node:properties() ~= 1 then error("Add property function test failed") end 80 | if node:numProperties() ~= 1 then error("Num properties function test failed") end 81 | 82 | print("Tests passed") 83 | --------------------------------------------------------------------------------