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" } local format, find, gmatch, match, concat = string.format, string.find, string.gmatch, string.match, table.concat local lpegmatch, lpegP, lpegCs = lpeg.match, lpeg.P, lpeg.Cs local texcount, texsetcount = tex.count, tex.setcount local rawget = rawget 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 report_references = logs.reporter("structure","references") local variables = interfaces.variables local constants = interfaces.constants local context = context local settings_to_array = utilities.parsers.settings_to_array local unsetvalue = attributes.unsetvalue -- 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 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 exporters = allocate() local imported = 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.exporters = exporters references.imported = imported references.filters = filters references.executers = executers references.handlers = handlers references.tobesaved = tobesaved references.collected = collected references.tobereferred = tobereferred references.referred = referred storage.register("structures/references/defined", references.defined, "structures.references.defined") local currentreference = nil 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 function initializer() -- can we use a tobesaved as metatable for collected? tobereferred = references.tobereferred referred = references.referred local function get(t,n) -- catch sparse, a bit slow but who cares for i=n,1,-1 do -- we could make a tree ... too much work local p = rawget(t,i) if p then return p end end end setmetatableindex(referred, get) end local function finalizer() -- make sparse local last for i=1,maxreferred do local r = tobereferred[i] if not last then last = r elseif r == last then tobereferred[i] = nil else last = r end end end job.register('structures.references.referred', tobereferred, initializer, finalizer) function references.referredpage(n) return referred[n] or referred[n] or texcount.realpageno end function references.registerpage(n) 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 texsetcount("global","locationcount",texcount.locationcount + 1) end function references.currentorder(kind,name) context(orders[kind] and orders[kind][name] or lastorder) end function references.set(kind,prefix,tag,data) for ref in gmatch(tag,"[^,]+") do local p, r = match(ref,"^(%-):(.-)$") if p and r then prefix, ref = p, r else prefix = "" end if ref ~= "" then local pd = tobesaved[prefix] if not pd then pd = { } tobesaved[prefix] = pd end pd[ref] = data context.dofinishsomereference(kind,prefix,ref) end end end function references.setandgetattribute(kind,prefix,tag,data,view) -- maybe do internal automatically here references.set(kind,prefix,tag,data) texcount.lastdestinationattribute = references.setinternalreference(prefix,tag,nil,view) or -0x7FFFFFFF end function references.enhance(prefix,tag,spec) local l = tobesaved[prefix][tag] if l then l.references.realpage = texcount.realpageno end end -- this reference parser is just an lpeg version of the tex based one local result = { } local lparent, rparent, lbrace, rbrace, dcolon, backslash = lpegP("("), lpegP(")"), lpegP("{"), lpegP("}"), lpegP("::"), lpegP("\\") local reset = lpegP("") / function() result = { } end local b_token = backslash / function(s) result.has_tex = true return s end local o_token = 1 - rparent - rbrace - lparent - lbrace local a_token = 1 - rbrace local s_token = 1 - lparent - lbrace - lparent - lbrace local i_token = 1 - lparent - lbrace local f_token = 1 - lparent - lbrace - dcolon local outer = (f_token )^1 / function (s) result.outer = s end local operation = lpegCs((b_token + o_token)^1) / function (s) result.operation = s end local arguments = lpegCs((b_token + a_token)^0) / function (s) result.arguments = s end local special = (s_token )^1 / function (s) result.special = s end local inner = (i_token )^1 / function (s) result.inner = s end local outer_reference = (outer * dcolon)^0 operation = outer_reference * operation -- special case: page(file::1) and file::page(1) local optional_arguments = (lbrace * arguments * rbrace)^0 local inner_reference = inner * optional_arguments local special_reference = special * lparent * (operation * optional_arguments + operation^0) * rparent local scanner = (reset * outer_reference * (special_reference + inner_reference)^-1 * -1) / function() return result end --~ function references.analyze(str) -- overloaded --~ return lpegmatch(scanner,str) --~ end function references.split(str) return lpegmatch(scanner,str or "") end --~ print(table.serialize(references.analyze(""))) --~ print(table.serialize(references.analyze("inner"))) --~ print(table.serialize(references.analyze("special(operation{argument,argument})"))) --~ print(table.serialize(references.analyze("special(operation)"))) --~ print(table.serialize(references.analyze("special()"))) --~ print(table.serialize(references.analyze("inner{argument}"))) --~ print(table.serialize(references.analyze("outer::"))) --~ print(table.serialize(references.analyze("outer::inner"))) --~ print(table.serialize(references.analyze("outer::special(operation{argument,argument})"))) --~ print(table.serialize(references.analyze("outer::special(operation)"))) --~ print(table.serialize(references.analyze("outer::special()"))) --~ print(table.serialize(references.analyze("outer::inner{argument}"))) --~ print(table.serialize(references.analyze("special(outer::operation)"))) -- -- -- related to strc-ini.lua -- -- -- references.resolvers = references.resolvers or { } function references.resolvers.section(var) local vi = lists.collected[var.i[2]] 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 references.resolvers.float = references.resolvers.section references.resolvers.description = references.resolvers.section references.resolvers.formula = references.resolvers.section references.resolvers.note = references.resolvers.section function references.resolvers.reference(var) local vi = var.i[2] 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) 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 prefix, reference = r.referenceprefix or "", r.reference 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 t = { kind, i } for s in gmatch(reference,"%s*([^,]+)") do if trace_referencing then report_references("list entry %s provides %s reference '%s' on realpage %s",i,kind,s,realpage) end d[s] = d[s] or t -- share them g[s] = g[s] or t -- first wins end end end end end 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, popcatcodes, txtcatcodes = context.pushcatcodes, context.popcatcodes, tex.txtcatcodes function references.urls.get(name,method,space) -- method: none, before, after, both, space: yes/no local u = urls[name] if u then local url, file = u[1], u[2] pushcatcodes(txtcatcodes) if file and file ~= "" then context("%s/%s",url,file) else context(url) end popcatcodes() end end function commands.doifurldefinedelse(name) commands.doifelse(urls[name]) end -- 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 -- 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] if f then context(f[1]) end 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 -- shared by urls and files function references.whatfrom(name) context((urls[name] and variables.url) or (files[name] and variables.file) or variables.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 --~ 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 description, file = f[1], f[2] --~ if description ~= "" then --~ context.dofromfiledescription(description) --~ else --~ context.dofromfileliteral(file) --~ end --~ end --~ end --~ 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 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 description, file = f[1], f[2] if description ~= "" then context.dofromfiledescription(description) else context.dofromfileliteral(file) end end end end -- export exporters.references = exporters.references or { } exporters.lists = exporters.lists or { } function exporters.references.generic(data) local useddata = {} local entries, userdata = data.entries, data.userdata if entries then for k, v in next, entries do useddata[k] = v end end if userdata then for k, v in next, userdata do useddata[k] = v end end return useddata end function exporters.lists.generic(data) local useddata = { } local titledata, numberdata = data.titledata, data.numberdata if titledata then useddata.title = titledata.title end if numberdata then local numbers = numberdata.numbers local t, tn = { }, 0 for i=1,#numbers do local n = numbers[i] if n ~= 0 then tn = tn + 1 t[tn] = n end end useddata.number = concat(t,".") end return useddata end local function referencer(data) local references = data.references local realpage = references.realpage local numberdata = jobpages.tobesaved[realpage] local specification = numberdata.specification return { realpage = references.realpage, number = numberdata.number, conversion = specification.conversion, -- prefix = only makes sense when bywhatever } end -- Exported and imported references ... not yet used but don't forget it -- and redo it. function references.export(usedname) local exported = { } local e_references, e_lists = exporters.references, exporters.lists local g_references, g_lists = e_references.generic, e_lists.generic -- todo: pagenumbers -- todo: some packing for prefix, references in next, references.tobesaved do local pe = exported[prefix] if not pe then pe = { } exported[prefix] = pe end for key, data in next, references do local metadata = data.metadata local exporter = e_references[metadata.kind] or g_references if exporter then pe[key] = { metadata = { kind = metadata.kind, catcodes = metadata.catcodes, coding = metadata.coding, -- we can omit "tex" }, useddata = exporter(data), pagedata = referencer(data), } end end end local pe = exported[""] if not pe then pe = { } exported[""] = pe end for n, data in next, lists.tobesaved do local metadata = data.metadata local exporter = e_lists[metadata.kind] or g_lists if exporter then local result = { metadata = { kind = metadata.kind, catcodes = metadata.catcodes, coding = metadata.coding, -- we can omit "tex" }, useddata = exporter(data), pagedata = referencer(data), } for key in gmatch(data.references.reference,"[^,]+") do pe[key] = result end end end local e = { references = exported, version = 1.00, } io.savedata(file.replacesuffix(usedname or tex.jobname,"tue"),table.serialize(e,true)) end function references.import(usedname) if usedname then local jdn = imported[usedname] if not jdn then local filename = files[usedname] if filename then -- only registered files filename = filename[1] else filename = usedname end local data = io.loaddata(file.replacesuffix(filename,"tue")) or "" if data == "" then interfaces.showmessage("references",24,filename) data = nil else data = loadstring(data) if data then data = data() end if data then -- version check end if not data then interfaces.showmessage("references",25,filename) end end if data then interfaces.showmessage("references",26,filename) jdn = data jdn.filename = filename else jdn = { filename = filename, references = { }, version = 1.00 } end imported[usedname] = jdn imported[filename] = jdn end return jdn else return nil end end function references.load(usedname) -- gone 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.registerspecial(name,action,...) --~ specials[name] = { action, ... } --~ end function references.reset(prefix,reference) local d = defined[prefix] if d then d[reference] = nil end end -- \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 texcount.referencehastexstate = 0 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 = lpegmatch(scanner,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: %s",ri or "?") end end end if set.has_tex then texcount.referencehastexstate = 1 end --~ table.print(set) return set else return { } end end -- prefix == "" is valid prefix which saves multistep lookup references.currentset = nil --~ local b, e = "\\ctxlua{local jc = structures.references.currentset;", "}" --~ local o, a = 'jc[%s].operation=[[%s]];', 'jc[%s].arguments=[[%s]];' --~ --~ function references.expandcurrent() -- todo: two booleans: o_has_tex& a_has_tex --~ local currentset = references.currentset --~ if currentset and currentset.has_tex then --~ local done = false --~ for i=1,#currentset do --~ local ci = currentset[i] --~ local operation = ci.operation --~ if operation then --~ if find(operation,"\\") then -- if o_has_tex then --~ if not done then --~ context(b) --~ done = true --~ end --~ context(o,i,operation) --~ end --~ end --~ local arguments = ci.arguments --~ if arguments then --~ if find(arguments,"\\") then -- if a_has_tex then --~ if not done then --~ context(b) --~ done = true --~ end --~ context(a,i,arguments) --~ end --~ end --~ end --~ if done then --~ context(e) --~ end --~ end --~ end 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 --~ local uo = urls[outer] --~ if uo then --~ special, operation, argument = "url", uo[1], inner or uo[2] -- maybe more is needed --~ else --~ local fo = files[outer] --~ if fo then --~ special, operation, argument = "file", fo[1], inner -- maybe more is needed --~ end --~ end local prefixsplitter = lpegCs(lpegP((1-lpegP(":"))^1 * lpegP(":"))) * lpegCs(lpegP(1)^1) -- todo: add lots of tracing here local n = 0 local function identify(prefix,reference) local set = resolve(prefix,reference) local bug = false n = n + 1 set.n = n for i=1,#set do local var = set[i] local special, inner, outer, arguments, operation = var.special, var.inner, var.outer, var.arguments, var.operation if special then local s = specials[special] if s then 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 else var.error = "unknown special" end elseif outer then local e = references.import(outer) if e then if inner then local r = e.references if r then r = r[prefix] if r then r = r[inner] if r 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", r } references.resolvers.reference(var) var.f = outer var.e = true -- external end end end if not r then r = e.derived if r then r = r[prefix] if r then r = r[inner] if r then -- outer::inner if arguments then -- outer::inner{argument} var.kind = "outer with inner with arguments" else -- outer::inner var.kind = "outer with inner" end var.i = r references.resolvers[r[1]](var) var.f = outer end end end end if not r then var.error = "unknown outer" 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 else -- outer:: var.kind = "outer" var.f = outer end else 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 } references.resolvers.reference(var) var.f = outer 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 else -- outer:: var.kind = "outer" var.f = outer end end else if arguments then local s = specials[inner] if s then -- inner{argument} var.kind = "special with arguments" else var.error = "unknown inner or special" end else -- 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 = lpegmatch(prefixsplitter,inner) -- 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 } references.resolvers.reference(var) var.kind = "inner" var.p = p else -- these are taken from other data structures (like lists) --~ print("!!!!!!!!!!!!!!",splitprefix,splitinner) --~ table.print(derived) 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 references.resolvers[i[1]](var) var.p = p else -- no prefixes here local s = specials[inner] if s then var.kind = "special" else i = (collected[""] and collected[""][inner]) or (derived [""] and derived [""][inner]) or (tobesaved[""] and tobesaved[""][inner]) if i then var.kind = "inner" var.i = { "reference", i } references.resolvers.reference(var) var.p = "" else var.error = "unknown inner or special" end end end end end end bug = bug or var.error set[i] = var end references.currentset = mark(set) -- mark, else in api doc --~ table.print(set,tostring(bug)) return set, bug end references.identify = identify local unknowns, nofunknowns = { }, 0 function references.doifelse(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 = format("[%s][%s]",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 --~ print("!!!!!!",not unknown) commands.doifelse(not unknown) end function references.reportproblems() -- might become local if nofunknowns > 0 then interfaces.showmessage("references",5,nofunknowns) -- 5 = unknown, 6 = illegal -- -- we need a proper logger specific for the log file -- texio.write_nl("log",format("%s unknown references",nofunknowns)) -- for k, v in table.sortedpairs(unknowns) do -- texio.write_nl("log",format("%s (n=%s)",k,v)) -- end -- texio.write_nl("log","") 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 == variables.yes then innermethod = "page" end end function references.setinnermethod() report_references("inner method is already set and frozen to '%s'",innermethod) end end function references.getinnermethod() return innermethod or "names" end directives.register("references.linkmethod", function(v) -- page mixed names references.setinnermethod(v) end) function references.setinternalreference(prefix,tag,internal,view) 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 .. ":" 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.getinternalreference(n) -- n points into list (todo: registers) local l = lists.collected[n] context(l and l.references.internal or n) end -- function references.getcurrentmetadata(tag) local data = currentreference and currentreference.i data = data and data.metadata and data.metadata[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 function references.getcurrentprefixspec(default) -- todo: message context.getreferencestructureprefix(currentmetadata("kind") or "?",currentmetadata("name") or "?",default or "?") end function references.filter(name,...) -- number page title ... local data = currentreference and currentreference.i if data then local kind = 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 '%s', kind '%s', using dedicated filter",name,kind) end filter(data,name,...) elseif trace_referencing then report_references("name '%s', kind '%s', using generic filter",name,kind) end elseif trace_referencing then report_references("name '%s', unknown kind",name) end elseif trace_referencing then report_references("name '%s', no reference",name) end 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 local numberdata = data.numberdata if numberdata then --~ print(table.serialize(prefixspec)) 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 -- imported 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 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.section = { } function filters.section.number(data,what,prefixspec) if data then local numberdata = data.numberdata if numberdata then sections.typesetnumber(numberdata,"number",prefixspec,numberdata) else local useddata = data.useddata if useddata and useddata.number then context(useddata.number) end 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 } 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 local function checkedpagestate(n,page) local r, p = referred[n] or texcount.realpageno, tonumber(page) if not p then return 0 elseif p > r then return 3 elseif p < r then return 2 else return 1 end end function references.analyze(actions) actions = actions or references.currentset if not actions then actions = { realpage = 0 } texcount.referencepagestate = 0 elseif actions.realpage then -- already analyzed texcount.referencepagestate = checkedpagestate(actions.n,actions.realpage) else -- 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 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) end end texcount.referencepagestate = checkedpagestate(actions.n,actions.realpage) else texcount.referencepagestate = 0 end end return actions end function references.realpage() -- special case, we always want result local cs = references.analyze() context(cs.realpage or 0) end local plist local function realpageofpage(p) if not plist then local pages = structures.pages.collected plist = { } for rp=1,#pages do plist[pages[rp].number] = rp end end return plist[p] end references.realpageofpage = realpageofpage -- 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"] -- weird, why is this code here and in lpdf-ano 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