diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/l-xml.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/l-xml.lua | 484 |
1 files changed, 305 insertions, 179 deletions
diff --git a/Master/texmf-dist/tex/context/base/l-xml.lua b/Master/texmf-dist/tex/context/base/l-xml.lua index b4e151ec32e..8b8a6ce4433 100644 --- a/Master/texmf-dist/tex/context/base/l-xml.lua +++ b/Master/texmf-dist/tex/context/base/l-xml.lua @@ -9,6 +9,8 @@ if not modules then modules = { } end modules ['l-xml'] = { -- RJ: key=value ... lpeg.Ca(lpeg.Cc({}) * (pattern-producing-key-and-value / rawset)^0) -- some code may move to l-xmlext +-- some day we will really compile the lpaths (just construct functions) +-- todo: some things per xml file, like namespace remapping --[[ldx-- <p>The parser used here is inspired by the variant discussed in the lua book, but @@ -37,15 +39,30 @@ optimize the code.</p> xml = xml or { } tex = tex or { } -xml.trace_lpath = false -xml.trace_print = false -xml.trace_remap = false +local concat, remove, insert = table.concat, table.remove, table.insert +local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring +local format, lower, gmatch, gsub, find = string.format, string.lower, string.gmatch, string.gsub, string.find -local format, concat = string.format, table.concat +--[[ldx-- +<p>This module can be used stand alone but also inside <l n='mkiv'/> in +which case it hooks into the tracker code. Therefore we provide a few +functions that set the tracers.</p> +--ldx]]-- ---~ local pairs, next, type = pairs, next, type +local trace_lpath, trace_remap = false, false --- todo: some things per xml file, liek namespace remapping +if trackers then + trackers.register("xml.lpath", function(v) trace_lpath = v end) + trackers.register("xml.remap", function(v) trace_remap = v end) +end + +function xml.settrace(str,value) + if str == "lpath" then + trace_lpath = value or false + elseif str == "remap" then + trace_remap = value or false + end +end --[[ldx-- <p>First a hack to enable namespace resolving. A namespace is characterized by @@ -72,7 +89,7 @@ do --ldx]]-- function xml.registerns(namespace, pattern) -- pattern can be an lpeg - check = check + lpeg.C(lpeg.P(pattern:lower())) / namespace + check = check + lpeg.C(lpeg.P(lower(pattern))) / namespace parse = lpeg.P { lpeg.P(check) + 1 * lpeg.V(1) } end @@ -87,7 +104,7 @@ do --ldx]]-- function xml.checkns(namespace,url) - local ns = parse:match(url:lower()) + local ns = parse:match(lower(url)) if ns and namespace ~= ns then xml.xmlns[namespace] = ns end @@ -105,7 +122,7 @@ do --ldx]]-- function xml.resolvens(url) - return parse:match(url:lower()) or "" + return parse:match(lower(url)) or "" end --[[ldx-- @@ -154,7 +171,7 @@ do -- not just one big nested table capture (lpeg overflow) - local remove, nsremap, resolvens = table.remove, xml.xmlns, xml.resolvens + local nsremap, resolvens = xml.xmlns, xml.resolvens local stack, top, dt, at, xmlns, errorstr, entities = {}, {}, {}, {}, {}, nil, {} @@ -172,6 +189,9 @@ do end local function add_attribute(namespace,tag,value) + if cleanup and #value > 0 then + value = cleanup(value) -- new + end if tag == "xmlns" then xmlns[#xmlns+1] = resolvens(value) at[tag] = value @@ -206,7 +226,8 @@ do end dt = top.dt dt[#dt+1] = toclose - if at.xmlns then +dt[0] = top + if toclose.at.xmlns then remove(xmlns) end end @@ -220,10 +241,10 @@ do local t = { ns=namespace or "", rn=resolved, tg=tag, at=at, dt={}, __p__ = top } dt[#dt+1] = t setmetatable(t, mt) - at = { } if at.xmlns then remove(xmlns) end + at = { } end local function add_text(text) if cleanup and #text > 0 then @@ -243,7 +264,7 @@ do end end local function set_message(txt) - errorstr = "garbage at the end of the file: " .. txt:gsub("([ \n\r\t]*)","") + errorstr = "garbage at the end of the file: " .. gsub(txt,"([ \n\r\t]*)","") end local P, S, R, C, V = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V @@ -291,7 +312,7 @@ do local somecomment = C((1 - endcomment )^0) local somecdata = C((1 - endcdata )^0) - function entity(k,v) entities[k] = v end + local function entity(k,v) entities[k] = v end local begindoctype = open * P("!DOCTYPE") local enddoctype = close @@ -387,7 +408,7 @@ do return root and not root.error end - xml.error_handler = (logs and logs.report) or (input and input.report) or print + xml.error_handler = (logs and logs.report) or (input and logs.report) or print end @@ -436,7 +457,7 @@ can speed up things a bit. The second argument is not to be used!</p> do - function copy(old,tables) + local function copy(old,tables) if old then tables = tables or { } local new = { } @@ -480,22 +501,19 @@ do elseif not nocommands then local ec = e.command if ec ~= nil then -- we can have all kind of types - -if e.special then -- todo test for true/false - local etg, edt = e.tg, e.dt - local spc = specialconverter and specialconverter[etg] - if spc then ---~ print("SPECIAL",etg,table.serialize(specialconverter), spc) - local result = spc(edt[1]) - if result then - handle(result) - return - else - -- no need to handle any further - end - end -end - + if e.special then + local etg, edt = e.tg, e.dt + local spc = specialconverter and specialconverter[etg] + if spc then + local result = spc(edt[1]) + if result then + handle(result) + return + else + -- no need to handle any further + end + end + end local xc = xml.command if xc then xc(e,ec) @@ -536,27 +554,17 @@ end local ats = eat and next(eat) and { } -- type test maybe faster if ats then if attributeconverter then - for k,v in pairs(eat) do + for k,v in next, eat do ats[#ats+1] = format('%s=%q',k,attributeconverter(v)) end else - for k,v in pairs(eat) do + for k,v in next, eat do ats[#ats+1] = format('%s=%q',k,v) end end end - if ern and xml.trace_remap and ern ~= ens then ---~ if ats then ---~ ats[#ats+1] = format("xmlns:remapped='%s'",ern) ---~ else ---~ ats = { format("xmlns:remapped='%s'",ern) } ---~ end ---~ if ats then ---~ ats[#ats+1] = format("remappedns='%s'",ens or '-') ---~ else ---~ ats = { format("remappedns='%s'",ens or '-') } ---~ end -ens = ern + if ern and trace_remap and ern ~= ens then + ens = ern end if ens ~= "" then if edt and #edt > 0 then @@ -600,7 +608,16 @@ ens = ern handle("<" .. etg .. ">") end for i=1,#edt do - serialize(edt[i],handle,textconverter,attributeconverter,specialconverter,nocommands) + local ei = edt[i] + if type(ei) == "string" then + if textconverter then + handle(textconverter(ei)) + else + handle(ei) + end + else + serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands) + end end -- handle(format("</%s>",etg)) handle("</" .. etg .. ">") @@ -623,7 +640,16 @@ ens = ern end else for i=1,#e do - serialize(e[i],handle,textconverter,attributeconverter,specialconverter,nocommands) + local ei = e[i] + if type(ei) == "string" then + if textconverter then + handle(textconverter(ei)) + else + handle(ei) + end + else + serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands) + end end end end @@ -633,15 +659,16 @@ ens = ern function xml.checkbom(root) -- can be made faster if root.ri then local dt, found = root.dt, false - for k,v in ipairs(dt) do + for k=1,#dt do + local v = dt[k] if type(v) == "table" and v.special and v.tg == "@pi" and v.dt:find("xml.*version=") then found = true break end end if not found then - table.insert(dt, 1, { special=true, ns="", tg="@pi@", dt = { "xml version='1.0' standalone='yes'"} } ) - table.insert(dt, 2, "\n" ) + insert(dt, 1, { special=true, ns="", tg="@pi@", dt = { "xml version='1.0' standalone='yes'"} } ) + insert(dt, 2, "\n" ) end end end @@ -653,14 +680,14 @@ ens = ern function xml.tostring(root) -- 25% overhead due to collecting if root then - if type(root) == 'string' then - return root - elseif next(root) then -- next is faster than type (and >0 test) - local result = { } - serialize(root,function(s) result[#result+1] = s end) - return concat(result,"") + if type(root) == 'string' then + return root + elseif next(root) then -- next is faster than type (and >0 test) + local result = { } + serialize(root,function(s) result[#result+1] = s end) + return concat(result,"") + end end - end return "" end @@ -725,6 +752,18 @@ function xml.content(root) -- bugged return (root and root.dt and xml.tostring(root.dt)) or "" end +function xml.isempty(root, pattern) + if pattern == "" or pattern == "*" then + pattern = nil + end + if pattern then + -- todo + return false + else + return not root or not root.dt or #root.dt == 0 or root.dt == "" + end +end + --[[ldx-- <p>The next helper erases an element but keeps the table as it is, and since empty strings are not serialized (effectively) it does @@ -767,11 +806,16 @@ of <l n='xpath'/> and since we're not compatible we call it <l n='lpath'/>. We will explain more about its usage in other documents.</p> --ldx]]-- +local lpathcalls = 0 -- statisctics +local lpathcached = 0 -- statisctics + do - xml.functions = xml.functions or { } + xml.functions = xml.functions or { } + xml.expressions = xml.expressions or { } - local functions = xml.functions + local functions = xml.functions + local expressions = xml.expressions local actions = { [10] = "stay", @@ -795,34 +839,32 @@ do [40] = "processing instruction", } - --~ local function make_expression(str) --could also be an lpeg - --~ str = str:gsub("@([a-zA-Z%-_]+)", "(a['%1'] or '')") - --~ str = str:gsub("position%(%)", "i") - --~ str = str:gsub("text%(%)", "t") - --~ str = str:gsub("!=", "~=") - --~ str = str:gsub("([^=!~<>])=([^=!~<>])", "%1==%2") - --~ str = str:gsub("([a-zA-Z%-_]+)%(", "functions.%1(") - --~ return str, loadstring(format("return function(functions,i,a,t) return %s end", str))() - --~ end - -- a rather dumb lpeg local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc - local lp_position = P("position()") / "id" + -- instead of using functions we just parse a few names which saves a call + -- later on + + local lp_position = P("position()") / "ps" + local lp_index = P("index()") / "id" local lp_text = P("text()") / "tx" - local lp_name = P("name()") / "((rt.ns~='' and rt.ns..':'..rt.tg) or '')" - local lp_tag = P("tag()") / "(rt.tg or '')" - local lp_ns = P("ns()") / "(rt.ns or '')" + local lp_name = P("name()") / "(ns~='' and ns..':'..tg)" -- "((rt.ns~='' and rt.ns..':'..rt.tg) or '')" + local lp_tag = P("tag()") / "tg" -- (rt.tg or '') + local lp_ns = P("ns()") / "ns" -- (rt.ns or '') local lp_noequal = P("!=") / "~=" + P("<=") + P(">=") + P("==") local lp_doequal = P("=") / "==" local lp_attribute = P("@") / "" * Cc("(at['") * R("az","AZ","--","__")^1 * Cc("'] or '')") - local lp_function = C(R("az","AZ","--","__")^1) * P("(") / function(t) - if functions[t] then - return "functions." .. t .. "(" + local lp_lua_function = C(R("az","AZ","--","__")^1 * (P(".") * R("az","AZ","--","__")^1)^1) * P("(") / function(t) -- todo: better . handling + return t .. "(" + end + + local lp_function = C(R("az","AZ","--","__")^1) * P("(") / function(t) -- todo: better . handling + if expressions[t] then + return "expressions." .. t .. "(" else - return "functions.error(" + return "expressions.error(" end end @@ -830,34 +872,45 @@ do local rparent = lpeg.P(")") local noparent = 1 - (lparent+rparent) local nested = lpeg.P{lparent * (noparent + lpeg.V(1))^0 * rparent} - local value = lpeg.P(lparent * lpeg.C((noparent + nested)^0) * rparent) + local value = lpeg.P(lparent * lpeg.C((noparent + nested)^0) * rparent) -- lpeg.P{"("*C(((1-S("()"))+V(1))^0)*")"} ---~ local value = P { "(" * C(((1 - S("()")) + V(1))^0) * ")" } + -- if we use a dedicated namespace then we don't need to pass rt and k local lp_special = (C(P("name")+P("text")+P("tag"))) * value / function(t,s) - if functions[t] then + if expressions[t] then if s then - return "functions." .. t .. "(rt,k," .. s ..")" + return "expressions." .. t .. "(r,k," .. s ..")" else - return "functions." .. t .. "(rt,k)" + return "expressions." .. t .. "(r,k)" end else - return "functions.error(" .. t .. ")" + return "expressions.error(" .. t .. ")" end end local converter = lpeg.Cs ( ( lp_position + + lp_index + lp_text + lp_name + -- fast one lp_special + lp_noequal + lp_doequal + lp_attribute + + lp_lua_function + lp_function + 1 )^1 ) + -- expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1 + + local template = [[ + return function(expressions,r,d,k,e,dt,ns,tg,id,ps) + local at, tx = e.at or { }, dt[1] or "" + return %s + end + ]] + local function make_expression(str) str = converter:match(str) - return str, loadstring(format("return function(functions,id,at,tx,rt,k) return %s end", str))() + return str, loadstring(format(template,str))() end local map = { } @@ -880,8 +933,6 @@ do local bar = P('|') local hat = P('^') local valid = R('az', 'AZ', '09') + S('_-') ---~ local name_yes = C(valid^1 + star) * colon * C(valid^1 + star) -- permits ns:* *:tg *:* ---~ local name_nop = C(P(true)) * C(valid^1) local name_yes = C(valid^1 + star) * colon * C(valid^1 + star) -- permits ns:* *:tg *:* local name_nop = Cc("*") * C(valid^1) local name = name_yes + name_nop @@ -967,7 +1018,7 @@ do local selector = ( instruction + - many + any + +--~ many + any + -- brrr, not here ! parent + stay + dont_position + position + dont_match_one_of_and_eq + dont_match_one_of_and_ne + @@ -979,6 +1030,7 @@ do has_attribute + has_value + dont_match_one_of + match_one_of + dont_match + match + + many + any + crap + empty ) @@ -1015,9 +1067,9 @@ do return { map[2] } end if m ~= 11 and m ~= 12 and m ~= 13 and m ~= 14 and m ~= 15 and m ~= 16 then - table.insert(map, 1, { 16 }) + insert(map, 1, { 16 }) end - -- print((table.serialize(map)):gsub("[ \n]+"," ")) + -- print(gsub(table.serialize(map),"[ \n]+"," ")) return map end end @@ -1026,13 +1078,15 @@ do local cache = { } function xml.lpath(pattern,trace) + lpathcalls = lpathcalls + 1 if type(pattern) == "string" then local result = cache[pattern] - if not result then + if result == nil then -- can be false which is valid -) result = compose(pattern) cache[pattern] = result + lpathcached = lpathcached + 1 end - if trace or xml.trace_lpath then + if trace or trace_lpath then xml.lshow(result) end return result @@ -1041,10 +1095,17 @@ do end end - local fallbackreport = (texio and texio.write) or io.write + function xml.cached_patterns() + return cache + end + +-- we run out of locals (limited to 200) +-- +-- local fallbackreport = (texio and texio.write) or io.write function xml.lshow(pattern,report) - report = report or fallbackreport +-- report = report or fallbackreport + report = report or (texio and texio.write) or io.write local lp = xml.lpath(pattern) if lp == false then report(" -: root\n") @@ -1054,7 +1115,8 @@ do if type(pattern) == "string" then report(format("pattern: %s\n",pattern)) end - for k,v in ipairs(lp) do + for k=1,#lp do + local v = lp[k] if #v > 1 then local t = { } for i=2,#v do @@ -1075,7 +1137,8 @@ do function xml.xshow(e,...) -- also handy when report is given, use () to isolate first e local t = { ... } - local report = (type(t[#t]) == "function" and t[#t]) or fallbackreport +-- local report = (type(t[#t]) == "function" and t[#t]) or fallbackreport + local report = (type(t[#t]) == "function" and t[#t]) or (texio and texio.write) or io.write if e == nil then report("<!-- no element -->\n") elseif type(e) ~= "table" then @@ -1108,22 +1171,25 @@ functions.</p> do - local functions = xml.functions + local functions = xml.functions + local expressions = xml.expressions - functions.contains = string.find - functions.find = string.find - functions.upper = string.upper - functions.lower = string.lower - functions.number = tonumber - functions.boolean = toboolean + expressions.contains = string.find + expressions.find = string.find + expressions.upper = string.upper + expressions.lower = string.lower + expressions.number = tonumber + expressions.boolean = toboolean - functions.oneof = function(s,...) -- slow + expressions.oneof = function(s,...) -- slow local t = {...} for i=1,#t do if s == t[i] then return true end end return false end - functions.error = function(str) - xml.error_handler("unknown function in lpath expression",str) + + expressions.error = function(str) + xml.error_handler("unknown function in lpath expression",str or "?") return false end + functions.text = function(root,k,n) -- unchecked, maybe one deeper local t = type(t) if t == "string" then @@ -1133,35 +1199,21 @@ do return (rdt and rdt[k]) or root[k] or "" end end - functions.name = function(root,k,n) - -- way too fuzzy - local found - if not k or not n then - local ns, tg = root.rn or root.ns or "", root.tg - if not tg then - for i=1,#root do - local e = root[i] - if type(e) == "table" then - found = e - break - end - end - elseif ns ~= "" then - return ns .. ":" .. tg - else - return tg - end + + functions.name = function(d,k,n) -- ns + tg + local found = false + n = n or 0 + if not k then + -- not found elseif n == 0 then - local e = root[k] - if type(e) ~= "table" then - found = e - end + local dk = d[k] + found = dk and (type(dk) == "table") and dk elseif n < 0 then for i=k-1,1,-1 do - local e = root[i] - if type(e) == "table" then + local di = d[i] + if type(di) == "table" then if n == -1 then - found = e + found = di break else n = n + 1 @@ -1169,12 +1221,11 @@ do end end else ---~ print(k,n,#root) - for i=k+1,#root,1 do - local e = root[i] - if type(e) == "table" then + for i=k+1,#d,1 do + local di = d[i] + if type(di) == "table" then if n == 1 then - found = e + found = di break else n = n - 1 @@ -1194,6 +1245,46 @@ do end end + functions.tag = function(d,k,n) -- only tg + local found = false + n = n or 0 + if not k then + -- not found + elseif n == 0 then + local dk = d[k] + found = dk and (type(dk) == "table") and dk + elseif n < 0 then + for i=k-1,1,-1 do + local di = d[i] + if type(di) == "table" then + if n == -1 then + found = di + break + else + n = n + 1 + end + end + end + else + for i=k+1,#d,1 do + local di = d[i] + if type(di) == "table" then + if n == 1 then + found = di + break + else + n = n - 1 + end + end + end + end + return (found and found.tg) or "" + end + + expressions.text = functions.text + expressions.name = functions.name + expressions.tag = functions.tag + local function traverse(root,pattern,handle,reverse,index,parent,wildcard) -- multiple only for tags, not for namespaces if not root then -- error return false @@ -1266,10 +1357,13 @@ do start, stop, step = stop, start, -1 end local idx = 0 + local hsh = { } -- this will slooow down the lot for k=start,stop,step do -- we used to have functions for all but a case is faster local e = rootdt[k] local ns, tg = e.rn or e.ns, e.tg if tg then + -- we can optimize this for simple searches, but it probably does not pay off + hsh[tg] = (hsh[tg] or 0) + 1 idx = idx + 1 if command == 30 then local ns_a, tg_a = action[3], action[4] @@ -1392,7 +1486,7 @@ do end if not action[2] then matched = not matched end if matched then - matched = action[6](functions,idx,e.at or { },edt[1],rootdt,k) + matched = action[6](expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1) end end if matched then -- combine tg test and at test @@ -1564,7 +1658,7 @@ do local rt, dt, dk traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk = r, d, k return true end) local ekat = (dt and dt[dk] and dt[dk].at) or (rt and rt.at) - return (ekat and (ekat[arguments] or ekat[arguments:gsub("^([\"\'])(.*)%1$","%2")])) or "" + return (ekat and (ekat[arguments] or ekat[gsub(arguments,"^([\"\'])(.*)%1$","%2")])) or "" end function xml.filters.text(root,pattern,arguments) -- ?? why index, tostring slow local dtk, rt, dt, dk = xml.filters.index(root,pattern,arguments) @@ -1626,9 +1720,6 @@ do function xml.filter(root,pattern) local kind, a, b, c = parser:match(pattern) ---~ if xml.trace_lpath then ---~ print(pattern,kind,a,b,c) ---~ end if kind == 1 or kind == 3 then return (filters[b] or default_filter)(root,a,c) elseif kind == 2 then @@ -1867,11 +1958,20 @@ do local r, d, k, element = m[1], m[2], m[3], m[4] if not before then k = k + 1 end if element.tg then - table.insert(d,k,element) -- untested - elseif element.dt then - for _,v in ipairs(element.dt) do -- i added - table.insert(d,k,v) - k = k + 1 + insert(d,k,element) -- untested +--~ elseif element.dt then +--~ for _,v in ipairs(element.dt) do -- i added +--~ insert(d,k,v) +--~ k = k + 1 +--~ end +--~ end + else + local edt = element.dt + if edt then + for i=1,#edt do + insert(d,k,edt[i]) + k = k + 1 + end end end end @@ -1932,7 +2032,7 @@ do end if not name then if ek.at then - for a in (attribute or "href"):gmatch("([^|]+)") do + for a in gmatch(attribute or "href","([^|]+)") do name = ek.at[a] if name then break end end @@ -1958,15 +2058,27 @@ do xml.each_element(xmldata, pattern, include) end - function xml.strip_whitespace(root, pattern) + function xml.strip_whitespace(root, pattern, nolines) -- strips all leading and trailing space ! traverse(root, lpath(pattern), function(r,d,k) local dkdt = d[k].dt if dkdt then -- can be optimized local t = { } for i=1,#dkdt do local str = dkdt[i] - if type(str) == "string" and str:find("^[ \n\r\t]*$") then - -- stripped + if type(str) == "string" then + + if str == "" then + -- stripped + else + if nolines then + str = gsub(str,"[ \n\r\t]+"," ") + end + if str == "" then + -- stripped + else + t[#t+1] = str + end + end else t[#t+1] = str end @@ -1976,9 +2088,8 @@ do end) end - function xml.rename_space(root, oldspace, newspace) -- fast variant + local function rename_space(root, oldspace, newspace) -- fast variant local ndt = #root.dt - local rename = xml.rename_space for i=1,ndt or 0 do local e = root[i] if type(e) == "table" then @@ -1990,12 +2101,14 @@ do end local edt = e.dt if edt then - rename(edt, oldspace, newspace) + rename_space(edt, oldspace, newspace) end end end end + xml.rename_space = rename_space + function xml.remap_tag(root, pattern, newtg) traverse(root, lpath(pattern), function(r,d,k) d[k].tg = newtg @@ -2074,10 +2187,12 @@ put them here instead of loading mode modules there then needed.</p> --ldx]]-- function xml.gsub(t,old,new) - if t.dt then - for k,v in ipairs(t.dt) do + local dt = t.dt + if dt then + for k=1,#dt do + local v = dt[k] if type(v) == "string" then - t.dt[k] = v:gsub(old,new) + dt[k] = gsub(v,old,new) else xml.gsub(v,old,new) end @@ -2102,9 +2217,9 @@ end --~ xml.escapes = { ['&'] = '&', ['<'] = '<', ['>'] = '>', ['"'] = '"' } --~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end ---~ function xml.escaped (str) return str:gsub("(.)" , xml.escapes ) end ---~ function xml.unescaped(str) return str:gsub("(&.-;)", xml.unescapes) end ---~ function xml.cleansed (str) return str:gsub("<.->" , '' ) end -- "%b<>" +--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end +--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end +--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>" do @@ -2136,6 +2251,10 @@ do local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0) + xml.escaped_pattern = escaped + xml.unescaped_pattern = unescaped + xml.cleansed_pattern = cleansed + function xml.escaped (str) return escaped :match(str) end function xml.unescaped(str) return unescaped:match(str) end function xml.cleansed (str) return cleansed :match(str) end @@ -2168,9 +2287,9 @@ original entity is returned.</p> do if unicode and unicode.utf8 then - xml.entities = xml.entities or { } -- xml.entities.handler == function + xml.entities = xml.entities or { } -- xml.entity_handler == function - function xml.entities.handler(e) + function xml.entity_handler(e) return format("[%s]",e) end @@ -2180,16 +2299,14 @@ do if unicode and unicode.utf8 then return char(tonumber(s,16)) end - local entities = xml.entities -- global entities - - function utfize(root) + local function utfize(root) local d = root.dt for k=1,#d do local dk = d[k] if type(dk) == "string" then -- test prevents copying if no match - if dk:find("&#x.-;") then - d[k] = dk:gsub("&#x(.-);",toutf) + if find(dk,"&#x.-;") then + d[k] = gsub(dk,"&#x(.-);",toutf) end else utfize(dk) @@ -2200,14 +2317,16 @@ do if unicode and unicode.utf8 then xml.utfize = utfize local function resolve(e) -- hex encoded always first, just to avoid mkii fallbacks - if e:find("#x") then + if find(e,"^#x") then return char(tonumber(e:sub(3),16)) + elseif find(e,"^#") then + return char(tonumber(e:sub(2))) else - local ee = entities[e] + local ee = xml.entities[e] -- we cannot shortcut this one (is reloaded) if ee then return ee else - local h = xml.entities.handler + local h = xml.entity_handler return (h and h(e)) or "&" .. e .. ";" end end @@ -2219,8 +2338,8 @@ do if unicode and unicode.utf8 then for k=1,#d do local dk = d[k] if type(dk) == "string" then - if dk:find("&.-;") then - d[k] = dk:gsub("&(.-);",resolve) + if find(dk,"&.-;") then + d[k] = gsub(dk,"&(.-);",resolve) end else resolve_entities(dk) @@ -2232,24 +2351,24 @@ do if unicode and unicode.utf8 then xml.resolve_entities = resolve_entities function xml.utfize_text(str) - if str:find("&#") then - return (str:gsub("&#x(.-);",toutf)) + if find(str,"&#") then + return (gsub(str,"&#x(.-);",toutf)) else return str end end function xml.resolve_text_entities(str) -- maybe an lpeg. maybe resolve inline - if str:find("&") then - return (str:gsub("&(.-);",resolve)) + if find(str,"&") then + return (gsub(str,"&(.-);",resolve)) else return str end end function xml.show_text_entities(str) - if str:find("&") then - return (str:gsub("&(.-);","[%1]")) + if find(str,"&") then + return (gsub(str,"&(.-);","[%1]")) else return str end @@ -2261,7 +2380,7 @@ do if unicode and unicode.utf8 then local documententities = root.entities local allentities = xml.entities if documententities then - for k, v in pairs(documententities) do + for k, v in next, documententities do allentities[k] = v end end @@ -2269,6 +2388,13 @@ do if unicode and unicode.utf8 then end end +function xml.statistics() + return { + lpathcalls = lpathcalls, + lpathcached = lpathcached, + } +end + -- xml.set_text_cleanup(xml.show_text_entities) -- xml.set_text_cleanup(xml.resolve_text_entities) @@ -2288,7 +2414,7 @@ end end --~ </a> --~ ]]) ---~ xml.trace_lpath = true +--~ xml.settrace("lpath",true) --~ xml.xshow(xml.first(x,"b[position() > 2 and position() < 5 and text() == 'ok']")) --~ xml.xshow(xml.first(x,"b[position() > 2 and position() < 5 and text() == upper('ok')]")) |