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 54484fabe..284418c48 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 |