if not modules then modules = { } end modules ['lxml-tst'] = { version = 1.001, comment = "companion to lxml-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local utf = unicode.utf8 local utfchar = utf.char local concat, insert, remove, gsub, find = table.concat, table.insert, table.remove local format, sub, gsub, find, gmatch, match = string.format, string.sub, string.gsub, string.find, string.gmatch, string.match local type, next, tonumber, tostring = type, next, tonumber, tostring local lpegmatch = lpeg.match local P, S, C, Cc = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc if not tex and not tex.sprint then tex = { sprint = function(catcodes,...) texio.write(table.concat{...}) end, print = function(catcodes,...) texio.write(table.concat{...}) end, write = function( ...) texio.write(table.concat{...}) end, } commands = { writestatus = logs.report } resolvers.loadbinfile = function(filename) return true, io.loaddata(filename) end end local texsprint, texprint, texwrite = tex.sprint, tex.print, tex.write local texcatcodes, ctxcatcodes, vrbcatcodes, notcatcodes = tex.texcatcodes, tex.ctxcatcodes, tex.vrbcatcodes, tex.notcatcodes local xmlelements, xmlcollected, xmlsetproperty = xml.elements, xml.collected, xml.setproperty local xmlparseapply, xmlwithelements = xml.parse_apply, xml.withelements local xmlserialize, xmlcollect, xmltext, xmltostring = xml.serialize, xml.collect, xml.text, xml.tostring local variables = (interfaces and interfaces.variables) or { } local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming local trace_setups = false trackers.register("lxml.setups", function(v) trace_setups = v end) local trace_loading = false trackers.register("lxml.loading", function(v) trace_loading = v end) local trace_access = false trackers.register("lxml.access", function(v) trace_access = v end) local trace_comments = false trackers.register("lxml.comments", function(v) trace_comments = v end) lxml = lxml or { } lxml.loaded = lxml.loaded or { } local loaded = lxml.loaded -- print(contextdirective("context-mathml-directive function reduction yes ")) -- print(contextdirective("context-mathml-directive function ")) xml.defaultprotocol = "tex" local finalizers = xml.finalizers finalizers.xml = finalizers.xml or { } finalizers.tex = finalizers.tex or { } -- this might look inefficient but it's actually rather efficient -- because we avoid tokenization of leading spaces and xml can be -- rather verbose (indented) local newline = lpeg.patterns.newline local space = lpeg.patterns.spacer local ampersand = P("&") local semicolon = P(";") local spacing = newline * space^0 local content = C((1-spacing-ampersand)^1) local verbose = C((1-(space+newline))^1) local entity = ampersand * C((1-semicolon)^1) * semicolon local xmltextcapture = ( space^0 * newline^2 * Cc("") / texprint + -- better ^-2 ? space^0 * newline * space^0 * Cc(" ") / texsprint + content / function(str) return texsprint(notcatcodes,str) end + -- was just texsprint, current catcodes regime is notcatcodes entity / xml.resolved_entity )^0 local ctxtextcapture = ( space^0 * newline^2 * Cc("") / texprint + -- better ^-2 ? space^0 * newline * space^0 * Cc(" ") / texsprint + content / function(str) return texsprint(ctxcatcodes,str) end + -- was just texsprint, current catcodes regime is notcatcodes entity / xml.resolved_entity )^0 local forceraw, rawroot = false, nil function lxml.startraw() forceraw = true end function lxml.stopraw() forceraw = false end function lxml.rawroot() return rawroot end --~ function lxml.rawpath(rootid) --~ if rawroot and type(rawroot) == "table" then --~ local text, path, rp --~ if not rawroot.dt then --~ text, path, rp = "text", "", rawroot[0] --~ else --~ path, rp = "tree", "", rawroot.__p__ --~ end --~ while rp do --~ local rptg = rp.tg --~ if rptg then --~ path = rptg .. "/" .. path --~ end --~ rp = rp.__p__ --~ end --~ return { rootid, "/" .. path, text } --~ end --~ end -- cdata local linecommand = "\\obeyedline" local spacecommand = "\\obeyedspace" -- "\\strut\\obeyedspace" local beforecommand = "" local aftercommand = "" local xmlverbosecapture = ( newline / function( ) texsprint(texcatcodes,linecommand,"{}") end + verbose / function(s) texsprint(vrbcatcodes,s) end + space / function( ) texsprint(texcatcodes,spacecommand,"{}") end )^0 local function toverbatim(str) if beforecommand then texsprint(texcatcodes,beforecommand,"{}") end lpegmatch(xmlverbosecapture,str) if aftercommand then texsprint(texcatcodes,aftercommand,"{}") end end function lxml.set_verbatim(before,after,obeyedline,obeyedspace) beforecommand, aftercommand, linecommand, spacecommand = before, after, obeyedline, obeyedspace end local obeycdata = true function lxml.set_cdata() obeycdata = true end function lxml.reset_cdata() obeycdata = false end -- cdata and verbatim lxml.set_verbatim("\\xmlcdatabefore", "\\xmlcdataafter", "\\xmlcdataobeyedline", "\\xmlcdataobeyedspace") -- local capture = (space^0*newline)^0 * capture * (space+newline)^0 * -1 function lxml.toverbatim(str) if beforecommand then texsprint(texcatcodes,beforecommand,"{}") end -- todo: add this to capture str = gsub(str,"^[ \t]+[\n\r]+","") str = gsub(str,"[ \t\n\r]+$","") lpegmatch(xmlverbosecapture,str) if aftercommand then texsprint(texcatcodes,aftercommand,"{}") end end -- storage function lxml.store(id,root,filename) loaded[id] = root xmlsetproperty(root,"name",id) if filename then xmlsetproperty(root,"filename",filename) end end local splitter = lpeg.splitat("::") lxml.idsplitter = splitter function lxml.splitid(id) local d, i = lpegmatch(splitter,id) if d then return d, i else return "", id end end local function get_id(id, qualified) if id then local lid = loaded[id] if lid then return lid elseif type(id) == "table" then return id else local d, i = lpegmatch(splitter,id) if d then local ld = loaded[d] if ld then local ldi = ld.index if ldi then local root = ldi[tonumber(i)] if root then if qualified then -- we need this else two args that confuse others return root, d else return root end elseif trace_access then logs.report("lxml","'%s' has no index entry '%s'",d,i) end elseif trace_access then logs.report("lxml","'%s' has no index",d) end elseif trace_access then logs.report("lxml","'%s' is not loaded",d) end elseif trace_access then logs.report("lxml","'%s' is not loaded",i) end end elseif trace_access then logs.report("lxml","invalid id (nil)") end end lxml.id = get_id lxml.get_id = get_id function lxml.root(id) return loaded[id] end -- index local nofindices = 0 local function addindex(name,check_sum,force) local root = get_id(name) if root and (not root.index or force) then -- weird, only called once local n, index, maxindex, check = 0, root.index or { }, root.maxindex or 0, root.check or { } local function nest(root) local dt = root.dt if not root.ix then maxindex = maxindex + 1 root.ix = maxindex check[maxindex] = root.tg -- still needed ? index[maxindex] = root n = n + 1 end if dt then for k=1,#dt do local dk = dt[k] if type(dk) == "table" then nest(dk) end end end end nest(root) nofindices = nofindices + n -- if type(name) ~= "string" then name = "unknown" end root.index = index root.maxindex = maxindex if trace_access then logs.report("lxml","%s indexed, %s nodes",tostring(name),maxindex) end end end lxml.addindex = addindex -- another cache local function lxmlparseapply(id,pattern) -- better inline, saves call return xmlparseapply({ get_id(id) }, pattern) end lxml.filter = lxmlparseapply function lxml.filterlist(list,pattern) for s in gmatch(list,"[^, ]+") do -- we could cache a table lxmlparseapply(s,pattern) end end lxml["function"] = function(id,name) local f = xml.functions[name] return f and f(get_id(id)) end -- rather new, indexed storage (backward refs), maybe i will merge this function lxml.checkindex(name) local root = get_id(name) return (root and root.index) or 0 end function lxml.withindex(name,n,command) -- will change as name is always there now local i, p = lpegmatch(splitter,n) if p then texsprint(ctxcatcodes,"\\xmlw{",command,"}{",n,"}") else texsprint(ctxcatcodes,"\\xmlw{",command,"}{",name,"::",n,"}") end end function lxml.getindex(name,n) -- will change as name is always there now local i, p = lpegmatch(splitter,n) if p then texsprint(ctxcatcodes,n) else texsprint(ctxcatcodes,name,"::",n) end end -- loading (to be redone, no overload) xml.originalload = xml.originalload or xml.load local noffiles, nofconverted = 0, 0 function xml.load(filename,settings) noffiles, nofconverted = noffiles + 1, nofconverted + 1 starttiming(xml) local ok, data = resolvers.loadbinfile(filename) local xmltable = xml.convert((ok and data) or "",settings) stoptiming(xml) return xmltable end local entities = xml.entities local function entityconverter(id,str) return entities[str] and "&"..str..";" -- feed back into tex end later end function lxml.convert(id,data,entities,compress) local settings = { unify_predefined_entities = true, } if compress and compress == variables.yes then settings.strip_cm_and_dt = true end if entities and entities == variables.yes then settings.utfize_entities = true settings.resolve_entities = function (str) return entityconverter(id,str) end end return xml.convert(data,settings) end function lxml.load(id,filename,compress,entities) filename = commands.preparedfile(filename) if trace_loading then commands.writestatus("lxml","loading file '%s' as '%s'",filename,id) end noffiles, nofconverted = noffiles + 1, nofconverted + 1 -- local xmltable = xml.load(filename) local ok, data = resolvers.loadbinfile(filename) local xmltable = lxml.convert(id,(ok and data) or "",compress,entities) lxml.store(id,xmltable,filename) return xmltable, filename end function lxml.register(id,xmltable,filename) lxml.store(id,xmltable,filename) return xmltable end function lxml.include(id,pattern,attribute,recurse) starttiming(xml) local root = get_id(id) xml.include(root,pattern,attribute,recurse,function(filename) if filename then filename = commands.preparedfile(filename) if file.dirname(filename) == "" and root.filename then local dn = file.dirname(root.filename) if dn ~= "" then filename = file.join(dn,filename) end end if trace_loading then commands.writestatus("lxml","including file: %s",filename) end noffiles, nofconverted = noffiles + 1, nofconverted + 1 return resolvers.loadtexfile(filename) or "" else return "" end end) stoptiming(xml) end function xml.getbuffer(name,compress,entities) -- we need to make sure that commands are processed if not name or name == "" then name = tex.jobname end nofconverted = nofconverted + 1 xmltostring(lxml.convert(name,concat(buffers.data[name] or {},""),compress,entities)) end function lxml.loadbuffer(id,name,compress,entities) if not name or name == "" then name = tex.jobname end starttiming(xml) nofconverted = nofconverted + 1 local xmltable = lxml.convert(id,buffers.collect(name or id,"\n"),compress,entities) lxml.store(id,xmltable) stoptiming(xml) return xmltable, name or id end function lxml.loaddata(id,str,compress,entities) starttiming(xml) nofconverted = nofconverted + 1 local xmltable = lxml.convert(id,str or "",compress,entities) lxml.store(id,xmltable) stoptiming(xml) return xmltable, id end function lxml.loadregistered(id) return loaded[id], id end -- e.command: -- -- string : setup -- true : text (no ) -- false : ignore -- function : call local function tex_doctype(e,handlers) -- ignore end local function tex_comment(e,handlers) if trace_comments then logs.report("lxml","comment: %s",e.dt[1]) end end local default_element_handler = xml.gethandlers("verbose").functions["@el@"] local function tex_element(e,handlers) local command = e.command if command == nil then default_element_handler(e,handlers) elseif command == true then -- text (no ) / so, no mkii fallback then handlers.serialize(e.dt,handlers) elseif command == false then -- ignore else local tc = type(command) if tc == "string" then local rootname, ix = e.name, e.ix if rootname then if not ix then addindex(rootname,false,true) ix = e.ix end texsprint(ctxcatcodes,"\\xmlw{",command,"}{",rootname,"::",ix,"}") else logs.report("lxml", "fatal error: no index for '%s'",command) texsprint(ctxcatcodes,"\\xmlw{",command,"}{",ix or 0,"}") end elseif tc == "function" then command(e) end end end local pihandlers = { } xml.pihandlers = pihandlers local kind = P("context-") * C((1-P("-"))^1) * P("-directive") local space = S(" \n\r") local spaces = space^0 local class = C((1-space)^0) local key = class local value = C(P(1-(space * -1))^0) local parser = kind * spaces * class * spaces * key * spaces * value pihandlers[#pihandlers+1] = function(str) -- local kind, class, key, value = lpegmatch(parser,str) if str then local a, b, c, d = lpegmatch(parser,str) if d then texsprint(ctxcatcodes,"\\xmlcontextdirective{",a",}{",b,"}{",c,"}{",d,"}") end end end local function tex_pi(e,handlers) local str = e.dt[1] for i=1,#pihandlers do pihandlers[i](str) end end local function tex_cdata(e,handlers) if obeycdata then toverbatim(e.dt[1]) end end local function tex_text(e) lpegmatch(xmltextcapture,e) end local function ctx_text(e) lpegmatch(ctxtextcapture,e) end local function tex_handle(...) -- logs.report("lxml", "error while flushing: %s", concat { ... }) texsprint(...) -- notcatcodes is active anyway end local xmltexhandler = xml.newhandlers { name = "tex", handle = tex_handle, functions = { -- ["@dc@"] = tex_document, ["@dt@"] = tex_doctype, -- ["@rt@"] = tex_root, ["@el@"] = tex_element, ["@pi@"] = tex_pi, ["@cm@"] = tex_comment, ["@cd@"] = tex_cdata, ["@tx@"] = tex_text, } } lxml.xmltexhandler = xmltexhandler function lxml.serialize(root) xmlserialize(root,xmltexhandler) end function lxml.setaction(id,pattern,action) local collected = lxmlparseapply(id,pattern) if collected then for c=1,#collected do collected[c].command = action end end end local function sprint(root) if root then local tr = type(root) if tr == "string" then -- can also be result of lpath -- rawroot = false lpegmatch(xmltextcapture,root) elseif tr == "table" then if forceraw then rawroot = root texwrite(xmltostring(root)) else xmlserialize(root,xmltexhandler) end end end end local function tprint(root) -- we can move sprint inline local tr = type(root) if tr == "table" then local n = #root if n == 0 then -- skip else for i=1,n do sprint(root[i]) end end elseif tr == "string" then lpegmatch(xmltextcapture,root) end end local function cprint(root) -- content if not root then -- rawroot = false -- quit elseif type(root) == 'string' then -- rawroot = false lpegmatch(xmltextcapture,root) else local rootdt = root.dt if forceraw then rawroot = root texwrite(xmltostring(rootdt or root)) else xmlserialize(rootdt or root,xmltexhandler) end end end xml.sprint = sprint local xmlsprint = sprint -- redo these names xml.tprint = tprint local xmltprint = tprint xml.cprint = cprint local xmlcprint = cprint -- now we can flush function lxml.main(id) xmlserialize(get_id(id),xmltexhandler) -- the real root (@rt@) end --~ -- lines (untested) --~ --~ local buffer = { } --~ --~ local xmllinescapture = ( --~ newline^2 / function() buffer[#buffer+1] = "" end + --~ newline / function() buffer[#buffer] = buffer[#buffer] .. " " end + --~ content / function(s) buffer[#buffer] = buffer[#buffer] .. s end --~ )^0 --~ --~ local xmllineshandler = table.copy(xmltexhandler) --~ --~ xmllineshandler.handle = function(...) lpegmatch(xmllinescapture,concat{ ... }) end --~ --~ function lines(root) --~ if not root then --~ -- rawroot = false --~ -- quit --~ elseif type(root) == 'string' then --~ -- rawroot = false --~ lpegmatch(xmllinescapture,root) --~ elseif next(root) then -- tr == 'table' --~ xmlserialize(root,xmllineshandler) --~ end --~ end --~ --~ function xml.lines(root) -- used at all? --~ buffer = { "" } --~ lines(root) --~ return result --~ end local function to_text(e) if e.command == nil then local etg = e.tg if etg and e.special and etg ~= "@rt@" then e.command = false -- i.e. skip else e.command = true -- i.e. no end end end local function to_none(e) if e.command == nil then e.command = false -- i.e. skip end end -- setups local setups = { } function lxml.set_command_to_text(id) xmlwithelements(get_id(id),to_text) end function lxml.set_command_to_none(id) xmlwithelements(get_id(id),to_none) end function lxml.installsetup(what,document,setup,where) document = document or "*" local sd = setups[document] if not sd then sd = { } setups[document] = sd end for k=1,#sd do if sd[k] == setup then sd[k] = nil break end end if what == 1 then if trace_loading then commands.writestatus("lxml","prepending setup %s for %s",setup,document) end insert(sd,1,setup) elseif what == 2 then if trace_loading then commands.writestatus("lxml","appending setup %s for %s",setup,document) end insert(sd,setup) elseif what == 3 then if trace_loading then commands.writestatus("lxml","inserting setup %s for %s before %s",setup,document,where) end table.insert_before_value(sd,setup,where) elseif what == 4 then if trace_loading then commands.writestatus("lxml","inserting setup %s for %s after %s",setup,document,where) end table.insert_after_value(sd,setup,where) end end function lxml.flushsetups(id,...) local done, list = { }, { ... } for i=1,#list do local document = list[i] local sd = setups[document] if sd then for k=1,#sd do local v= sd[k] if not done[v] then if trace_loading then commands.writestatus("lxml","applying setup %02i = %s to %s",k,v,document) end texsprint(ctxcatcodes,"\\xmlsetup{",id,"}{",v,"}") done[v] = true end end elseif trace_loading then commands.writestatus("lxml","no setups for %s",document) end end end function lxml.resetsetups(document) if trace_loading then commands.writestatus("lxml","resetting all setups for %s",document) end setups[document] = { } end function lxml.removesetup(document,setup) local s = setups[document] if s then for i=1,#s do if s[i] == setup then if trace_loading then commands.writestatus("lxml","removing setup %s for %s",setup,document) end remove(t,i) break end end end end function lxml.setsetup(id,pattern,setup) if not setup or setup == "" or setup == "*" or setup == "-" or setup == "+" then local collected = lxmlparseapply(id,pattern) if collected then if trace_setups then for c=1, #collected do local e = collected[c] local ix = e.ix or 0 if setup == "-" then e.command = false logs.report("lxml","lpath matched (a) %5i: %s = %s -> skipped",c,ix,setup) elseif setup == "+" then e.command = true logs.report("lxml","lpath matched (b) %5i: %s = %s -> text",c,ix,setup) else local tg = e.tg if tg then -- to be sure e.command = tg local ns = e.rn or e.ns if ns == "" then logs.report("lxml","lpath matched (c) %5i: %s = %s -> %s",c,ix,tg,tg) else logs.report("lxml","lpath matched (d) %5i: %s = %s:%s -> %s",c,ix,ns,tg,tg) end end end end else for c=1, #collected do local e = collected[c] if setup == "-" then e.command = false elseif setup == "+" then e.command = true else e.command = e.tg end end end elseif trace_setups then logs.report("lxml","no lpath matches for %s",pattern) end else local a, b = match(setup,"^(.+:)([%*%-])$") if a and b then local collected = lxmlparseapply(id,pattern) if collected then if trace_setups then for c=1, #collected do local e = collected[c] local ns, tg, ix = e.rn or e.ns, e.tg, e.ix or 0 if b == "-" then e.command = false if ns == "" then logs.report("lxml","lpath matched (e) %5i: %s = %s -> skipped",c,ix,tg) else logs.report("lxml","lpath matched (f) %5i: %s = %s:%s -> skipped",c,ix,ns,tg) end elseif b == "+" then e.command = true if ns == "" then logs.report("lxml","lpath matched (g) %5i: %s = %s -> text",c,ix,tg) else logs.report("lxml","lpath matched (h) %5i: %s = %s:%s -> text",c,ix,ns,tg) end else e.command = a .. tg if ns == "" then logs.report("lxml","lpath matched (i) %5i: %s = %s -> %s",c,ix,tg,e.command) else logs.report("lxml","lpath matched (j) %5i: %s = %s:%s -> %s",c,ix,ns,tg,e.command) end end end else for c=1, #collected do local e = collected[c] if b == "-" then e.command = false elseif b == "+" then e.command = true else e.command = a .. e.tg end end end elseif trace_setups then logs.report("lxml","no lpath matches for %s",pattern) end else local collected = lxmlparseapply(id,pattern) if collected then if trace_setups then for c=1, #collected do local e = collected[c] e.command = setup local ns, tg, ix = e.rn or e.ns, e.tg, e.ix or 0 if ns == "" then logs.report("lxml","lpath matched (k) %5i: %s = %s -> %s",c,ix,tg,setup) else logs.report("lxml","lpath matched (l) %5i: %s = %s:%s -> %s",c,ix,ns,tg,setup) end end else for c=1, #collected do collected[c].command = setup end end elseif trace_setups then logs.report("lxml","no lpath matches for %s",pattern) end end end end -- finalizers local finalizers = xml.finalizers.tex local function first(collected) if collected then xmlsprint(collected[1]) end end local function last(collected) if collected then xmlsprint(collected[#collected]) end end local function all(collected) if collected then for c=1,#collected do xmlsprint(collected[c]) end end end local function reverse(collected) if collected then for c=#collected,1,-1 do xmlsprint(collected[c]) end end end local function count(collected) texwrite((collected and #collected) or 0) end local function position(collected,n) -- todo: if not n then == match if collected then n = tonumber(n) or 0 if n < 0 then n = #collected + n + 1 end if n > 0 then xmlsprint(collected[n]) end end end local function match(collected) -- is match in preceding collected, never change, see bibxml texwrite((collected and collected[1].mi) or 0) end local function index(collected,n) if collected then n = tonumber(n) or 0 if n < 0 then n = #collected + n + 1 end if n > 0 then texwrite(collected[n].ni or 0) end end end local function command(collected,cmd) if collected then for c=1,#collected do local e = collected[c] local ix = e.ix if not ix then lxml.addindex(e.name,false,true) ix = e.ix end texsprint(ctxcatcodes,"\\xmlw{",cmd,"}{",e.name,"::",ix,"}") end end end local function attribute(collected,a,default) if collected and #collected > 0 then local at = collected[1].at local str = (at and at[a]) or default if str and str ~= "" then texsprint(notcatcodes,str) end elseif default then texsprint(notcatcodes,default) end end local function chainattribute(collected,arguments) -- todo: optional levels if collected then local e = collected[1] while e do local at = e.at if at then local a = at[arguments] if a then texsprint(notcatcodes,a) end else break -- error end e = e.__p__ end end end local function text(collected) if collected then local nc = #collected if nc == 1 then -- hardly any gain so this will go cprint(collected[1]) else for c=1,nc do cprint(collected[c]) end end end end local function ctxtext(collected) if collected then for c=1,#collected do texsprint(ctxcatcodes,collected[1].dt) end end end local lowerchars, upperchars = characters.lower, characters.upper local function lower(collected) if collected then for c=1,#collected do texsprint(ctxcatcodes,lowerchars(collected[1].dt[1])) end end end local function upper(collected) if collected then for c=1,#collected do texsprint(ctxcatcodes,upperchars(collected[1].dt[1])) end end end local function number(collected) if collected then local n = 0 for c=1,#collected do n = n + tonumber(collected[c].dt[1] or 0) end texwrite(n) end end local function concatrange(collected,start,stop,separator,lastseparator,textonly) -- test this on mml if collected then local nofcollected = #collected local separator = separator or "" local lastseparator = lastseparator or separator or "" start, stop = (start == "" and 1) or tonumber(start) or 1, (stop == "" and nofcollected) or tonumber(stop) or nofcollected if stop < 0 then stop = nofcollected + stop end -- -1 == last-1 for i=start,stop do if textonly then xmlcprint(collected[i]) else xmlsprint(collected[i]) end if i == nofcollected then -- nothing elseif i == nofcollected-1 and lastseparator ~= "" then texsprint(ctxcatcodes,lastseparator) elseif separator ~= "" then texsprint(ctxcatcodes,separator) end end end end local function concat(collected,separator,lastseparator,textonly) -- test this on mml concatrange(collected,false,false,separator,lastseparator,textonly) end finalizers.first = first finalizers.last = last finalizers.all = all finalizers.reverse = reverse finalizers.count = count finalizers.command = command finalizers.attribute = attribute finalizers.text = text finalizers.lower = lower finalizers.upper = upper finalizers.ctxtext = ctxtext finalizers.context = ctxtext finalizers.position = position finalizers.match = match finalizers.index = index finalizers.concat = concat finalizers.concatrange = concatrange finalizers.chainattribute = chainattribute finalizers.default = all -- !! local concat = table.concat function finalizers.tag(collected) if collected then local c if n == 0 or not n then c = collected[1] elseif n > 1 then c = collected[n] else c = collected[#collected-n+1] end if c then texsprint(c.tg) end end end function finalizers.name(collected) if collected then local c if n == 0 or not n then c = collected[1] elseif n > 1 then c = collected[n] else c = collected[#collected-n+1] end if c then if c.ns == "" then texsprint(c.tg) else texsprint(c.ns,":",c.tg) end end end end function finalizers.tags(collected,nonamespace) if collected then for c=1,#collected do local e = collected[c] local ns, tg = e.ns, e.tg if nonamespace or ns == "" then texsprint(tg) else texsprint(ns,":",tg) end end end end -- local function verbatim(id,before,after) local root = get_id(id) if root then if before then texsprint(ctxcatcodes,before,"[",root.tg or "?","]") end lxml.toverbatim(xmltostring(root.dt)) if after then texsprint(ctxcatcodes,after) end end end function lxml.inlineverbatim(id) verbatim(id,"\\startxmlinlineverbatim","\\stopxmlinlineverbatim") end function lxml.displayverbatim(id) verbatim(id,"\\startxmldisplayverbatim","\\stopxmldisplayverbatim") end lxml.verbatim = verbatim -- helpers function lxml.first(id,pattern) local collected = lxmlparseapply(id,pattern) if collected then first(collected) end end function lxml.last(id,pattern) local collected = lxmlparseapply(id,pattern) if collected then last(collected) end end function lxml.all(id,pattern) local collected = lxmlparseapply(id,pattern) if collected then all(collected) end end function lxml.count(id,pattern) -- always needs to produce a result so no test here count(lxmlparseapply(id,pattern)) end function lxml.attribute(id,pattern,a,default) local collected = lxmlparseapply(id,pattern) if collected then attribute(collected,a,default) end end function lxml.raw(id,pattern) -- the content, untouched by commands local collected = (pattern and lxmlparseapply(id,pattern)) or get_id(id) if collected then texsprint(xmltostring(collected[1].dt)) end end function lxml.context(id,pattern) -- the content, untouched by commands if not pattern then local collected = get_id(id) -- texsprint(ctxcatcodes,collected.dt[1]) ctx_text(collected.dt[1]) else local collected = lxmlparseapply(id,pattern) or get_id(id) if collected and #collected > 0 then texsprint(ctxcatcodes,collected[1].dt) end end end function lxml.text(id,pattern) local collected = (pattern and lxmlparseapply(id,pattern)) or get_id(id) if collected then text(collected) end end lxml.content = text function lxml.position(id,pattern,n) local collected = lxmlparseapply(id,pattern) if collected then position(collected,n) end end function lxml.chainattribute(id,pattern,a,default) local collected = lxmlparseapply(id,pattern) if collected then chainattribute(collected,a,default) end end function lxml.concatrange(id,pattern,start,stop,separator,lastseparator,textonly) -- test this on mml concatrange(lxmlparseapply(id,pattern),start,stop,separator,lastseparator,textonly) end function lxml.concat(id,pattern,separator,lastseparator,textonly) concatrange(lxmlparseapply(id,pattern),false,false,separator,lastseparator,textonly) end function lxml.element(id,n) position(lxmlparseapply(id,"/*"),n) end lxml.index = lxml.position function lxml.pos(id) local root = get_id(id) texwrite((root and root.ni) or 0) end function lxml.att(id,a,default) local root = get_id(id) if root then local at = root.at local str = (at and at[a]) or default if str and str ~= "" then texsprint(notcatcodes,str) end elseif default then texsprint(notcatcodes,default) end end function lxml.name(id) -- or remapped name? -> lxml.info, combine local r = get_id(id) local ns = r.rn or r.ns or "" if ns ~= "" then texsprint(ns,":",r.tg) else texsprint(r.tg) end end function lxml.match(id) -- or remapped name? -> lxml.info, combine texsprint(get_id(id).mi or 0) end function lxml.tag(id) -- tag vs name -> also in l-xml tag->name texsprint(get_id(id).tg or "") end function lxml.namespace(id) -- or remapped name? local root = get_id(id) texsprint(root.rn or root.ns or "") end function lxml.flush(id) id = get_id(id) local dt = id and id.dt if dt then xmlsprint(dt) end end function lxml.snippet(id,i) local e = get_id(id) if e then local edt = e.dt if edt then xmlsprint(edt[i]) end end end function lxml.direct(id) xmlsprint(get_id(id)) end function lxml.command(id,pattern,cmd) local i, p = get_id(id,true) local collected = lxmlparseapply(i,pattern) if collected then local rootname = p or i.name for c=1,#collected do local e = collected[c] local ix = e.ix if not ix then addindex(rootname,false,true) ix = e.ix end texsprint(ctxcatcodes,"\\xmlw{",cmd,"}{",rootname,"::",ix,"}") end end end -- loops function lxml.collected(id,pattern,reverse) return xmlcollected(get_id(id),pattern,reverse) end function lxml.elements(id,pattern,reverse) return xmlelements(get_id(id),pattern,reverse) end -- obscure ones lxml.info = lxml.name -- testers local found, empty = xml.found, xml.empty local doif, doifnot, doifelse = commands.doif, commands.doifnot, commands.doifelse function lxml.doif (id,pattern) doif (found(get_id(id),pattern)) end function lxml.doifnot (id,pattern) doifnot (found(get_id(id),pattern)) end function lxml.doifelse (id,pattern) doifelse(found(get_id(id),pattern)) end function lxml.doiftext (id,pattern) doif (not empty(get_id(id),pattern)) end function lxml.doifnottext (id,pattern) doifnot (not empty(get_id(id),pattern)) end function lxml.doifelsetext (id,pattern) doifelse(not empty(get_id(id),pattern)) end -- special case: "*" and "" -> self else lpath lookup --~ function lxml.doifelseempty(id,pattern) doifelse(isempty(get_id(id),pattern ~= "" and pattern ~= nil)) end -- not yet done, pattern -- status info statistics.register("xml load time", function() if noffiles > 0 or nofconverted > 0 then return format("%s seconds, %s files, %s converted", statistics.elapsedtime(xml), noffiles, nofconverted) else return nil end end) statistics.register("lxml preparation time", function() local calls, cached = xml.lpathcalls(), xml.lpathcached() if calls > 0 or cached > 0 then return format("%s seconds, %s nodes, %s lpath calls, %s cached calls", statistics.elapsedtime(lxml), nofindices, calls, cached) else return nil end end) statistics.register("lxml lpath profile", function() local p = xml.profiled if p and next(p) then local s = table.sortedkeys(p) local tested, matched, finalized = 0, 0, 0 texio.write_nl("log","\nbegin of lxml profile\n") texio.write_nl("log","\n tested matched finalized pattern\n\n") for i=1,#s do local pattern = s[i] local pp = p[pattern] local t, m, f = pp.tested, pp.matched, pp.finalized tested, matched, finalized = tested + t, matched + m, finalized + f texio.write_nl("log",format("%9i %9i %9i %s",t,m,f,pattern)) end texio.write_nl("log","\nend of lxml profile\n") return format("%s patterns, %s tested, %s matched, %s finalized (see log for details)",#s,tested,matched,finalized) else return nil end end) -- misc function lxml.nonspace(id,pattern) -- slow, todo loop xmltprint(xmlcollect(get_id(id),pattern,true)) end function lxml.strip(id,pattern,nolines,anywhere) xml.strip(get_id(id),pattern,nolines,anywhere) end function lxml.stripped(id,pattern,nolines) local str = xmltext(get_id(id),pattern) or "" str = gsub(str,"^%s*(.-)%s*$","%1") if nolines then str = gsub(str,"%s+"," ") end xmlsprint(str) end function lxml.delete(id,pattern) xml.delete(get_id(id),pattern) end