diff options
Diffstat (limited to 'tex/context/base/strc-ref.lua')
-rw-r--r-- | tex/context/base/strc-ref.lua | 4316 |
1 files changed, 2158 insertions, 2158 deletions
diff --git a/tex/context/base/strc-ref.lua b/tex/context/base/strc-ref.lua index 284418c48..54484fabe 100644 --- a/tex/context/base/strc-ref.lua +++ b/tex/context/base/strc-ref.lua @@ -1,2158 +1,2158 @@ -if not modules then modules = { } end modules ['strc-ref'] = { - version = 1.001, - comment = "companion to strc-ref.mkiv", - author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "see context related readme files" -} - --- beware, this is a first step in the rewrite (just getting rid of --- the tuo file); later all access and parsing will also move to lua - --- the useddata and pagedata names might change --- todo: pack exported data - --- todo: autoload components when ::: - -local format, find, gmatch, match, concat = string.format, string.find, string.gmatch, string.match, table.concat -local texcount, texsetcount = tex.count, tex.setcount -local rawget, tonumber = rawget, tonumber -local lpegmatch = lpeg.match -local copytable = table.copy -local formatters = string.formatters - -local allocate = utilities.storage.allocate -local mark = utilities.storage.mark -local setmetatableindex = table.setmetatableindex - -local trace_referencing = false trackers.register("structures.referencing", function(v) trace_referencing = v end) -local trace_analyzing = false trackers.register("structures.referencing.analyzing", function(v) trace_analyzing = v end) -local trace_identifying = false trackers.register("structures.referencing.identifying", function(v) trace_identifying = v end) -local trace_importing = false trackers.register("structures.referencing.importing", function(v) trace_importing = v end) -local trace_empty = false trackers.register("structures.referencing.empty", function(v) trace_empty = v end) - -local check_duplicates = true - -directives.register("structures.referencing.checkduplicates", function(v) - check_duplicates = v -end) - -local report_references = logs.reporter("references") -local report_unknown = logs.reporter("references","unknown") -local report_identifying = logs.reporter("references","identifying") -local report_importing = logs.reporter("references","importing") -local report_empty = logs.reporter("references","empty") - -local variables = interfaces.variables -local constants = interfaces.constants -local context = context - -local v_default = variables.default -local v_url = variables.url -local v_file = variables.file -local v_unknown = variables.unknown -local v_yes = variables.yes - -local texcount = tex.count -local texconditionals = tex.conditionals - -local productcomponent = resolvers.jobs.productcomponent -local justacomponent = resolvers.jobs.justacomponent - -local logsnewline = logs.newline -local logspushtarget = logs.pushtarget -local logspoptarget = logs.poptarget - -local settings_to_array = utilities.parsers.settings_to_array -local unsetvalue = attributes.unsetvalue - -local structures = structures -local helpers = structures.helpers -local sections = structures.sections -local references = structures.references -local lists = structures.lists -local counters = structures.counters - --- some might become local - -references.defined = references.defined or allocate() - -local defined = references.defined -local derived = allocate() -local specials = allocate() -local runners = allocate() -local internals = allocate() -local filters = allocate() -local executers = allocate() -local handlers = allocate() -local tobesaved = allocate() -local collected = allocate() -local tobereferred = allocate() -local referred = allocate() - -references.derived = derived -references.specials = specials -references.runners = runners -references.internals = internals -references.filters = filters -references.executers = executers -references.handlers = handlers -references.tobesaved = tobesaved -references.collected = collected -references.tobereferred = tobereferred -references.referred = referred - -local splitreference = references.splitreference -local splitprefix = references.splitcomponent -- replaces: references.splitprefix -local prefixsplitter = references.prefixsplitter -local componentsplitter = references.componentsplitter - -local currentreference = nil - -storage.register("structures/references/defined", references.defined, "structures.references.defined") - -local initializers = { } -local finalizers = { } - -function references.registerinitializer(func) -- we could use a token register instead - initializers[#initializers+1] = func -end -function references.registerfinalizer(func) -- we could use a token register instead - finalizers[#finalizers+1] = func -end - -local function initializer() -- can we use a tobesaved as metatable for collected? - tobesaved = references.tobesaved - collected = references.collected - for i=1,#initializers do - initializers[i](tobesaved,collected) - end -end - -local function finalizer() - for i=1,#finalizers do - finalizers[i](tobesaved) - end -end - -job.register('structures.references.collected', tobesaved, initializer, finalizer) - -local maxreferred = 1 -local nofreferred = 0 - --- local function initializer() -- can we use a tobesaved as metatable for collected? --- tobereferred = references.tobereferred --- referred = references.referred --- nofreferred = #referred --- end - -local function initializer() -- can we use a tobesaved as metatable for collected? - tobereferred = references.tobereferred - referred = references.referred - setmetatableindex(referred,get) -- hm, what is get ? -end - --- We make the array sparse (maybe a finalizer should optionally return a table) because --- there can be quite some page links involved. We only store one action number per page --- which is normally good enough for what we want (e.g. see above/below) and we do --- a combination of a binary search and traverse backwards. A previous implementation --- always did a traverse and was pretty slow on a large number of links (given that this --- methods was used). It took me about a day to locate this as a bottleneck in processing --- a 2500 page interactive document with 60 links per page. In that case, traversing --- thousands of slots per link then brings processing to a grinding halt (especially when --- there are no slots at all, which is the case in a first run). - -local sparsetobereferred = { } - -local function finalizer() - local lastr, lasti - local n = 0 - for i=1,maxreferred do - local r = tobereferred[i] - if not lastr then - lastr = r - lasti = i - elseif r ~= lastr then - n = n + 1 - sparsetobereferred[n] = { lastr, lasti } - lastr = r - lasti = i - end - end - if lastr then - n = n + 1 - sparsetobereferred[n] = { lastr, lasti } - end -end - -job.register('structures.references.referred', sparsetobereferred, initializer, finalizer) - -local function referredpage(n) - local max = nofreferred - if max > 0 then - -- find match - local min = 1 - while true do - local mid = floor((min+max)/2) - local r = referred[mid] - local m = r[2] - if n == m then - return r[1] - elseif n > m then - min = mid + 1 - else - max = mid - 1 - end - if min > max then - break - end - end - -- find first previous - for i=min,1,-1 do - local r = referred[i] - if r and r[2] < n then - return r[1] - end - end - end - -- fallback - return texcount.realpageno -end - -references.referredpage = referredpage - -function references.registerpage(n) -- called in the backend code - if not tobereferred[n] then - if n > maxreferred then - maxreferred = n - end - tobereferred[n] = texcount.realpageno - end -end - --- todo: delay split till later as in destinations we split anyway - -local orders, lastorder = { }, 0 - -local function setnextorder(kind,name) - lastorder = 0 - if kind and name then - local ok = orders[kind] - if not ok then - ok = { } - orders[kind] = ok - end - lastorder = (ok[name] or 0) + 1 - ok[name] = lastorder - end - texsetcount("global","locationorder",lastorder) -end - -references.setnextorder = setnextorder - -function references.setnextinternal(kind,name) - setnextorder(kind,name) -- always incremented with internal - local n = texcount.locationcount + 1 - texsetcount("global","locationcount",n) - return n -end - -function references.currentorder(kind,name) - return orders[kind] and orders[kind][name] or lastorder -end - -local function setcomponent(data) - -- we might consider doing this at the tex end, just like prefix - local component = productcomponent() - if component then - local references = data and data.references - if references then - references.component = component - end - return component - end - -- but for the moment we do it here (experiment) -end - -commands.setnextinternalreference = references.setnextinternal - -function commands.currentreferenceorder(kind,name) - context(references.currentorder(kind,name)) -end - -references.setcomponent = setcomponent - -function references.set(kind,prefix,tag,data) --- setcomponent(data) - local pd = tobesaved[prefix] -- nicer is a metatable - if not pd then - pd = { } - tobesaved[prefix] = pd - end - local n = 0 - for ref in gmatch(tag,"[^,]+") do - if ref ~= "" then - if check_duplicates and pd[ref] then - if prefix and prefix ~= "" then - report_references("redundant reference %a in namespace %a",ref,prefix) - else - report_references("redundant reference %a",ref) - end - else - n = n + 1 - pd[ref] = data - context.dofinishsomereference(kind,prefix,ref) - end - end - end - return n > 0 -end - -function references.enhance(prefix,tag) - local l = tobesaved[prefix][tag] - if l then - l.references.realpage = texcount.realpageno - end -end - -commands.enhancereference = references.enhance - --- -- -- related to strc-ini.lua -- -- -- - -references.resolvers = references.resolvers or { } -local resolvers = references.resolvers - -local function getfromlist(var) - local vi = var.i - if vi then - vi = vi[3] or lists.collected[vi[2]] - if vi then - local r = vi.references and vi.references - if r then - r = r.realpage - end - if not r then - r = vi.pagedata and vi.pagedata - if r then - r = r.realpage - end - end - var.i = vi - var.r = r or 1 - else - var.i = nil - var.r = 1 - end - else - var.i = nil - var.r = 1 - end -end - --- resolvers.section = getfromlist --- resolvers.float = getfromlist --- resolvers.description = getfromlist --- resolvers.formula = getfromlist --- resolvers.note = getfromlist - -setmetatableindex(resolvers,function(t,k) - local v = getfromlist - resolvers[k] = v - return v -end) - -function resolvers.reference(var) - local vi = var.i[2] -- check - if vi then - var.i = vi - var.r = (vi.references and vi.references.realpage) or (vi.pagedata and vi.pagedata.realpage) or 1 - else - var.i = nil - var.r = 1 - end -end - -local function register_from_lists(collected,derived,pages,sections) - local g = derived[""] if not g then g = { } derived[""] = g end -- global - for i=1,#collected do - local entry = collected[i] - local m, r = entry.metadata, entry.references - if m and r then - local reference = r.reference or "" - local prefix = r.referenceprefix or "" - local component = r.component and r.component or "" - if reference ~= "" then - local kind, realpage = m.kind, r.realpage - if kind and realpage then - local d = derived[prefix] - if not d then - d = { } - derived[prefix] = d - end - local c = derived[component] - if not c then - c = { } - derived[component] = c - end - local t = { kind, i, entry } - for s in gmatch(reference,"%s*([^,]+)") do - if trace_referencing then - report_references("list entry %a provides %a reference %a on realpage %a",i,kind,s,realpage) - end - c[s] = c[s] or t -- share them - d[s] = d[s] or t -- share them - g[s] = g[s] or t -- first wins - end - end - end - end - end --- inspect(derived) -end - -references.registerinitializer(function() register_from_lists(lists.collected,derived) end) - --- urls - -references.urls = references.urls or { } -references.urls.data = references.urls.data or { } - -local urls = references.urls.data - -function references.urls.define(name,url,file,description) - if name and name ~= "" then - urls[name] = { url or "", file or "", description or url or file or ""} - end -end - -local pushcatcodes = context.pushcatcodes -local popcatcodes = context.popcatcodes -local txtcatcodes = catcodes.numbers.txtcatcodes -- or just use "txtcatcodes" - -function references.urls.get(name) - local u = urls[name] - if u then - local url, file = u[1], u[2] - if file and file ~= "" then - return formatters["%s/%s"](url,file) - else - return url - end - end -end - -function commands.geturl(name) - local url = references.urls.get(name) - if url and url ~= "" then - pushcatcodes(txtcatcodes) - context(url) - popcatcodes() - end -end - --- function commands.gethyphenatedurl(name,...) --- local url = references.urls.get(name) --- if url and url ~= "" then --- hyphenatedurl(url,...) --- end --- end - -function commands.doifurldefinedelse(name) - commands.doifelse(urls[name]) -end - -commands.useurl= references.urls.define - --- files - -references.files = references.files or { } -references.files.data = references.files.data or { } - -local files = references.files.data - -function references.files.define(name,file,description) - if name and name ~= "" then - files[name] = { file or "", description or file or "" } - end -end - -function references.files.get(name,method,space) -- method: none, before, after, both, space: yes/no - local f = files[name] - if f then - context(f[1]) - end -end - -function commands.doiffiledefinedelse(name) - commands.doifelse(files[name]) -end - -commands.usefile= references.files.define - --- helpers - -function references.checkedfile(whatever) -- return whatever if not resolved - if whatever then - local w = files[whatever] - if w then - return w[1] - else - return whatever - end - end -end - -function references.checkedurl(whatever) -- return whatever if not resolved - if whatever then - local w = urls[whatever] - if w then - local u, f = w[1], w[2] - if f and f ~= "" then - return u .. "/" .. f - else - return u - end - else - return whatever - end - end -end - -function references.checkedfileorurl(whatever,default) -- return nil, nil if not resolved - if whatever then - local w = files[whatever] - if w then - return w[1], nil - else - local w = urls[whatever] - if w then - local u, f = w[1], w[2] - if f and f ~= "" then - return nil, u .. "/" .. f - else - return nil, u - end - end - end - end - return default -end - --- programs - -references.programs = references.programs or { } -references.programs.data = references.programs.data or { } - -local programs = references.programs.data - -function references.programs.define(name,file,description) - if name and name ~= "" then - programs[name] = { file or "", description or file or ""} - end -end - -function references.programs.get(name) - local f = programs[name] - return f and f[1] -end - -function references.checkedprogram(whatever) -- return whatever if not resolved - if whatever then - local w = programs[whatever] - if w then - return w[1] - else - return whatever - end - end -end - -commands.defineprogram = references.programs.define - -function commands.getprogram(name) - local f = programs[name] - if f then - context(f[1]) - end -end - --- shared by urls and files - -function references.whatfrom(name) - context((urls[name] and v_url) or (files[name] and v_file) or v_unknown) -end - -function references.from(name) - local u = urls[name] - if u then - local url, file, description = u[1], u[2], u[3] - if description ~= "" then - return description - -- ok - elseif file and file ~= "" then - return url .. "/" .. file - else - return url - end - else - local f = files[name] - if f then - local file, description = f[1], f[2] - if description ~= "" then - return description - else - return file - end - end - end -end - -function commands.from(name) - local u = urls[name] - if u then - local url, file, description = u[1], u[2], u[3] - if description ~= "" then - context.dofromurldescription(description) - -- ok - elseif file and file ~= "" then - context.dofromurlliteral(url .. "/" .. file) - else - context.dofromurlliteral(url) - end - else - local f = files[name] - if f then - local file, description = f[1], f[2] - if description ~= "" then - context.dofromfiledescription(description) - else - context.dofromfileliteral(file) - end - end - end -end - -function references.define(prefix,reference,list) - local d = defined[prefix] if not d then d = { } defined[prefix] = d end - d[reference] = { "defined", list } -end - -function references.reset(prefix,reference) - local d = defined[prefix] - if d then - d[reference] = nil - end -end - -commands.definereference = references.define -commands.resetreference = references.reset - --- \primaryreferencefoundaction --- \secondaryreferencefoundaction --- \referenceunknownaction - --- t.special t.operation t.arguments t.outer t.inner - --- to what extend do we check the non prefixed variant - -local strict = false - -local function resolve(prefix,reference,args,set) -- we start with prefix,reference - if reference and reference ~= "" then - if not set then - set = { prefix = prefix, reference = reference } - else - set.reference = set.reference or reference - set.prefix = set.prefix or prefix - end - local r = settings_to_array(reference) - for i=1,#r do - local ri = r[i] - local d - if strict then - d = defined[prefix] or defined[""] - d = d and d[ri] - else - d = defined[prefix] - d = d and d[ri] - if not d then - d = defined[""] - d = d and d[ri] - end - end - if d then - resolve(prefix,d[2],nil,set) - else - local var = splitreference(ri) - if var then - var.reference = ri - local vo, vi = var.outer, var.inner - if not vo and vi then - -- to be checked - if strict then - d = defined[prefix] or defined[""] - d = d and d[vi] - else - d = defined[prefix] - d = d and d[vi] - if not d then - d = defined[""] - d = d and d[vi] - end - end - -- - if d then - resolve(prefix,d[2],var.arguments,set) -- args can be nil - else - if args then var.arguments = args end - set[#set+1] = var - end - else - if args then var.arguments = args end - set[#set+1] = var - end - if var.has_tex then - set.has_tex = true - end - else - -- report_references("funny pattern %a",ri) - end - end - end - return set - else - return { } - end -end - --- prefix == "" is valid prefix which saves multistep lookup - -references.currentset = nil - -function commands.setreferenceoperation(k,v) - references.currentset[k].operation = v -end - -function commands.setreferencearguments(k,v) - references.currentset[k].arguments = v -end - -local expandreferenceoperation = context.expandreferenceoperation -local expandreferencearguments = context.expandreferencearguments - -function references.expandcurrent() -- todo: two booleans: o_has_tex& a_has_tex - local currentset = references.currentset - if currentset and currentset.has_tex then - for i=1,#currentset do - local ci = currentset[i] - local operation = ci.operation - if operation and find(operation,"\\") then -- if o_has_tex then - expandreferenceoperation(i,operation) - end - local arguments = ci.arguments - if arguments and find(arguments,"\\") then -- if a_has_tex then - expandreferencearguments(i,arguments) - end - end - end -end - -commands.expandcurrentreference = references.expandcurrent -- for the moment the same - -local externals = { } - --- we have prefixes but also components: --- --- : prefix --- :: always external --- ::: internal (for products) or external (for components) - -local function loadexternalreferences(name,utilitydata) - local struc = utilitydata.structures - if struc then - local external = struc.references.collected -- direct references - local lists = struc.lists.collected -- indirect references (derived) - local pages = struc.pages.collected -- pagenumber data - -- a bit weird one, as we don't have the externals in the collected - for prefix, set in next, external do - for reference, data in next, set do - if trace_importing then - report_importing("registering %a reference, kind %a, name %a, prefix %a, reference %a", - "external","regular",name,prefix,reference) - end - local section = reference.section - local realpage = reference.realpage - if section then - reference.sectiondata = lists[section] - end - if realpage then - reference.pagedata = pages[realpage] - end - end - end - for i=1,#lists do - local entry = lists[i] - local metadata = entry.metadata - local references = entry.references - if metadata and references then - local reference = references.reference - if reference and reference ~= "" then - local kind = metadata.kind - local realpage = references.realpage - if kind and realpage then - references.pagedata = pages[realpage] - local prefix = references.referenceprefix or "" - local target = external[prefix] - if not target then - target = { } - external[prefix] = target - end - for s in gmatch(reference,"%s*([^,]+)") do - if trace_importing then - report_importing("registering %s reference, kind %a, name %a, prefix %a, reference %a", - "external",kind,name,prefix,s) - end - target[s] = target[s] or entry - end - end - end - end - end - externals[name] = external - return external - end -end - -local externalfiles = { } - -table.setmetatableindex(externalfiles, function(t,k) - local v = files[k] - if not v then - v = { k, k } - end - externalfiles[k] = v - return v -end) - -table.setmetatableindex(externals,function(t,k) -- either or not automatically - local filename = externalfiles[k][1] -- filename - local fullname = file.replacesuffix(filename,"tuc") - if lfs.isfile(fullname) then -- todo: use other locator - local utilitydata = job.loadother(fullname) - if utilitydata then - local external = loadexternalreferences(k,utilitydata) - t[k] = external or false - return external - end - end - t[k] = false - return false -end) - -local productdata = allocate { - productreferences = { }, - componentreferences = { }, - components = { }, -} - -references.productdata = productdata - -local function loadproductreferences(productname,componentname,utilitydata) - local struc = utilitydata.structures - if struc then - local productreferences = struc.references.collected -- direct references - local lists = struc.lists.collected -- indirect references (derived) - local pages = struc.pages.collected -- pagenumber data - -- we use indirect tables to save room but as they are eventually - -- just references we resolve them to data here (the mechanisms - -- that use this data check for indirectness) - for prefix, set in next, productreferences do - for reference, data in next, set do - if trace_importing then - report_importing("registering %s reference, kind %a, name %a, prefix %a, reference %a", - "product","regular",productname,prefix,reference) - end - local section = reference.section - local realpage = reference.realpage - if section then - reference.sectiondata = lists[section] - end - if realpage then - reference.pagedata = pages[realpage] - end - end - end - -- - local componentreferences = { } - for i=1,#lists do - local entry = lists[i] - local metadata = entry.metadata - local references = entry.references - if metadata and references then - local reference = references.reference - if reference and reference ~= "" then - local kind = metadata.kind - local realpage = references.realpage - if kind and realpage then - references.pagedata = pages[realpage] - local prefix = references.referenceprefix or "" - local component = references.component - local ctarget, ptarget - if not component or component == componentname then - -- skip - else - -- one level up - local external = componentreferences[component] - if not external then - external = { } - componentreferences[component] = external - end - if component == prefix then - prefix = "" - end - ctarget = external[prefix] - if not ctarget then - ctarget = { } - external[prefix] = ctarget - end - end - ptarget = productreferences[prefix] - if not ptarget then - ptarget = { } - productreferences[prefix] = ptarget - end - for s in gmatch(reference,"%s*([^,]+)") do - if ptarget then - if trace_importing then - report_importing("registering %s reference, kind %a, name %a, prefix %a, reference %a", - "product",kind,productname,prefix,s) - end - ptarget[s] = ptarget[s] or entry - end - if ctarget then - if trace_importing then - report_importing("registering %s reference, kind %a, name %a, prefix %a, referenc %a", - "component",kind,productname,prefix,s) - end - ctarget[s] = ctarget[s] or entry - end - end - end - end - end - end - productdata.productreferences = productreferences -- not yet used - productdata.componentreferences = componentreferences - end -end - -local function loadproductvariables(product,component,utilitydata) - local struc = utilitydata.structures - if struc then - local lists = struc.lists and struc.lists.collected - if lists then - local pages = struc.pages and struc.pages.collected - for i=1,#lists do - local li = lists[i] - if li.metadata.kind == "section" and li.references.component == component then - local firstsection = li - if firstsection.numberdata then - local numbers = firstsection.numberdata.numbers - if numbers then - if trace_importing then - report_importing("initializing section number to %:t",numbers) - end - productdata.firstsection = firstsection - structures.documents.preset(numbers) - end - end - if pages and firstsection.references then - local firstpage = pages[firstsection.references.realpage] - local number = firstpage and firstpage.number - if number then - if trace_importing then - report_importing("initializing page number to %a",number) - end - productdata.firstpage = firstpage - counters.set("userpage",1,number) - end - end - break - end - end - end - end -end - -local function componentlist(tree,target) - local branches = tree and tree.branches - if branches then - for i=1,#branches do - local branch = branches[i] - local type = branch.type - if type == "component" then - if target then - target[#target+1] = branch.name - else - target = { branch.name } - end - elseif type == "product" or type == "component" then - target = componentlist(branch,target) - end - end - end - return target -end - -local function loadproductcomponents(product,component,utilitydata) - local job = utilitydata.job - productdata.components = componentlist(job and job.structure and job.structure.collected) or { } -end - -references.registerinitializer(function(tobesaved,collected) - -- not that much related to tobesaved or collected - productdata.components = componentlist(job.structure.collected) or { } -end) - -function structures.references.loadpresets(product,component) -- we can consider a special components hash - if product and component and product~= "" and component ~= "" and not productdata.product then -- maybe: productdata.filename ~= filename - productdata.product = product - productdata.component = component - local fullname = file.replacesuffix(product,"tuc") - if lfs.isfile(fullname) then -- todo: use other locator - local utilitydata = job.loadother(fullname) - if utilitydata then - if trace_importing then - report_importing("loading references for component %a of product %a from %a",component,product,fullname) - end - loadproductvariables (product,component,utilitydata) - loadproductreferences(product,component,utilitydata) - loadproductcomponents(product,component,utilitydata) - -- inspect(productdata) - end - end - end -end - -structures.references.productdata = productdata - -local useproduct = commands.useproduct - -if useproduct then - - function commands.useproduct(product) - useproduct(product) - if texconditionals.autocrossfilereferences then - local component = justacomponent() - if component then - if trace_referencing or trace_importing then - report_references("loading presets for component %a of product %a",component,product) - end - structures.references.loadpresets(product,component) - end - end - end - -end - --- productdata.firstsection.numberdata.numbers --- productdata.firstpage.number - -local function report_identify_special(set,var,i,type) - local reference = set.reference - local prefix = set.prefix or "" - local special = var.special - local error = var.error - local kind = var.kind - if error then - report_identifying("type %a, reference %a, index %a, prefix %a, special %a, error %a",type,reference,i,prefix,special,error) - else - report_identifying("type %a, reference %a, index %a, prefix %a, special %a, kind %a",type,reference,i,prefix,special,kind) - end -end - -local function report_identify_arguments(set,var,i,type) - local reference = set.reference - local prefix = set.prefix or "" - local arguments = var.arguments - local error = var.error - local kind = var.kind - if error then - report_identifying("type %a, reference %a, index %a, prefix %a, arguments %a, error %a",type,reference,i,prefix,arguments,error) - else - report_identifying("type %a, reference %a, index %a, prefix %a, arguments %a, kind %a",type,reference,i,prefix,arguments,kind) - end -end - -local function report_identify_outer(set,var,i,type) - local reference = set.reference - local prefix = set.prefix or "" - local outer = var.outer - local error = var.error - local kind = var.kind - if outer then - if error then - report_identifying("type %a, reference %a, index %a, prefix %a, outer %a, error %a",type,reference,i,prefix,outer,error) - else - report_identifying("type %a, reference %a, index %a, prefix %a, outer %a, kind %a",type,reference,i,prefix,outer,kind) - end - else - if error then - report_identifying("type %a, reference %a, index %a, prefix %a, error %a",type,reference,i,prefix,error) - else - report_identifying("type %a, reference %a, index %a, prefix %a, kind %a",type,reference,i,prefix,kind) - end - end -end - -local function identify_special(set,var,i) - local special = var.special - local s = specials[special] - if s then - local outer = var.outer - local operation = var.operation - local arguments = var.arguments - if outer then - if operation then - -- special(outer::operation) - var.kind = "special outer with operation" - else - -- special() - var.kind = "special outer" - end - var.f = outer - elseif operation then - if arguments then - -- special(operation{argument,argument}) - var.kind = "special operation with arguments" - else - -- special(operation) - var.kind = "special operation" - end - else - -- special() - var.kind = "special" - end - if trace_identifying then - report_identify_special(set,var,i,"1a") - end - else - var.error = "unknown special" - end - return var -end - -local function identify_arguments(set,var,i) - local s = specials[var.inner] - if s then - -- inner{argument} - var.kind = "special with arguments" - else - var.error = "unknown inner or special" - end - if trace_identifying then - report_identify_arguments(set,var,i,"3a") - end - return var -end - -local function identify_inner(set,var,prefix,collected,derived,tobesaved) - local inner = var.inner - local outer = var.outer - -- inner ... we could move the prefix logic into the parser so that we have 'm for each entry - -- foo:bar -> foo == prefix (first we try the global one) - -- -:bar -> ignore prefix - local p, i = prefix, nil - local splitprefix, splitinner - -- the next test is a safeguard when references are auto loaded from outer - if inner then - splitprefix, splitinner = lpegmatch(prefixsplitter,inner) - end - -- these are taken from other anonymous references - if splitprefix and splitinner then - if splitprefix == "-" then - i = collected[""] - i = i and i[splitinner] - if i then - p = "" - end - else - i = collected[splitprefix] - i = i and i[splitinner] - if i then - p = splitprefix - end - end - end - -- todo: strict here - if not i then - i = collected[prefix] - i = i and i[inner] - if i then - p = prefix - end - end - if not i and prefix ~= "" then - i = collected[""] - i = i and i[inner] - if i then - p = "" - end - end - if i then - var.i = { "reference", i } - resolvers.reference(var) - var.kind = "inner" - var.p = p - elseif derived then - -- these are taken from other data structures (like lists) - if splitprefix and splitinner then - if splitprefix == "-" then - i = derived[""] - i = i and i[splitinner] - if i then - p = "" - end - else - i = derived[splitprefix] - i = i and i[splitinner] - if i then - p = splitprefix - end - end - end - if not i then - i = derived[prefix] - i = i and i[inner] - if i then - p = prefix - end - end - if not i and prefix ~= "" then - i = derived[""] - i = i and i[inner] - if i then - p = "" - end - end - if i then - var.kind = "inner" - var.i = i - var.p = p - local ri = resolvers[i[1]] - if ri then - ri(var) - else - -- can't happen as we catch it with a metatable now - report_references("unknown inner resolver for %a",i[1]) - end - else - -- no prefixes here - local s = specials[inner] - if s then - var.kind = "special" - else - i = (collected and collected[""] and collected[""][inner]) or - (derived and derived [""] and derived [""][inner]) or - (tobesaved and tobesaved[""] and tobesaved[""][inner]) - if i then - var.kind = "inner" - var.i = { "reference", i } - resolvers.reference(var) - var.p = "" - else - var.error = "unknown inner or special" - end - end - end - end - return var -end - -local function identify_outer(set,var,i) - local outer = var.outer - local inner = var.inner - local external = externals[outer] - if external then - local v = copytable(var) - v = identify_inner(set,v,nil,external) - if v.i and not v.error then - v.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,v,i,"2a") - end - return v - end - v = copytable(var) - local v = identify_inner(set,v,v.outer,external) - if v.i and not v.error then - v.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,v,i,"2b") - end - return v - end - end - local external = productdata.componentreferences[outer] - if external then - local v = identify_inner(set,copytable(var),nil,external) - if v.i and not v.error then - v.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,v,i,"2c") - end - return v - end - end - local external = productdata.productreferences[outer] - if external then - local vi = external[inner] - if vi then - var.kind = "outer with inner" - var.i = vi - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"2d") - end - return var - end - end - -- the rest - local special = var.special - local arguments = var.arguments - local operation = var.operation - if inner then - if arguments then - -- outer::inner{argument} - var.kind = "outer with inner with arguments" - else - -- outer::inner - var.kind = "outer with inner" - end - var.i = { "reference", inner } - resolvers.reference(var) - var.f = outer - if trace_identifying then - report_identify_outer(set,var,i,"2e") - end - elseif special then - local s = specials[special] - if s then - if operation then - if arguments then - -- outer::special(operation{argument,argument}) - var.kind = "outer with special and operation and arguments" - else - -- outer::special(operation) - var.kind = "outer with special and operation" - end - else - -- outer::special() - var.kind = "outer with special" - end - var.f = outer - else - var.error = "unknown outer with special" - end - if trace_identifying then - report_identify_outer(set,var,i,"2f") - end - else - -- outer:: - var.kind = "outer" - var.f = outer - if trace_identifying then - report_identify_outer(set,var,i,"2g") - end - end - return var -end - -local function identify_inner_or_outer(set,var,i) - -- here we fall back on product data - local inner = var.inner - if inner and inner ~= "" then - local v = identify_inner(set,copytable(var),set.prefix,collected,derived,tobesaved) - if v.i and not v.error then - v.kind = "inner" -- check this - if trace_identifying then - report_identify_outer(set,v,i,"4a") - end - return v - end - -local components = job.structure.components - -if components then - for i=1,#components do - local component = components[i] - local data = collected[component] - local vi = data and data[inner] - if vi then - var.outer = component - var.i = vi - var.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"4x") - end - return var - end - end -end - - local componentreferences = productdata.componentreferences - local productreferences = productdata.productreferences - local components = productdata.components - if components and componentreferences then - -- for component, data in next, productdata.componentreferences do -- better do this in order of processing: - for i=1,#components do - local component = components[i] - local data = componentreferences[component] - if data then - local d = data[""] - local vi = d and d[inner] - if vi then - var.outer = component - var.i = vi - var.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"4b") - end - return var - end - end - end - end - local component, inner = lpegmatch(componentsplitter,inner) - if component then - local data = componentreferences and componentreferences[component] - if data then - local d = data[""] - local vi = d and d[inner] - if vi then - var.inner = inner - var.outer = component - var.i = vi - var.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"4c") - end - return var - end - end - local data = productreferences and productreferences[component] - if data then - local vi = data[inner] - if vi then - var.inner = inner - var.outer = component - var.i = vi - var.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"4d") - end - return var - end - end - end - var.error = "unknown inner" - else - var.error = "no inner" - end - if trace_identifying then - report_identify_outer(set,var,i,"4e") - end - return var -end - --- local function identify_inner_or_outer(set,var,i) --- -- we might consider first checking with a prefix prepended and then without --- -- which is better for fig:oeps --- local var = do_identify_inner_or_outer(set,var,i) --- if var.error then --- local prefix = set.prefix --- if prefix and prefix ~= "" then --- var.inner = prefix .. ':' .. var.inner --- var.error = nil --- return do_identify_inner_or_outer(set,var,i) --- end --- end --- return var --- end - -local function identify_inner_component(set,var,i) - -- we're in a product (maybe ignore when same as component) - local component = var.component - identify_inner(set,var,component,collected,derived,tobesaved) - if trace_identifying then - report_identify_outer(set,var,i,"5a") - end - return var -end - -local function identify_outer_component(set,var,i) - local component = var.component - local inner = var.inner - local data = productdata.componentreferences[component] - if data then - local d = data[""] - local vi = d and d[inner] - if vi then - var.inner = inner - var.outer = component - var.i = vi - var.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"6a") - end - return var - end - end - local data = productdata.productreferences[component] - if data then - local vi = data[inner] - if vi then - var.inner = inner - var.outer = component - var.i = vi - var.kind = "outer with inner" - set.external = true - if trace_identifying then - report_identify_outer(set,var,i,"6b") - end - return var - end - end - var.error = "unknown component" - if trace_identifying then - report_identify_outer(set,var,i,"6c") - end - return var -end - -local nofidentified = 0 - -local function identify(prefix,reference) - if not reference then - prefix = "" - reference = prefix - end - local set = resolve(prefix,reference) - local bug = false - texcount.referencehastexstate = set.has_tex and 1 or 0 - nofidentified = nofidentified + 1 - set.n = nofidentified - for i=1,#set do - local var = set[i] - if var.special then - var = identify_special(set,var,i) - elseif var.outer then - var = identify_outer(set,var,i) - elseif var.arguments then - var = identify_arguments(set,var,i) - elseif not var.component then - var = identify_inner_or_outer(set,var,i) - elseif productcomponent() then - var = identify_inner_component(set,var,i) - else - var = identify_outer_component(set,var,i) - end - set[i] = var - bug = bug or var.error - end - references.currentset = mark(set) -- mark, else in api doc - if trace_analyzing then - report_references(table.serialize(set,reference)) - end - return set, bug -end - -references.identify = identify - -local unknowns, nofunknowns, f_valid = { }, 0, formatters["[%s][%s]"] - -function references.valid(prefix,reference,highlight,newwindow,layer) - local set, bug = identify(prefix,reference) - local unknown = bug or #set == 0 - if unknown then - currentreference = nil -- will go away - local str = f_valid(prefix,reference) - local u = unknowns[str] - if not u then - interfaces.showmessage("references",1,str) -- 1 = unknown, 4 = illegal - unknowns[str] = 1 - nofunknowns = nofunknowns + 1 - else - unknowns[str] = u + 1 - end - else - set.highlight, set.newwindow, set.layer = highlight, newwindow, layer - currentreference = set[1] - end - -- we can do the expansion here which saves a call - return not unknown -end - -function commands.doifelsereference(prefix,reference,highlight,newwindow,layer) - commands.doifelse(references.valid(prefix,reference,highlight,newwindow,layer)) -end - -function references.reportproblems() -- might become local - if nofunknowns > 0 then - statistics.register("cross referencing", function() - return format("%s identified, %s unknown",nofidentified,nofunknowns) - end) - logspushtarget("logfile") - logsnewline() - report_references("start problematic references") - logsnewline() - for k, v in table.sortedpairs(unknowns) do - report_unknown("%4i: %s",v,k) - end - logsnewline() - report_references("stop problematic references") - logsnewline() - logspoptarget() - end -end - -luatex.registerstopactions(references.reportproblems) - -local innermethod = "names" - -function references.setinnermethod(m) - if m then - if m == "page" or m == "mixed" or m == "names" then - innermethod = m - elseif m == true or m == v_yes then - innermethod = "page" - end - end - function references.setinnermethod() - report_references("inner method is already set and frozen to %a",innermethod) - end -end - -function references.getinnermethod() - return innermethod or "names" -end - -directives.register("references.linkmethod", function(v) -- page mixed names - references.setinnermethod(v) -end) - --- this is inconsistent - -function references.setinternalreference(prefix,tag,internal,view) -- needs checking - if innermethod == "page" then - return unsetvalue - else - local t, tn = { }, 0 -- maybe add to current - if tag then - if prefix and prefix ~= "" then - prefix = prefix .. ":" -- watch out, : here - for ref in gmatch(tag,"[^,]+") do - tn = tn + 1 - t[tn] = prefix .. ref - end - else - for ref in gmatch(tag,"[^,]+") do - tn = tn + 1 - t[tn] = ref - end - end - end - if internal and innermethod == "names" then -- mixed or page - tn = tn + 1 - t[tn] = "aut:" .. internal - end - local destination = references.mark(t,nil,nil,view) -- returns an attribute - texcount.lastdestinationattribute = destination - return destination - end -end - -function references.setandgetattribute(kind,prefix,tag,data,view) -- maybe do internal automatically here - local attr = references.set(kind,prefix,tag,data) and references.setinternalreference(prefix,tag,nil,view) or unsetvalue - texcount.lastdestinationattribute = attr - return attr -end - -commands.setreferenceattribute = references.setandgetattribute - -function references.getinternalreference(n) -- n points into list (todo: registers) - local l = lists.collected[n] - return l and l.references.internal or n -end - -function commands.setinternalreference(prefix,tag,internal,view) -- needs checking - context(references.setinternalreference(prefix,tag,internal,view)) -end - -function commands.getinternalreference(n) -- this will also be a texcount - local l = lists.collected[n] - context(l and l.references.internal or n) -end - --- - -function references.getcurrentmetadata(tag) - local data = currentreference and currentreference.i - return data and data.metadata and data.metadata[tag] -end - -function commands.getcurrentreferencemetadata(tag) - local data = references.getcurrentmetadata(tag) - if data then - context(data) - end -end - -local function currentmetadata(tag) - local data = currentreference and currentreference.i - return data and data.metadata and data.metadata[tag] -end - -references.currentmetadata = currentmetadata - -local function getcurrentprefixspec(default) - -- todo: message - return currentmetadata("kind") or "?", currentmetadata("name") or "?", default or "?" -end - -references.getcurrentprefixspec = getcurrentprefixspec - -function commands.getcurrentprefixspec(default) - context.getreferencestructureprefix(getcurrentprefixspec(default)) -end - -function references.filter(name,...) -- number page title ... - local data = currentreference and currentreference.i -- maybe we should take realpage from here - if data then - if name == "realpage" then - local cs = references.analyze() -- normally already analyzed but also sets state - context(tonumber(cs.realpage) or 0) -- todo, return and in command namespace - else -- assumes data is table - local kind = type(data) == "table" and data.metadata and data.metadata.kind - if kind then - local filter = filters[kind] or filters.generic - filter = filter and (filter[name] or filter.unknown or filters.generic[name] or filters.generic.unknown) - if filter then - if trace_referencing then - report_references("name %a, kind %a, using dedicated filter",name,kind) - end - filter(data,name,...) - elseif trace_referencing then - report_references("name %a, kind %a, using generic filter",name,kind) - end - elseif trace_referencing then - report_references("name %a, unknown kind",name) - end - end - elseif name == "realpage" then - context(0) - elseif trace_referencing then - report_references("name %a, no reference",name) - end -end - -function references.filterdefault() - return references.filter("default",getcurrentprefixspec(v_default)) -end - -function commands.currentreferencedefault(tag) - if not tag then tag = "default" end - references.filter(tag,context.delayed(getcurrentprefixspec(tag))) -end - -filters.generic = { } - -function filters.generic.title(data) - if data then - local titledata = data.titledata or data.useddata - if titledata then - helpers.title(titledata.title or "?",data.metadata) - end - end -end - -function filters.generic.text(data) - if data then - local entries = data.entries or data.useddata - if entries then - helpers.title(entries.text or "?",data.metadata) - end - end -end - -function filters.generic.number(data,what,prefixspec) -- todo: spec and then no stopper - if data then - numberdata = lists.reordered(data) -- data.numberdata - if numberdata then - helpers.prefix(data,prefixspec) - sections.typesetnumber(numberdata,"number",numberdata) - else - local useddata = data.useddata - if useddata and useddsta.number then - context(useddata.number) - end - end - end -end - -filters.generic.default = filters.generic.text - -function filters.generic.page(data,prefixspec,pagespec) - local pagedata = data.pagedata - if pagedata then - local number, conversion = pagedata.number, pagedata.conversion - if not number then - -- error - elseif conversion then - context.convertnumber(conversion,number) - else - context(number) - end - else - helpers.prefixpage(data,prefixspec,pagespec) - end -end - -filters.user = { } - -function filters.user.unknown(data,name) - if data then - local userdata = data.userdata - local userkind = userdata and userdata.kind - if userkind then - local filter = filters[userkind] or filters.generic - filter = filter and (filter[name] or filter.unknown) - if filter then - filter(data,name) - return - end - end - local namedata = userdata and userdata[name] - if namedata then - context(namedata) - end - end -end - -filters.text = { } - -function filters.text.title(data) - helpers.title(data.entries.text or "?",data.metadata) -end - --- no longer considered useful: --- --- function filters.text.number(data) --- helpers.title(data.entries.text or "?",data.metadata) --- end - -function filters.text.page(data,prefixspec,pagespec) - helpers.prefixpage(data,prefixspec,pagespec) -end - -filters.full = { } - -filters.full.title = filters.text.title -filters.full.page = filters.text.page - -filters.section = { } - -function filters.section.number(data,what,prefixspec) - if data then - local numberdata = data.numberdata - if not numberdata then - local useddata = data.useddata - if useddata and useddata.number then - context(useddata.number) - end - elseif numberdata.hidenumber then - local references = data.references - if trace_empty then - report_empty("reference %a has a hidden number",references.reference) - context.emptyreference() -- maybe an option - end - else - sections.typesetnumber(numberdata,"number",prefixspec,numberdata) - end - end -end - -filters.section.title = filters.generic.title -filters.section.page = filters.generic.page -filters.section.default = filters.section.number - --- filters.note = { default = filters.generic.number } --- filters.formula = { default = filters.generic.number } --- filters.float = { default = filters.generic.number } --- filters.description = { default = filters.generic.number } --- filters.item = { default = filters.generic.number } - -setmetatableindex(filters, function(t,k) -- beware, test with rawget - local v = { default = filters.generic.number } -- not copy as it might be extended differently - t[k] = v - return v -end) - --- function references.sectiontitle(n) --- helpers.sectiontitle(lists.collected[tonumber(n) or 0]) --- end - --- function references.sectionnumber(n) --- helpers.sectionnumber(lists.collected[tonumber(n) or 0]) --- end - --- function references.sectionpage(n,prefixspec,pagespec) --- helpers.prefixedpage(lists.collected[tonumber(n) or 0],prefixspec,pagespec) --- end - --- analyze - -references.testrunners = references.testrunners or { } -references.testspecials = references.testspecials or { } - -local runners = references.testrunners -local specials = references.testspecials - --- We need to prevent ending up in the 'relative location' analyzer as it is --- pretty slow (progressively). In the pagebody one can best check the reference --- real page to determine if we need contrastlocation as that is more lightweight. - -local function checkedpagestate(n,page) - local r, p = referredpage(n), tonumber(page) - if not p then - return 0 - elseif p > r then - return 3 -- after - elseif p < r then - return 2 -- before - else - return 1 -- same - end -end - -local function setreferencerealpage(actions) - actions = actions or references.currentset - if not actions then - return 0 - else - local realpage = actions.realpage - if realpage then - return realpage - end - local nofactions = #actions - if nofactions > 0 then - for i=1,nofactions do - local a = actions[i] - local what = runners[a.kind] - if what then - what = what(a,actions) -- needs documentation - end - end - realpage = actions.realpage - if realpage then - return realpage - end - end - actions.realpage = 0 - return 0 - end -end - --- we store some analysis data alongside the indexed array --- at this moment only the real reference page is analyzed --- normally such an analysis happens in the backend code - -function references.analyze(actions) - actions = actions or references.currentset - if not actions then - actions = { realpage = 0, pagestate = 0 } - elseif actions.pagestate then - -- already done - else - local realpage = actions.realpage or setreferencerealpage(actions) - if realpage == 0 then - actions.pagestate = 0 - elseif actions.external then - actions.pagestate = 0 - else - actions.pagestate = checkedpagestate(actions.n,realpage) - end - end - return actions -end - -function commands.referencepagestate(actions) - actions = actions or references.currentset - if not actions then - context(0) - else - if not actions.pagestate then - references.analyze(actions) -- delayed unless explicitly asked for - end - context(actions.pagestate) - end -end - -function commands.referencerealpage(actions) - actions = actions or references.currentset - context(not actions and 0 or actions.realpage or setreferencerealpage(actions)) -end - -local plist, nofrealpages - -local function realpageofpage(p) -- the last one counts ! - if not plist then - local pages = structures.pages.collected - nofrealpages = #pages - plist = { } - for rp=1,nofrealpages do - plist[pages[rp].number] = rp - end - references.nofrealpages = nofrealpages - end - return plist[p] -end - -references.realpageofpage = realpageofpage - -function references.checkedrealpage(r) - if not plist then - realpageofpage(r) -- just initialize - end - if not r then - return texcount.realpageno - elseif r < 1 then - return 1 - elseif r > nofrealpages then - return nofrealpages - else - return r - end -end - --- use local ? - -local pages = allocate { - [variables.firstpage] = function() return counters.record("realpage")["first"] end, - [variables.previouspage] = function() return counters.record("realpage")["previous"] end, - [variables.nextpage] = function() return counters.record("realpage")["next"] end, - [variables.lastpage] = function() return counters.record("realpage")["last"] end, - - [variables.firstsubpage] = function() return counters.record("subpage" )["first"] end, - [variables.previoussubpage] = function() return counters.record("subpage" )["previous"] end, - [variables.nextsubpage] = function() return counters.record("subpage" )["next"] end, - [variables.lastsubpage] = function() return counters.record("subpage" )["last"] end, - - [variables.forward] = function() return counters.record("realpage")["forward"] end, - [variables.backward] = function() return counters.record("realpage")["backward"] end, -} - -references.pages = pages - --- maybe some day i will merge this in the backend code with a testmode (so each --- runner then implements a branch) - -runners["inner"] = function(var,actions) - local r = var.r - if r then - actions.realpage = r - end -end - -runners["special"] = function(var,actions) - local handler = specials[var.special] - return handler and handler(var,actions) -end - -runners["special operation"] = runners["special"] -runners["special operation with arguments"] = runners["special"] - --- These are the testspecials not the real ones. They are used to --- check the validity. - -function specials.internal(var,actions) - local v = references.internals[tonumber(var.operation)] - local r = v and v.references.realpage - if r then - actions.realpage = r - end -end - -specials.i = specials.internal - -function specials.page(var,actions) - local o = var.operation - local p = pages[o] - if type(p) == "function" then - p = p() - else - p = tonumber(realpageofpage(tonumber(o))) - end - if p then - var.r = p - actions.realpage = actions.realpage or p -- first wins - end -end - -function specials.realpage(var,actions) - local p = tonumber(var.operation) - if p then - var.r = p - actions.realpage = actions.realpage or p -- first wins - end -end - -function specials.userpage(var,actions) - local p = tonumber(realpageofpage(var.operation)) - if p then - var.r = p - actions.realpage = actions.realpage or p -- first wins - end -end - -function specials.deltapage(var,actions) - local p = tonumber(var.operation) - if p then - p = references.checkedrealpage(p + texcount.realpageno) - var.r = p - actions.realpage = actions.realpage or p -- first wins - end -end - -function specials.section(var,actions) - local sectionname = var.arguments - local destination = var.operation - local internal = structures.sections.internalreference(sectionname,destination) - if internal then - var.special = "internal" - var.operation = internal - var.arguments = nil - specials.internal(var,actions) - end -end - --- needs a better split ^^^ - -commands.filterreference = references.filter -commands.filterdefaultreference = references.filterdefault - --- done differently now: - -function references.export(usedname) end -function references.import(usedname) end -function references.load (usedname) end - -commands.exportreferences = references.export +if not modules then modules = { } end modules ['strc-ref'] = {
+ version = 1.001,
+ comment = "companion to strc-ref.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- beware, this is a first step in the rewrite (just getting rid of
+-- the tuo file); later all access and parsing will also move to lua
+
+-- the useddata and pagedata names might change
+-- todo: pack exported data
+
+-- todo: autoload components when :::
+
+local format, find, gmatch, match, concat = string.format, string.find, string.gmatch, string.match, table.concat
+local texcount, texsetcount = tex.count, tex.setcount
+local rawget, tonumber = rawget, tonumber
+local lpegmatch = lpeg.match
+local copytable = table.copy
+local formatters = string.formatters
+
+local allocate = utilities.storage.allocate
+local mark = utilities.storage.mark
+local setmetatableindex = table.setmetatableindex
+
+local trace_referencing = false trackers.register("structures.referencing", function(v) trace_referencing = v end)
+local trace_analyzing = false trackers.register("structures.referencing.analyzing", function(v) trace_analyzing = v end)
+local trace_identifying = false trackers.register("structures.referencing.identifying", function(v) trace_identifying = v end)
+local trace_importing = false trackers.register("structures.referencing.importing", function(v) trace_importing = v end)
+local trace_empty = false trackers.register("structures.referencing.empty", function(v) trace_empty = v end)
+
+local check_duplicates = true
+
+directives.register("structures.referencing.checkduplicates", function(v)
+ check_duplicates = v
+end)
+
+local report_references = logs.reporter("references")
+local report_unknown = logs.reporter("references","unknown")
+local report_identifying = logs.reporter("references","identifying")
+local report_importing = logs.reporter("references","importing")
+local report_empty = logs.reporter("references","empty")
+
+local variables = interfaces.variables
+local constants = interfaces.constants
+local context = context
+
+local v_default = variables.default
+local v_url = variables.url
+local v_file = variables.file
+local v_unknown = variables.unknown
+local v_yes = variables.yes
+
+local texcount = tex.count
+local texconditionals = tex.conditionals
+
+local productcomponent = resolvers.jobs.productcomponent
+local justacomponent = resolvers.jobs.justacomponent
+
+local logsnewline = logs.newline
+local logspushtarget = logs.pushtarget
+local logspoptarget = logs.poptarget
+
+local settings_to_array = utilities.parsers.settings_to_array
+local unsetvalue = attributes.unsetvalue
+
+local structures = structures
+local helpers = structures.helpers
+local sections = structures.sections
+local references = structures.references
+local lists = structures.lists
+local counters = structures.counters
+
+-- some might become local
+
+references.defined = references.defined or allocate()
+
+local defined = references.defined
+local derived = allocate()
+local specials = allocate()
+local runners = allocate()
+local internals = allocate()
+local filters = allocate()
+local executers = allocate()
+local handlers = allocate()
+local tobesaved = allocate()
+local collected = allocate()
+local tobereferred = allocate()
+local referred = allocate()
+
+references.derived = derived
+references.specials = specials
+references.runners = runners
+references.internals = internals
+references.filters = filters
+references.executers = executers
+references.handlers = handlers
+references.tobesaved = tobesaved
+references.collected = collected
+references.tobereferred = tobereferred
+references.referred = referred
+
+local splitreference = references.splitreference
+local splitprefix = references.splitcomponent -- replaces: references.splitprefix
+local prefixsplitter = references.prefixsplitter
+local componentsplitter = references.componentsplitter
+
+local currentreference = nil
+
+storage.register("structures/references/defined", references.defined, "structures.references.defined")
+
+local initializers = { }
+local finalizers = { }
+
+function references.registerinitializer(func) -- we could use a token register instead
+ initializers[#initializers+1] = func
+end
+function references.registerfinalizer(func) -- we could use a token register instead
+ finalizers[#finalizers+1] = func
+end
+
+local function initializer() -- can we use a tobesaved as metatable for collected?
+ tobesaved = references.tobesaved
+ collected = references.collected
+ for i=1,#initializers do
+ initializers[i](tobesaved,collected)
+ end
+end
+
+local function finalizer()
+ for i=1,#finalizers do
+ finalizers[i](tobesaved)
+ end
+end
+
+job.register('structures.references.collected', tobesaved, initializer, finalizer)
+
+local maxreferred = 1
+local nofreferred = 0
+
+-- local function initializer() -- can we use a tobesaved as metatable for collected?
+-- tobereferred = references.tobereferred
+-- referred = references.referred
+-- nofreferred = #referred
+-- end
+
+local function initializer() -- can we use a tobesaved as metatable for collected?
+ tobereferred = references.tobereferred
+ referred = references.referred
+ setmetatableindex(referred,get) -- hm, what is get ?
+end
+
+-- We make the array sparse (maybe a finalizer should optionally return a table) because
+-- there can be quite some page links involved. We only store one action number per page
+-- which is normally good enough for what we want (e.g. see above/below) and we do
+-- a combination of a binary search and traverse backwards. A previous implementation
+-- always did a traverse and was pretty slow on a large number of links (given that this
+-- methods was used). It took me about a day to locate this as a bottleneck in processing
+-- a 2500 page interactive document with 60 links per page. In that case, traversing
+-- thousands of slots per link then brings processing to a grinding halt (especially when
+-- there are no slots at all, which is the case in a first run).
+
+local sparsetobereferred = { }
+
+local function finalizer()
+ local lastr, lasti
+ local n = 0
+ for i=1,maxreferred do
+ local r = tobereferred[i]
+ if not lastr then
+ lastr = r
+ lasti = i
+ elseif r ~= lastr then
+ n = n + 1
+ sparsetobereferred[n] = { lastr, lasti }
+ lastr = r
+ lasti = i
+ end
+ end
+ if lastr then
+ n = n + 1
+ sparsetobereferred[n] = { lastr, lasti }
+ end
+end
+
+job.register('structures.references.referred', sparsetobereferred, initializer, finalizer)
+
+local function referredpage(n)
+ local max = nofreferred
+ if max > 0 then
+ -- find match
+ local min = 1
+ while true do
+ local mid = floor((min+max)/2)
+ local r = referred[mid]
+ local m = r[2]
+ if n == m then
+ return r[1]
+ elseif n > m then
+ min = mid + 1
+ else
+ max = mid - 1
+ end
+ if min > max then
+ break
+ end
+ end
+ -- find first previous
+ for i=min,1,-1 do
+ local r = referred[i]
+ if r and r[2] < n then
+ return r[1]
+ end
+ end
+ end
+ -- fallback
+ return texcount.realpageno
+end
+
+references.referredpage = referredpage
+
+function references.registerpage(n) -- called in the backend code
+ if not tobereferred[n] then
+ if n > maxreferred then
+ maxreferred = n
+ end
+ tobereferred[n] = texcount.realpageno
+ end
+end
+
+-- todo: delay split till later as in destinations we split anyway
+
+local orders, lastorder = { }, 0
+
+local function setnextorder(kind,name)
+ lastorder = 0
+ if kind and name then
+ local ok = orders[kind]
+ if not ok then
+ ok = { }
+ orders[kind] = ok
+ end
+ lastorder = (ok[name] or 0) + 1
+ ok[name] = lastorder
+ end
+ texsetcount("global","locationorder",lastorder)
+end
+
+references.setnextorder = setnextorder
+
+function references.setnextinternal(kind,name)
+ setnextorder(kind,name) -- always incremented with internal
+ local n = texcount.locationcount + 1
+ texsetcount("global","locationcount",n)
+ return n
+end
+
+function references.currentorder(kind,name)
+ return orders[kind] and orders[kind][name] or lastorder
+end
+
+local function setcomponent(data)
+ -- we might consider doing this at the tex end, just like prefix
+ local component = productcomponent()
+ if component then
+ local references = data and data.references
+ if references then
+ references.component = component
+ end
+ return component
+ end
+ -- but for the moment we do it here (experiment)
+end
+
+commands.setnextinternalreference = references.setnextinternal
+
+function commands.currentreferenceorder(kind,name)
+ context(references.currentorder(kind,name))
+end
+
+references.setcomponent = setcomponent
+
+function references.set(kind,prefix,tag,data)
+-- setcomponent(data)
+ local pd = tobesaved[prefix] -- nicer is a metatable
+ if not pd then
+ pd = { }
+ tobesaved[prefix] = pd
+ end
+ local n = 0
+ for ref in gmatch(tag,"[^,]+") do
+ if ref ~= "" then
+ if check_duplicates and pd[ref] then
+ if prefix and prefix ~= "" then
+ report_references("redundant reference %a in namespace %a",ref,prefix)
+ else
+ report_references("redundant reference %a",ref)
+ end
+ else
+ n = n + 1
+ pd[ref] = data
+ context.dofinishsomereference(kind,prefix,ref)
+ end
+ end
+ end
+ return n > 0
+end
+
+function references.enhance(prefix,tag)
+ local l = tobesaved[prefix][tag]
+ if l then
+ l.references.realpage = texcount.realpageno
+ end
+end
+
+commands.enhancereference = references.enhance
+
+-- -- -- related to strc-ini.lua -- -- --
+
+references.resolvers = references.resolvers or { }
+local resolvers = references.resolvers
+
+local function getfromlist(var)
+ local vi = var.i
+ if vi then
+ vi = vi[3] or lists.collected[vi[2]]
+ if vi then
+ local r = vi.references and vi.references
+ if r then
+ r = r.realpage
+ end
+ if not r then
+ r = vi.pagedata and vi.pagedata
+ if r then
+ r = r.realpage
+ end
+ end
+ var.i = vi
+ var.r = r or 1
+ else
+ var.i = nil
+ var.r = 1
+ end
+ else
+ var.i = nil
+ var.r = 1
+ end
+end
+
+-- resolvers.section = getfromlist
+-- resolvers.float = getfromlist
+-- resolvers.description = getfromlist
+-- resolvers.formula = getfromlist
+-- resolvers.note = getfromlist
+
+setmetatableindex(resolvers,function(t,k)
+ local v = getfromlist
+ resolvers[k] = v
+ return v
+end)
+
+function resolvers.reference(var)
+ local vi = var.i[2] -- check
+ if vi then
+ var.i = vi
+ var.r = (vi.references and vi.references.realpage) or (vi.pagedata and vi.pagedata.realpage) or 1
+ else
+ var.i = nil
+ var.r = 1
+ end
+end
+
+local function register_from_lists(collected,derived,pages,sections)
+ local g = derived[""] if not g then g = { } derived[""] = g end -- global
+ for i=1,#collected do
+ local entry = collected[i]
+ local m, r = entry.metadata, entry.references
+ if m and r then
+ local reference = r.reference or ""
+ local prefix = r.referenceprefix or ""
+ local component = r.component and r.component or ""
+ if reference ~= "" then
+ local kind, realpage = m.kind, r.realpage
+ if kind and realpage then
+ local d = derived[prefix]
+ if not d then
+ d = { }
+ derived[prefix] = d
+ end
+ local c = derived[component]
+ if not c then
+ c = { }
+ derived[component] = c
+ end
+ local t = { kind, i, entry }
+ for s in gmatch(reference,"%s*([^,]+)") do
+ if trace_referencing then
+ report_references("list entry %a provides %a reference %a on realpage %a",i,kind,s,realpage)
+ end
+ c[s] = c[s] or t -- share them
+ d[s] = d[s] or t -- share them
+ g[s] = g[s] or t -- first wins
+ end
+ end
+ end
+ end
+ end
+-- inspect(derived)
+end
+
+references.registerinitializer(function() register_from_lists(lists.collected,derived) end)
+
+-- urls
+
+references.urls = references.urls or { }
+references.urls.data = references.urls.data or { }
+
+local urls = references.urls.data
+
+function references.urls.define(name,url,file,description)
+ if name and name ~= "" then
+ urls[name] = { url or "", file or "", description or url or file or ""}
+ end
+end
+
+local pushcatcodes = context.pushcatcodes
+local popcatcodes = context.popcatcodes
+local txtcatcodes = catcodes.numbers.txtcatcodes -- or just use "txtcatcodes"
+
+function references.urls.get(name)
+ local u = urls[name]
+ if u then
+ local url, file = u[1], u[2]
+ if file and file ~= "" then
+ return formatters["%s/%s"](url,file)
+ else
+ return url
+ end
+ end
+end
+
+function commands.geturl(name)
+ local url = references.urls.get(name)
+ if url and url ~= "" then
+ pushcatcodes(txtcatcodes)
+ context(url)
+ popcatcodes()
+ end
+end
+
+-- function commands.gethyphenatedurl(name,...)
+-- local url = references.urls.get(name)
+-- if url and url ~= "" then
+-- hyphenatedurl(url,...)
+-- end
+-- end
+
+function commands.doifurldefinedelse(name)
+ commands.doifelse(urls[name])
+end
+
+commands.useurl= references.urls.define
+
+-- files
+
+references.files = references.files or { }
+references.files.data = references.files.data or { }
+
+local files = references.files.data
+
+function references.files.define(name,file,description)
+ if name and name ~= "" then
+ files[name] = { file or "", description or file or "" }
+ end
+end
+
+function references.files.get(name,method,space) -- method: none, before, after, both, space: yes/no
+ local f = files[name]
+ if f then
+ context(f[1])
+ end
+end
+
+function commands.doiffiledefinedelse(name)
+ commands.doifelse(files[name])
+end
+
+commands.usefile= references.files.define
+
+-- helpers
+
+function references.checkedfile(whatever) -- return whatever if not resolved
+ if whatever then
+ local w = files[whatever]
+ if w then
+ return w[1]
+ else
+ return whatever
+ end
+ end
+end
+
+function references.checkedurl(whatever) -- return whatever if not resolved
+ if whatever then
+ local w = urls[whatever]
+ if w then
+ local u, f = w[1], w[2]
+ if f and f ~= "" then
+ return u .. "/" .. f
+ else
+ return u
+ end
+ else
+ return whatever
+ end
+ end
+end
+
+function references.checkedfileorurl(whatever,default) -- return nil, nil if not resolved
+ if whatever then
+ local w = files[whatever]
+ if w then
+ return w[1], nil
+ else
+ local w = urls[whatever]
+ if w then
+ local u, f = w[1], w[2]
+ if f and f ~= "" then
+ return nil, u .. "/" .. f
+ else
+ return nil, u
+ end
+ end
+ end
+ end
+ return default
+end
+
+-- programs
+
+references.programs = references.programs or { }
+references.programs.data = references.programs.data or { }
+
+local programs = references.programs.data
+
+function references.programs.define(name,file,description)
+ if name and name ~= "" then
+ programs[name] = { file or "", description or file or ""}
+ end
+end
+
+function references.programs.get(name)
+ local f = programs[name]
+ return f and f[1]
+end
+
+function references.checkedprogram(whatever) -- return whatever if not resolved
+ if whatever then
+ local w = programs[whatever]
+ if w then
+ return w[1]
+ else
+ return whatever
+ end
+ end
+end
+
+commands.defineprogram = references.programs.define
+
+function commands.getprogram(name)
+ local f = programs[name]
+ if f then
+ context(f[1])
+ end
+end
+
+-- shared by urls and files
+
+function references.whatfrom(name)
+ context((urls[name] and v_url) or (files[name] and v_file) or v_unknown)
+end
+
+function references.from(name)
+ local u = urls[name]
+ if u then
+ local url, file, description = u[1], u[2], u[3]
+ if description ~= "" then
+ return description
+ -- ok
+ elseif file and file ~= "" then
+ return url .. "/" .. file
+ else
+ return url
+ end
+ else
+ local f = files[name]
+ if f then
+ local file, description = f[1], f[2]
+ if description ~= "" then
+ return description
+ else
+ return file
+ end
+ end
+ end
+end
+
+function commands.from(name)
+ local u = urls[name]
+ if u then
+ local url, file, description = u[1], u[2], u[3]
+ if description ~= "" then
+ context.dofromurldescription(description)
+ -- ok
+ elseif file and file ~= "" then
+ context.dofromurlliteral(url .. "/" .. file)
+ else
+ context.dofromurlliteral(url)
+ end
+ else
+ local f = files[name]
+ if f then
+ local file, description = f[1], f[2]
+ if description ~= "" then
+ context.dofromfiledescription(description)
+ else
+ context.dofromfileliteral(file)
+ end
+ end
+ end
+end
+
+function references.define(prefix,reference,list)
+ local d = defined[prefix] if not d then d = { } defined[prefix] = d end
+ d[reference] = { "defined", list }
+end
+
+function references.reset(prefix,reference)
+ local d = defined[prefix]
+ if d then
+ d[reference] = nil
+ end
+end
+
+commands.definereference = references.define
+commands.resetreference = references.reset
+
+-- \primaryreferencefoundaction
+-- \secondaryreferencefoundaction
+-- \referenceunknownaction
+
+-- t.special t.operation t.arguments t.outer t.inner
+
+-- to what extend do we check the non prefixed variant
+
+local strict = false
+
+local function resolve(prefix,reference,args,set) -- we start with prefix,reference
+ if reference and reference ~= "" then
+ if not set then
+ set = { prefix = prefix, reference = reference }
+ else
+ set.reference = set.reference or reference
+ set.prefix = set.prefix or prefix
+ end
+ local r = settings_to_array(reference)
+ for i=1,#r do
+ local ri = r[i]
+ local d
+ if strict then
+ d = defined[prefix] or defined[""]
+ d = d and d[ri]
+ else
+ d = defined[prefix]
+ d = d and d[ri]
+ if not d then
+ d = defined[""]
+ d = d and d[ri]
+ end
+ end
+ if d then
+ resolve(prefix,d[2],nil,set)
+ else
+ local var = splitreference(ri)
+ if var then
+ var.reference = ri
+ local vo, vi = var.outer, var.inner
+ if not vo and vi then
+ -- to be checked
+ if strict then
+ d = defined[prefix] or defined[""]
+ d = d and d[vi]
+ else
+ d = defined[prefix]
+ d = d and d[vi]
+ if not d then
+ d = defined[""]
+ d = d and d[vi]
+ end
+ end
+ --
+ if d then
+ resolve(prefix,d[2],var.arguments,set) -- args can be nil
+ else
+ if args then var.arguments = args end
+ set[#set+1] = var
+ end
+ else
+ if args then var.arguments = args end
+ set[#set+1] = var
+ end
+ if var.has_tex then
+ set.has_tex = true
+ end
+ else
+ -- report_references("funny pattern %a",ri)
+ end
+ end
+ end
+ return set
+ else
+ return { }
+ end
+end
+
+-- prefix == "" is valid prefix which saves multistep lookup
+
+references.currentset = nil
+
+function commands.setreferenceoperation(k,v)
+ references.currentset[k].operation = v
+end
+
+function commands.setreferencearguments(k,v)
+ references.currentset[k].arguments = v
+end
+
+local expandreferenceoperation = context.expandreferenceoperation
+local expandreferencearguments = context.expandreferencearguments
+
+function references.expandcurrent() -- todo: two booleans: o_has_tex& a_has_tex
+ local currentset = references.currentset
+ if currentset and currentset.has_tex then
+ for i=1,#currentset do
+ local ci = currentset[i]
+ local operation = ci.operation
+ if operation and find(operation,"\\") then -- if o_has_tex then
+ expandreferenceoperation(i,operation)
+ end
+ local arguments = ci.arguments
+ if arguments and find(arguments,"\\") then -- if a_has_tex then
+ expandreferencearguments(i,arguments)
+ end
+ end
+ end
+end
+
+commands.expandcurrentreference = references.expandcurrent -- for the moment the same
+
+local externals = { }
+
+-- we have prefixes but also components:
+--
+-- : prefix
+-- :: always external
+-- ::: internal (for products) or external (for components)
+
+local function loadexternalreferences(name,utilitydata)
+ local struc = utilitydata.structures
+ if struc then
+ local external = struc.references.collected -- direct references
+ local lists = struc.lists.collected -- indirect references (derived)
+ local pages = struc.pages.collected -- pagenumber data
+ -- a bit weird one, as we don't have the externals in the collected
+ for prefix, set in next, external do
+ for reference, data in next, set do
+ if trace_importing then
+ report_importing("registering %a reference, kind %a, name %a, prefix %a, reference %a",
+ "external","regular",name,prefix,reference)
+ end
+ local section = reference.section
+ local realpage = reference.realpage
+ if section then
+ reference.sectiondata = lists[section]
+ end
+ if realpage then
+ reference.pagedata = pages[realpage]
+ end
+ end
+ end
+ for i=1,#lists do
+ local entry = lists[i]
+ local metadata = entry.metadata
+ local references = entry.references
+ if metadata and references then
+ local reference = references.reference
+ if reference and reference ~= "" then
+ local kind = metadata.kind
+ local realpage = references.realpage
+ if kind and realpage then
+ references.pagedata = pages[realpage]
+ local prefix = references.referenceprefix or ""
+ local target = external[prefix]
+ if not target then
+ target = { }
+ external[prefix] = target
+ end
+ for s in gmatch(reference,"%s*([^,]+)") do
+ if trace_importing then
+ report_importing("registering %s reference, kind %a, name %a, prefix %a, reference %a",
+ "external",kind,name,prefix,s)
+ end
+ target[s] = target[s] or entry
+ end
+ end
+ end
+ end
+ end
+ externals[name] = external
+ return external
+ end
+end
+
+local externalfiles = { }
+
+table.setmetatableindex(externalfiles, function(t,k)
+ local v = files[k]
+ if not v then
+ v = { k, k }
+ end
+ externalfiles[k] = v
+ return v
+end)
+
+table.setmetatableindex(externals,function(t,k) -- either or not automatically
+ local filename = externalfiles[k][1] -- filename
+ local fullname = file.replacesuffix(filename,"tuc")
+ if lfs.isfile(fullname) then -- todo: use other locator
+ local utilitydata = job.loadother(fullname)
+ if utilitydata then
+ local external = loadexternalreferences(k,utilitydata)
+ t[k] = external or false
+ return external
+ end
+ end
+ t[k] = false
+ return false
+end)
+
+local productdata = allocate {
+ productreferences = { },
+ componentreferences = { },
+ components = { },
+}
+
+references.productdata = productdata
+
+local function loadproductreferences(productname,componentname,utilitydata)
+ local struc = utilitydata.structures
+ if struc then
+ local productreferences = struc.references.collected -- direct references
+ local lists = struc.lists.collected -- indirect references (derived)
+ local pages = struc.pages.collected -- pagenumber data
+ -- we use indirect tables to save room but as they are eventually
+ -- just references we resolve them to data here (the mechanisms
+ -- that use this data check for indirectness)
+ for prefix, set in next, productreferences do
+ for reference, data in next, set do
+ if trace_importing then
+ report_importing("registering %s reference, kind %a, name %a, prefix %a, reference %a",
+ "product","regular",productname,prefix,reference)
+ end
+ local section = reference.section
+ local realpage = reference.realpage
+ if section then
+ reference.sectiondata = lists[section]
+ end
+ if realpage then
+ reference.pagedata = pages[realpage]
+ end
+ end
+ end
+ --
+ local componentreferences = { }
+ for i=1,#lists do
+ local entry = lists[i]
+ local metadata = entry.metadata
+ local references = entry.references
+ if metadata and references then
+ local reference = references.reference
+ if reference and reference ~= "" then
+ local kind = metadata.kind
+ local realpage = references.realpage
+ if kind and realpage then
+ references.pagedata = pages[realpage]
+ local prefix = references.referenceprefix or ""
+ local component = references.component
+ local ctarget, ptarget
+ if not component or component == componentname then
+ -- skip
+ else
+ -- one level up
+ local external = componentreferences[component]
+ if not external then
+ external = { }
+ componentreferences[component] = external
+ end
+ if component == prefix then
+ prefix = ""
+ end
+ ctarget = external[prefix]
+ if not ctarget then
+ ctarget = { }
+ external[prefix] = ctarget
+ end
+ end
+ ptarget = productreferences[prefix]
+ if not ptarget then
+ ptarget = { }
+ productreferences[prefix] = ptarget
+ end
+ for s in gmatch(reference,"%s*([^,]+)") do
+ if ptarget then
+ if trace_importing then
+ report_importing("registering %s reference, kind %a, name %a, prefix %a, reference %a",
+ "product",kind,productname,prefix,s)
+ end
+ ptarget[s] = ptarget[s] or entry
+ end
+ if ctarget then
+ if trace_importing then
+ report_importing("registering %s reference, kind %a, name %a, prefix %a, referenc %a",
+ "component",kind,productname,prefix,s)
+ end
+ ctarget[s] = ctarget[s] or entry
+ end
+ end
+ end
+ end
+ end
+ end
+ productdata.productreferences = productreferences -- not yet used
+ productdata.componentreferences = componentreferences
+ end
+end
+
+local function loadproductvariables(product,component,utilitydata)
+ local struc = utilitydata.structures
+ if struc then
+ local lists = struc.lists and struc.lists.collected
+ if lists then
+ local pages = struc.pages and struc.pages.collected
+ for i=1,#lists do
+ local li = lists[i]
+ if li.metadata.kind == "section" and li.references.component == component then
+ local firstsection = li
+ if firstsection.numberdata then
+ local numbers = firstsection.numberdata.numbers
+ if numbers then
+ if trace_importing then
+ report_importing("initializing section number to %:t",numbers)
+ end
+ productdata.firstsection = firstsection
+ structures.documents.preset(numbers)
+ end
+ end
+ if pages and firstsection.references then
+ local firstpage = pages[firstsection.references.realpage]
+ local number = firstpage and firstpage.number
+ if number then
+ if trace_importing then
+ report_importing("initializing page number to %a",number)
+ end
+ productdata.firstpage = firstpage
+ counters.set("userpage",1,number)
+ end
+ end
+ break
+ end
+ end
+ end
+ end
+end
+
+local function componentlist(tree,target)
+ local branches = tree and tree.branches
+ if branches then
+ for i=1,#branches do
+ local branch = branches[i]
+ local type = branch.type
+ if type == "component" then
+ if target then
+ target[#target+1] = branch.name
+ else
+ target = { branch.name }
+ end
+ elseif type == "product" or type == "component" then
+ target = componentlist(branch,target)
+ end
+ end
+ end
+ return target
+end
+
+local function loadproductcomponents(product,component,utilitydata)
+ local job = utilitydata.job
+ productdata.components = componentlist(job and job.structure and job.structure.collected) or { }
+end
+
+references.registerinitializer(function(tobesaved,collected)
+ -- not that much related to tobesaved or collected
+ productdata.components = componentlist(job.structure.collected) or { }
+end)
+
+function structures.references.loadpresets(product,component) -- we can consider a special components hash
+ if product and component and product~= "" and component ~= "" and not productdata.product then -- maybe: productdata.filename ~= filename
+ productdata.product = product
+ productdata.component = component
+ local fullname = file.replacesuffix(product,"tuc")
+ if lfs.isfile(fullname) then -- todo: use other locator
+ local utilitydata = job.loadother(fullname)
+ if utilitydata then
+ if trace_importing then
+ report_importing("loading references for component %a of product %a from %a",component,product,fullname)
+ end
+ loadproductvariables (product,component,utilitydata)
+ loadproductreferences(product,component,utilitydata)
+ loadproductcomponents(product,component,utilitydata)
+ -- inspect(productdata)
+ end
+ end
+ end
+end
+
+structures.references.productdata = productdata
+
+local useproduct = commands.useproduct
+
+if useproduct then
+
+ function commands.useproduct(product)
+ useproduct(product)
+ if texconditionals.autocrossfilereferences then
+ local component = justacomponent()
+ if component then
+ if trace_referencing or trace_importing then
+ report_references("loading presets for component %a of product %a",component,product)
+ end
+ structures.references.loadpresets(product,component)
+ end
+ end
+ end
+
+end
+
+-- productdata.firstsection.numberdata.numbers
+-- productdata.firstpage.number
+
+local function report_identify_special(set,var,i,type)
+ local reference = set.reference
+ local prefix = set.prefix or ""
+ local special = var.special
+ local error = var.error
+ local kind = var.kind
+ if error then
+ report_identifying("type %a, reference %a, index %a, prefix %a, special %a, error %a",type,reference,i,prefix,special,error)
+ else
+ report_identifying("type %a, reference %a, index %a, prefix %a, special %a, kind %a",type,reference,i,prefix,special,kind)
+ end
+end
+
+local function report_identify_arguments(set,var,i,type)
+ local reference = set.reference
+ local prefix = set.prefix or ""
+ local arguments = var.arguments
+ local error = var.error
+ local kind = var.kind
+ if error then
+ report_identifying("type %a, reference %a, index %a, prefix %a, arguments %a, error %a",type,reference,i,prefix,arguments,error)
+ else
+ report_identifying("type %a, reference %a, index %a, prefix %a, arguments %a, kind %a",type,reference,i,prefix,arguments,kind)
+ end
+end
+
+local function report_identify_outer(set,var,i,type)
+ local reference = set.reference
+ local prefix = set.prefix or ""
+ local outer = var.outer
+ local error = var.error
+ local kind = var.kind
+ if outer then
+ if error then
+ report_identifying("type %a, reference %a, index %a, prefix %a, outer %a, error %a",type,reference,i,prefix,outer,error)
+ else
+ report_identifying("type %a, reference %a, index %a, prefix %a, outer %a, kind %a",type,reference,i,prefix,outer,kind)
+ end
+ else
+ if error then
+ report_identifying("type %a, reference %a, index %a, prefix %a, error %a",type,reference,i,prefix,error)
+ else
+ report_identifying("type %a, reference %a, index %a, prefix %a, kind %a",type,reference,i,prefix,kind)
+ end
+ end
+end
+
+local function identify_special(set,var,i)
+ local special = var.special
+ local s = specials[special]
+ if s then
+ local outer = var.outer
+ local operation = var.operation
+ local arguments = var.arguments
+ if outer then
+ if operation then
+ -- special(outer::operation)
+ var.kind = "special outer with operation"
+ else
+ -- special()
+ var.kind = "special outer"
+ end
+ var.f = outer
+ elseif operation then
+ if arguments then
+ -- special(operation{argument,argument})
+ var.kind = "special operation with arguments"
+ else
+ -- special(operation)
+ var.kind = "special operation"
+ end
+ else
+ -- special()
+ var.kind = "special"
+ end
+ if trace_identifying then
+ report_identify_special(set,var,i,"1a")
+ end
+ else
+ var.error = "unknown special"
+ end
+ return var
+end
+
+local function identify_arguments(set,var,i)
+ local s = specials[var.inner]
+ if s then
+ -- inner{argument}
+ var.kind = "special with arguments"
+ else
+ var.error = "unknown inner or special"
+ end
+ if trace_identifying then
+ report_identify_arguments(set,var,i,"3a")
+ end
+ return var
+end
+
+local function identify_inner(set,var,prefix,collected,derived,tobesaved)
+ local inner = var.inner
+ local outer = var.outer
+ -- inner ... we could move the prefix logic into the parser so that we have 'm for each entry
+ -- foo:bar -> foo == prefix (first we try the global one)
+ -- -:bar -> ignore prefix
+ local p, i = prefix, nil
+ local splitprefix, splitinner
+ -- the next test is a safeguard when references are auto loaded from outer
+ if inner then
+ splitprefix, splitinner = lpegmatch(prefixsplitter,inner)
+ end
+ -- these are taken from other anonymous references
+ if splitprefix and splitinner then
+ if splitprefix == "-" then
+ i = collected[""]
+ i = i and i[splitinner]
+ if i then
+ p = ""
+ end
+ else
+ i = collected[splitprefix]
+ i = i and i[splitinner]
+ if i then
+ p = splitprefix
+ end
+ end
+ end
+ -- todo: strict here
+ if not i then
+ i = collected[prefix]
+ i = i and i[inner]
+ if i then
+ p = prefix
+ end
+ end
+ if not i and prefix ~= "" then
+ i = collected[""]
+ i = i and i[inner]
+ if i then
+ p = ""
+ end
+ end
+ if i then
+ var.i = { "reference", i }
+ resolvers.reference(var)
+ var.kind = "inner"
+ var.p = p
+ elseif derived then
+ -- these are taken from other data structures (like lists)
+ if splitprefix and splitinner then
+ if splitprefix == "-" then
+ i = derived[""]
+ i = i and i[splitinner]
+ if i then
+ p = ""
+ end
+ else
+ i = derived[splitprefix]
+ i = i and i[splitinner]
+ if i then
+ p = splitprefix
+ end
+ end
+ end
+ if not i then
+ i = derived[prefix]
+ i = i and i[inner]
+ if i then
+ p = prefix
+ end
+ end
+ if not i and prefix ~= "" then
+ i = derived[""]
+ i = i and i[inner]
+ if i then
+ p = ""
+ end
+ end
+ if i then
+ var.kind = "inner"
+ var.i = i
+ var.p = p
+ local ri = resolvers[i[1]]
+ if ri then
+ ri(var)
+ else
+ -- can't happen as we catch it with a metatable now
+ report_references("unknown inner resolver for %a",i[1])
+ end
+ else
+ -- no prefixes here
+ local s = specials[inner]
+ if s then
+ var.kind = "special"
+ else
+ i = (collected and collected[""] and collected[""][inner]) or
+ (derived and derived [""] and derived [""][inner]) or
+ (tobesaved and tobesaved[""] and tobesaved[""][inner])
+ if i then
+ var.kind = "inner"
+ var.i = { "reference", i }
+ resolvers.reference(var)
+ var.p = ""
+ else
+ var.error = "unknown inner or special"
+ end
+ end
+ end
+ end
+ return var
+end
+
+local function identify_outer(set,var,i)
+ local outer = var.outer
+ local inner = var.inner
+ local external = externals[outer]
+ if external then
+ local v = copytable(var)
+ v = identify_inner(set,v,nil,external)
+ if v.i and not v.error then
+ v.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,v,i,"2a")
+ end
+ return v
+ end
+ v = copytable(var)
+ local v = identify_inner(set,v,v.outer,external)
+ if v.i and not v.error then
+ v.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,v,i,"2b")
+ end
+ return v
+ end
+ end
+ local external = productdata.componentreferences[outer]
+ if external then
+ local v = identify_inner(set,copytable(var),nil,external)
+ if v.i and not v.error then
+ v.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,v,i,"2c")
+ end
+ return v
+ end
+ end
+ local external = productdata.productreferences[outer]
+ if external then
+ local vi = external[inner]
+ if vi then
+ var.kind = "outer with inner"
+ var.i = vi
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"2d")
+ end
+ return var
+ end
+ end
+ -- the rest
+ local special = var.special
+ local arguments = var.arguments
+ local operation = var.operation
+ if inner then
+ if arguments then
+ -- outer::inner{argument}
+ var.kind = "outer with inner with arguments"
+ else
+ -- outer::inner
+ var.kind = "outer with inner"
+ end
+ var.i = { "reference", inner }
+ resolvers.reference(var)
+ var.f = outer
+ if trace_identifying then
+ report_identify_outer(set,var,i,"2e")
+ end
+ elseif special then
+ local s = specials[special]
+ if s then
+ if operation then
+ if arguments then
+ -- outer::special(operation{argument,argument})
+ var.kind = "outer with special and operation and arguments"
+ else
+ -- outer::special(operation)
+ var.kind = "outer with special and operation"
+ end
+ else
+ -- outer::special()
+ var.kind = "outer with special"
+ end
+ var.f = outer
+ else
+ var.error = "unknown outer with special"
+ end
+ if trace_identifying then
+ report_identify_outer(set,var,i,"2f")
+ end
+ else
+ -- outer::
+ var.kind = "outer"
+ var.f = outer
+ if trace_identifying then
+ report_identify_outer(set,var,i,"2g")
+ end
+ end
+ return var
+end
+
+local function identify_inner_or_outer(set,var,i)
+ -- here we fall back on product data
+ local inner = var.inner
+ if inner and inner ~= "" then
+ local v = identify_inner(set,copytable(var),set.prefix,collected,derived,tobesaved)
+ if v.i and not v.error then
+ v.kind = "inner" -- check this
+ if trace_identifying then
+ report_identify_outer(set,v,i,"4a")
+ end
+ return v
+ end
+
+local components = job.structure.components
+
+if components then
+ for i=1,#components do
+ local component = components[i]
+ local data = collected[component]
+ local vi = data and data[inner]
+ if vi then
+ var.outer = component
+ var.i = vi
+ var.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"4x")
+ end
+ return var
+ end
+ end
+end
+
+ local componentreferences = productdata.componentreferences
+ local productreferences = productdata.productreferences
+ local components = productdata.components
+ if components and componentreferences then
+ -- for component, data in next, productdata.componentreferences do -- better do this in order of processing:
+ for i=1,#components do
+ local component = components[i]
+ local data = componentreferences[component]
+ if data then
+ local d = data[""]
+ local vi = d and d[inner]
+ if vi then
+ var.outer = component
+ var.i = vi
+ var.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"4b")
+ end
+ return var
+ end
+ end
+ end
+ end
+ local component, inner = lpegmatch(componentsplitter,inner)
+ if component then
+ local data = componentreferences and componentreferences[component]
+ if data then
+ local d = data[""]
+ local vi = d and d[inner]
+ if vi then
+ var.inner = inner
+ var.outer = component
+ var.i = vi
+ var.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"4c")
+ end
+ return var
+ end
+ end
+ local data = productreferences and productreferences[component]
+ if data then
+ local vi = data[inner]
+ if vi then
+ var.inner = inner
+ var.outer = component
+ var.i = vi
+ var.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"4d")
+ end
+ return var
+ end
+ end
+ end
+ var.error = "unknown inner"
+ else
+ var.error = "no inner"
+ end
+ if trace_identifying then
+ report_identify_outer(set,var,i,"4e")
+ end
+ return var
+end
+
+-- local function identify_inner_or_outer(set,var,i)
+-- -- we might consider first checking with a prefix prepended and then without
+-- -- which is better for fig:oeps
+-- local var = do_identify_inner_or_outer(set,var,i)
+-- if var.error then
+-- local prefix = set.prefix
+-- if prefix and prefix ~= "" then
+-- var.inner = prefix .. ':' .. var.inner
+-- var.error = nil
+-- return do_identify_inner_or_outer(set,var,i)
+-- end
+-- end
+-- return var
+-- end
+
+local function identify_inner_component(set,var,i)
+ -- we're in a product (maybe ignore when same as component)
+ local component = var.component
+ identify_inner(set,var,component,collected,derived,tobesaved)
+ if trace_identifying then
+ report_identify_outer(set,var,i,"5a")
+ end
+ return var
+end
+
+local function identify_outer_component(set,var,i)
+ local component = var.component
+ local inner = var.inner
+ local data = productdata.componentreferences[component]
+ if data then
+ local d = data[""]
+ local vi = d and d[inner]
+ if vi then
+ var.inner = inner
+ var.outer = component
+ var.i = vi
+ var.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"6a")
+ end
+ return var
+ end
+ end
+ local data = productdata.productreferences[component]
+ if data then
+ local vi = data[inner]
+ if vi then
+ var.inner = inner
+ var.outer = component
+ var.i = vi
+ var.kind = "outer with inner"
+ set.external = true
+ if trace_identifying then
+ report_identify_outer(set,var,i,"6b")
+ end
+ return var
+ end
+ end
+ var.error = "unknown component"
+ if trace_identifying then
+ report_identify_outer(set,var,i,"6c")
+ end
+ return var
+end
+
+local nofidentified = 0
+
+local function identify(prefix,reference)
+ if not reference then
+ prefix = ""
+ reference = prefix
+ end
+ local set = resolve(prefix,reference)
+ local bug = false
+ texcount.referencehastexstate = set.has_tex and 1 or 0
+ nofidentified = nofidentified + 1
+ set.n = nofidentified
+ for i=1,#set do
+ local var = set[i]
+ if var.special then
+ var = identify_special(set,var,i)
+ elseif var.outer then
+ var = identify_outer(set,var,i)
+ elseif var.arguments then
+ var = identify_arguments(set,var,i)
+ elseif not var.component then
+ var = identify_inner_or_outer(set,var,i)
+ elseif productcomponent() then
+ var = identify_inner_component(set,var,i)
+ else
+ var = identify_outer_component(set,var,i)
+ end
+ set[i] = var
+ bug = bug or var.error
+ end
+ references.currentset = mark(set) -- mark, else in api doc
+ if trace_analyzing then
+ report_references(table.serialize(set,reference))
+ end
+ return set, bug
+end
+
+references.identify = identify
+
+local unknowns, nofunknowns, f_valid = { }, 0, formatters["[%s][%s]"]
+
+function references.valid(prefix,reference,highlight,newwindow,layer)
+ local set, bug = identify(prefix,reference)
+ local unknown = bug or #set == 0
+ if unknown then
+ currentreference = nil -- will go away
+ local str = f_valid(prefix,reference)
+ local u = unknowns[str]
+ if not u then
+ interfaces.showmessage("references",1,str) -- 1 = unknown, 4 = illegal
+ unknowns[str] = 1
+ nofunknowns = nofunknowns + 1
+ else
+ unknowns[str] = u + 1
+ end
+ else
+ set.highlight, set.newwindow, set.layer = highlight, newwindow, layer
+ currentreference = set[1]
+ end
+ -- we can do the expansion here which saves a call
+ return not unknown
+end
+
+function commands.doifelsereference(prefix,reference,highlight,newwindow,layer)
+ commands.doifelse(references.valid(prefix,reference,highlight,newwindow,layer))
+end
+
+function references.reportproblems() -- might become local
+ if nofunknowns > 0 then
+ statistics.register("cross referencing", function()
+ return format("%s identified, %s unknown",nofidentified,nofunknowns)
+ end)
+ logspushtarget("logfile")
+ logsnewline()
+ report_references("start problematic references")
+ logsnewline()
+ for k, v in table.sortedpairs(unknowns) do
+ report_unknown("%4i: %s",v,k)
+ end
+ logsnewline()
+ report_references("stop problematic references")
+ logsnewline()
+ logspoptarget()
+ end
+end
+
+luatex.registerstopactions(references.reportproblems)
+
+local innermethod = "names"
+
+function references.setinnermethod(m)
+ if m then
+ if m == "page" or m == "mixed" or m == "names" then
+ innermethod = m
+ elseif m == true or m == v_yes then
+ innermethod = "page"
+ end
+ end
+ function references.setinnermethod()
+ report_references("inner method is already set and frozen to %a",innermethod)
+ end
+end
+
+function references.getinnermethod()
+ return innermethod or "names"
+end
+
+directives.register("references.linkmethod", function(v) -- page mixed names
+ references.setinnermethod(v)
+end)
+
+-- this is inconsistent
+
+function references.setinternalreference(prefix,tag,internal,view) -- needs checking
+ if innermethod == "page" then
+ return unsetvalue
+ else
+ local t, tn = { }, 0 -- maybe add to current
+ if tag then
+ if prefix and prefix ~= "" then
+ prefix = prefix .. ":" -- watch out, : here
+ for ref in gmatch(tag,"[^,]+") do
+ tn = tn + 1
+ t[tn] = prefix .. ref
+ end
+ else
+ for ref in gmatch(tag,"[^,]+") do
+ tn = tn + 1
+ t[tn] = ref
+ end
+ end
+ end
+ if internal and innermethod == "names" then -- mixed or page
+ tn = tn + 1
+ t[tn] = "aut:" .. internal
+ end
+ local destination = references.mark(t,nil,nil,view) -- returns an attribute
+ texcount.lastdestinationattribute = destination
+ return destination
+ end
+end
+
+function references.setandgetattribute(kind,prefix,tag,data,view) -- maybe do internal automatically here
+ local attr = references.set(kind,prefix,tag,data) and references.setinternalreference(prefix,tag,nil,view) or unsetvalue
+ texcount.lastdestinationattribute = attr
+ return attr
+end
+
+commands.setreferenceattribute = references.setandgetattribute
+
+function references.getinternalreference(n) -- n points into list (todo: registers)
+ local l = lists.collected[n]
+ return l and l.references.internal or n
+end
+
+function commands.setinternalreference(prefix,tag,internal,view) -- needs checking
+ context(references.setinternalreference(prefix,tag,internal,view))
+end
+
+function commands.getinternalreference(n) -- this will also be a texcount
+ local l = lists.collected[n]
+ context(l and l.references.internal or n)
+end
+
+--
+
+function references.getcurrentmetadata(tag)
+ local data = currentreference and currentreference.i
+ return data and data.metadata and data.metadata[tag]
+end
+
+function commands.getcurrentreferencemetadata(tag)
+ local data = references.getcurrentmetadata(tag)
+ if data then
+ context(data)
+ end
+end
+
+local function currentmetadata(tag)
+ local data = currentreference and currentreference.i
+ return data and data.metadata and data.metadata[tag]
+end
+
+references.currentmetadata = currentmetadata
+
+local function getcurrentprefixspec(default)
+ -- todo: message
+ return currentmetadata("kind") or "?", currentmetadata("name") or "?", default or "?"
+end
+
+references.getcurrentprefixspec = getcurrentprefixspec
+
+function commands.getcurrentprefixspec(default)
+ context.getreferencestructureprefix(getcurrentprefixspec(default))
+end
+
+function references.filter(name,...) -- number page title ...
+ local data = currentreference and currentreference.i -- maybe we should take realpage from here
+ if data then
+ if name == "realpage" then
+ local cs = references.analyze() -- normally already analyzed but also sets state
+ context(tonumber(cs.realpage) or 0) -- todo, return and in command namespace
+ else -- assumes data is table
+ local kind = type(data) == "table" and data.metadata and data.metadata.kind
+ if kind then
+ local filter = filters[kind] or filters.generic
+ filter = filter and (filter[name] or filter.unknown or filters.generic[name] or filters.generic.unknown)
+ if filter then
+ if trace_referencing then
+ report_references("name %a, kind %a, using dedicated filter",name,kind)
+ end
+ filter(data,name,...)
+ elseif trace_referencing then
+ report_references("name %a, kind %a, using generic filter",name,kind)
+ end
+ elseif trace_referencing then
+ report_references("name %a, unknown kind",name)
+ end
+ end
+ elseif name == "realpage" then
+ context(0)
+ elseif trace_referencing then
+ report_references("name %a, no reference",name)
+ end
+end
+
+function references.filterdefault()
+ return references.filter("default",getcurrentprefixspec(v_default))
+end
+
+function commands.currentreferencedefault(tag)
+ if not tag then tag = "default" end
+ references.filter(tag,context.delayed(getcurrentprefixspec(tag)))
+end
+
+filters.generic = { }
+
+function filters.generic.title(data)
+ if data then
+ local titledata = data.titledata or data.useddata
+ if titledata then
+ helpers.title(titledata.title or "?",data.metadata)
+ end
+ end
+end
+
+function filters.generic.text(data)
+ if data then
+ local entries = data.entries or data.useddata
+ if entries then
+ helpers.title(entries.text or "?",data.metadata)
+ end
+ end
+end
+
+function filters.generic.number(data,what,prefixspec) -- todo: spec and then no stopper
+ if data then
+ numberdata = lists.reordered(data) -- data.numberdata
+ if numberdata then
+ helpers.prefix(data,prefixspec)
+ sections.typesetnumber(numberdata,"number",numberdata)
+ else
+ local useddata = data.useddata
+ if useddata and useddsta.number then
+ context(useddata.number)
+ end
+ end
+ end
+end
+
+filters.generic.default = filters.generic.text
+
+function filters.generic.page(data,prefixspec,pagespec)
+ local pagedata = data.pagedata
+ if pagedata then
+ local number, conversion = pagedata.number, pagedata.conversion
+ if not number then
+ -- error
+ elseif conversion then
+ context.convertnumber(conversion,number)
+ else
+ context(number)
+ end
+ else
+ helpers.prefixpage(data,prefixspec,pagespec)
+ end
+end
+
+filters.user = { }
+
+function filters.user.unknown(data,name)
+ if data then
+ local userdata = data.userdata
+ local userkind = userdata and userdata.kind
+ if userkind then
+ local filter = filters[userkind] or filters.generic
+ filter = filter and (filter[name] or filter.unknown)
+ if filter then
+ filter(data,name)
+ return
+ end
+ end
+ local namedata = userdata and userdata[name]
+ if namedata then
+ context(namedata)
+ end
+ end
+end
+
+filters.text = { }
+
+function filters.text.title(data)
+ helpers.title(data.entries.text or "?",data.metadata)
+end
+
+-- no longer considered useful:
+--
+-- function filters.text.number(data)
+-- helpers.title(data.entries.text or "?",data.metadata)
+-- end
+
+function filters.text.page(data,prefixspec,pagespec)
+ helpers.prefixpage(data,prefixspec,pagespec)
+end
+
+filters.full = { }
+
+filters.full.title = filters.text.title
+filters.full.page = filters.text.page
+
+filters.section = { }
+
+function filters.section.number(data,what,prefixspec)
+ if data then
+ local numberdata = data.numberdata
+ if not numberdata then
+ local useddata = data.useddata
+ if useddata and useddata.number then
+ context(useddata.number)
+ end
+ elseif numberdata.hidenumber then
+ local references = data.references
+ if trace_empty then
+ report_empty("reference %a has a hidden number",references.reference)
+ context.emptyreference() -- maybe an option
+ end
+ else
+ sections.typesetnumber(numberdata,"number",prefixspec,numberdata)
+ end
+ end
+end
+
+filters.section.title = filters.generic.title
+filters.section.page = filters.generic.page
+filters.section.default = filters.section.number
+
+-- filters.note = { default = filters.generic.number }
+-- filters.formula = { default = filters.generic.number }
+-- filters.float = { default = filters.generic.number }
+-- filters.description = { default = filters.generic.number }
+-- filters.item = { default = filters.generic.number }
+
+setmetatableindex(filters, function(t,k) -- beware, test with rawget
+ local v = { default = filters.generic.number } -- not copy as it might be extended differently
+ t[k] = v
+ return v
+end)
+
+-- function references.sectiontitle(n)
+-- helpers.sectiontitle(lists.collected[tonumber(n) or 0])
+-- end
+
+-- function references.sectionnumber(n)
+-- helpers.sectionnumber(lists.collected[tonumber(n) or 0])
+-- end
+
+-- function references.sectionpage(n,prefixspec,pagespec)
+-- helpers.prefixedpage(lists.collected[tonumber(n) or 0],prefixspec,pagespec)
+-- end
+
+-- analyze
+
+references.testrunners = references.testrunners or { }
+references.testspecials = references.testspecials or { }
+
+local runners = references.testrunners
+local specials = references.testspecials
+
+-- We need to prevent ending up in the 'relative location' analyzer as it is
+-- pretty slow (progressively). In the pagebody one can best check the reference
+-- real page to determine if we need contrastlocation as that is more lightweight.
+
+local function checkedpagestate(n,page)
+ local r, p = referredpage(n), tonumber(page)
+ if not p then
+ return 0
+ elseif p > r then
+ return 3 -- after
+ elseif p < r then
+ return 2 -- before
+ else
+ return 1 -- same
+ end
+end
+
+local function setreferencerealpage(actions)
+ actions = actions or references.currentset
+ if not actions then
+ return 0
+ else
+ local realpage = actions.realpage
+ if realpage then
+ return realpage
+ end
+ local nofactions = #actions
+ if nofactions > 0 then
+ for i=1,nofactions do
+ local a = actions[i]
+ local what = runners[a.kind]
+ if what then
+ what = what(a,actions) -- needs documentation
+ end
+ end
+ realpage = actions.realpage
+ if realpage then
+ return realpage
+ end
+ end
+ actions.realpage = 0
+ return 0
+ end
+end
+
+-- we store some analysis data alongside the indexed array
+-- at this moment only the real reference page is analyzed
+-- normally such an analysis happens in the backend code
+
+function references.analyze(actions)
+ actions = actions or references.currentset
+ if not actions then
+ actions = { realpage = 0, pagestate = 0 }
+ elseif actions.pagestate then
+ -- already done
+ else
+ local realpage = actions.realpage or setreferencerealpage(actions)
+ if realpage == 0 then
+ actions.pagestate = 0
+ elseif actions.external then
+ actions.pagestate = 0
+ else
+ actions.pagestate = checkedpagestate(actions.n,realpage)
+ end
+ end
+ return actions
+end
+
+function commands.referencepagestate(actions)
+ actions = actions or references.currentset
+ if not actions then
+ context(0)
+ else
+ if not actions.pagestate then
+ references.analyze(actions) -- delayed unless explicitly asked for
+ end
+ context(actions.pagestate)
+ end
+end
+
+function commands.referencerealpage(actions)
+ actions = actions or references.currentset
+ context(not actions and 0 or actions.realpage or setreferencerealpage(actions))
+end
+
+local plist, nofrealpages
+
+local function realpageofpage(p) -- the last one counts !
+ if not plist then
+ local pages = structures.pages.collected
+ nofrealpages = #pages
+ plist = { }
+ for rp=1,nofrealpages do
+ plist[pages[rp].number] = rp
+ end
+ references.nofrealpages = nofrealpages
+ end
+ return plist[p]
+end
+
+references.realpageofpage = realpageofpage
+
+function references.checkedrealpage(r)
+ if not plist then
+ realpageofpage(r) -- just initialize
+ end
+ if not r then
+ return texcount.realpageno
+ elseif r < 1 then
+ return 1
+ elseif r > nofrealpages then
+ return nofrealpages
+ else
+ return r
+ end
+end
+
+-- use local ?
+
+local pages = allocate {
+ [variables.firstpage] = function() return counters.record("realpage")["first"] end,
+ [variables.previouspage] = function() return counters.record("realpage")["previous"] end,
+ [variables.nextpage] = function() return counters.record("realpage")["next"] end,
+ [variables.lastpage] = function() return counters.record("realpage")["last"] end,
+
+ [variables.firstsubpage] = function() return counters.record("subpage" )["first"] end,
+ [variables.previoussubpage] = function() return counters.record("subpage" )["previous"] end,
+ [variables.nextsubpage] = function() return counters.record("subpage" )["next"] end,
+ [variables.lastsubpage] = function() return counters.record("subpage" )["last"] end,
+
+ [variables.forward] = function() return counters.record("realpage")["forward"] end,
+ [variables.backward] = function() return counters.record("realpage")["backward"] end,
+}
+
+references.pages = pages
+
+-- maybe some day i will merge this in the backend code with a testmode (so each
+-- runner then implements a branch)
+
+runners["inner"] = function(var,actions)
+ local r = var.r
+ if r then
+ actions.realpage = r
+ end
+end
+
+runners["special"] = function(var,actions)
+ local handler = specials[var.special]
+ return handler and handler(var,actions)
+end
+
+runners["special operation"] = runners["special"]
+runners["special operation with arguments"] = runners["special"]
+
+-- These are the testspecials not the real ones. They are used to
+-- check the validity.
+
+function specials.internal(var,actions)
+ local v = references.internals[tonumber(var.operation)]
+ local r = v and v.references.realpage
+ if r then
+ actions.realpage = r
+ end
+end
+
+specials.i = specials.internal
+
+function specials.page(var,actions)
+ local o = var.operation
+ local p = pages[o]
+ if type(p) == "function" then
+ p = p()
+ else
+ p = tonumber(realpageofpage(tonumber(o)))
+ end
+ if p then
+ var.r = p
+ actions.realpage = actions.realpage or p -- first wins
+ end
+end
+
+function specials.realpage(var,actions)
+ local p = tonumber(var.operation)
+ if p then
+ var.r = p
+ actions.realpage = actions.realpage or p -- first wins
+ end
+end
+
+function specials.userpage(var,actions)
+ local p = tonumber(realpageofpage(var.operation))
+ if p then
+ var.r = p
+ actions.realpage = actions.realpage or p -- first wins
+ end
+end
+
+function specials.deltapage(var,actions)
+ local p = tonumber(var.operation)
+ if p then
+ p = references.checkedrealpage(p + texcount.realpageno)
+ var.r = p
+ actions.realpage = actions.realpage or p -- first wins
+ end
+end
+
+function specials.section(var,actions)
+ local sectionname = var.arguments
+ local destination = var.operation
+ local internal = structures.sections.internalreference(sectionname,destination)
+ if internal then
+ var.special = "internal"
+ var.operation = internal
+ var.arguments = nil
+ specials.internal(var,actions)
+ end
+end
+
+-- needs a better split ^^^
+
+commands.filterreference = references.filter
+commands.filterdefaultreference = references.filterdefault
+
+-- done differently now:
+
+function references.export(usedname) end
+function references.import(usedname) end
+function references.load (usedname) end
+
+commands.exportreferences = references.export
|