-- See Copyright Notice in license.html -- Based on Luiz Henrique de Figueiredo's lxml: -- http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lxml local table = require"table" local tinsert, tremove = table.insert, table.remove local assert, tostring, type = assert, tostring, type -- auxiliary functions ------------------------------------------------------- local function starttag (p, tag, attr) local stack = p:getcallbacks().stack local newelement = {[0] = tag} for i = 1, #attr do local attrname = attr[i] local attrvalue = attr[attrname] newelement[attrname] = attrvalue end tinsert(stack, newelement) end local function endtag (p, tag) local stack = p:getcallbacks().stack local element = tremove(stack) assert(element[0] == tag, "Error while closing element: table[0] should be `".. tostring(tag).."' but is `"..tostring(element[0]).."'") local level = #stack tinsert(stack[level], element) end local function text (p, txt) local stack = p:getcallbacks().stack local element = stack[#stack] local n = #element if type(element[n]) == "string" and n > 0 then element[n] = element[n] .. txt else tinsert(element, txt) end end -- main function ------------------------------------------------------------- local function parse (o, opts) local opts = opts or {} local c = { StartElement = starttag, EndElement = endtag, CharacterData = text, _nonstrict = true, stack = {{}}, } local p if opts.threat then c.threat = opts.threat p = require("lxp.threat").new(c, opts.separator) else p = require("lxp").new(c, opts.separator) end local to = type(o) if to == "string" then local status, err, line, col, pos = p:parse(o) if not status then return nil, err, line, col, pos end else local iter if to == "table" then local i = 0 iter = function() i = i + 1; return o[i] end elseif to == "function" then iter = o elseif to == "userdata" and o.read then iter = function() local l = o:read() if l then return l.."\n" end end else error ("Bad argument #1 to parse: expected a string, a table, a function or a file, but got "..to, 2) end for l in iter do local status, err, line, col, pos = p:parse(l) if not status then return nil, err, line, col, pos end end end local status, err, line, col, pos = p:parse() -- close document if not status then return nil, err, line, col, pos end p:close() return c.stack[1][1] end -- utility functions --------------------------------------------------------- local function compact (t) -- remove empty entries local n = 0 for i = 1, #t do local v = t[i] if v then n = n+1 if n ~= i then t[n] = v t[i] = nil end else t[i] = nil end end return t end local function clean (t) -- remove empty strings for i = 1, #t do local v = t[i] local tv = type(v) if tv == "table" then clean (v) elseif tv == "string" and v:match"^%s*$" then t[i] = false end end return compact (t) end local function torecord (t) -- move 1-value subtables to table entries for i = 1, #t do local v = t[i] if type(v) == "table" then if #v == 1 and type(v[1]) == "string" and t[v[0]] == nil then t[v[0]] = v[1] t[i] = false else torecord (v) end end end return compact (t) end return { clean = clean, compact = compact, -- TODO: internal only, should not be exported parse = parse, torecord = torecord, }