2 local coroutine
= coroutine
;
3 local tonumber = tonumber;
5 local setmetatable
, getmetatable
= setmetatable
, getmetatable
;
8 local deadroutine
= coroutine
.create(function() end);
9 coroutine
.resume(deadroutine
);
13 local entity_map
= setmetatable({
19 }, {__index
= function(_
, s
)
20 if s
:sub(1,1) == "#" then
21 if s
:sub(2,2) == "x" then
22 return string.char(tonumber(s
:sub(3), 16));
24 return string.char(tonumber(s
:sub(2)));
29 local function xml_unescape(str
)
30 return (str
:gsub("&(.-);", entity_map
));
32 local function parse_tag(s
)
33 local name
,sattr
=(s
):gmatch("([^%s]+)(.*)")();
35 for a
,b
in (sattr
):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr
[a
] = xml_unescape(b
); end
39 local function parser(data
, handlers
, ns_separator
)
40 local function read_until(str
)
41 local pos
= data
:find(str
, nil, true);
43 data
= data
..coroutine
.yield();
44 pos
= data
:find(str
, nil, true);
46 local r
= data
:sub(1, pos
);
47 data
= data
:sub(pos
+1);
50 local function read_before(str
)
51 local pos
= data
:find(str
, nil, true);
53 data
= data
..coroutine
.yield();
54 pos
= data
:find(str
, nil, true);
56 local r
= data
:sub(1, pos
-1);
61 while #data
== 0 do data
= coroutine
.yield(); end
65 local ns
= { xml
= "http://www.w3.org/XML/1998/namespace" };
67 local function apply_ns(name
, dodefault
)
68 local prefix
,n
= name
:match("^([^:]*):(.*)$");
69 if prefix
and ns
[prefix
] then
70 return ns
[prefix
]..ns_separator
..n
;
72 if dodefault
and ns
[""] then
73 return ns
[""]..ns_separator
..name
;
77 local function push(tag, attr
)
78 ns
= setmetatable({}, ns
);
79 for k
,v
in pairs(attr
) do
80 local xmlns
= k
== "xmlns" and "" or k
:match("^xmlns:(.*)$");
86 local newattr
, n
= {}, 0;
87 for k
,v
in pairs(attr
) do
93 tag = apply_ns(tag, true);
100 ns
= getmetatable(ns
);
105 if peek() == "<" then
106 local elem
= read_until(">"):sub(2,-2);
107 if elem
:sub(1,1) == "!" or elem
:sub(1,1) == "?" then -- neglect comments and processing-instructions
108 elseif elem
:sub(1,1) == "/" then -- end tag
111 handlers
:EndElement(name
); -- TODO check for start-end tag name match
112 elseif elem
:sub(-1,-1) == "/" then -- empty tag
113 elem
= elem
:sub(1,-2);
114 local name
,attr
= parse_tag(elem
);
115 name
,attr
= push(name
,attr
);
116 handlers
:StartElement(name
,attr
);
118 handlers
:EndElement(name
);
120 local name
,attr
= parse_tag(elem
);
121 name
,attr
= push(name
,attr
);
122 handlers
:StartElement(name
,attr
);
125 local text
= read_before("<");
126 handlers
:CharacterData(xml_unescape(text
));
131 function new(handlers
, ns_separator
)
132 local co
= coroutine
.create(parser
);
134 parse
= function(self
, data
)
139 local success
, result
= coroutine
.resume(co
, data
, handlers
, ns_separator
);
142 return nil, result
; -- error
144 return true; -- success