-- originally from http://lua-users.org/wiki/LuaXml -- modified by me a bit entity_escapes = { ["<"]="<", [">"]=">", ["&"]="&", ['"']=""", ["'"]="'" } entity_unescapes = {} for k,v in pairs(entity_escapes) do entity_unescapes[v]=k end function escape(s) return s:gsub("[<>&'\"]",entity_escapes) end function unescape(s) return s:gsub("&[a-z]+;",entity_unescapes) end local function parseargs(s) local arg = {} string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function (w, _, a) arg[w] = a end) return arg end local function tag(x) return setmetatable(x, {__call=function(x, label) -- search for child with given label for _,c in ipairs(x) do if c.label == label then return c end end return nil end}) end local psingle local function pmulti(s, i, parent) ::again:: local nexti, child = psingle(s, i) if child.close and child.label == parent.label then return nexti, parent else table.insert(parent, child) i = nexti goto again end end psingle = function(s, i) i = i or 1 local ts,j,c,label,xarg,empty = s:find("<(%/?)([%w:]+)(.-)(%/?)>", i) if not ts then local rest = s:sub(i) if rest:find("<",i) then error('ill formed (eof?)') elseif #rest == 0 then error('empty string') else return i+#rest, unescape(rest) end end local nexti = j+1 local pretext = s:sub(i, ts-1) if not pretext:find("^%s*$") then -- not entirely whitespace return ts, unescape(pretext) end if empty == "/" then return nexti, tag{label=label, xarg=parseargs(xarg), empty=true} elseif c == "" then -- start tag return pmulti(s, nexti, tag{label=label, xarg=parseargs(xarg)}) else -- end tag return nexti, tag{label=label, close=true} end end local wrap do local _, cqa = pcall(require, 'cqueues.auxlib') wrap = cqa and cqa.wrap or coroutine.wrap end local function stanzae(getmore) return wrap(function() local buf = '' while true do local ok, ni, el = pcall(psingle, buf) if ok then coroutine.yield(el) buf = buf:sub(ni) else local more = assert(getmore()) buf = buf .. more end end end) end local safestr_mt = {name='SAFESTR', __tostring=function(x) return x.s end} local function safestr(s) return setmetatable({s=s}, safestr_mt) end local function _xmlify(x) if getmetatable(x) == safestr_mt then return x elseif type(x) == 'string' then return safestr(escape(x)) elseif type(x) == 'table' then -- must be a tag local function argstr(t) local argstring = '' for k,v in pairs(t) do argstring = argstring .. (" %s='%s'"):format(k, _xmlify(v)) end return argstring end if x.empty then return safestr(("<%s%s/>"):format(x.label, argstr(x.xarg))) else local open = ("<%s%s>"):format(x.label, argstr(x.xarg)) local close = (""):format(x.label) local children = {} for _,child in ipairs(x) do table.insert(children, tostring(_xmlify(child))) end return safestr(open .. table.concat(children, '') .. close) end end end local function xmlify(...) local n = select('#',...) if n <= 1 then return _xmlify(...) end local out = {} for i = 1, n do out[i] = tostring(_xmlify(select(i, ...))) end return table.concat(out, '\n') end -- dsl for input to xmlify. doesn't do escaping or anything. -- THERE IS NO ESCAPE local X = setmetatable({}, {__index=function(_,label) return function(tab) local out = {} local xarg = {} local empty = true for k, v in pairs(tab) do if type(k) == 'number' then -- child empty = nil out[k] = v elseif type(k) == 'string' then -- attrib xarg[k] = v end end out.label = label out.xarg = xarg out.empty = empty return out end end}) return { psingle = psingle, stanzae = stanzae, wrap=wrap, xmlify=xmlify, X=X }