@@ -62,7 +62,7 @@ function dom:starttag(tag)
6262 _children = {}
6363 }
6464
65- if self .root == nil then
65+ if not self .root then
6666 self .root = node
6767 end
6868
7676-- @param tag a {name, attrs} table
7777-- where name is the name of the tag and attrs
7878-- is a table containing the attributes of the tag
79- function dom :endtag (tag , s )
79+ function dom :endtag (tag )
8080 -- Table representing the containing tag of the current tag
8181 local prev = self ._stack [# self ._stack ]
8282
@@ -86,6 +86,22 @@ function dom:endtag(tag, s)
8686
8787 table.remove (self ._stack )
8888 self .current = self ._stack [# self ._stack ]
89+ if not self .current then
90+ local node = { _children = {}, _type = " ROOT" }
91+ if self .decl then
92+ table.insert (node ._children , self .decl )
93+ self .decl = nil
94+ end
95+ if self .dtd then
96+ table.insert (node ._children , self .dtd )
97+ self .dtd = nil
98+ end
99+ if self .root then
100+ table.insert (node ._children , self .root )
101+ self .root = node
102+ end
103+ self .current = node
104+ end
89105end
90106
91107--- Parses a tag content.
@@ -127,27 +143,128 @@ end
127143-- where name is the name of the tag and attrs
128144-- is a table containing the attributes of the tag
129145function dom :decl (tag )
130- if self .options .declNode then
131- local node = { _type = " DECL" ,
132- _name = tag .name ,
133- _attr = tag .attrs ,
134- }
135- table.insert (self .current ._children , node )
136- end
146+ if self .options .declNode then
147+ self .decl = { _type = " DECL" ,
148+ _name = tag .name ,
149+ _attr = tag .attrs ,
150+ }
151+ end
137152end
138153
139154--- Parses a DTD tag.
140- -- @param tag a {name, attrs } table
141- -- where name is the name of the tag and attrs
155+ -- @param tag a {name, value } table
156+ -- where name is the name of the tag and value
142157-- is a table containing the attributes of the tag
143158function dom :dtd (tag )
144- if self .options .dtdNode then
145- local node = { _type = " DTD" ,
146- _name = tag .name ,
147- _attr = tag .attrs ,
148- }
149- table.insert (self .current ._children , node )
150- end
159+ if self .options .dtdNode then
160+ self .dtd = { _type = " DTD" ,
161+ _name = tag .name ,
162+ _text = tag .value
163+ }
164+ end
165+ end
166+
167+ --- XML escape characters for a TEXT node.
168+ -- @param s a string
169+ -- @return @p s XML escaped.
170+ local function xmlEscape (s )
171+ s = string.gsub (s , ' &' , ' &' )
172+ s = string.gsub (s , ' <' , ' <' )
173+ return string.gsub (s , ' >' , ' >' )
174+ end
175+
176+ --- return a string of XML attributes
177+ -- @param tab table with XML attribute pairs. key and value are supposed to be strings.
178+ -- @return a string.
179+ local function attrsToStr (tab )
180+ if not tab then
181+ return ' '
182+ end
183+ if type (tab ) == ' table' then
184+ local s = ' '
185+ for n ,v in pairs (tab ) do
186+ -- determine a safe quote character
187+ local val = tostring (v )
188+ local found_single_quote = string.find (val , " '" )
189+ local found_double_quote = string.find (val , ' "' )
190+ local quot = ' "'
191+ if found_single_quote and found_double_quote then
192+ -- XML escape both quote characters
193+ val = string.gsub (val , ' "' , ' "' )
194+ val = string.gsub (val , " '" , ' '' )
195+ elseif found_double_quote then
196+ quot = " '"
197+ end
198+ s = ' ' .. tostring (n ) .. ' =' .. quot .. val .. quot
199+ end
200+ return s
201+ end
202+ return ' BUG:unknown type:' .. type (tab )
203+ end
204+
205+ --- return a XML formatted string of @p node.
206+ -- @param node a Node object (table) of the xml2lua DOM tree structure.
207+ -- @return a string.
208+ local function toXmlStr (node , indentLevel )
209+ if not node then
210+ return ' BUG:node==nil'
211+ end
212+ if not node ._type then
213+ return ' BUG:node._type==nil'
214+ end
215+
216+ local indent = ' '
217+ for i = 0 , indentLevel + 1 , 1 do
218+ indent = indent .. ' '
219+ end
220+
221+ if node ._type == ' ROOT' then
222+ local s = ' '
223+ for i , n in pairs (node ._children ) do
224+ s = s .. toXmlStr (n , indentLevel + 2 )
225+ end
226+ return s
227+ elseif node ._type == ' ELEMENT' then
228+ local s = indent .. ' <' .. node ._name .. attrsToStr (node ._attr )
229+
230+ -- check if ELEMENT has no children
231+ if not node ._children or
232+ # node ._children == 0 then
233+ return s .. ' />\n '
234+ end
235+
236+ s = s .. ' >\n '
237+
238+ for i , n in pairs (node ._children ) do
239+ local xx = toXmlStr (n , indentLevel + 2 )
240+ if not xx then
241+ print (' BUG:xx==nil' )
242+ else
243+ s = s .. xx
244+ end
245+ end
246+
247+ return s .. indent .. ' </' .. node ._name .. ' >\n '
248+
249+ elseif node ._type == ' TEXT' then
250+ return indent .. xmlEscape (node ._text ) .. ' \n '
251+ elseif node ._type == ' COMMENT' then
252+ return indent .. ' <!--' .. node ._text .. ' -->\n '
253+ elseif node ._type == ' PI' then
254+ return indent .. ' <?' .. node ._name .. ' ' .. node ._attr ._text .. ' ?>\n '
255+ elseif node ._type == ' DECL' then
256+ return indent .. ' <?' .. node ._name .. attrsToStr (node ._attr ) .. ' ?>\n '
257+ elseif node ._type == ' DTD' then
258+ return indent .. ' <!' .. node ._name .. ' ' .. node ._text .. ' >\n '
259+ end
260+ return ' BUG:unknown type:' .. tostring (node ._type )
261+ end
262+
263+ --- create a string in XML format from the dom root object @p node.
264+ -- @param node a root object, typically created with `dom` XML parser handler.
265+ -- @return a string, XML formatted.
266+ function dom :toXml (node )
267+ return toXmlStr (node , - 4 )
151268end
152269
153270--- Parses CDATA tag content.
0 commit comments