XML to table parser

Posted by horacebury, Posted on January 25, 2012, Last updated February 2, 2012

0 votes

Please see the updated version: http://developer.anscamobile.com/code/much-improved-dump-function-and-xml-simplify

http://blog.anscamobile.com/2011/07/how-to-use-xml-files-in-corona/

In the blog post above Jon Beebe explains how to use XML in lua via a small library module. The one downside with this was that tables returned, while well structured, are not structured with the XML naming schema of the XML file. I have added a method to do that.

The full listing follows, but given the XML snippet in the blog post, here are some example statements which will retrieve the data. (Please note that writing the data back is not handled for the newer structure.)

1
2
3
4
5
6
7
-- returned table 'inbox' passed into function as: xml:convertToTable( inbox, t )
 
-- the number of message elements
print( #t.message )
 
-- the second message's subject
print( t.message[2].subject )

xml.lua:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module(..., package.seeall)
 
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
--
-- xml.lua - XML parser for use with the Corona SDK.
--
-- version: 1.1
--
-- CHANGELOG:
--
-- 1.1 - Fixed base directory issue with the loadFile() function.
--
-- NOTE: This is a modified version of Alexander Makeev's Lua-only XML parser
-- found here: http://lua-users.org/wiki/LuaXml
--
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
 
function newParser()
 
        XmlParser = {};
        
        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, "&apos;", "'");
                value = string.gsub (value, ">", ">");
                value = string.gsub (value, "<", "<");
                value = string.gsub (value, "&", "&");
                return value;
        end
           
        function XmlParser:ParseArgs(s)
          local arg = {}
          string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a)
                        arg[w] = self:FromXmlString(a);
                end)
          return arg
        end
        
        function XmlParser:ParseXmlText(xmlText)
          local stack = {}
          local top = {name=nil,value=nil,properties={},child={}}
          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
                  top.value=(top.value or "")..self:FromXmlString(text);
                end
                if empty == "/" then  -- empty element tag
                  table.insert(top.child, {name=label,value=nil,properties=self:ParseArgs(xarg),child={}})
                elseif c == "" then   -- start tag
                  top = {name=label, value=nil, properties=self:ParseArgs(xarg), child={}}
                  table.insert(stack, top)   -- new level
                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
                  table.insert(top.child, toclose)
                end
                i = j+1
          end
          local text = string.sub(xmlText, i);
          if not string.find(text, "^%s*$") then
                  stack[#stack].value=(stack[#stack].value or "")..self:FromXmlString(text);
          end
          if #stack > 1 then
                error("XmlParser: unclosed "..stack[stack.n].name)
          end
          return stack[1].child[1];
        end
        
        function XmlParser:loadFile(xmlFilename, base)
                if not base then
                        base = system.ResourceDirectory
                end
                        
                local path = system.pathForFile( xmlFilename, base )
                local hFile, err = io.open(path,"r");
                
                if hFile and not err then
                        local xmlText=hFile:read("*a"); -- read file content
                        io.close(hFile);
                        return self:ParseXmlText(xmlText),nil;
                else
                        print( err )
                        return nil
                end
        end
        
        function XmlParser:convertToTable( parent, t )
                for k, v in pairs(parent.properties) do
                        t[k] = v
                end
                
                if (#parent.child == 0) then
                        t.value = parent.value
                        return
                end
                
                for i=1, #parent.child do
                        local childname = parent.child[i].name
                        local collection = t[childname]
                        if (collection == nil) then
                                collection = {}
                                t[childname] = collection
                        end
                        collection[#collection+1] = {}
                        XmlParser:convertToTable( parent.child[i], collection[#collection] )
                end
        end
        
        return XmlParser
end