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