summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/context/lua/luatools.lua37
-rw-r--r--scripts/context/lua/mtx-cache.lua4
-rw-r--r--scripts/context/lua/mtx-chars.lua90
-rw-r--r--scripts/context/lua/mtx-context.lua455
-rw-r--r--scripts/context/lua/mtxrun.lua1834
-rw-r--r--scripts/context/lua/x-ldx.lua94
-rw-r--r--scripts/context/ruby/texmfstart.rb1
7 files changed, 2427 insertions, 88 deletions
diff --git a/scripts/context/lua/luatools.lua b/scripts/context/lua/luatools.lua
index b138b5e65..84899275c 100644
--- a/scripts/context/lua/luatools.lua
+++ b/scripts/context/lua/luatools.lua
@@ -2790,7 +2790,7 @@ end
function input.list_configurations(instance)
for _,key in pairs(table.sortedkeys(instance.kpsevars)) do
- if not instance.pattern or (instance.pattern=="") or key:find(instance.pattern) then
+ if not instance.pattern or instance.pattern == "" or key:find(instance.pattern) then
print(key.."\n")
for i,c in ipairs(instance.order) do
local str = c[key]
@@ -4998,16 +4998,21 @@ input.defaultlibs = { -- not all are needed
-- todo: use environment.argument() instead of environment.arguments[]
-instance.engine = environment.arguments["engine"] or 'luatex'
-instance.progname = environment.arguments["progname"] or 'context'
-instance.luaname = environment.arguments["luafile"] or "" -- environment.ownname or ""
-instance.lualibs = environment.arguments["lualibs"] or table.concat(input.defaultlibs,",")
-instance.allresults = environment.arguments["all"] or false
-instance.pattern = environment.arguments["pattern"] or nil
-instance.sortdata = environment.arguments["sort"] or false
-instance.kpseonly = not environment.arguments["all"] or false
-instance.my_format = environment.arguments["format"] or instance.format
-instance.lsrmode = environment.arguments["lsr"] or false
+instance.engine = environment.arguments["engine"] or 'luatex'
+instance.progname = environment.arguments["progname"] or 'context'
+instance.luaname = environment.arguments["luafile"] or "" -- environment.ownname or ""
+instance.lualibs = environment.arguments["lualibs"] or table.concat(input.defaultlibs,",")
+instance.allresults = environment.arguments["all"] or false
+instance.pattern = environment.arguments["pattern"] or nil
+instance.sortdata = environment.arguments["sort"] or false
+instance.kpseonly = not environment.arguments["all"] or false
+instance.my_format = environment.arguments["format"] or instance.format
+instance.lsrmode = environment.arguments["lsr"] or false
+
+if type(instance.pattern) == 'boolean' then
+ input.report("invalid pattern specification") -- toto, force verbose for one message
+ instance.pattern = nil
+end
if environment.arguments["trace"] then input.settrace(environment.arguments["trace"]) end
@@ -5248,10 +5253,10 @@ elseif environment.arguments["var-value"] or environment.arguments["show-value"]
input.for_files(instance, input.var_value, environment.files)
elseif environment.arguments["find-file"] then
input.my_prepare_b(instance)
- instance.format = environment.arguments["format"] or instance.format
- if environment.arguments["pattern"] then
+ instance.format = environment.arguments["format"] or instance.format
+ if instance.pattern then
instance.allresults = true
- input.for_files(instance, input.find_files, { environment.arguments["pattern"] }, instance.my_format)
+ input.for_files(instance, input.find_files, { instance.pattern }, instance.my_format)
else
input.for_files(instance, input.find_files, environment.files, instance.my_format)
end
@@ -5261,11 +5266,11 @@ elseif environment.arguments["find-file"] then
elseif environment.arguments["format-path"] then
input.my_prepare_b(instance)
input.report(caches.setpath(instance,"format"))
-elseif environment.arguments["pattern"] then
+elseif instance.pattern then -- brrr
input.my_prepare_b(instance)
instance.format = environment.arguments["format"] or instance.format
instance.allresults = true
- input.for_files(instance, input.find_files, { environment.arguments["pattern"] }, instance.my_format)
+ input.for_files(instance, input.find_files, { instance.pattern }, instance.my_format)
elseif environment.arguments["help"] or (environment.files[1]=='help') or (#environment.files==0) then
if not input.verbose then
input.verbose = true
diff --git a/scripts/context/lua/mtx-cache.lua b/scripts/context/lua/mtx-cache.lua
index 0b0983d1b..8bd3b7a79 100644
--- a/scripts/context/lua/mtx-cache.lua
+++ b/scripts/context/lua/mtx-cache.lua
@@ -6,14 +6,14 @@ scripts = scripts or { }
scripts.cache = scripts.cache or { }
function scripts.cache.collect_one(...)
- local path = cache.setpath(instance,...)
+ local path = caches.setpath(instance,...)
local tmas = dir.glob(path .. "/*tma")
local tmcs = dir.glob(path .. "/*tmc")
return path, tmas, tmcs
end
function scripts.cache.collect_two(...)
- local path = cache.setpath(instance,...)
+ local path = caches.setpath(instance,...)
local rest = dir.glob(path .. "/**/*")
return path, rest
end
diff --git a/scripts/context/lua/mtx-chars.lua b/scripts/context/lua/mtx-chars.lua
new file mode 100644
index 000000000..28d7b4a40
--- /dev/null
+++ b/scripts/context/lua/mtx-chars.lua
@@ -0,0 +1,90 @@
+dofile(input.find_file(instance,"luat-log.lua"))
+
+texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+
+scripts = scripts or { }
+scripts.chars = scripts.chars or { }
+
+function scripts.chars.stixtomkiv(inname,outname)
+ if inname == "" then
+ logs.report("aquiring math data","invalid datafilename")
+ end
+ local f = io.open(inname)
+ if not f then
+ logs.report("aquiring math data","invalid datafile")
+ else
+ logs.report("aquiring math data","processing " .. inname)
+ if not outname or outname == "" then
+ outname = "char-mth.lua"
+ end
+ local classes = {
+ N = "normal",
+ A = "alphabetic",
+ D = "diacritic",
+ P = "punctuation",
+ B = "binary",
+ R = "relation",
+ L = "large",
+ O = "opening",
+ C = "closing",
+ F = "fence"
+ }
+ local format, concat = string.format, table.concat
+ local valid, done = false, { }
+ local g = io.open(outname,'w')
+ g:write([[
+-- filename : char-mth.lua
+-- comment : companion to char-mth.tex (in ConTeXt)
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- license : see context related readme files
+-- comment : generated from data file downloaded from STIX website
+
+if not versions then versions = { } end versions['char-mth'] = 1.001
+if not characters then characters = { } end
+ ]])
+ g:write(format("\ncharacters.math = {\n"))
+ for l in f:lines() do
+ if not valid then
+ valid = l:find("AMS/TeX name")
+ end
+ if valid then
+ local unicode = l:sub(2,6)
+ if unicode:sub(1,1) ~= " " and unicode ~= "" and not done[unicode] then
+ local mathclass, adobename, texname = l:sub(57,57) or "", l:sub(13,36) or "", l:sub(84,109) or ""
+ texname, adobename = texname:gsub("[\\ ]",""), adobename:gsub("[\\ ]","")
+ local t = { }
+ if mathclass ~= "" then t[#t+1] = format("mathclass='%s'", classes[mathclass] or "unknown") end
+ if adobename ~= "" then t[#t+1] = format("adobename='%s'", adobename ) end
+ if texname ~= "" then t[#t+1] = format("texname='%s'" , texname ) end
+ if #t > 0 then
+ g:write(format("\t[0x%s] = { %s },\n",unicode, concat(t,", ")))
+ end
+ done[unicode] = true
+ end
+ end
+ end
+ if not valid then
+ g:write("\t-- The data file is corrupt, invalid or maybe the format has changed.\n")
+ logs.report("aquiring math data","problems with data table")
+ else
+ logs.report("aquiring math data","table saved in " .. outname)
+ end
+ g:write("}\n")
+ g:close()
+ f:close()
+ end
+end
+
+banner = banner .. " | character tools "
+
+messages.help = [[
+--stix convert stix table to math table
+]]
+
+if environment.argument("stix") then
+ local inname = environment.files[1] or ""
+ local outname = environment.files[2] or ""
+ scripts.chars.stixtomkiv(inname,outname)
+else
+ input.help(banner,messages.help)
+end
diff --git a/scripts/context/lua/mtx-context.lua b/scripts/context/lua/mtx-context.lua
index 864fd2267..c444dfd1a 100644
--- a/scripts/context/lua/mtx-context.lua
+++ b/scripts/context/lua/mtx-context.lua
@@ -5,14 +5,26 @@ texmf.instance = instance -- we need to get rid of this / maybe current instance
scripts = scripts or { }
scripts.context = scripts.context or { }
+-- l-file
+
+function file.needsupdate(oldfile,newfile)
+ return true
+end
+function file.syncmtimes(oldfile,newfile)
+end
+
+-- l-io
+
function io.copydata(fromfile,tofile)
io.savedata(tofile,io.loaddata(fromfile) or "")
end
+-- luat-inp
+
function input.locate_format(name) -- move this to core / luat-xxx
local barename, fmtname = name:gsub("%.%a+$",""), ""
if input.usecache then
- local path = file.join(cache.setpath(instance,"formats")) -- maybe platform
+ local path = file.join(caches.setpath(instance,"formats")) -- maybe platform
fmtname = file.join(path,barename..".fmt") or ""
end
if fmtname == "" then
@@ -31,6 +43,326 @@ function input.locate_format(name) -- move this to core / luat-xxx
return nil, nil
end
+-- ctx
+
+ctxrunner = { }
+
+do
+
+ function ctxrunner.filtered(str,method)
+ str = tostring(str)
+ if method == 'name' then str = file.removesuffix(file.basename(str))
+ elseif method == 'path' then str = file.dirname(str)
+ elseif method == 'suffix' then str = file.extname(str)
+ elseif method == 'nosuffix' then str = file.removesuffix(str)
+ elseif method == 'nopath' then str = file.basename(str)
+ elseif method == 'base' then str = file.basename(str)
+ -- elseif method == 'full' then
+ -- elseif method == 'complete' then
+ -- elseif method == 'expand' then -- str = file.expand_path(str)
+ end
+ return str:gsub("\\","/")
+ end
+
+ function ctxrunner.substitute(e,str)
+ local attributes = e.at
+ if str and attributes then
+ if attributes['method'] then
+ str = ctsrunner.filtered(str,attributes['method'])
+ end
+ if str == "" and attributes['default'] then
+ str = attributes['default']
+ end
+ end
+ return str
+ end
+
+ function ctxrunner.reflag(flags)
+ local t = { }
+ for _, flag in pairs(flags) do
+ local key, value = flag:match("^(.-)=(.+)$")
+ if key and value then
+ t[key] = value
+ else
+ t[flag] = true
+ end
+ end
+ return t
+ end
+
+ function ctxrunner.substitute(str)
+ return str
+ end
+
+ function ctxrunner.justtext(str)
+ str = xml.unescaped(tostring(str))
+ str = xml.cleansed(str)
+ str = str:gsub("\\+",'/')
+ str = str:gsub("%s+",' ')
+ return str
+ end
+
+ function ctxrunner.new()
+ return {
+ ctxname = "",
+ jobname = "",
+ xmldata = nil,
+ suffix = "prep",
+ locations = { '..', '../..' },
+ variables = { },
+ messages = { },
+ environments = { },
+ modules = { },
+ filters = { },
+ flags = { },
+ modes = { },
+ prepfiles = { },
+ paths = { },
+ }
+ end
+
+ function ctxrunner.savelog(ctxdata,ctlname)
+ local function yn(b)
+ if b then return 'yes' else return 'no' end
+ end
+ if not ctlname or ctlname == "" or ctlname == ctxdata.jobname then
+ if ctxdata.jobname then
+ ctlname = file.replacesuffix(ctxdata.jobname,'ctl')
+ elseif ctxdata.ctxname then
+ ctlname = file.replacesuffix(ctxdata.ctxname,'ctl')
+ else
+ input.report(string.format("invalid ctl name %s",ctlname or "?"))
+ return
+ end
+ end
+ if table.is_empty(ctxdata.prepfiles) then
+ input.report("nothing prepared, no ctl file saved")
+ os.remove(ctlname)
+ else
+ input.report(string.format("saving logdata in %s",ctlname))
+ f = io.open(ctlname,'w')
+ if f then
+ f:write("<?xml version='1.0' standalone='yes'?>\n\n")
+ f:write(string.format("<ctx:preplist local='%s'>\n",yn(ctxdata.runlocal)))
+--~ for name, value in pairs(ctxdata.prepfiles) do
+ for _, name in ipairs(table.sortedkeys(ctxdata.prepfiles)) do
+ f:write(string.format("\t<ctx:prepfile done='%s'>%s</ctx:prepfile>\n",yn(ctxdata.prepfiles[name]),name))
+ end
+ f:write("</ctx:preplist>\n")
+ f:close()
+ end
+ end
+ end
+
+ function ctxrunner.register_path(ctxdata,path)
+ -- test if exists
+ ctxdata.paths[ctxdata.paths+1] = path
+ end
+
+ function ctxrunner.trace(ctxdata)
+ print(table.serialize(ctxdata.messages))
+ print(table.serialize(ctxdata.flags))
+ print(table.serialize(ctxdata.environments))
+ print(table.serialize(ctxdata.modules))
+ print(table.serialize(ctxdata.filters))
+ print(table.serialize(ctxdata.modes))
+ print(xml.serialize(ctxdata.xmldata))
+ end
+
+ function ctxrunner.manipulate(ctxdata,ctxname,defaultname)
+
+ if not ctxdata.jobname or ctxdata.jobname == "" then
+ return
+ end
+
+ ctxdata.ctxname = ctxname or file.removesuffix(ctxdata.jobname) or ""
+
+ if ctxdata.ctxname == "" then
+ return
+ end
+
+ ctxdata.jobname = file.addsuffix(ctxdata.jobname,'tex')
+ ctxdata.ctxname = file.addsuffix(ctxdata.ctxname,'ctx')
+
+ input.report("jobname:",ctxdata.jobname)
+ input.report("ctxname:",ctxdata.ctxname)
+
+ -- mtxrun should resolve kpse: and file:
+
+ local usedname = ctxdata.ctxname
+ local found = io.exists(usedname)
+
+ if not found then
+ for _, path in (ctsrunner.locations) do
+ local fullname = file.join(path,ctxdata.ctxname)
+ if io.exists(fullname) then
+ usedname, found = fullname, true
+ break
+ end
+ end
+ end
+
+ if not found and defaultname and defaultname ~= "" and file.exists(defaultname) then
+ usedname, found = defaultname, true
+ end
+
+ if not found then
+ return
+ end
+
+ ctxdata.xmldata = xml.load(usedname)
+
+ if not ctxdata.xmldata then
+ return
+ else
+ -- test for valid, can be text file
+ end
+
+ xml.include(ctxdata.xmldata,'ctx:include','name', table.append({'.', file.dirname(ctxdata.ctxname)},ctxdata.locations))
+
+ ctxdata.variables['job'] = ctxdata.jobname
+
+ ctxdata.flags = xml.all_texts(ctxdata.xmldata,"/ctx:job/ctx:flags/ctx:flag",true)
+ ctxdata.environments = xml.all_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:environment",true)
+ ctxdata.modules = xml.all_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:module",true)
+ ctxdata.filters = xml.all_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:filter",true)
+ ctxdata.modes = xml.all_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:mode",true)
+ ctxdata.messages = xml.all_texts(ctxdata.xmldata,"ctx:message",true)
+
+ ctxdata.flags = ctxrunner.reflag(ctxdata.flags)
+
+ for _, message in ipairs(ctxdata.messages) do
+ -- message ctxdata.justtext(xml.tostring(message))
+ end
+
+ --~ REXML::XPath.each(root,"//ctx:block") do |blk|
+ --~ if @jobname && blk.attributes['pattern'] then
+ --~ root.delete(blk) unless @jobname =~ /#{blk.attributes['pattern']}/
+ --~ else
+ --~ root.delete(blk)
+ --~ end
+ --~ end
+
+ xml.each(ctxdata.xmldata,"ctx:value[@name='job']", function(ek,e,k)
+ e[k] = ctxdata.variables['job'] or ""
+ end)
+
+ local commands = { }
+ xml.each(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/ctx:processor", function(r,d,k)
+ local ek = d[k]
+ commands[ek.at and ek.at['name'] or "unknown"] = ek
+ end)
+
+ local suffix = xml.first(ctxdata.xmldata,"/ctx:job/ctx:preprocess/@suffix") or ctxdata.suffix
+ local runlocal = xml.first(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/@local")
+
+ runlocal = toboolean(runlocal)
+
+ for _, files in ipairs(xml.filters.elements(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:files")) do
+ for _, pattern in ipairs(xml.filters.elements(files,"ctx:file")) do
+
+ preprocessor = pattern.at['processor'] or ""
+
+ if preprocessor ~= "" then
+
+ ctxdata.variables['old'] = ctxdata.jobname
+ xml.each(ctxdata.xmldata,"ctx:value", function(r,d,k)
+ local ek = d[k]
+ local ekat = ek.at['name']
+ if ekat == 'old' then
+ d[k] = ctxrunner.substitute(ctxdata.variables[ekat] or "")
+ end
+ end)
+
+ pattern = ctxrunner.justtext(xml.tostring(pattern))
+
+ local oldfiles = dir.glob(pattern)
+ local pluspath = false
+ if #oldfiles == 0 then
+ -- message: no files match pattern
+ for _, p in ipairs(ctxdata.paths) do
+ local oldfiles = dir.glob(path.join(p,pattern))
+ if #oldfiles > 0 then
+ pluspath = true
+ break
+ end
+ end
+ end
+ if #oldfiles == 0 then
+ -- message: no old files
+ else
+ for _, oldfile in ipairs(oldfiles) do
+ newfile = oldfile .. "." .. suffix -- addsuffix will add one only
+ if ctxdata.runlocal then
+ newfile = file.basename(newfile)
+ end
+ if oldfile ~= newfile and file.needsupdate(oldfile,newfile) then
+ -- message: oldfile needs preprocessing
+ -- os.remove(newfile)
+ for _, pp in ipairs(preprocessor:split(',')) do
+ local command = commands[pp]
+ if command then
+ command = xml.copy(command)
+ local suf = (command.at and command.at['suffix']) or ctxdata.suffix
+ if suf then
+ newfile = oldfile .. "." .. suf
+ end
+ if ctxdata.runlocal then
+ newfile = file.basename(newfile)
+ end
+ xml.each(command,"ctx:old", function(r,d,k)
+ d[k] = ctxrunner.substitute(oldfile)
+ end)
+ xml.each(command,"ctx:new", function(r,d,k)
+ d[k] = ctxrunner.substitute(newfile)
+ end)
+ -- message: preprocessing #{oldfile} into #{newfile} using #{pp}
+ ctxdata.variables['old'] = oldfile
+ ctxdata.variables['new'] = newfile
+ xml.each(command,"ctx:value", function(r,d,k)
+ local ek = d[k]
+ local ekat = ek.at and ek.at['name']
+ if ekat then
+ d[k] = ctxrunner.substitute(ctxdata.variables[ekat] or "")
+ end
+ end)
+ -- potential optimization: when mtxrun run internal
+ command = ctxrunner.justtext(command) -- command is still xml element here
+ input.report("command",command)
+ local result = os.execute(command)
+ if result > 0 then
+ input.report("error, return code",result)
+ end
+ if ctxdata.runlocal then
+ oldfile = file.basename(oldfile)
+ end
+ end
+ end
+ if io.exists(newfile) then
+ file.syncmtimes(oldfile,newfile)
+ ctxdata.prepfiles[oldfile] = true
+ else
+ input.report("error, check target location of new file", newfile)
+ ctxdata.prepfiles[oldfile] = false
+ end
+ else
+ input.report("old file needs no preprocessing")
+ ctxdata.prepfiles[oldfile] = io.exists(newfile)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ ctxrunner.savelog(ctxdata)
+
+ end
+
+end
+
+-- rest
+
scripts.context.multipass = {
suffixes = { ".tuo", ".tuc" },
nofruns = 8,
@@ -62,13 +394,17 @@ scripts.context.backends = {
dvips = 'dvips'
}
-function scripts.context.multipass.makeoptionfile(jobname)
+function scripts.context.multipass.makeoptionfile(jobname,ctxdata)
+ -- take jobname from ctx
local f = io.open(jobname..".top","w")
if f then
local finalrun, kindofrun, currentrun = false, 0, 0
+--~ local function someflag(flag)
+--~ return (ctxdata and ctxdata.flags[flag]) or environment.argument(flag)
+--~ end
+ local someflag = environment.argument
local function setvalue(flag,format,hash,default)
- local a = environment.argument(flag)
- a = a or default
+ local a = someflag(flag) or default
if a and a ~= "" then
if hash then
if hash[a] then
@@ -80,15 +416,21 @@ function scripts.context.multipass.makeoptionfile(jobname)
end
end
local function setvalues(flag,format)
- local a = environment.argument(flag)
- if a and a ~= "" then
- for _, v in a:gmatch("([^,]+)") do
+ if type(flag) == "table" then
+ for _, v in pairs(flag) do
f:write(format:format(v),"\n")
end
+ else
+ local a = someflag(flag)
+ if a and a ~= "" then
+ for _, v in a:gmatch("%s*([^,]+)") do
+ f:write(format:format(v),"\n")
+ end
+ end
end
end
local function setfixed(flag,format,...)
- if environment.argument(flag) then
+ if someflag(flag) then
f:write(format:format(...),"\n")
end
end
@@ -96,36 +438,42 @@ function scripts.context.multipass.makeoptionfile(jobname)
f:write(format:format(...),"\n")
end
setalways("\\unprotect")
- setvalue('output' , "\\setupoutput[%s]", scripts.context.backends, 'pdftex')
- setalways( "\\setupsystem[\\c!n=%s,\\c!m=%s]", kindofrun, currentrun)
- setalways( "\\setupsystem[\\c!type=%s]",os.platform)
- setfixed ("batchmode" , "\\batchmode")
- setfixed ("nonstopmode", "\\nonstopmode")
- setfixed ("paranoid" , "\\def\\maxreadlevel{1}")
- setvalue ("modefile" , "\\readlocfile{%s}{}{}")
- setvalue ("result" , "\\setupsystem[file=%s]")
- setvalue("path" , "\\usepath[%s]")
- setfixed("color" , "\\setupcolors[\\c!state=\\v!start]")
- setfixed("nompmode" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
- setfixed("nomprun" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
- setfixed("automprun" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
- setfixed("fast" , "\\fastmode\n")
- setfixed("silentmode" , "\\silentmode\n")
- setvalue("separation" , "\\setupcolors[\\c!split=%s]")
- setvalue("setuppath" , "\\setupsystem[\\c!directory={%s}]")
- setfixed("noarrange" , "\\setuparranging[\\v!disable]")
+ setvalue('output' , "\\setupoutput[%s]", scripts.context.backends, 'pdftex')
+ setalways( "\\setupsystem[\\c!n=%s,\\c!m=%s]", kindofrun, currentrun)
+ setalways( "\\setupsystem[\\c!type=%s]",os.platform)
+ setfixed ("batchmode" , "\\batchmode")
+ setfixed ("nonstopmode" , "\\nonstopmode")
+ setfixed ("tracefiles" , "\\tracefilestrue")
+ setfixed ("paranoid" , "\\def\\maxreadlevel{1}")
+ setvalues("modefile" , "\\readlocfile{%s}{}{}")
+ setvalue ("result" , "\\setupsystem[file=%s]")
+ setvalues("path" , "\\usepath[%s]")
+ setfixed ("color" , "\\setupcolors[\\c!state=\\v!start]")
+ setfixed ("nompmode" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
+ setfixed ("nomprun" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
+ setfixed ("automprun" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
+ setfixed ("fast" , "\\fastmode\n")
+ setfixed ("silentmode" , "\\silentmode\n")
+ setvalue ("separation" , "\\setupcolors[\\c!split=%s]")
+ setvalue ("setuppath" , "\\setupsystem[\\c!directory={%s}]")
+ setfixed ("noarrange" , "\\setuparranging[\\v!disable]")
if environment.argument('arrange') and not finalrun then
- setalways( "\\setuparranging[\\v!disable]")
- end
- setvalue("modes" , "\\enablemode[%s]")
- setvalue("mode" , "\\enablemode[%s]")
- setvalue("arguments" , "\\setupenv[%s]")
- setvalue("randomseed" , "\\setupsystem[\\c!random=%s]")
- setvalue("filters" , "\\useXMLfilter[%s]")
- setvalue("usemodules" , "\\usemodule[%s]")
- setvalue("environments", "\\environment %s ")
- setalways( "\\protect")
- setalways( "\\endinput")
+ setalways( "\\setuparranging[\\v!disable]")
+ end
+ setvalue ("arguments" , "\\setupenv[%s]")
+ setvalue ("randomseed" , "\\setupsystem[\\c!random=%s]")
+ setvalues("modes" , "\\enablemode[%s]")
+ setvalues("mode" , "\\enablemode[%s]")
+ setvalues("filters" , "\\useXMLfilter[%s]")
+ setvalues("usemodules" , "\\usemodule[%s]")
+ setvalues("environments" , "\\environment %s ")
+ -- ctx stuff
+ setvalues(ctxdata.modes, "\\enablemode[%s]")
+ setvalues(ctxdata.modules, "\\usemodule[%s]")
+ setvalues(ctxdata.environments, "\\environment %s ")
+ -- done
+ setalways( "\\protect")
+ setalways( "\\endinput")
f:close()
end
end
@@ -148,8 +496,12 @@ function scripts.context.multipass.copytuifile(jobname)
end
end
-function scripts.context.run()
+function scripts.context.run(ctxdata)
-- todo: interface
+for k,v in pairs(ctxdata.flags) do
+ environment.setargument(k,v)
+end
+
local files = environment.files
if #files > 0 then
input.identify_cnf(instance)
@@ -164,15 +516,28 @@ function scripts.context.run()
if pathname ~= "" and pathname ~= "." then
filename = "./" .. filename
end
+ -- also other stubs
+ if environment.argument("forcexml") then
+ local stubname = file.replacesuffix(file.basename(filename),'run')
+ local f = io.open(stubname,'w')
+ if f then
+ f:write("\\starttext\n")
+ f:write(string.format("\\processXMLfilegrouped{%s}\n",filename))
+ f:write("\\stoptext\n")
+ f:close()
+ filename = stubname
+ end
+ end
+ --
local command = "luatex --fmt=" .. string.quote(formatfile) .. " --lua=" .. string.quote(scriptfile) .. " " .. string.quote(filename)
local oldhash, newhash = scripts.context.multipass.hashfiles(jobname), { }
- scripts.context.multipass.makeoptionfile(jobname)
+ scripts.context.multipass.makeoptionfile(jobname,ctxdata)
for i=1, scripts.context.multipass.nofruns do
input.report(string.format("run %s: %s",i,command))
local returncode = os.execute(command)
input.report("return code: " .. returncode)
if returncode > 0 then
- input.reportr("fatal error, run aborted")
+ input.report("fatal error, run aborted")
break
else
scripts.context.multipass.copyluafile(jobname)
@@ -201,11 +566,19 @@ function scripts.context.make()
end
end
+function scripts.context.ctx()
+ local ctxdata = ctxrunner.new()
+ ctxdata.jobname = environment.files[1]
+ ctxrunner.manipulate(ctxdata,environment.argument("ctx"))
+ scripts.context.run(ctxdata)
+end
+
banner = banner .. " | context tools "
messages.help = [[
--run process (one or more) files
--make generate formats
+--ctx=name use ctx file
]]
input.verbose = true
@@ -215,6 +588,8 @@ if environment.argument("run") then
scripts.context.run()
elseif environment.argument("make") then
scripts.context.make()
+elseif environment.argument("ctx") then
+ scripts.context.ctx()
else
input.help(banner,messages.help)
end
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',
diff --git a/scripts/context/lua/x-ldx.lua b/scripts/context/lua/x-ldx.lua
index 3914b2907..780022e9a 100644
--- a/scripts/context/lua/x-ldx.lua
+++ b/scripts/context/lua/x-ldx.lua
@@ -110,7 +110,7 @@ construction.
do
local e = { [">"] = "&gt;", ["<"] = "&lt;", ["&"] = "&amp;" }
function ldx.escape(str)
- return str:gsub("([><&])",e)
+ return (str:gsub("([><&])",e))
end
end
@@ -124,11 +124,13 @@ that we can use a different font to highlight them.
ldx.make_index = true
-function ldx.enhance(data)
+function ldx.enhance(data) -- i need to use lpeg and then we can properly autoindent -)
local e = ldx.escape
for _,v in pairs(data) do
if v.code then
local dqs, sqs, com, cmt, cod = { }, { }, { }, { }, e(v.code)
+cod = cod:gsub('\\"', "##d##")
+cod = cod:gsub("\\'", "##s##")
cod = cod:gsub("%-%-%[%[.-%]%]%-%-", function(s)
cmt[#cmt+1] = s
return "[[[[".. #cmt .."]]]]"
@@ -137,14 +139,12 @@ function ldx.enhance(data)
com[#com+1] = s
return "[[".. #com .."]]"
end)
---~ cod = cod:gsub('\\"', "<<>>")
---~ cod = cod:gsub("\\'", "<>")
cod = cod:gsub("(%b\"\")", function(s)
- dqs[#dqs+1] = s:sub(2,-2)
+ dqs[#dqs+1] = s:sub(2,-2) or ""
return "<<<<".. #dqs ..">>>>"
end)
cod = cod:gsub("(%b\'\')", function(s)
- sqs[#sqs+1] = s:sub(2,-2)
+ sqs[#sqs+1] = s:sub(2,-2) or ""
return "<<".. #sqs ..">>"
end)
cod = cod:gsub("(%a+)",function(key)
@@ -155,20 +155,92 @@ function ldx.enhance(data)
return key
end
end)
+ cod = cod:gsub("<<<<(%d+)>>>>", function(s)
+ return "<ldx:dqs>" .. dqs[tonumber(s)] .. "</ldx:dqs>"
+ end)
+ cod = cod:gsub("<<(%d+)>>", function(s)
+ return "<ldx:sqs>" .. sqs[tonumber(s)] .. "</ldx:sqs>"
+ end)
cod = cod:gsub("%[%[%[%[(%d+)%]%]%]%]", function(s)
return cmt[tonumber(s)]
end)
cod = cod:gsub("%[%[(%d+)%]%]", function(s)
return "<ldx:com>" .. com[tonumber(s)] .. "</ldx:com>"
end)
- cod = cod:gsub("<<<<(%d+)>>>>", function(s)
- return "<ldx:dqs>" .. dqs[tonumber(s)] .. "</ldx:dqs>"
+cod = cod:gsub("##d##", "\\\"")
+cod = cod:gsub("##s##", "\\\'")
+ if ldx.make_index then
+ local lines = cod:split("\n")
+ local f = "(<ldx:key class='1'>function</ldx:key>)%s+([%w%.]+)%s*%("
+ for k,v in pairs(lines) do
+ -- functies
+ v = v:gsub(f,function(key, str)
+ return "<ldx:function>" .. str .. "</ldx:function>("
+ end)
+ -- variables
+ v = v:gsub("^([%w][%w%,%s]-)(=[^=])",function(str, rest)
+ local t = string.split(str, ",%s*")
+ for k,v in pairs(t) do
+ t[k] = "<ldx:variable>" .. v .. "</ldx:variable>"
+ end
+ return table.join(t,", ") .. rest
+ end)
+ -- so far
+ lines[k] = v
+ end
+ v.code = table.concat(lines,"\n")
+ else
+ v.code = cod
+ end
+ end
+ end
+end
+
+function ldx.enhance(data) -- i need to use lpeg and then we can properly autoindent -)
+ local e = ldx.escape
+ for _,v in pairs(data) do
+ if v.code then
+ local dqs, sqs, com, cmt, cod = { }, { }, { }, { }, e(v.code)
+ cod = cod:gsub('\\"', "##d##")
+ cod = cod:gsub("\\'", "##s##")
+ cod = cod:gsub("%-%-%[%[.-%]%]%-%-", function(s)
+ cmt[#cmt+1] = s
+ return "<l<<<".. #cmt ..">>>l>"
end)
- cod = cod:gsub("<<(%d+)>>", function(s)
+ cod = cod:gsub("%-%-([^\n]*)", function(s)
+ com[#com+1] = s
+ return "<c<<<".. #com ..">>>c>"
+ end)
+ cod = cod:gsub("(%b\"\")", function(s)
+ dqs[#dqs+1] = s:sub(2,-2) or ""
+ return "<d<<<".. #dqs ..">>>d>"
+ end)
+ cod = cod:gsub("(%b\'\')", function(s)
+ sqs[#sqs+1] = s:sub(2,-2) or ""
+ return "<s<<<".. #sqs ..">>>s>"
+ end)
+ cod = cod:gsub("(%a+)",function(key)
+ local class = ldx.keywords.reserved[key]
+ if class then
+ return "<ldx:key class='" .. class .. "'>" .. key .. "</ldx:key>"
+ else
+ return key
+ end
+ end)
+ cod = cod:gsub("<s<<<(%d+)>>>s>", function(s)
return "<ldx:sqs>" .. sqs[tonumber(s)] .. "</ldx:sqs>"
end)
---~ cod = cod:gsub("<<>>", "\\\"")
---~ cod = cod:gsub("<>" , "\\\'")
+ cod = cod:gsub("<d<<<(%d+)>>>d>", function(s)
+ return "<ldx:dqs>" .. dqs[tonumber(s)] .. "</ldx:dqs>"
+ end)
+ cod = cod:gsub("<c<<<(%d+)>>>c>", function(s)
+ return "<ldx:com>" .. com[tonumber(s)] .. "</ldx:com>"
+ end)
+ cod = cod:gsub("<l<<<(%d+)>>>l>", function(s)
+ return cmt[tonumber(s)]
+ end)
+ cod = cod:gsub("##d##", "\\\"")
+ cod = cod:gsub("##s##", "\\\'")
if ldx.make_index then
local lines = cod:split("\n")
local f = "(<ldx:key class='1'>function</ldx:key>)%s+([%w%.]+)%s*%("
diff --git a/scripts/context/ruby/texmfstart.rb b/scripts/context/ruby/texmfstart.rb
index 895484024..e43929213 100644
--- a/scripts/context/ruby/texmfstart.rb
+++ b/scripts/context/ruby/texmfstart.rb
@@ -1765,6 +1765,7 @@ def runoneof(application,fullname,browserpermitted)
if browserpermitted && launch(fullname) then
return true
else
+ fullname = quoted(fullname) # added because MM ran into problems
report("starting #{$filename}") unless $report
output("\n") if $report && $verbose
applications = $applications[application.downcase]