if not modules then modules = { } end modules ['lxml-ini'] = { version = 1.001, comment = "companion to lxml-ini.tex", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- todo: speed up: remember last index/match combination local texsprint, texprint = tex.sprint or print, tex.print or print local format, concat = string.format, table.concat local type, next, tonumber = type, next, tonumber document = document or { } document.xml = document.xml or { } -- todo: loaded and myself per document so that we can garbage collect buffers lxml = { } lxml.loaded = { } lxml.myself = { } local loaded = lxml.loaded local myself = lxml.myself lxml.self = myself -- be backward compatible for a while local function get_id(id) return (type(id) == "table" and id) or loaded[id] or myself[tonumber(id)] -- no need for tonumber if we pass without "" end lxml.id = get_id function lxml.root(id) return loaded[id] end do xml.specialhandler = xml.specialhandler or { } local specialhandler = xml.specialhandler local serialize = xml.serialize local crlf = lpeg.P("\r\n") local cr = lpeg.P("\r") local lf = lpeg.P("\n") local space = lpeg.S(" \t\f\v") local newline = crlf + cr + lf local spacing = space^0 * newline * space^0 local content = lpeg.C((1-spacing)^1) local verbose = lpeg.C((1-(space+newline))^1) local capture = ( newline^2 * lpeg.Cc("") / texprint + newline * lpeg.Cc(" ") / texsprint + content / texsprint )^0 local function sprint(root) if not root then -- quit else local tr = type(root) if tr == "string" then -- can also be result of lpath capture:match(root) elseif tr == "table" then serialize(root,sprint,nil,nil,specialhandler) end end end xml.sprint = sprint function xml.tprint(root) -- we can move sprint inline local tr = type(root) if tr == "table" then local n = #root if n == 0 then sprint("") -- empty element, else no setup triggered (check this! ) else for i=1,n do sprint(root[i]) end end elseif tr == "string" then sprint(root) end end function xml.cprint(root) -- content if not root then -- quit elseif type(root) == 'string' then capture:match(root) elseif root.dt then -- the main one serialize(root.dt,sprint,nil,nil,specialhandler) else -- probably dt serialize(root,sprint,nil,nil,specialhandler) end end -- lines (untested) local buffer = { } local capture = ( newline^2 / function() buffer[#buffer+1] = "" end + newline / function() buffer[#buffer] = buffer[#buffer] .. " " end + content / function(s) buffer[#buffer] = buffer[#buffer] .. s end )^0 function lines(root) if not root then -- quit elseif type(root) == 'string' then capture:match(root) elseif next(root) then -- tr == 'table' serialize(root, lines) end end function xml.lines(root) buffer = { "" } lines(root) return result end -- cdata local linecommand = "\\obeyedline" local spacecommand = "\\obeyedspace" -- "\\strut\\obeyedspace" local beforecommand = "" local aftercommand = "" local capture = ( newline / function( ) texsprint(tex.texcatcodes,linecommand .. "{}") end + verbose / function(s) texsprint(tex.vrbcatcodes,s) end + space / function( ) texsprint(tex.texcatcodes,spacecommand .. "{}") end )^0 function toverbatim(str) if beforecommand then texsprint(tex.texcatcodes,beforecommand .. "{}") end capture:match(str) if aftercommand then texsprint(tex.texcatcodes,aftercommand .. "{}") end end function lxml.set_verbatim(before,after,obeyedline,obeyedspace) beforecommand, aftercommand, linecommand, spacecommand = before, after, obeyedline, obeyedspace end function lxml.set_cdata() specialhandler['@cd@'] = toverbatim end function lxml.reset_cdata() specialhandler['@cd@'] = nil end function lxml.verbatim(id,before,after) local root = get_id(id) if before then texsprint(tex.ctxcatcodes,format("%s[%s]",before,root.tg)) end serialize(root.dt,toverbatim,nil,nil,nil,true) -- was root if after then texsprint(tex.ctxcatcodes,after) end end function lxml.inlineverbatim(id) lxml.verbatim(id,"\\startxmlinlineverbatim","\\stopxmlinlineverbatim") end function lxml.displayverbatim(id) lxml.verbatim(id,"\\startxmldisplayverbatim","\\stopxmldisplayverbatim") end local pihandlers = { } specialhandler['@pi@'] = function(str) for i=1,#pihandlers do pihandlers[i](str) end end xml.pihandlers = pihandlers local kind = lpeg.P("context-") * lpeg.C((1-lpeg.P("-"))^1) * lpeg.P("-directive") local space = lpeg.S(" \n\r") local spaces = space^0 local class = lpeg.C((1-space)^0) local key = class local value = lpeg.C(lpeg.P(1-(space * -1))^0) local parser = kind * spaces * class * spaces * key * spaces * value pihandlers[#pihandlers+1] = function(str) -- local kind, class, key, value = parser:match(str) texsprint(tex.ctxcatcodes,format("\\xmlcontextdirective{%s}{%s}{%s}{%s}",parser:match(str))) end -- print(contextdirective("context-mathml-directive function reduction yes yes ")) -- print(contextdirective("context-mathml-directive function ")) end local xmlsprint = xml.sprint local xmltprint = xml.tprint -- redefine xml load xml.originalload = xml.load function xml.load(filename) input.starttiming(xml) local xmldata = xml.convert((filename and input.loadtexfile(texmf.instance,filename)) or "") input.stoptiming(xml) return xmldata end function lxml.load(id,filename) loaded[id] = xml.load(filename) return loaded[id], filename end function lxml.include(id,pattern,attribute,recurse) input.starttiming(xml) xml.include(get_id(id),pattern,attribute,recurse,function(name) return (name and input.loadtexfile(texmf.instance,name)) or "" end) input.stoptiming(xml) end function lxml.utfize(id) xml.utfize(get_id(id)) end local xmlfilter, xmlfirst, xmllast, xmlall = xml.filter, xml.first, xml.last, xml.all local xmlcollect, xmlcontent, xmlcollect_texts, xmlcollect_tags = xml.collect, xml.content, xml.collect_texts, xml.collect_tags local xmlattribute, xmlindex = xml.filters.attribute, xml.filters.index local xmlelements = xml.elements function lxml.filter(id,pattern) xmlsprint(xmlfilter(get_id(id),pattern)) end function lxml.first(id,pattern) xmlsprint(xmlfirst(get_id(id),pattern)) end function lxml.last(id,pattern) xmlsprint(xmllast(get_id(id),pattern)) end function lxml.all(id,pattern) xmltprint(xmlcollect(get_id(id),pattern)) --~ faster, no intermediate table, we need to clean up l-xml --~ xml.traverse(get_id(id), xml.lpath(pattern), function(r,d,k) xmlsprint(d[k]) return false end) end function lxml.nonspace(id,pattern) xmltprint(xmlcollect(get_id(id),pattern,true)) end function lxml.content(id,pattern) xmlsprint(xmlcontent(get_id(id),pattern) or "") end function lxml.strip(id,pattern) xml.strip(get_id(id),pattern) end function lxml.text(id,pattern) xmltprint(xmlcollect_texts(get_id(id),pattern) or {}) end function lxml.tags(id,pattern) local tags = xmlcollect_tags(get_id(id),pattern) if tags then texsprint(concat(tags,",")) end end function lxml.raw(id,pattern) -- the content, untouched by commands local c = xmlfilter(get_id(id),pattern) if c then texsprint(concat(c.dt,"")) end end function lxml.snippet(id,i) local e = lxml.id(id) if e then local edt = e.dt xmlsprint((edt and edt[i]) or "") end end function lxml.stripped(id,pattern) local str = xmlcontent(get_id(id),pattern) or "" xmlsprint((str:gsub("^%s*(.-)%s*$","%1"))) end function lxml.flush(id) xmlsprint(get_id(id).dt) end function lxml.direct(id) xmlsprint(get_id(id)) end function lxml.index(id,pattern,i) xmlsprint((xmlindex(get_id(id),pattern,i))) end function lxml.attribute(id,pattern,a,default) --todo: snelle xmlatt local str = xmlattribute(get_id(id),pattern,a) or "" texsprint((str == "" and default) or str) end function lxml.count(id,pattern) texsprint(xml.count(get_id(id),pattern) or 0) end function lxml.name(id) -- or remapped name? -> lxml.info, combine local r = get_id(id) local ns = t.rn or r.ns or "" if ns ~= "" then texsprint(ns .. ":" .. r.tg) else texsprint(r.tg) end 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.concat(id,what,separator,lastseparator) --~ texsprint(concat(xml.collect_texts(get_id(id),what,true),separator or "")) --~ end function lxml.concatrange(id,what,start,stop,separator,lastseparator) -- test this on mml local t = xml.collect_elements(lxml.id(id),what,true) -- ignorespaces local separator = separator or "" local lastseparator = lastseparator or separator or "" start, stop = (start == "" and 1) or tonumber(start) or 1, (stop == "" and #t) or tonumber(stop) or #t if stop < 0 then stop = #t + stop end -- -1 == last-1 for i=start,stop do xmlsprint(t[i]) if i == #t then -- nothing elseif i == #t-1 and lastseparator ~= "" then texsprint(tex.ctxcatcodes,lastseparator) elseif separator ~= "" then texsprint(tex.ctxcatcodes,separator) end end end function lxml.concat(id,what,separator,lastseparator) lxml.concatrange(id,what,false,false,separator,lastseparator) end -- string : setup -- true : text (no ) -- false : ignore -- function : call -- todo: free self after usage, i.e. after the setup, which -- means a call to lua; we can also choose a proper maximum -- and cycle or maybe free on demand -- problems with empty elements -- we use a real tex.sprint, else spaces go wrong -- maybe just a .. because this happens often function xml.command(root, command) local tc = type(command) if tc == "string" then -- setup local n = #myself + 1 myself[n] = root texsprint(tex.ctxcatcodes,format("\\xmlsetup{%i}{%s}",n,command)) elseif tc == "function" then -- function command(root) elseif command == true then -- text (no ) / so, no mkii fallback then xmltprint(root.dt) elseif command == false then -- ignore else -- fuzzy, so ignore too end end function lxml.setaction(id,pattern,action) for rt, dt, dk in xmlelements(get_id(id),pattern) do dt[dk].command = action end end lxml.trace_setups = false function lxml.setsetup(id,pattern,setup) local trace = lxml.trace_setups if not setup or setup == "" or setup == "*" or setup == "-" or setup == "+" then for rt, dt, dk in xmlelements(get_id(id),pattern) do local dtdk = dt and dt[dk] or rt local ns, tg = dtdk.rn or dtdk.ns, dtdk.tg local command = (ns == "" and tg) or (ns .. ":" .. tg) if setup == "-" then dtdk.command = false if trace then texio.write_nl(format("lpath matched -> %s -> skipped", command)) end elseif setup == "+" then dtdk.command = true if trace then texio.write_nl(format("lpath matched -> %s -> text", command)) end else dtdk.command = command if trace then texio.write_nl(format("lpath matched -> %s -> %s", command, command)) end end end else local a, b = setup:match("^(.+:)([%*%-])$") if a and b then for rt, dt, dk in xmlelements(get_id(id),pattern) do local dtdk = (dt and dt[dk]) or rt local ns, tg = dtdk.rn or dtdk.ns, dtdk.tg if b == "-" then dtdk.command = false if trace then if ns == "" then texio.write_nl(format("lpath matched -> %s -> skipped", tg)) else texio.write_nl(format("lpath matched -> %s:%s -> skipped", ns, tg)) end end elseif b == "+" then dtdk.command = true if trace then if ns == "" then texio.write_nl(format("lpath matched -> %s -> text", tg)) else texio.write_nl(format("lpath matched -> %s:%s -> text", ns, tg)) end end else dtdk.command = a .. tg if trace then if ns == "" then texio.write_nl(format("lpath matched -> %s -> %s", tg, dtdk.command)) else texio.write_nl(format("lpath matched -> %s:%s -> %s", ns, tg, dtdk.command)) end end end end else if trace then texio.write_nl(format("lpath pattern -> %s -> %s", pattern, setup)) end for rt, dt, dk in xmlelements(get_id(id),pattern) do local dtdk = (dt and dt[dk]) or rt dtdk.command = setup if trace then local ns, tg = dtdk.rn or dtdk.ns, dtdk.tg if ns == "" then texio.write_nl(format("lpath matched -> %s -> %s", tg, setup)) else texio.write_nl(format("lpath matched -> %s:%s -> %s", ns, tg, setup)) end end end end end end function lxml.idx(id,pattern,i) -- hm, hashed, needed? local r = get_id(id) if r then local rp = r.patterns if not rp then rp = { } r.patterns = rp end if not rp[pattern] then rp[pattern] = xml.collect_elements(r,pattern) -- dd, rr end local rpi = rp[pattern] and rp[pattern][i] if rpi then xmlsprint(rpi) end end end function lxml.info(id) id = get_id(id) local ns, tg = id.ns, id.tg if ns and ns ~= "" then -- best make a function tg = ns .. ":" .. tg else tg = tg or "?" end texsprint(tg) end do local traverse = xml.traverse local lpath = xml.lpath local function command(root,pattern,cmd) -- met zonder '' cmd = cmd:gsub("^([\'\"])(.-)%1$", "%2") traverse(root, lpath(pattern), function(r,d,k) -- this can become pretty large local m = (d and d[k]) or r -- brrr this r, maybe away if type(m) == "table" then -- probbaly a bug local n = #myself + 1 myself[n] = m texsprint(tex.ctxcatcodes,format("\\xmlsetup{%s}{%s}",n,cmd)) end end) end xml.filters.command = command function lxml.command(id,pattern,cmd) command(get_id(id),pattern,cmd) end end do --~ --~ --~ --~ --~ --~ --~ lxml.directives = { } local data = { setup = { }, before = { }, after = { } } function lxml.directives.load(filename) if texmf then local fullname = input.find_file(texmf.instance,filename) or "" if fullname ~= "" then filename = fullname end end local root = xml.load(filename) for r, d, k in xmlelements(root,"directive") do local dk = d[k] local at = dk.at local attribute, value, element = at.attribute or "", at.value or "", at.element or '*' local setup, before, after = at.setup or "", at.before or "", at.after or "" if attribute ~= "" and value ~= "" then local key = format("%s::%s::%s",element,attribute,value) local t = data[key] or { } if setup ~= "" then t.setup = setup end if before ~= "" then t.before = before end if after ~= "" then t.after = after end data[key] = t end end end function lxml.directives.setup(root,attribute,element) lxml.directives.handle_setup('setup',root,attribute,element) end function lxml.directives.before(root,attribute,element) lxml.directives.handle_setup('before',root,attribute,element) end function lxml.directives.after(root,attribute,element) lxml.directives.handle_setup('after',root,attribute,element) end function lxml.directives.handle_setup(category,root,attribute,element) root = get_id(root) attribute = attribute if attribute then local value = root.at[attribute] if value then if not element then local ns, tg = root.rn or root.ns, root.tg if ns == "" then element = tg else element = ns .. ':' .. tg end end local setup = data[format("%s::%s::%s",element,attribute,value)] if setup then setup = setup[category] end if setup then texsprint(tex.ctxcatcodes,format("\\directsetup{%s}",setup)) else setup = data[format("%s::%s::*",element,attribute)] if setup then setup = setup[category] end if setup then texsprint(tex.ctxcatcodes,format("\\directsetup{%s}",setup:gsub('%*',value))) end end end end end end function xml.getbuffer(name) -- we need to make sure that commands are processed if not name or name == "" then name = tex.jobname end xml.tostring(xml.convert(concat(buffers.data[name] or {},""))) end function lxml.loadbuffer(id,name) if not name or name == "" then name = tex.jobname end input.starttiming(xml) loaded[id] = xml.convert(buffers.collect(name or id,"\n")) input.stoptiming(xml) return loaded[id], name or id end function lxml.loaddata(id,str) input.starttiming(xml) loaded[id] = xml.convert(str or "") input.stoptiming(xml) return loaded[id], id end -- for the moment here: lxml.set_verbatim("\\xmlcdatabefore", "\\xmlcdataafter", "\\xmlcdataobeyedline", "\\xmlcdataobeyedspace") lxml.set_cdata() do local traced = { } function lxml.trace_text_entities(str) return str:gsub("&(.-);",function(s) traced[s] = (traced[s] or 0) + 1 return "["..s.."]" end) end function lxml.show_text_entities() for k,v in ipairs(table.sortedkeys(traced)) do local h = v:match("^#x(.-)$") if h then local d = tonumber(h,16) local u = unicode.utf8.char(d) texio.write_nl(format("entity: %s / %s / %s / n=%s",h,d,u,traced[v])) else texio.write_nl(format("entity: %s / n=%s",v,traced[v])) end end end end -- yes or no ... do local function with_elements_only(e,handle) if e and handle then local etg = e.tg if etg then if e.special and etg ~= "@rt@" then if resthandle then resthandle(e) end else local edt = e.dt if edt then for i=1,#edt do local e = edt[i] if type(e) == "table" then handle(e) with_elements_only(e,handle) end end end end end end end local function with_elements_only(e,handle,depth) if e and handle then local edt = e.dt if edt then depth = depth or 0 for i=1,#edt do local e = edt[i] if type(e) == "table" then handle(e,depth) with_elements_only(e,handle,depth+1) end end end end end xml.with_elements_only = with_elements_only 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 -- can be made faster: just recurse over whole table, todo function lxml.set_command_to_text(id) xml.with_elements_only(get_id(id),to_text) end function lxml.set_command_to_none(id) xml.with_elements_only(get_id(id),to_none) end function lxml.get_command_status(id) local status, stack = {}, {} local function get(e,d) local ns, tg = e.ns, e.tg local name = tg if ns ~= "" then name = ns .. ":" .. tg end stack[d] = name local ec = e.command if ec == true then ec = "system: text" elseif ec == false then ec = "system: skip" elseif ec == nil then ec = "system: not set" elseif type(ec) == "string" then ec = "setup: " .. ec else -- function ec = tostring(ec) end local tag = table.concat(stack," => ",1,d) local s = status[tag] if not s then s = { } status[tag] = s end s[ec] = (s[ec] or 0) + 1 end if id then xml.with_elements_only(get_id(id),get) return status else local t = { } for id, _ in pairs(lxml.loaded) do t[id] = lxml.get_command_status(id) end return t end end end