summaryrefslogtreecommitdiff
path: root/scripts/context/lua/mtxrun.lua
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/context/lua/mtxrun.lua')
-rw-r--r--scripts/context/lua/mtxrun.lua1834
1 files changed, 1815 insertions, 19 deletions
diff --git a/scripts/context/lua/mtxrun.lua b/scripts/context/lua/mtxrun.lua
index caf43180f..2a3a496a3 100644
--- a/scripts/context/lua/mtxrun.lua
+++ b/scripts/context/lua/mtxrun.lua
@@ -147,6 +147,10 @@ function string.piecewise(str, pat, fnc) -- variant of split
for k in string.splitter(str,pat) do fnc(k) end
end
+--~ function string.piecewise(str, pat, fnc) -- variant of split
+--~ for k in str:splitter(pat) do fnc(k) end
+--~ end
+
--~ do if lpeg then
--~ -- this alternative is 30% faster esp when we cache them
@@ -447,11 +451,15 @@ if not table.fastcopy then
local new = { }
for k,v in pairs(old) do
if type(v) == "table" then
- new[k] = table.copy(v)
+ new[k] = table.fastcopy(v) -- was just table.copy
else
new[k] = v
end
end
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
return new
else
return { }
@@ -462,30 +470,32 @@ end
if not table.copy then
- function table.copy(t, _lookup_table) -- taken from lua wiki
- _lookup_table = _lookup_table or { }
+ function table.copy(t, tables) -- taken from lua wiki, slightly adapted
+ tables = tables or { }
local tcopy = {}
- if not _lookup_table[t] then
- _lookup_table[t] = tcopy
+ if not tables[t] then
+ tables[t] = tcopy
end
- for i,v in pairs(t) do
+ for i,v in pairs(t) do -- brrr, what happens with sparse indexed
if type(i) == "table" then
- if _lookup_table[i] then
- i = _lookup_table[i]
+ if tables[i] then
+ i = tables[i]
else
- i = table.copy(i, _lookup_table)
+ i = table.copy(i, tables)
end
end
if type(v) ~= "table" then
tcopy[i] = v
+ elseif tables[v] then
+ tcopy[i] = tables[v]
else
- if _lookup_table[v] then
- tcopy[i] = _lookup_table[v]
- else
- tcopy[i] = table.copy(v, _lookup_table)
- end
+ tcopy[i] = table.copy(v, tables)
end
end
+ local mt = getmetatable(t)
+ if mt then
+ setmetatable(tcopy,mt)
+ end
return tcopy
end
@@ -1090,6 +1100,31 @@ if not versions then versions = { } end versions['l-number'] = 1.001
if not number then number = { } end
+-- a,b,c,d,e,f = number.toset(100101)
+
+function number.toset(n)
+ return (tostring(n)):match("(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)")
+end
+
+-- the lpeg way is slower on 8 digits, but faster on 4 digits, some 7.5%
+-- on
+--
+-- for i=1,1000000 do
+-- local a,b,c,d,e,f,g,h = number.toset(12345678)
+-- local a,b,c,d = number.toset(1234)
+-- local a,b,c = number.toset(123)
+-- end
+--
+-- of course dedicated "(.)(.)(.)(.)" matches are even faster
+
+do
+ local one = lpeg.C(1-lpeg.S(''))^1
+
+ function number.toset(n)
+ return lpeg.match(one,tostring(n))
+ end
+end
+
-- filename : l-os.lua
@@ -1144,7 +1179,7 @@ function file.addsuffix(filename, suffix)
end
function file.replacesuffix(filename, suffix)
- return filename:gsub("%.%a+$", "." .. suffix)
+ return (filename:gsub("%.%a+$", "." .. suffix))
end
function file.dirname(name)
@@ -1156,7 +1191,7 @@ function file.basename(name)
end
function file.extname(name)
- return name:match("^.+%.(.-)$") or ""
+ return name:match("^.+%.([^/\\]-)$") or ""
end
function file.join(...)
@@ -1357,6 +1392,8 @@ function toboolean(str)
return str == "true" or str == "yes" or str == "on" or str == "1"
elseif type(str) == "number" then
return tonumber(str) ~= 0
+ elseif type(str) == "nil" then
+ return false
else
return str
end
@@ -1382,6 +1419,1755 @@ function boolean.falsetrue()
end
+if not modules then modules = { } end modules ['l-xml'] = {
+ 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"
+}
+
+-- todo: ns, tg = s:match("^(.-):?([^:]+)$")
+
+--[[ldx--
+<p>The parser used here is inspired by the variant discussed in the lua book, but
+handles comment and processing instructions, has a different structure, provides
+parent access; a first version used different tricky but was less optimized to we
+went this route.</p>
+
+<p>Expecially the lpath code is experimental, we will support some of xpath, but
+only things that make sense for us; as compensation it is possible to hook in your
+own functions. Apart from preprocessing content for <l n='context'/> we also need
+this module for process management, like handling <l n='ctx'/> and <l n='rlx'/>
+files.</p>
+
+<typing>
+a/b/c /*/c (todo: a/b/(pattern)/d)
+a/b/c/first() a/b/c/last() a/b/c/index(n) a/b/c/index(-n)
+a/b/c/text() a/b/c/text(1) a/b/c/text(-1) a/b/c/text(n)
+</typing>
+
+<p>Beware, the interface may change. For instance at, ns, tg, dt may get more
+verbose names. Once the code is stable we will also remove some tracing and
+optimize the code.</p>
+--ldx]]--
+
+xml = xml or { }
+tex = tex or { }
+
+xml.trace_lpath = false
+xml.trace_print = false
+
+--[[ldx--
+<p>First a hack to enable namespace resolving.</p>
+--ldx]]--
+
+do
+
+ xml.xmlns = { }
+
+ local data = { }
+
+ function xml.registerns(namespace,pattern)
+ data[#data+1] = { namespace:lower(), pattern:lower() }
+ end
+
+ function xml.checkns(namespace,url)
+ url = url:lower()
+ for i=1,#data do
+ local d = data[i]
+ if url:find(d[2]) then
+ if namespace ~= d[1] then
+ xml.xmlns[namespace] = d[1]
+ end
+ end
+ end
+ end
+
+ function xml.resolvens(url)
+ url = url:lower()
+ for i=1,#data do
+ local d = data[i]
+ if url:find(d[2]) then
+ return d[1]
+ end
+ end
+ return ""
+ end
+
+end
+
+--[[ldx--
+<p>Next comes the loader. The dreadful doctype comes in many disguises:</p>
+
+<typing>
+<!DOCTYPE Something PUBLIC "... ..." "..." [ ... ] >
+<!DOCTYPE Something PUBLIC "... ..." "..." >
+<!DOCTYPE Something SYSTEM "... ..." [ ... ] >
+<!DOCTYPE Something SYSTEM "... ..." >
+<!DOCTYPE Something [ ... ] >
+<!DOCTYPE Something >
+</typing>
+--ldx]]--
+
+do
+
+ -- Loading 12 cont-*.xml and keys-*.xml files totaling to 2.62 MBytes takes 1.1 sec
+ -- on a windows vista laptop with dual core 7600 (2.3 Ghz), which is not that bad.
+ -- Of this half time is spent on doctype etc parsing.
+
+ local doctype_patterns = {
+ "<!DOCTYPE%s+(.-%s+PUBLIC%s+%b\"\"%s+%b\"\"%s+%b[])%s*>",
+ "<!DOCTYPE%s+(.-%s+PUBLIC%s+%b\"\"%s+%b\"\")%s*>",
+ "<!DOCTYPE%s+(.-%s+SYSTEM%s+%b\"\"%s+%b[])%s*>",
+ "<!DOCTYPE%s+(.-%s+SYSTEM%s+%b\"\")%s*>",
+ "<!DOCTYPE%s+(.-%s%b[])%s*>",
+ "<!DOCTYPE%s+(.-)%s*>"
+ }
+
+ -- We assume no "<" which is the lunatic part of the xml spec
+ -- especially since ">" is permitted; otherwise we need a char
+ -- by char parser ... more something for later ... normally
+ -- entities will be used anyway.
+
+ -- data = data:gsub(nothing done) is still a copy so we find first
+
+ local function prepare(data,text)
+ -- pack (for backward compatibility)
+ if type(data) == "table" then
+ data = table.concat(data,"")
+ end
+ -- CDATA
+ if data:find("<%!%[CDATA%[") then
+ data = data:gsub("<%!%[CDATA%[(.-)%]%]>", function(txt)
+ text[#text+1] = txt or ""
+ return string.format("<@cd@>%s</@cd@>",#text)
+ end)
+ end
+ -- DOCTYPE
+ if data:find("<!DOCTYPE ") then
+ data = data:gsub("^(.-)(<[^%!%?])", function(a,b)
+ if a:find("<!DOCTYPE ") then
+ for _,v in ipairs(doctype_patterns) do
+ a = a:gsub(v, function(d)
+ text[#text+1] = d or ""
+ return string.format("<@dd@>%s</@dd@>",#text)
+ end)
+ end
+ end
+ return a .. b
+ end,1)
+ end
+ -- comment / does not catch doctype
+ data = data:gsub("<%!%-%-(.-)%-%->", function(txt)
+ text[#text+1] = txt or ""
+ return string.format("<@cm@>%s</@cm@>",#text)
+ end)
+ -- processing instructions / altijd 1
+ data = data:gsub("<%?(.-)%?>", function(txt)
+ text[#text+1] = txt or ""
+ return string.format("<@pi@>%s</@pi@>",#text)
+ end)
+ return data, text
+ end
+
+ -- maybe we will move the @tg@ stuff to a dedicated key, say 'st'; this will speed up
+ -- serializing and testing
+
+ function xml.convert(data,no_root,collapse)
+ local crap = { }
+ data, crap = prepare(data, crap)
+ local nsremap = xml.xmlns
+ local remove = table.remove
+ local stack, top = {}, {}
+ local i, j, errorstr = 1, 1, nil
+ stack[#stack+1] = top
+ top.dt = { }
+ local dt = top.dt
+ local id = 0
+ local namespaces = { }
+ local mt = { __tostring = xml.text }
+ while true do
+ local ni, first, attributes, last, fulltag
+ ni, j, first, fulltag, attributes, last = data:find("<(/-)([^%s%>/]+)%s*([^>]-)%s*(/-)>", j)
+ if not ni then break end
+ local namespace, tag = fulltag:match("^(.-):(.+)$")
+ if attributes ~= "" then
+ local t = {}
+ for ns, tag, _, value in attributes:gmatch("(%w-):?(%w+)=([\"\'])(.-)%3") do
+ if tag == "xmlns" then -- not ok yet
+ namespaces[#stack] = xml.resolvens(value)
+ elseif ns == "" then
+ t[tag] = value
+ elseif ns == "xmlns" then
+ xml.checkns(tag,value)
+ else
+ t[tag] = value
+ end
+ end
+ attributes = t
+ else
+ attributes = { }
+ end
+ if namespace then -- realtime remapping
+ namespace = nsremap[namespace] or namespace
+ else
+ namespace, tag = namespaces[#stack] or "", fulltag
+ end
+ local text = data:sub(i, ni-1)
+ if text == "" or (collapse and text:find("^%s*$")) then
+ -- no need for empty text nodes, beware, also packs <a>x y z</a>
+ -- so is not that useful unless used with empty elements
+ else
+ dt[#dt+1] = text
+ end
+ if first == "/" then
+ -- end tag
+ local toclose = remove(stack) -- remove top
+ top = stack[#stack]
+ namespaces[#stack] = nil
+ if #stack < 1 then
+ errorstr = string.format("nothing to close with %s", tag)
+ break
+ elseif toclose.tg ~= tag then -- no namespace check
+ errorstr = string.format("unable to close %s with %s", toclose.tg, tag)
+ break
+ end
+ if tag:find("^@..@$") then
+ dt[1] = crap[tonumber(dt[1])] or ""
+ end
+ dt = top.dt
+ dt[#dt+1] = toclose
+ elseif last == "/" then
+ -- empty element tag
+ dt[#dt+1] = { ns = namespace, tg = tag, dt = { }, at = attributes, __p__ = top }
+ -- setmetatable(top, { __tostring = xml.text })
+ setmetatable(top, mt)
+ else
+ -- begin tag
+ top = { ns = namespace, tg = tag, dt = { }, at = attributes, __p__ = stack[#stack] }
+ -- setmetatable(top, { __tostring = xml.text })
+ setmetatable(top, mt)
+ dt = top.dt
+ stack[#stack+1] = top
+ end
+ i = j + 1
+ end
+ if not errorstr then
+ local text = data:sub(i)
+ if dt and not text:find("^%s*$") then
+ dt[#dt+1] = text
+ end
+ if #stack > 1 then
+ errorstr = string.format("unclosed %s", stack[#stack].tg)
+ end
+ end
+ if errorstr then
+ stack = { { tg = "error", dt = { errorstr } } }
+ -- setmetatable(stack, { __tostring = xml.text })
+ setmetatable(stack, mt)
+ end
+ if no_root then
+ return stack[1]
+ else
+ local t = { ns = "", tg = '@rt@', dt = stack[1].dt }
+ -- setmetatable(t, { __tostring = xml.text })
+ setmetatable(t, mt)
+ for k,v in ipairs(t.dt) do
+ if type(v) == "table" and v.tg ~= "@pi@" and v.tg ~= "@dd@" and v.tg ~= "@cm@" then
+ t.ri = k -- rootindex
+ break
+ end
+ end
+ return t
+ end
+ end
+
+ function xml.copy(old,tables,parent) -- fast one
+ tables = tables or { }
+ if old then
+ local new = { }
+ if not table[old] then
+ table[old] = new
+ end
+ for i,v in pairs(old) do
+ -- new[i] = (type(v) == "table" and (table[v] or xml.copy(v, tables, table))) or v
+ if type(v) == "table" then
+ new[i] = table[v] or xml.copy(v, tables, table)
+ else
+ new[i] = v
+ end
+ end
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
+ return new
+ else
+ return { }
+ end
+ end
+
+end
+
+function xml.load(filename,collapse)
+ if type(filename) == "string" then
+ local root, f = { }, io.open(filename,'r') -- no longer 'rb'
+ if f then
+ root = xml.convert(f:read("*all"),false,collapse)
+ f:close()
+ end
+ return root
+ else
+ return xml.convert(filename:read("*all"),false,collapse)
+ end
+end
+
+function xml.root(root)
+ return (root.ri and root.dt[root.ri]) or root
+end
+
+function xml.toxml(data,collapse)
+ local t = { xml.convert(data,true,collapse) }
+ if #t > 1 then
+ return t
+ else
+ return t[1]
+ end
+end
+
+function xml.serialize(e, handle, textconverter, attributeconverter)
+ handle = handle or (tex and tex.sprint) or io.write
+ if not e then
+ -- quit
+ elseif e.command and xml.command then -- test for command == "" ?
+ xml.command(e)
+ elseif e.tg then
+ local format, serialize = string.format, xml.serialize
+ local ens, etg, eat, edt = e.ns, e.tg, e.at, e.dt
+ -- no spaces, so no flush needed (check)
+ if etg == "@pi@" then
+ handle(format("<?%s?>",edt[1]))
+ elseif etg == "@cm@" then
+ handle(format("<!--%s-->",edt[1]))
+ elseif etg == "@cd@" then
+ handle(format("<![CDATA[%s]]>",edt[1]))
+ elseif etg == "@dd@" then
+ handle(format("<!DOCTYPE %s>",edt[1]))
+ elseif etg == "@rt@" then
+ serialize(edt,handle,textconverter,attributeconverter)
+ else
+ local ats = eat and next(eat) and { }
+ if ats then
+ if attributeconverter then
+ for k,v in pairs(eat) do
+ ats[#ats+1] = format('%s=%q',k,attributeconverter(v))
+ end
+ else
+ for k,v in pairs(eat) do
+ ats[#ats+1] = format('%s=%q',k,v)
+ end
+ end
+ end
+ if ens ~= "" then
+ if edt and #edt > 0 then
+ if ats then
+ handle(format("<%s:%s %s>",ens,etg,table.concat(ats," ")))
+ else
+ handle(format("<%s:%s>",ens,etg))
+ end
+ for i=1,#edt do
+ serialize(edt[i],handle,textconverter,attributeconverter)
+ end
+ handle(format("</%s:%s>",ens,etg))
+ else
+ if ats then
+ handle(format("<%s:%s %s/>",ens,etg,table.concat(ats," ")))
+ else
+ handle(format("<%s:%s/>",ens,etg))
+ end
+ end
+ else
+ if edt and #edt > 0 then
+ if ats then
+ handle(format("<%s %s>",etg,table.concat(ats," ")))
+ else
+ handle(format("<%s>",etg))
+ end
+ for i=1,#edt do
+ serialize(edt[i],handle,textconverter,attributeconverter)
+ end
+ handle(format("</%s>",etg))
+ else
+ if ats then
+ handle(format("<%s %s/>",etg,table.concat(ats," ")))
+ else
+ handle(format("<%s/>",etg))
+ end
+ end
+ end
+ end
+ elseif type(e) == "string" then
+ if textconverter then
+ handle(textconverter(e))
+ else
+ handle(e)
+ end
+ else
+ for i=1,#e do
+ xml.serialize(e[i],handle,textconverter,attributeconverter)
+ end
+ end
+end
+
+function xml.string(e,handle) -- weird one that may become obsolete
+ if e.tg then
+ local edt = e.dt
+ if edt then
+ for i=1,#edt do
+ xml.string(edt[i],handle)
+ end
+ end
+ else
+ handle(e)
+ end
+end
+
+function xml.save(root,name)
+ local f = io.open(name,"w")
+ if f then
+ xml.serialize(root,function(s) f:write(s) end)
+ f:close()
+ end
+end
+
+function xml.stringify(root)
+ if root then
+ if type(root) == 'string' then
+ return root
+ elseif next(root) then
+ local result = { }
+ xml.serialize(root,function(s) result[#result+1] = s end)
+ return table.concat(result,"")
+ end
+ end
+ return ""
+end
+
+xml.tostring = xml.stringify
+
+do
+
+ -- print
+
+ local newline = lpeg.P("\n")
+ local space = lpeg.P(" ")
+ local content = lpeg.C((1-newline)^1)
+
+ if tex then
+
+ -- taco: we need a kind of raw print into tex, i.e. embedded \n's become lineendings
+ -- for tex and an empty line a par; could be a c-wrapper around existing stuff; i
+ -- played a lot with tex.print but that does not work ok (should be obeylines save)
+
+ local buffer = {}
+
+ local function cprint(s)
+ buffer[#buffer+1] = s
+ end
+ local function nprint( )
+ if #buffer > 0 then
+ if xml.trace_print then
+ texio.write_nl(string.format("tex.print : [[[%s]]]", table.join(buffer)))
+ end
+ tex.print(table.join(buffer))
+ buffer = {}
+ else
+ if xml.trace_print then
+ texio.write_nl(string.format("tex.print : [[[%s]]]", ""))
+ end
+ tex.print("")
+ end
+ end
+ local function fprint()
+ if #buffer > 0 then
+ if xml.trace_print then
+ texio.write_nl(string.format("tex.sprint: [[[%s]]]", table.join(buffer)))
+ end
+ tex.sprint(table.join(buffer))
+ buffer = { }
+ end
+ end
+
+ local line_n = newline / nprint
+ local line_c = content / cprint
+ local capture = (line_n + line_c)^0
+
+ local function sprint(root)
+ if not root then
+ -- quit
+ elseif type(root) == 'string' then
+ lpeg.match(capture,root)
+ elseif next(root) then
+ xml.serialize(root, sprint, nil, nil, true)
+ end
+ end
+
+ function xml.sprint(root)
+ buffer = {}
+ sprint(root)
+ if #buffer > 0 then
+ nprint()
+ end
+ end
+
+ xml.sflush = fprint
+
+ else
+
+ function xml.sprint(root)
+ if not root then
+ -- quit
+ elseif type(root) == 'string' then
+ print(root)
+ elseif next(root) then
+ xml.serialize(root, xml.sprint, nil, nil, true)
+ end
+ end
+
+ end
+
+ function xml.tprint(root)
+ if type(root) == "table" then
+ for i=1,#root do
+ xml.sprint(root[i])
+ end
+ elseif type(root) == "string" then
+ xml.sprint(root)
+ end
+ end
+
+ -- lines (looks hackery, but we cannot pass variables in capture functions)
+
+ local buffer, flush = {}, nil
+
+ local function cprint(s)
+ buffer[#buffer+1] = s
+ end
+ local function nprint()
+ flush()
+ end
+
+ local line_n = newline / nprint
+ local line_c = content / cprint
+ local capture = (line_n + line_c)^0
+
+ function lines(root)
+ if not root then
+ -- quit
+ elseif type(root) == 'string' then
+ lpeg.match(capture,root)
+ elseif next(root) then
+ xml.serialize(root, lines)
+ end
+ end
+
+ function xml.lines(root)
+ local result = { }
+ flush = function()
+ result[#result+1] = table.join(buffer)
+ buffer = { }
+ end
+ buffer = {}
+ lines(root)
+ if #buffer > 0 then
+ result[#result+1] = table.join(buffer)
+ end
+ return result
+ end
+
+end
+
+function xml.text(root)
+ return (root and xml.stringify(root)) or ""
+end
+
+function xml.content(root)
+ return (root and root.dt and xml.tostring(root.dt)) or ""
+end
+
+function xml.body(t) -- removes initial pi
+ if t and t.dt and t.tg == "@rt@" then
+ for k,v in ipairs(t.dt) do
+ if type(v) == "table" and v.tg ~= "@pi@" then
+ return v
+ end
+ end
+ end
+ return t
+end
+
+-- call: e[k] = xml.empty() or xml.empty(e,k)
+
+function xml.empty(e,k) -- erases an element but keeps the table intact
+ if e and k then
+ e[k] = ""
+ return e[k]
+ else
+ return ""
+ end
+end
+
+-- call: e[k] = xml.assign(t) or xml.assign(e,k,t)
+
+function xml.assign(e,k,t) -- assigns xml tree / more testing will be done
+ if e and k then
+ if type(t) == "table" then
+ e[k] = xml.body(t)
+ else
+ e[k] = t -- no parsing
+ end
+ return e[k]
+ else
+ return xml.body(t)
+ end
+end
+
+-- 0=nomatch 1=match 2=wildcard 3=ancestor
+
+-- "tag"
+-- "tag1/tag2/tag3"
+-- "*/tag1/tag2/tag3"
+-- "/tag1/tag2/tag3"
+-- "/tag1/tag2|tag3"
+-- "tag[@att='value']
+-- "tag1|tag2[@att='value']
+
+function xml.tag(e)
+ return e.tg or ""
+end
+
+function xml.att(e,a)
+ return (e.at and e.at[a]) or ""
+end
+
+xml.attribute = xml.att
+
+--~ local cache = { }
+
+--~ local function f_fault ( ) return 0 end
+--~ local function f_wildcard( ) return 2 end
+--~ local function f_result (b) if b then return 1 else return 0 end end
+
+--~ function xml.lpath(str) --maybe @rt@ special
+--~ if not str or str == "" then
+--~ str = "*"
+--~ end
+--~ local m = cache[str]
+--~ if not m then
+--~ -- todo: text()
+--~ if type(str) == "table" then
+--~ if xml.trace_lpath then print("lpath", "table" , "inherit") end
+--~ m = str
+--~ elseif str == "/" then
+--~ if xml.trace_lpath then print("lpath", "/", "root") end
+--~ m = false
+--~ elseif str == "*" then
+--~ if xml.trace_lpath then print("lpath", "no string or *", "wildcard") end
+--~ m = true
+--~ else
+--~ str = str:gsub("^//","") -- any
+--~ if str == "" then
+--~ if xml.trace_lpath then print("lpath", "//", "wildcard") end
+--~ m = true
+--~ else
+--~ m = { }
+--~ if not str:find("^/") then
+--~ m[1] = 2
+--~ end
+--~ for v in str:gmatch("([^/]+)") do
+--~ if v == "" or v == "*" then
+--~ if #m > 0 then -- when not, then we get problems with root being second (after <?xml ...?> (we could start at dt[2])
+--~ if xml.trace_lpath then print("lpath", "empty or *", "wildcard") end
+--~ m[#m+1] = 2
+--~ end
+--~ elseif v == ".." then
+--~ if xml.trace_lpath then print("lpath", "..", "ancestor") end
+--~ m[#m+1] = 3
+--~ else
+--~ local a, b = v:match("^(.+)::(.-)$")
+--~ if a and b then
+--~ if a == "ancestor" then
+--~ if xml.trace_lpath then print("lpath", a, "ancestor") end
+--~ m[#m+1] = 3
+--~ -- todo: b
+--~ elseif a == "pi" then
+--~ if xml.trace_lpath then print("lpath", a, "processing instruction") end
+--~ local expr = "^" .. b .. " "
+--~ m[#m+1] = function(e)
+--~ if e.tg == '@pi@' and e.dt[1]:find(expr) then
+--~ return 6
+--~ else
+--~ return 0
+--~ end
+--~ end
+--~ end
+--~ else
+--~ local n, a, t = v:match("^(.-)%[@(.-)=(.-)%]$")
+--~ if n and a and t then
+--~ -- todo: namespace, negate
+--~ -- t = t:gsub("^\'(.*)\'$", "%1")
+--~ -- t = t:gsub("^\"(.*)\"$", "%1")
+--~ -- t = t:sub(2,-2) -- "" or '' mandate
+--~ t = t:gsub("^([\'\"])(.-)%1$", "%2")
+--~ if n:find("|") then
+--~ local tt = n:split("|")
+--~ if xml.trace_lpath then print("lpath", "match", t, n) end
+--~ m[#m+1] = function(e,i)
+--~ for i=1,#tt do
+--~ if e.at and e.tg == tt[i] and e.at[a] == t then return 1 end
+--~ end
+--~ return 0
+--~ end
+--~ else
+--~ if xml.trace_lpath then print("lpath", "match", t, n) end
+--~ m[#m+1] = function(e)
+--~ if e.at and e.ns == s and e.tg == n and e.at[a] == t then
+--~ return 1
+--~ else
+--~ return 0
+--~ end
+--~ end
+--~ end
+--~ else -- todo, better tracing (string.format, ook negate etc)
+--~ local negate = v:sub(1,1) == '^'
+--~ if negate then v = v:sub(2) end
+--~ if v:find("|") then
+--~ local t = { }
+--~ for s in v:gmatch("([^|]+)") do
+--~ local ns, tg = s:match("^(.-):(.+)$")
+--~ if tg == "*" then
+--~ if xml.trace_lpath then print("lpath", "or wildcard", ns, tg) end
+--~ t[#t+1] = function(e) return e.ns == ns end
+--~ elseif tg then
+--~ if xml.trace_lpath then print("lpath", "or match", ns, tg) end
+--~ t[#t+1] = function(e) return e.ns == ns and e.tg == tg end
+--~ else
+--~ if xml.trace_lpath then print("lpath", "or match", s) end
+--~ t[#t+1] = function(e) return e.ns == "" and e.tg == s end
+--~ end
+--~ end
+--~ if negate then
+--~ m[#m+1] = function(e)
+--~ for i=1,#t do if t[i](e) then return 0 end end return 1
+--~ end
+--~ else
+--~ m[#m+1] = function(e)
+--~ for i=1,#t do if t[i](e) then return 1 end end return 0
+--~ end
+--~ end
+--~ else
+--~ if xml.trace_lpath then print("lpath", "match", v) end
+--~ local ns, tg = v:match("^(.-):(.+)$")
+--~ if not tg then ns, tg = "", v end
+--~ if tg == "*" then
+--~ if ns ~= "" then
+--~ m[#m+1] = function(e)
+--~ if ns == e.ns then return 1 else return 0 end
+--~ end
+--~ end
+--~ elseif negate then
+--~ m[#m+1] = function(e)
+--~ if ns == e.ns and tg == e.tg then return 0 else return 1 end
+--~ end
+--~ else
+--~ m[#m+1] = function(e)
+--~ if ns == e.ns and tg == e.tg then return 1 else return 0 end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ if xml.trace_lpath then
+--~ print("# lpath criteria:", (type(m) == "table" and #m) or "none")
+--~ end
+--~ cache[str] = m
+--~ end
+--~ return m
+--~ end
+
+--~ -- if handle returns true, then quit
+
+--~ function xml.traverse(root,pattern,handle,reverse,index,wildcard)
+--~ if not root then -- error
+--~ return false
+--~ elseif pattern == false then -- root
+--~ handle(root,root.dt,root.ri)
+--~ return false
+--~ elseif pattern == true then -- wildcard
+--~ local traverse = xml.traverse
+--~ local rootdt = root.dt
+--~ if rootdt then
+--~ local start, stop, step = 1, #rootdt, 1
+--~ if reverse then
+--~ start, stop, step = stop, start, -1
+--~ end
+--~ for k=start,stop,step do
+--~ if handle(root,rootdt,root.ri or k) then return false end
+--~ if not traverse(rootdt[k],true,handle,reverse) then return false end
+--~ end
+--~ end
+--~ return false
+--~ elseif root and root.dt then
+--~ index = index or 1
+--~ local match = pattern[index] or f_wildcard
+--~ local traverse = xml.traverse
+--~ local rootdt = root.dt
+--~ local start, stop, step = 1, #rootdt, 1
+--~ if reverse and index == #pattern then -- maybe no index test here / error?
+--~ start, stop, step = stop, start, -1
+--~ end
+--~ for k=start,stop,step do
+--~ local e = rootdt[k]
+--~ if e.tg then
+--~ local m = (type(match) == "function" and match(e,root)) or match
+--~ if m == 1 then -- match
+--~ if index < #pattern then
+--~ if not traverse(e,pattern,handle,reverse,index+1) then return false end
+--~ else
+--~ if handle(root,rootdt,root.ri or k) then
+--~ return false
+--~ end
+--~ -- tricky, where do we pick up, is this ok now
+--~ if pattern[1] == 2 then -- start again with new root (we need a way to inhibit this)
+--~ if not traverse(e,pattern,handle,reverse,1) then return false end
+--~ end
+--~ end
+--~ elseif m == 2 then -- wildcard
+--~ if index < #pattern then
+--~ -- <parent><a><b></b><c></c></a></parent> : "a" (true) "/a" (true) "b" (true) "/b" (false)
+--~ -- not good yet, we need to pick up any prev level which is 2
+--~ local p = pattern[2]
+--~ if index == 1 and p then
+--~ local mm = (type(p) == "function" and p(e,root)) or p -- pattern[2](e,root)
+--~ if mm == 1 then
+--~ if #pattern == 2 then
+--~ if handle(root,rootdt,k) then
+--~ return false
+--~ end
+--~ -- hack
+--~ if pattern[1] == 2 then -- start again with new root (we need a way to inhibit this)
+--~ if not traverse(e,pattern,handle,reverse,1) then return false end
+--~ end
+--~ else
+--~ if not traverse(e,pattern,handle,reverse,3) then return false end
+--~ end
+--~ else
+--~ if not traverse(e,pattern,handle,reverse,index+1,true) then return false end
+--~ end
+--~ else
+--~ if not traverse(e,pattern,handle,reverse,index+1,true) then return false end
+--~ end
+--~ elseif handle(root,rootdt,k) then
+--~ return false
+--~ end
+--~ elseif m == 3 then -- ancestor
+--~ local ep = e.__p__
+--~ if index < #pattern then
+--~ if not traverse(ep,pattern,handle,reverse,index+1) then return false end
+--~ elseif handle(root,rootdt,k) then
+--~ return false
+--~ end
+--~ elseif m == 4 then -- just root
+--~ if handle(root,rootdt,k) then
+--~ return false
+--~ end
+--~ elseif m == 6 then -- pi
+--~ if handle(root,rootdt,k) then
+--~ return false
+--~ end
+--~ elseif wildcard then -- maybe two kind of wildcards: * ** //
+--~ if not traverse(e,pattern,handle,reverse,index,wildcard) then return false end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ return true
+--~ end
+
+--~ Y a/b
+--~ Y /a/b
+--~ Y a/*/b
+--~ Y a//b
+--~ Y child::
+--~ Y .//
+--~ Y ..
+--~ N id("tag")
+--~ Y parent::
+--~ Y child::
+--~ N preceding-sibling:: (same name)
+--~ N following-sibling:: (same name)
+--~ N preceding-sibling-of-self:: (same name)
+--~ N following-sibling-or-self:: (same name)
+--~ Y ancestor::
+--~ N descendent::
+--~ N preceding::
+--~ N following::
+--~ N self::node()
+--~ N node() == alles
+--~ N a[position()=5]
+--~ Y a[5]
+--~ Y a[-5]
+--~ N a[first()]
+--~ N a[last()]
+--~ Y a/(b|c|d)/e/f
+--~ N (c/d|e)
+--~ Y a/b[@bla]
+--~ Y a/b[@bla='oeps']
+--~ Y a/b[@bla=='oeps']
+--~ Y a/b[@bla<>'oeps']
+--~ Y a/b[@bla!='oeps']
+--~ Y a/b/@bla
+
+--~ Y ^/a/c (root)
+--~ Y ^^/a/c (docroot)
+--~ Y root::a/c (docroot)
+
+--~ no wild card functions (yet)
+
+--~ s = "/a//b/*/(c|d|e)/(f|g)/h[4]/h/child::i/j/(a/b)/p[-1]/q[4]/ancestor::q/r/../s/./t[@bla='true']/k"
+
+-- // == /**/
+-- / = ^ (root)
+
+do
+
+ function analyze(str)
+ if not str then
+ return ""
+ else
+ local tmp, result, map, key = { }, { }, { }, str
+ str = str:gsub("(%b[])", function(s) tmp[#tmp+1] = s return '[['..#tmp..']]' end)
+ str = str:gsub("(%b())", function(s) tmp[#tmp+1] = s return '[['..#tmp..']]' end)
+ str = str:gsub("(%^+)([^/])", "%1/%2")
+ str = str:gsub("//+", "/**/")
+ str = str:gsub(".*root::", "^/")
+ str = str:gsub("child::", "")
+ str = str:gsub("ancestor::", "../")
+ str = str:gsub("self::", "./")
+ str = str:gsub("^/", "^/")
+ for s in str:gmatch("([^/]+)") do
+ s = s:gsub("%[%[(%d+)%]%]",function(n) return tmp[tonumber(n)] end)
+ result[#result+1] = s
+ end
+ cache[key] = result
+ return result
+ end
+ end
+
+ actions = {
+ [10] = "stay",
+ [11] = "parent",
+ [12] = "subtree root",
+ [13] = "document root",
+ [14] = "any",
+ [15] = "many",
+ [16] = "initial",
+ [20] = "match",
+ [21] = "match one of",
+ [22] = "match and attribute eq",
+ [23] = "match and attribute ne",
+ [23] = "match and attribute present",
+ [30] = "select",
+ [40] = "processing instruction",
+ }
+
+ function compose(result)
+ if not result or #result == 0 then
+ -- wildcard
+ return true
+ elseif #result == 1 then
+ local r = result[1][1]
+ if r == "14" or r == "15" then
+ -- wildcard
+ return true
+ elseif r == "12" then
+ -- root
+ return false
+ end
+ end
+ local map = { }
+ for r=1,#result do
+ local ri = result[r]
+ if ri == "." then
+ -- skip
+ elseif ri == ".." then
+ map[#map+1] = { 11 }
+ elseif ri == "^" then
+ map[#map+1] = { 12 }
+ elseif ri == "^^" then
+ map[#map+1] = { 13 }
+ elseif ri == "*" then
+ map[#map+1] = { 14 }
+ elseif ri == "**" then
+ map[#map+1] = { 15 }
+ else
+ local m = ri:match("^%((.*)%)$") -- (a|b|c)
+ if m or ri:find('|') then
+ m = m or ri
+ if m:find("[%[%]%(%)%/]") then -- []()/
+ -- error
+ else
+ local t = { 21 }
+ for s in m:gmatch("([^|])") do
+ local ns, tg = s:match("^(.-):?([^:]+)$")
+ t[#t+1] = ns
+ t[#t+1] = tg
+ end
+ map[#map+1] = t
+ end
+ else
+ local s, f = ri:match("^(.-)%[%s*(.+)%s*%]$") --aaa[bbb]
+ if s and f then
+ local ns, tg = s:match("^(.-):?([^:]+)$")
+ local at, op, vl = f:match("^@(.-)([!=<>]?)([^!=<>]+)$") -- [@a=='b']
+ if op and op ~= "" then
+ if op == '=' or op == '==' then
+ map[#map+1] = { 22, ns, tg, at, (vl:gsub("^([\'\"])(.*)%1$", "%2")) }
+ elseif op == '<>' or op == '!=' then
+ map[#map+1] = { 23, ns, tg, at, (vl:gsub("^([\'\"])(.*)%1$", "%2")) }
+ else
+ -- error
+ end
+ elseif f:find("^([%-%+%d]+)$")then
+ map[#map+1] = { 30, ns, tg, tonumber(f) }
+ elseif vl ~= "" then
+ map[#map+1] = { 24, ns, tg, vl }
+ end
+ else
+ local pi = ri:match("^pi::(.-)$")
+ if pi then
+ map[#map+1] = { 40, pi }
+ else
+ map[#map+1] = { 20, ri:match("^(.-):?([^:]+)$") }
+ end
+ end
+ end
+ end
+ end
+ -- if we have a symbol, we can prepend that to the string, which is faster
+ local mm = map[1] or { }
+ local r = mm[1] or 0
+ if #map == 1 then
+ if r == 14 or r == 15 then
+ -- wildcard
+ return true
+ elseif r == 12 then
+ -- root
+ return false
+ end
+ end
+ if r ~= 11 and r ~= 12 and r ~= 13 and r ~= 14 and r ~= 15 then
+ table.insert(map, 1, { 16 })
+ end
+ return map
+ end
+
+ cache = { }
+
+ function xml.lpath(pattern)
+ if type(pattern) == "string" then
+ local result = cache[pattern]
+ if not result then
+ result = compose(analyze(pattern))
+ cache[pattern] = result
+ end
+ if xml.trace_lpath then
+ xml.lshow(result)
+ end
+ return result
+ else
+ return pattern
+ end
+ end
+
+ function xml.lshow(pattern)
+ local lp = xml.lpath(pattern)
+ if lp == false then
+ print("root")
+ elseif lp == true then
+ print("wildcard")
+ else
+ if type(pattern) ~= "table" then
+ print("pattern: " .. tostring(pattern))
+ end
+ for k,v in ipairs(lp) do
+ print(k,actions[v[1]],table.join(v," ",2))
+ end
+ end
+ end
+
+ function xml.traverse(root,pattern,handle,reverse,index,wildcard)
+ if not root then -- error
+ return false
+ elseif pattern == false then -- root
+ handle(root,root.dt,root.ri)
+ return false
+ elseif pattern == true then -- wildcard
+ local traverse = xml.traverse
+ local rootdt = root.dt
+ if rootdt then
+ local start, stop, step = 1, #rootdt, 1
+ if reverse then
+ start, stop, step = stop, start, -1
+ end
+ for k=start,stop,step do
+ if handle(root,rootdt,root.ri or k) then return false end
+ if not traverse(rootdt[k],true,handle,reverse) then return false end
+ end
+ end
+ return false
+ elseif root and root.dt then
+ index = index or 1
+ local action = pattern[index]
+ local command = action[1]
+ if (command == 16 or command == 12) and index == 1 then -- initial
+ wildcard = true
+ index = index + 1
+ action = pattern[index]
+ command = action[1]
+ end
+ local traverse = xml.traverse
+ local rootdt = root.dt
+ local start, stop, step, n, dn = 1, #rootdt, 1, 0, 1
+ if command == 30 then
+ if action[4] < 0 then
+ start, stop, step = stop, start, -1
+ dn = -1
+ end
+ elseif reverse and index == #pattern then
+ start, stop, step = stop, start, -1
+ end
+ for k=start,stop,step do
+ local e = rootdt[k]
+ local ns, tg = e.ns, e.tg
+ if tg then
+ if command == 30 then
+ if ns == action[2] and tg == action[3] then
+ n = n + dn
+ if n == action[4] then
+ if index == #pattern then
+ if handle(root,rootdt,root.ri or k) then return false end
+ else
+ if not traverse(e,pattern,handle,reverse,index+1) then return false end
+ end
+ break
+ end
+ elseif wildcard then
+ if not traverse(e,pattern,handle,reverse,index,true) then return false end
+ end
+ else
+ local matched = false
+ if command == 20 then -- match
+ matched = ns == action[2] and tg == action[3]
+ elseif command == 21 then -- match one of
+ for i=2,#action,2 do
+ if ns == action[i] and tg == action[i+1] then
+ matched = true
+ break
+ end
+ end
+ elseif command == 22 then -- eq
+ matched = ns == action[2] and tg == action[3] and e.at[action[4]] == action[5]
+ elseif command == 23 then -- ne
+ matched = ns == action[2] and tg == action[3] and e.at[action[4]] ~= action[5]
+ elseif command == 24 then -- present
+ matched = ns == action[2] and tg == action[3] and e.at[action[4]]
+ end
+ if matched then -- combine tg test and at test
+ if index == #pattern then
+ if handle(root,rootdt,root.ri or k) then return false end
+ else
+ if not traverse(e,pattern,handle,reverse,index+1) then return false end
+ end
+ elseif command == 14 then -- any
+ if index == #pattern then
+ if handle(root,rootdt,root.ri or k) then return false end
+ else
+ if not traverse(e,pattern,handle,reverse,index+1) then return false end
+ end
+ elseif command == 15 then -- many
+ if index == #pattern then
+ if handle(root,rootdt,root.ri or k) then return false end
+ else
+ if not traverse(e,pattern,handle,reverse,index+1,true) then return false end
+ end
+ elseif command == 11 then -- parent
+ local ep = e.__p__
+ if index < #pattern then
+ if not traverse(ep,pattern,handle,reverse,index+1) then return false end
+ elseif handle(root,rootdt,k) then
+ return false
+ end
+ break
+ elseif command == 40 and tg == "@pi@" then -- pi
+ local pi = action[2]
+ if pi ~= "" then
+ local pt = e.dt[1]
+ if pt and pt:find(pi) then
+ if handle(root,rootdt,k) then
+ return false
+ end
+ end
+ elseif handle(root,rootdt,k) then
+ return false
+ end
+ elseif wildcard then
+ if not traverse(e,pattern,handle,reverse,index,true) then return false end
+ end
+ end
+ end
+ end
+ end
+ return true
+ end
+
+ local traverse, lpath, convert = xml.traverse, xml.lpath, xml.convert
+
+ xml.filters = { }
+
+ function xml.filters.default(root,pattern)
+ local rt, dt, dk
+ traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end)
+ return dt and dt[dk], rt, dt, dk
+ end
+ function xml.filters.reverse(root,pattern)
+ local rt, dt, dk
+ traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end, 'reverse')
+ return dt and dt[dk], rt, dt, dk
+ end
+ function xml.filters.count(root, pattern,everything)
+ local n = 0
+ traverse(root, lpath(pattern), function(r,d,t)
+ if everything or type(d[t]) == "table" then
+ n = n + 1
+ end
+ end)
+ return n
+ end
+ function xml.filters.elements(root, pattern) -- == all
+ local t = { }
+ traverse(root, lpath(pattern), function(r,d,k)
+ local e = d[k]
+ if e then
+ t[#t+1] = e
+ end
+ end)
+ return t
+ end
+ function xml.filters.texts(root, pattern)
+ local t = { }
+ traverse(root, lpath(pattern), function(r,d,k)
+ local e = d[k]
+ if e and e.dt then
+ t[#t+1] = e.dt
+ end
+ end)
+ return t
+ end
+ function xml.filters.first(root,pattern)
+ local rt, dt, dk
+ traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end)
+ return dt and dt[dk], rt, dt, dk
+ end
+ function xml.filters.last(root,pattern)
+ local rt, dt, dk
+ traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end, 'reverse')
+ return dt and dt[dk], rt, dt, dk
+ end
+ function xml.filters.index(root,pattern,arguments)
+ local rt, dt, dk, reverse, i = nil, nil, nil, false, tonumber(arguments or '1') or 1
+ if i and i ~= 0 then
+ if i < 0 then
+ reverse, i = true, -i
+ end
+ traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk, i = r, d, k, i-1 return i == 0 end, reverse)
+ if i == 0 then
+ return dt and dt[dk], rt, dt, dk
+ else
+ return nil, nil, nil, nil
+ end
+ else
+ return nil, nil, nil, nil
+ end
+ end
+ function xml.filters.attributes(root,pattern,arguments)
+ 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
+ if ekat then
+ if arguments then
+ return ekat[arguments] or "", rt, dt, dk
+ else
+ return ekat, rt, dt, dk
+ end
+ else
+ return { }, rt, dt, dk
+ end
+ end
+ function xml.filters.attribute(root,pattern,arguments)
+ 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
+ return (ekat and ekat[arguments]) or ""
+ end
+ function xml.filters.text(root,pattern,arguments)
+ local ek, dt, dk, rt = xml.filters.index(root,pattern,arguments)
+ return (ek and ek.dt) or "", rt, dt, dk
+ end
+
+ function xml.filter(root,pattern)
+ local pat, fun, arg = pattern:match("^(.+)/(.-)%((.*)%)$")
+ if fun then
+ return (xml.filters[fun] or xml.filters.default)(root,pat,arg)
+ else
+ pat, arg = pattern:match("^(.+)/@(.-)$")
+ if arg then
+ return xml.filters.attributes(root,pat,arg)
+ else
+ return xml.filters.default(root,pattern)
+ end
+ end
+ end
+
+ xml.filters.position = xml.filters.index
+
+ -- these may go away
+
+ xml.index_element = xml.filters.index
+ xml.count_elements = xml.filters.count
+ xml.first_element = xml.filters.first
+ xml.last_element = xml.filters.last
+ xml.index_text = xml.filters.text
+ xml.first_text = function (root,pattern) return xml.filters.text(root,pattern, 1) end
+ xml.last_text = function (root,pattern) return xml.filters.text(root,pattern,-1) end
+
+ -- so far
+
+ function xml.get_text(root,pattern,reverse)
+ local rt, dt, dk
+ traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk = r, d, k return true end, reverse)
+ local ek = dt and dt[dk]
+ return (ek and ek.dt) or "", rt, dt, dk
+ end
+
+ function xml.each_element(root, pattern, handle, reverse)
+ local ok
+ traverse(root, lpath(pattern), function(r,d,k) ok = true handle(r,d,k) end, reverse)
+ return ok
+ end
+
+ function xml.get_element(root,pattern,reverse)
+ local rt, dt, dk
+ traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk = r, d, k return true end, reverse)
+ return dt and dt[dk], rt, dt, dk
+ end
+
+ -- these may change
+
+ function xml.all_elements(root, pattern, ignorespaces) -- ok?
+ local rr, dd = { }, { }
+ traverse(root, lpath(pattern), function(r,d,k)
+ local dk = d and d[k]
+ if dk then
+ if ignorespaces and type(dk) == "string" and dk:find("^[\s\n]*$") then
+ -- ignore
+ else
+ local n = #rr+1
+ rr[n], dd[n] = r, dk
+ end
+ end
+ end)
+ return dd, rr
+ end
+
+ function xml.all_texts(root, pattern, flatten) -- crap
+ local t, r = { }, { }
+ traverse(root, lpath(pattern), function(r,d,k)
+ if d then
+ local ek = d[k]
+ local tx = ek and ek.dt
+ if flatten then
+ if tx then
+ t[#t+1] = xml.tostring(tx) or ""
+ else
+ t[#t+1] = ""
+ end
+ else
+ t[#t+1] = tx or ""
+ end
+ else
+ t[#t+1] = ""
+ end
+ r[#r+1] = r
+ end)
+ return t, r
+ end
+
+ function xml.inject_element(root, pattern, element, prepend)
+ if root and element then
+ local matches, collect = { }, nil
+ if type(element) == "string" then
+ element = convert(element,true)
+ end
+ if element then
+ collect = function(r,d,k) matches[#matches+1] = { r, d, k, element } end
+ traverse(root, lpath(pattern), collect)
+ for i=1,#matches do
+ local m = matches[i]
+ local r, d, k, element, edt = m[1], m[2], m[3], m[4], nil
+ if element.ri then
+ element = element.dt[element.ri].dt
+ else
+ element = element.dt
+ end
+ if r.ri then
+ edt = r.dt[r.ri].dt
+ else
+ edt = d and d[k] and d[k].dt
+ end
+ if edt then
+ local be, af
+ if prepend then
+ be, af = xml.copy(element), edt
+ else
+ be, af = edt, xml.copy(element)
+ end
+ for i=1,#af do
+ be[#be+1] = af[i]
+ end
+ if r.ri then
+ r.dt[r.ri].dt = be
+ else
+ d[k].dt = be
+ end
+ else
+ -- r.dt = element.dt -- todo
+ end
+ end
+ end
+ end
+ end
+
+ -- todo: copy !
+
+ function xml.insert_element(root, pattern, element, before) -- todo: element als functie
+ if root and element then
+ if pattern == "/" then
+ xml.inject_element(root, pattern, element, before) -- todo: element als functie
+ else
+ local matches, collect = { }, nil
+ if type(element) == "string" then
+ element = convert(element,true)
+ end
+ if element and element.ri then
+ element = element.dt[element.ri]
+ end
+ if element then
+ collect = function(r,d,k) matches[#matches+1] = { r, d, k, element } end
+ traverse(root, lpath(pattern), collect)
+ for i=#matches,1,-1 do
+ local m = matches[i]
+ 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
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- first, last, each
+
+ xml.insert_element_after = xml.insert_element
+ xml.insert_element_before = function(r,p,e) xml.insert_element(r,p,e,true) end
+ xml.inject_element_after = xml.inject_element
+ xml.inject_element_before = function(r,p,e) xml.inject_element(r,p,e,true) end
+
+ function xml.delete_element(root, pattern)
+ local matches, deleted = { }, { }
+ local collect = function(r,d,k) matches[#matches+1] = { r, d, k } end
+ traverse(root, lpath(pattern), collect)
+ for i=#matches,1,-1 do
+ local m = matches[i]
+ deleted[#deleted+1] = table.remove(m[2],m[3])
+ end
+ return deleted
+ end
+
+ function xml.replace_element(root, pattern, element)
+ if type(element) == "string" then
+ element = convert(element,true)
+ end
+ if element and element.ri then
+ element = element.dt[element.ri]
+ end
+ if element then
+ traverse(root, lpath(pattern), function(rm, d, k)
+ d[k] = element.dt -- maybe not clever enough
+ end)
+ end
+ end
+
+ function xml.process(root, pattern, handle)
+ traverse(root, lpath(pattern), function(r,d,k)
+ if d[k].dt then
+ for k,v in ipairs(d[k].dt) do
+ if v.tg then handle(v) end
+ end
+ end
+ end)
+ end
+
+ function xml.strip(root, pattern)
+ traverse(root, lpath(pattern), function(r,d,k)
+ local dkdt = d[k].dt
+ if dkdt then
+ local t = { }
+ for i=1,#dkdt do
+ local str = dkdt[i]
+ if type(str) == "string" and str:find("^[\032\010\012\013]*$") then
+ -- stripped
+ else
+ t[#t+1] = str
+ end
+ end
+ d[k].dt = t
+ end
+ end)
+ end
+
+ --
+
+ function xml.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
+ if e.ns == oldspace then
+ e.ns = newspace
+ end
+ local edt = e.dt
+ if edt then
+ rename(edt, oldspace, newspace)
+ end
+ end
+ end
+ end
+
+ function xml.remap_tag(root, pattern, newtg)
+ traverse(root, lpath(pattern), function(r,d,k)
+ d[k].tg = newtg
+ end)
+ end
+ function xml.remap_namespace(root, pattern, newns)
+ traverse(root, lpath(pattern), function(r,d,k)
+ d[k].ns = newns
+ end)
+ end
+
+ -- function xml.process_attributes(root, pattern, handle)
+ -- traverse(root, lpath(pattern), function(e,k) handle(e[k].at) end)
+ -- end
+
+ function xml.process_attributes(root, pattern, handle)
+ traverse(root, lpath(pattern), function(r,d,k)
+ local ek = d[k]
+ local a = ek.at or { }
+ handle(a)
+ if next(a) then
+ ek.at = a
+ else
+ ek.at = nil
+ end
+ end)
+ end
+
+ function xml.package(tag,attributes,data)
+ local n, t = tag:match("^(.-):(.+)$")
+ if attributes then
+ return { ns = n or "", tg = t or tag, dt = data or "", at = attributes }
+ else
+ return { ns = n or "", tg = t or tag, dt = data or "" }
+ end
+ end
+
+ -- some special functions, handy for the manual:
+
+ function xml.gsub(t,old,new)
+ if t.dt then
+ for k,v in ipairs(t.dt) do
+ if type(v) == "string" then
+ t.dt[k] = v:gsub(old,new)
+ else
+ xml.gsub(v,old,new)
+ end
+ end
+ end
+ end
+
+ function xml.strip_leading_spaces(ek, e, k) -- cosmetic, for manual
+ if e and k and e[k-1] and type(e[k-1]) == "string" then
+ local s = e[k-1]:match("\n(%s+)")
+ xml.gsub(ek,"\n"..string.rep(" ",#s),"\n")
+ end
+ end
+
+ function xml.serialize_path(root,lpath,handle)
+ local ek, e, k = xml.first_element(root,lpath)
+ ek = xml.copy(ek)
+ xml.strip_leading_spaces(ek,e,k)
+ xml.serialize(ek,handle)
+ end
+
+ -- http://www.lua.org/pil/9.3.html (or of course the book)
+ --
+ -- it's nice to have an iterator but it comes with some extra overhead
+ --
+ -- for r, d, k in xml.elements(xml.load('text.xml'),"title") do print(d[k]) end
+
+ function xml.elements(root,pattern,reverse)
+ return coroutine.wrap(function() traverse(root, lpath(pattern), coroutine.yield, reverse) end)
+ end
+
+ -- the iterator variant needs 1.5 times the runtime of the function variant
+ --
+ -- function xml.filters.first(root,pattern)
+ -- for rt,dt,dk in xml.elements(root,pattern)
+ -- return dt and dt[dk], rt, dt, dk
+ -- end
+ -- return nil, nil, nil, nil
+ -- end
+
+ -- todo xml.gmatch for text
+
+end
+
+xml.count = xml.filters.count
+xml.index = xml.filters.index
+xml.position = xml.filters.index
+xml.first = xml.filters.first
+xml.last = xml.filters.last
+
+xml.each = xml.each_element
+xml.all = xml.all_elements
+
+xml.insert = xml.insert_element_after
+xml.inject = xml.inject_element_after
+xml.after = xml.insert_element_after
+xml.before = xml.insert_element_before
+xml.delete = xml.delete_element
+xml.replace = xml.replace_element
+
+-- a few helpers, the may move to lxml modules
+
+function xml.include(xmldata,element,attribute,pathlist,collapse)
+ element = element or 'ctx:include'
+ attribute = attribute or 'name'
+ pathlist = pathlist or { '.' }
+ -- todo, check op ri
+ local function include(r,d,k)
+ local ek = d[k]
+ local name = (ek.at and ek.at[attribute]) or ""
+ if name ~= "" then
+ -- maybe file lookup in tree
+ local fullname
+ for _, path in ipairs(pathlist) do
+ if path == '.' then
+ fullname = name
+ else
+ fullname = file.join(path,name)
+ end
+ local f = io.open(fullname)
+ if f then
+ xml.assign(d,k,xml.load(f,collapse))
+ f:close()
+ break
+ else
+ xml.empty(d,k)
+ end
+ end
+ else
+ xml.empty(d,k)
+ end
+ end
+ while xml.each(xmldata, element, include) do end
+end
+
+xml.escapes = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
+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.join(t,separator,lastseparator)
+ local result = { }
+ for k,v in pairs(t) do
+ result[k] = xml.tostring(v)
+ end
+ if lastseparator then
+ return table.join(result,separator,1,#result-1) .. lastseparator .. result[#result]
+ else
+ return table.join(result,separator)
+ end
+end
+
+
+do if utf then
+
+ local function toutf(s)
+ return utf.char(tonumber(s,16))
+ end
+
+ function xml.utfize(root)
+ local d = root.dt
+ for k=1,#d do
+ local dk = d[k]
+ if type(dk) == "string" then
+ d[k] = dk:gsub("&#x(.-);",toutf)
+ else
+ xml.utfize(dk)
+ end
+ end
+ end
+
+else
+ function xml.utfize()
+ print("entity to utf conversion is not available")
+ end
+
+end end
+
+--- examples
+
+--~ for _, e in ipairs(xml.filters.elements(ctxrunner.xmldata,"ctx:message")) do
+--~ print(">>>",xml.tostring(e.dt))
+--~ end
+
+
-- filename : l-utils.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
@@ -1497,10 +3283,15 @@ function utils.merger.selfclean(name)
)
end
+utils.lua.compile_strip = true
+
function utils.lua.compile(luafile, lucfile)
-- utils.report("compiling",luafile,"into",lucfile)
os.remove(lucfile)
- local command = "-s -o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ if utils.lua.compile_strip then
+ command = "-s " .. command
+ end
if os.execute("texluac " .. command) == 0 then
return true
elseif os.execute("luac " .. command) == 0 then
@@ -1598,6 +3389,10 @@ function environment.showarguments()
end
end
+function environment.setargument(name,value)
+ environment.arguments[name] = value
+end
+
function environment.argument(name)
if environment.arguments[name] then
return environment.arguments[name]
@@ -3765,8 +5560,8 @@ do -- local report
function containers.is_valid(container, name)
if name and name ~= "" then
- local cs = container.storage[name]
- return cs and not table.is_empty(cs) and cs.cache_version == container.version
+ local storage = container.storage[name]
+ return storage and not table.is_empty(storage) and storage.cache_version == container.version
else
return false
end
@@ -4061,6 +5856,7 @@ own.libs = { -- todo: check which ones are really needed
'l-file.lua',
'l-dir.lua',
'l-boolean.lua',
+ 'l-xml.lua',
-- 'l-unicode.lua',
'l-utils.lua',
-- 'l-tex.lua',