| 1 | local st = require "util.stanza"; |
| 2 | |
| 3 | -- XML parser |
| 4 | local parse_xml = (function() |
| 5 | local entity_map = setmetatable({ |
| 6 | ["amp"] = "&"; |
| 7 | ["gt"] = ">"; |
| 8 | ["lt"] = "<"; |
| 9 | ["apos"] = "'"; |
| 10 | ["quot"] = "\""; |
| 11 | }, {__index = function(_, s) |
| 12 | if s:sub(1,1) == "#" then |
| 13 | if s:sub(2,2) == "x" then |
| 14 | return string.char(tonumber(s:sub(3), 16)); |
| 15 | else |
| 16 | return string.char(tonumber(s:sub(2))); |
| 17 | end |
| 18 | end |
| 19 | end |
| 20 | }); |
| 21 | local function xml_unescape(str) |
| 22 | return (str:gsub("&(.-);", entity_map)); |
| 23 | end |
| 24 | local function parse_tag(s) |
| 25 | local name,sattr=(s):gmatch("([^%s]+)(.*)")(); |
| 26 | local attr = {}; |
| 27 | for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end |
| 28 | return name, attr; |
| 29 | end |
| 30 | return function(xml) |
| 31 | local stanza = st.stanza("root"); |
| 32 | local regexp = "<([^>]*)>([^<]*)"; |
| 33 | for elem, text in xml:gmatch(regexp) do |
| 34 | if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions |
| 35 | elseif elem:sub(1,1) == "/" then -- end tag |
| 36 | elem = elem:sub(2); |
| 37 | stanza:up(); -- TODO check for start-end tag name match |
| 38 | elseif elem:sub(-1,-1) == "/" then -- empty tag |
| 39 | elem = elem:sub(1,-2); |
| 40 | local name,attr = parse_tag(elem); |
| 41 | stanza:tag(name, attr):up(); |
| 42 | else -- start tag |
| 43 | local name,attr = parse_tag(elem); |
| 44 | stanza:tag(name, attr); |
| 45 | end |
| 46 | if #text ~= 0 then -- text |
| 47 | stanza:text(xml_unescape(text)); |
| 48 | end |
| 49 | end |
| 50 | return stanza.tags[1]; |
| 51 | end |
| 52 | end)(); |
| 53 | -- end of XML parser |
| 54 | |
| 55 | return parse_xml; |