diff options
| author | Hans Hagen <pragma@wxs.nl> | 2007-09-10 21:31:00 +0200 | 
|---|---|---|
| committer | Hans Hagen <pragma@wxs.nl> | 2007-09-10 21:31:00 +0200 | 
| commit | 104ea1dae3d609aeb395e19658ad6ea7d4c85eea (patch) | |
| tree | 9e0f83de78120bf8e227025ea69d4a94cbda83e4 /scripts | |
| parent | deecfe09c774d4c2835f6999b2cdd9ca07e9bdae (diff) | |
| download | context-104ea1dae3d609aeb395e19658ad6ea7d4c85eea.tar.gz | |
stable 2007.09.10 21:31
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/context/lua/luatools.lua | 37 | ||||
| -rw-r--r-- | scripts/context/lua/mtx-cache.lua | 4 | ||||
| -rw-r--r-- | scripts/context/lua/mtx-chars.lua | 90 | ||||
| -rw-r--r-- | scripts/context/lua/mtx-context.lua | 455 | ||||
| -rw-r--r-- | scripts/context/lua/mtxrun.lua | 1834 | ||||
| -rw-r--r-- | scripts/context/lua/x-ldx.lua | 94 | ||||
| -rw-r--r-- | scripts/context/ruby/texmfstart.rb | 1 | 
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   = { ['&'] = '&', ['<'] = '<', ['>'] = '>', ['"'] = '"' } +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 = { [">"] = ">", ["<"] = "<", ["&"] = "&" }      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] | 
