-- Roberto Ierusalimschy's simple/simplistic XML parser, modified to -- fit the LxpTree interface. -- As an XML parser, it's quite incomplete but it works enough to -- handle most XML-RPC servers. -- Now supports entity unquoting as well. -- Tag names may contain ".". if LxpTree then return end local Public, Private = {}, {} LxpTree = Public Private.unquote_entity_map = {quot='"', apos="'", amp="&", lt="<", gt=">"} function Private.unquote_entity(name) local charcode if strsub(name, 1, 1) == "#" then if strsub(name, 2, 2) == "x" then charcode = tonumber(strsub(name, 3), 16) else charcode = tonumber(strsub(name, 2), 10) end return strchar(charcode) end local replacement = %Private.unquote_entity_map[name] if not replacement then error("unknown XML entity") end return replacement end function Private.unquote(s) local new_s = gsub(s, "&([xa-zA-Z0-9#]+);", %Private.unquote_entity) return new_s end function Private.parseargs (s) local arg = {} local unquote = %Private.unquote gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) %arg[w] = %unquote(a) end) return arg end function Private.parseMethod(self, s) return %Public.parse(s) end function Public.makeParser () return {parse=%Private.parseMethod} end function Public.parse (s) local stack = {n=0} local top = {n=0} tinsert(stack, top) local ni,c,label,args, empty local i, j = 1, 1 while 1 do ni,j,c,label,args, empty = strfind(s, "<(%/?)([%w.]+)(.-)(%/?)>", j) if not ni then break end local text = strsub(s, i, ni-1) if not strfind(text, "^%s*$") then tinsert(top, %Private.unquote(text)) end if empty == "/" then -- empty element tag tinsert(top, {label; n=1, attr=%Private.parseargs(args), empty=1}) elseif c == "" then -- start tag top = {label; n=1, attr=%Private.parseargs(args)} tinsert(stack, top) -- new level else -- end tag local toclose = tremove(stack) -- remove top top = stack[stack.n] if stack.n < 1 then error("nothing to close with "..label) end if toclose[1] ~= label then error("trying to close "..toclose[1].." with "..label) end tinsert(top, toclose) end i = j+1 end local text = strsub(s, i) if not strfind(text, "^%s*$") then tinsert(stack[stack.n], %Private.unquote(text)) end if stack.n > 1 then error("unclosed "..stack[stack.n][1]) end -- Gross hack local lead = stack[1][1] if type(lead) == "string" and strfind(lead, "^<%?xml") then return stack[1][2] else return stack[1][1] end end