diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/lxml-aux.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/lxml-aux.lua | 1010 |
1 files changed, 1010 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/lxml-aux.lua b/Master/texmf-dist/tex/context/base/mkiv/lxml-aux.lua new file mode 100644 index 00000000000..4112db1e5fa --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkiv/lxml-aux.lua @@ -0,0 +1,1010 @@ +if not modules then modules = { } end modules ['lxml-aux'] = { + version = 1.001, + comment = "this module is the basis for the lxml-* ones", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- not all functions here make sense anymore vbut we keep them for +-- compatibility reasons + +local trace_manipulations = false trackers.register("lxml.manipulations", function(v) trace_manipulations = v end) +local trace_inclusions = false trackers.register("lxml.inclusions", function(v) trace_inclusions = v end) + +local report_xml = logs.reporter("xml") + +local xml = xml + +local xmlcopy, xmlname = xml.copy, xml.name +local xmlinheritedconvert = xml.inheritedconvert +local xmlapplylpath = xml.applylpath +local xmlfilter = xml.filter + +local type, next, setmetatable, getmetatable = type, next, setmetatable, getmetatable +local insert, remove, fastcopy, concat = table.insert, table.remove, table.fastcopy, table.concat +local gmatch, gsub, format, find, strip = string.gmatch, string.gsub, string.format, string.find, string.strip +local utfbyte = utf.byte +local lpegmatch = lpeg.match +local striplinepatterns = utilities.strings.striplinepatterns + +local function report(what,pattern,c,e) + report_xml("%s element %a, root %a, position %a, index %a, pattern %a",what,xmlname(e),xmlname(e.__p__),c,e.ni,pattern) +end + +local function withelements(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) + withelements(e,handle,depth+1) + end + end + end + end +end + +xml.withelements = withelements + +function xml.withelement(e,n,handle) -- slow + if e and n ~= 0 and handle then + local edt = e.dt + if edt then + if n > 0 then + for i=1,#edt do + local ei = edt[i] + if type(ei) == "table" then + if n == 1 then + handle(ei) + return + else + n = n - 1 + end + end + end + elseif n < 0 then + for i=#edt,1,-1 do + local ei = edt[i] + if type(ei) == "table" then + if n == -1 then + handle(ei) + return + else + n = n + 1 + end + end + end + end + end + end +end + +function xml.each(root,pattern,handle,reverse) + local collected = xmlapplylpath(root,pattern) + if collected then + if handle then + if reverse then + for c=#collected,1,-1 do + handle(collected[c]) + end + else + for c=1,#collected do + handle(collected[c]) + end + end + end + return collected + end +end + +function xml.processattributes(root,pattern,handle) + local collected = xmlapplylpath(root,pattern) + if collected and handle then + for c=1,#collected do + handle(collected[c].at) + end + end + return collected +end + +--[[ldx-- +<p>The following functions collect elements and texts.</p> +--ldx]]-- + +-- are these still needed -> lxml-cmp.lua + +function xml.collect(root, pattern) + return xmlapplylpath(root,pattern) +end + +function xml.collecttexts(root, pattern, flatten) -- todo: variant with handle + local collected = xmlapplylpath(root,pattern) + if collected and flatten then + local xmltostring = xml.tostring + for c=1,#collected do + collected[c] = xmltostring(collected[c].dt) + end + end + return collected or { } +end + +function xml.collect_tags(root, pattern, nonamespace) + local collected = xmlapplylpath(root,pattern) + if collected then + local t, n = { }, 0 + for c=1,#collected do + local e = collected[c] + local ns, tg = e.ns, e.tg + n = n + 1 + if nonamespace then + t[n] = tg + elseif ns == "" then + t[n] = tg + else + t[n] = ns .. ":" .. tg + end + end + return t + end +end + +--[[ldx-- +<p>We've now arrived at the functions that manipulate the tree.</p> +--ldx]]-- + +local no_root = { no_root = true } + +local function redo_ni(d) + for k=1,#d do + local dk = d[k] + if type(dk) == "table" then + dk.ni = k + end + end +end + +xml.reindex = redo_ni + +local function xmltoelement(whatever,root) + if not whatever then + return nil + end + local element + if type(whatever) == "string" then + element = xmlinheritedconvert(whatever,root) -- beware, not really a root + else + element = whatever -- we assume a table + end + if element.error then + return whatever -- string + end + if element then + --~ if element.ri then + --~ element = element.dt[element.ri].dt + --~ else + --~ element = element.dt + --~ end + end + return element +end + +xml.toelement = xmltoelement + +local function copiedelement(element,newparent) + if type(element) == "string" then + return element + else + element = xmlcopy(element).dt + if newparent and type(element) == "table" then + element.__p__ = newparent + end + return element + end +end + +function xml.delete(root,pattern) + if not pattern or pattern == "" then + local p = root.__p__ + if p then + if trace_manipulations then + report('deleting',"--",c,root) + end + local d = p.dt + remove(d,root.ni) + redo_ni(d) -- can be made faster and inlined + end + else + local collected = xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + local e = collected[c] + local p = e.__p__ + if p then + if trace_manipulations then + report('deleting',pattern,c,e) + end + local d = p.dt + local ni = e.ni + if ni <= #d then + if false then + p.dt[ni] = "" + else + -- what if multiple deleted in one set + remove(d,ni) + redo_ni(d) -- can be made faster and inlined + end + else + -- disturbing + end + end + end + end + end +end + +function xml.replace(root,pattern,whatever) + local element = root and xmltoelement(whatever,root) + local collected = element and xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + local e = collected[c] + local p = e.__p__ + if p then + if trace_manipulations then + report('replacing',pattern,c,e) + end + local d = p.dt + d[e.ni] = copiedelement(element,p) + redo_ni(d) -- probably not needed + end + end + end +end + +local function wrap(e,wrapper) + local t = { + rn = e.rn, + tg = e.tg, + ns = e.ns, + at = e.at, + dt = e.dt, + __p__ = e, + } + setmetatable(t,getmetatable(e)) + e.rn = wrapper.rn or e.rn or "" + e.tg = wrapper.tg or e.tg or "" + e.ns = wrapper.ns or e.ns or "" + e.at = fastcopy(wrapper.at) + e.dt = { t } +end + +function xml.wrap(root,pattern,whatever) + if whatever then + local wrapper = xmltoelement(whatever,root) + local collected = xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + local e = collected[c] + if trace_manipulations then + report('wrapping',pattern,c,e) + end + wrap(e,wrapper) + end + end + else + wrap(root,xmltoelement(pattern)) + end +end + +local function inject_element(root,pattern,whatever,prepend) + local element = root and xmltoelement(whatever,root) + local collected = element and xmlapplylpath(root,pattern) + local function inject_e(e) + local r = e.__p__ + local d, k, rri = r.dt, e.ni, r.ri + local edt = (rri and d[rri].dt) or (d and d[k] and d[k].dt) + if edt then + local be, af + local cp = copiedelement(element,e) + if prepend then + be, af = cp, edt + else + be, af = edt, cp + end + local bn = #be + for i=1,#af do + bn = bn + 1 + be[bn] = af[i] + end + if rri then + r.dt[rri].dt = be + else + d[k].dt = be + end + redo_ni(d) + end + end + if not collected then + -- nothing + elseif collected.tg then + -- first or so + inject_e(collected) + else + for c=1,#collected do + inject_e(collected[c]) + end + end +end + +local function insert_element(root,pattern,whatever,before) -- todo: element als functie + local element = root and xmltoelement(whatever,root) + local collected = element and xmlapplylpath(root,pattern) + local function insert_e(e) + local r = e.__p__ + local d, k = r.dt, e.ni + if not before then + k = k + 1 + end + insert(d,k,copiedelement(element,r)) + redo_ni(d) + end + if not collected then + -- nothing + elseif collected.tg then + -- first or so + insert_e(collected) + else + for c=1,#collected do + insert_e(collected[c]) + end + end +end + +xml.insert_element = insert_element +xml.insertafter = insert_element +xml.insertbefore = function(r,p,e) insert_element(r,p,e,true) end +xml.injectafter = inject_element +xml.injectbefore = function(r,p,e) inject_element(r,p,e,true) end + +-- loaddata can restrict loading + +local function include(xmldata,pattern,attribute,recursive,loaddata,level) + -- attribute = attribute or 'href' + pattern = pattern or 'include' + loaddata = loaddata or io.loaddata + local collected = xmlapplylpath(xmldata,pattern) + if collected then + if not level then + level = 1 + end + for c=1,#collected do + local ek = collected[c] + local name = nil + local ekdt = ek.dt + if ekdt then + local ekat = ek.at + local ekrt = ek.__p__ + if ekrt then + local epdt = ekrt.dt + if not attribute or attribute == "" then + name = (type(ekdt) == "table" and ekdt[1]) or ekdt -- check, probably always tab or str + end + if not name then + for a in gmatch(attribute or "href","([^|]+)") do + name = ekat[a] + if name then + break + end + end + end + local data = nil + if name and name ~= "" then + data = loaddata(name) or "" + if trace_inclusions then + report_xml("including %s bytes from %a at level %s by pattern %a and attribute %a (%srecursing)",#data,name,level,pattern,attribute or "",recursive and "" or "not ") + end + end + if not data or data == "" then + epdt[ek.ni] = "" -- xml.empty(d,k) + elseif ekat["parse"] == "text" then + -- for the moment hard coded + epdt[ek.ni] = xml.escaped(data) -- d[k] = xml.escaped(data) + else + local xi = xmlinheritedconvert(data,xmldata) + if not xi then + epdt[ek.ni] = "" -- xml.empty(d,k) + else + if recursive then + include(xi,pattern,attribute,recursive,loaddata,level+1) + end + local child = xml.body(xi) -- xml.assign(d,k,xi) + child.__p__ = ekrt + child.__f__ = name -- handy for tracing + epdt[ek.ni] = child + local settings = xmldata.settings + local inclusions = settings and settings.inclusions + if inclusions then + inclusions[#inclusions+1] = name + elseif settings then + settings.inclusions = { name } + else + settings = { inclusions = { name } } + xmldata.settings = settings + end + if child.er then + local badinclusions = settings.badinclusions + if badinclusions then + badinclusions[#badinclusions+1] = name + else + settings.badinclusions = { name } + end + end + end + end + end + end + end + end +end + +xml.include = include + +function xml.inclusion(e,default) + while e do + local f = e.__f__ + if f then + return f + else + e = e.__p__ + end + end + return default +end + +local function getinclusions(key,e,sorted) + while e do + local settings = e.settings + if settings then + local inclusions = settings[key] + if inclusions then + inclusions = table.unique(inclusions) -- a copy + if sorted then + table.sort(inclusions) -- so we sort the copy + end + return inclusions -- and return the copy + else + e = e.__p__ + end + else + e = e.__p__ + end + end +end + +function xml.inclusions(e,sorted) + return getinclusions("inclusions",e,sorted) +end + +function xml.badinclusions(e,sorted) + return getinclusions("badinclusions",e,sorted) +end + +local b_collapser = lpeg.patterns.b_collapser +local m_collapser = lpeg.patterns.m_collapser +local e_collapser = lpeg.patterns.e_collapser + +local b_stripper = lpeg.patterns.b_stripper +local m_stripper = lpeg.patterns.m_stripper +local e_stripper = lpeg.patterns.e_stripper + +local lpegmatch = lpeg.match + +local function stripelement(e,nolines,anywhere) + local edt = e.dt + if edt then + local n = #edt + if n == 0 then + return e -- convenient + elseif anywhere then + local t = { } + local m = 0 + for e=1,n do + local str = edt[e] + if type(str) ~= "string" then + m = m + 1 + t[m] = str + elseif str ~= "" then + if nolines then + str = lpegmatch((n == 1 and b_collapser) or (n == m and e_collapser) or m_collapser,str) + else + str = lpegmatch((n == 1 and b_stripper) or (n == m and e_stripper) or m_stripper,str) + end + if str ~= "" then + m = m + 1 + t[m] = str + end + end + end + e.dt = t + else + local str = edt[1] + if type(str) == "string" then + if str ~= "" then + str = lpegmatch(nolines and b_collapser or b_stripper,str) + end + if str == "" then + remove(edt,1) + n = n - 1 + else + edt[1] = str + end + end + if n > 0 then + str = edt[n] + if type(str) == "string" then + if str == "" then + remove(edt) + else + str = lpegmatch(nolines and e_collapser or e_stripper,str) + if str == "" then + remove(edt) + else + edt[n] = str + end + end + end + end + end + end + return e -- convenient +end + +xml.stripelement = stripelement + +function xml.strip(root,pattern,nolines,anywhere) -- strips all leading and trailing spacing + local collected = xmlapplylpath(root,pattern) -- beware, indices no longer are valid now + if collected then + for i=1,#collected do + stripelement(collected[i],nolines,anywhere) + end + end +end + +local function renamespace(root, oldspace, newspace) -- fast variant + local ndt = #root.dt + for i=1,ndt or 0 do + local e = root[i] + if type(e) == "table" then + if e.ns == oldspace then + e.ns = newspace + if e.rn then + e.rn = newspace + end + end + local edt = e.dt + if edt then + renamespace(edt, oldspace, newspace) + end + end + end +end + +xml.renamespace = renamespace + +function xml.remaptag(root, pattern, newtg) + local collected = xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + collected[c].tg = newtg + end + end +end + +function xml.remapnamespace(root, pattern, newns) + local collected = xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + collected[c].ns = newns + end + end +end + +function xml.checknamespace(root, pattern, newns) + local collected = xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + local e = collected[c] + if (not e.rn or e.rn == "") and e.ns == "" then + e.rn = newns + end + end + end +end + +function xml.remapname(root, pattern, newtg, newns, newrn) + local collected = xmlapplylpath(root,pattern) + if collected then + for c=1,#collected do + local e = collected[c] + e.tg, e.ns, e.rn = newtg, newns, newrn + end + end +end + +--[[ldx-- +<p>Helper (for q2p).</p> +--ldx]]-- + +function xml.cdatatotext(e) + local dt = e.dt + if #dt == 1 then + local first = dt[1] + if first.tg == "@cd@" then + e.dt = first.dt + end + else + -- maybe option + end +end + +-- local x = xml.convert("<x><a>1<b>2</b>3</a></x>") +-- xml.texttocdata(xml.first(x,"a")) +-- print(x) -- <x><![CDATA[1<b>2</b>3]]></x> + +function xml.texttocdata(e) -- could be a finalizer + local dt = e.dt + local s = xml.tostring(dt) -- no shortcut? + e.tg = "@cd@" + e.special = true + e.ns = "" + e.rn = "" + e.dt = { s } + e.at = nil +end + +-- local x = xml.convert("<x><a>1<b>2</b>3</a></x>") +-- xml.tocdata(xml.first(x,"a")) +-- print(x) -- <x><![CDATA[<a>1<b>2</b>3</a>]]></x> + +function xml.elementtocdata(e) -- could be a finalizer + local dt = e.dt + local s = xml.tostring(e) -- no shortcut? + e.tg = "@cd@" + e.special = true + e.ns = "" + e.rn = "" + e.dt = { s } + e.at = nil +end + +xml.builtinentities = table.tohash { "amp", "quot", "apos", "lt", "gt" } -- used often so share + +local entities = characters and characters.entities or nil +local builtinentities = xml.builtinentities + +function xml.addentitiesdoctype(root,option) -- we could also have a 'resolve' i.e. inline hex + if not entities then + require("char-ent") + entities = characters.entities + end + if entities and root and root.tg == "@rt@" and root.statistics then + local list = { } + local hexify = option == "hexadecimal" + for k, v in table.sortedhash(root.statistics.entities.names) do + if not builtinentities[k] then + local e = entities[k] + if not e then + e = format("[%s]",k) + elseif hexify then + e = format("&#%05X;",utfbyte(k)) + end + list[#list+1] = format(" <!ENTITY %s %q >",k,e) + end + end + local dt = root.dt + local n = dt[1].tg == "@pi@" and 2 or 1 + if #list > 0 then + insert(dt, n, { "\n" }) + insert(dt, n, { + tg = "@dt@", -- beware, doctype is unparsed + dt = { format("Something [\n%s\n] ",concat(list)) }, + ns = "", + special = true, + }) + insert(dt, n, { "\n\n" }) + else + -- insert(dt, n, { table.serialize(root.statistics) }) + end + end +end + +-- local str = [==[ +-- <?xml version='1.0' standalone='yes' ?> +-- <root> +-- <a>test test { test</a> +-- <b><![CDATA[oeps]]></b> +-- </root> +-- ]==] +-- +-- local x = xml.convert(str) +-- xml.addentitiesdoctype(x,"hexadecimal") +-- print(x) + +--[[ldx-- +<p>Here are a few synonyms.</p> +--ldx]]-- + +xml.all = xml.each +xml.insert = xml.insertafter +xml.inject = xml.injectafter +xml.after = xml.insertafter +xml.before = xml.insertbefore +xml.process = xml.each + +-- obsolete + +xml.obsolete = xml.obsolete or { } +local obsolete = xml.obsolete + +xml.strip_whitespace = xml.strip obsolete.strip_whitespace = xml.strip +xml.collect_elements = xml.collect obsolete.collect_elements = xml.collect +xml.delete_element = xml.delete obsolete.delete_element = xml.delete +xml.replace_element = xml.replace obsolete.replace_element = xml.replacet +xml.each_element = xml.each obsolete.each_element = xml.each +xml.process_elements = xml.process obsolete.process_elements = xml.process +xml.insert_element_after = xml.insertafter obsolete.insert_element_after = xml.insertafter +xml.insert_element_before = xml.insertbefore obsolete.insert_element_before = xml.insertbefore +xml.inject_element_after = xml.injectafter obsolete.inject_element_after = xml.injectafter +xml.inject_element_before = xml.injectbefore obsolete.inject_element_before = xml.injectbefore +xml.process_attributes = xml.processattributes obsolete.process_attributes = xml.processattributes +xml.collect_texts = xml.collecttexts obsolete.collect_texts = xml.collecttexts +xml.inject_element = xml.inject obsolete.inject_element = xml.inject +xml.remap_tag = xml.remaptag obsolete.remap_tag = xml.remaptag +xml.remap_name = xml.remapname obsolete.remap_name = xml.remapname +xml.remap_namespace = xml.remapnamespace obsolete.remap_namespace = xml.remapnamespace + +-- new (probably ok) + +function xml.cdata(e) + if e then + local dt = e.dt + if dt and #dt == 1 then + local first = dt[1] + return first.tg == "@cd@" and first.dt[1] or "" + end + end + return "" +end + +function xml.finalizers.xml.cdata(collected) + if collected then + local e = collected[1] + if e then + local dt = e.dt + if dt and #dt == 1 then + local first = dt[1] + return first.tg == "@cd@" and first.dt[1] or "" + end + end + end + return "" +end + +function xml.insertcomment(e,str,n) + insert(e.dt,n or 1,{ + tg = "@cm@", + ns = "", + special = true, + at = { }, + dt = { str }, + }) +end + +function xml.insertcdata(e,str,n) + insert(e.dt,n or 1,{ + tg = "@cd@", + ns = "", + special = true, + at = { }, + dt = { str }, + }) +end + +function xml.setcomment(e,str,n) + e.dt = { { + tg = "@cm@", + ns = "", + special = true, + at = { }, + dt = { str }, + } } +end + +function xml.setcdata(e,str) + e.dt = { { + tg = "@cd@", + ns = "", + special = true, + at = { }, + dt = { str }, + } } +end + +-- maybe helpers like this will move to an autoloader + +function xml.separate(x,pattern) + local collected = xmlapplylpath(x,pattern) + if collected then + for c=1,#collected do + local e = collected[c] + local d = e.dt + if d == x then + report_xml("warning: xml.separate changes root") + x = d + end + local t, n = { "\n" }, 1 + local i, nd = 1, #d + while i <= nd do + while i <= nd do + local di = d[i] + if type(di) == "string" then + if di == "\n" or find(di,"^%s+$") then -- first test is speedup + i = i + 1 + else + d[i] = strip(di) + break + end + else + break + end + end + if i > nd then + break + end + t[n+1] = "\n" + t[n+2] = d[i] + t[n+3] = "\n" + n = n + 3 + i = i + 1 + end + t[n+1] = "\n" + setmetatable(t,getmetatable(d)) + e.dt = t + end + end + return x +end + +-- + +local helpers = xml.helpers or { } +xml.helpers = helpers + +local function normal(e,action) + local edt = e.dt + if edt then + for i=1,#edt do + local str = edt[i] + if type(str) == "string" and str ~= "" then + edt[i] = action(str) + end + end + end +end + +local function recurse(e,action) + local edt = e.dt + if edt then + for i=1,#edt do + local str = edt[i] + if type(str) ~= "string" then + recurse(str,action) -- ,recursive + elseif str ~= "" then + edt[i] = action(str) + end + end + end +end + +function helpers.recursetext(collected,action,recursive) + if recursive then + for i=1,#collected do + recurse(collected[i],action) + end + else + for i=1,#collected do + normal(collected[i],action) + end + end +end + +-- on request ... undocumented ... +-- +-- _tag : element name +-- _type : node type (_element can be an option) +-- _namespace : only if given +-- +-- [1..n] : text or table +-- key : value or attribite 'key' +-- +-- local str = [[ +-- <?xml version="1.0" ?> +-- <a one="1"> +-- <!-- rubish --> +-- <b two="1"/> +-- <b two="2"> +-- c > d +-- </b> +-- </a> +-- ]] +-- +-- inspect(xml.totable(xml.convert(str))) +-- inspect(xml.totable(xml.convert(str),true)) +-- inspect(xml.totable(xml.convert(str),true,true)) + +local specials = { + ["@rt@"] = "root", + ["@pi@"] = "instruction", + ["@cm@"] = "comment", + ["@dt@"] = "declaration", + ["@cd@"] = "cdata", +} + +local function convert(x,strip,flat) + local ns = x.ns + local tg = x.tg + local at = x.at + local dt = x.dt + local node = flat and { + [0] = (not x.special and (ns ~= "" and ns .. ":" .. tg or tg)) or nil, + } or { + _namespace = ns ~= "" and ns or nil, + _tag = not x.special and tg or nil, + _type = specials[tg] or "_element", + } + if at then + for k, v in next, at do + node[k] = v + end + end + local n = 0 + for i=1,#dt do + local di = dt[i] + if type(di) == "table" then + if flat and di.special then + -- ignore + else + di = convert(di,strip,flat) + if di then + n = n + 1 + node[n] = di + end + end + elseif strip then + di = lpegmatch(strip,di) + if di ~= "" then + n = n + 1 + node[n] = di + end + else + n = n + 1 + node[n] = di + end + end + if next(node) then + return node + end +end + +function xml.totable(x,strip,flat) + if type(x) == "table" then + if strip then + strip = striplinepatterns[strip] + end + return convert(x,strip,flat) + end +end |