summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/strc-reg.lua
diff options
context:
space:
mode:
authorContext Git Mirror Bot <phg42.2a@gmail.com>2016-01-12 17:15:07 +0100
committerContext Git Mirror Bot <phg42.2a@gmail.com>2016-01-12 17:15:07 +0100
commit8d8d528d2ad52599f11250cfc567fea4f37f2a8b (patch)
tree94286bc131ef7d994f9432febaf03fe23d10eef8 /tex/context/base/mkiv/strc-reg.lua
parentf5aed2e51223c36c84c5f25a6cad238b2af59087 (diff)
downloadcontext-8d8d528d2ad52599f11250cfc567fea4f37f2a8b.tar.gz
2016-01-12 16:26:00
Diffstat (limited to 'tex/context/base/mkiv/strc-reg.lua')
-rw-r--r--tex/context/base/mkiv/strc-reg.lua1347
1 files changed, 1347 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/strc-reg.lua b/tex/context/base/mkiv/strc-reg.lua
new file mode 100644
index 000000000..ed3292195
--- /dev/null
+++ b/tex/context/base/mkiv/strc-reg.lua
@@ -0,0 +1,1347 @@
+if not modules then modules = { } end modules ['strc-reg'] = {
+ version = 1.001,
+ comment = "companion to strc-reg.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local next, type = next, type
+local format, gmatch = string.format, string.gmatch
+local equal, concat, remove = table.are_equal, table.concat, table.remove
+local utfchar = utf.char
+local lpegmatch = lpeg.match
+local allocate = utilities.storage.allocate
+
+local trace_registers = false trackers.register("structures.registers", function(v) trace_registers = v end)
+
+local report_registers = logs.reporter("structure","registers")
+
+local structures = structures
+local registers = structures.registers
+local helpers = structures.helpers
+local sections = structures.sections
+local documents = structures.documents
+local pages = structures.pages
+local references = structures.references
+
+local usedinternals = references.usedinternals
+
+local mappings = sorters.mappings
+local entries = sorters.entries
+local replacements = sorters.replacements
+
+local processors = typesetters.processors
+local splitprocessor = processors.split
+
+local texgetcount = tex.getcount
+
+local variables = interfaces.variables
+local v_forward = variables.forward
+local v_all = variables.all
+local v_yes = variables.yes
+local v_current = variables.current
+local v_previous = variables.previous
+local v_text = variables.text
+
+local context = context
+local commands = commands
+
+local implement = interfaces.implement
+
+local matchingtilldepth = sections.matchingtilldepth
+local numberatdepth = sections.numberatdepth
+local currentlevel = sections.currentlevel
+local currentid = sections.currentid
+
+local touserdata = helpers.touserdata
+
+local internalreferences = references.internals
+local setinternalreference = references.setinternalreference
+
+local setmetatableindex = table.setmetatableindex
+local texsetattribute = tex.setattribute
+
+local a_destination = attributes.private('destination')
+
+local absmaxlevel = 5 -- \c_strc_registers_maxlevel
+
+local h_prefixpage = helpers.prefixpage
+local h_prefixlastpage = helpers.prefixlastpage
+local h_title = helpers.title
+
+local ctx_startregisteroutput = context.startregisteroutput
+local ctx_stopregisteroutput = context.stopregisteroutput
+local ctx_startregistersection = context.startregistersection
+local ctx_stopregistersection = context.stopregistersection
+local ctx_startregisterentries = context.startregisterentries
+local ctx_stopregisterentries = context.stopregisterentries
+local ctx_startregisterentry = context.startregisterentry
+local ctx_stopregisterentry = context.stopregisterentry
+local ctx_startregisterpages = context.startregisterpages
+local ctx_stopregisterpages = context.stopregisterpages
+local ctx_startregisterseewords = context.startregisterseewords
+local ctx_stopregisterseewords = context.stopregisterseewords
+local ctx_registerentry = context.registerentry
+local ctx_registerseeword = context.registerseeword
+local ctx_registerpagerange = context.registerpagerange
+local ctx_registeronepage = context.registeronepage
+
+-- possible export, but ugly code (overloads)
+--
+-- local output, section, entries, nofentries, pages, words, rawtext
+--
+-- h_title = function(a,b) rawtext = a end
+--
+-- local function ctx_startregisteroutput()
+-- output = { }
+-- section = nil
+-- entries = nil
+-- nofentries = nil
+-- pages = nil
+-- words = nil
+-- rawtext = nil
+-- end
+-- local function ctx_stopregisteroutput()
+-- inspect(output)
+-- output = nil
+-- section = nil
+-- entries = nil
+-- nofentries = nil
+-- pages = nil
+-- words = nil
+-- rawtext = nil
+-- end
+-- local function ctx_startregistersection(tag)
+-- section = { }
+-- output[#output+1] = {
+-- section = section,
+-- tag = tag,
+-- }
+-- end
+-- local function ctx_stopregistersection()
+-- end
+-- local function ctx_startregisterentries(n)
+-- entries = { }
+-- nofentries = 0
+-- section[#section+1] = entries
+-- end
+-- local function ctx_stopregisterentries()
+-- end
+-- local function ctx_startregisterentry(n) -- or subentries (nested?)
+-- nofentries = nofentries + 1
+-- entry = { }
+-- entries[nofentries] = entry
+-- end
+-- local function ctx_stopregisterentry()
+-- nofentries = nofentries - 1
+-- entry = entries[nofentries]
+-- end
+-- local function ctx_startregisterpages()
+-- pages = { }
+-- entry.pages = pages
+-- end
+-- local function ctx_stopregisterpages()
+-- end
+-- local function ctx_startregisterseewords()
+-- words = { }
+-- entry.words = words
+-- end
+-- local function ctx_stopregisterseewords()
+-- end
+-- local function ctx_registerentry(processor,internal,seeparent,text)
+-- text()
+-- entry.text = {
+-- processor = processor,
+-- internal = internal,
+-- seeparent = seeparent,
+-- text = rawtext,
+-- }
+-- end
+-- local function ctx_registerseeword(i,n,processor,internal,seeindex,seetext)
+-- seetext()
+-- entry.words[i] = {
+-- processor = processor,
+-- internal = internal,
+-- seeparent = seeparent,
+-- seetext = rawtext,
+-- }
+-- end
+-- local function ctx_registerpagerange(fprocessor,finternal,frealpage,lprocessor,linternal,lrealpage)
+-- pages[#pages+1] = {
+-- first = {
+-- processor = fprocessor,
+-- internal = finternal,
+-- realpage = frealpage,
+-- },
+-- last = {
+-- processor = lprocessor,
+-- internal = linternal,
+-- realpage = lrealpage,
+-- },
+-- }
+-- end
+-- local function ctx_registeronepage(processor,internal,realpage)
+-- pages[#pages+1] = {
+-- processor = processor,
+-- internal = internal,
+-- realpage = realpage,
+-- }
+-- end
+
+-- some day we will share registers and lists (although there are some conceptual
+-- differences in the application of keywords)
+
+local function filtercollected(names,criterium,number,collected,prevmode)
+ if not criterium or criterium == "" then
+ criterium = v_all
+ end
+ local data = documents.data
+ local numbers = data.numbers
+ local depth = data.depth
+ local hash = { }
+ local result = { }
+ local nofresult = 0
+ local all = not names or names == "" or names == v_all
+ local detail = nil
+ if not all then
+ for s in gmatch(names,"[^, ]+") do
+ hash[s] = true
+ end
+ end
+ if criterium == v_all or criterium == v_text then
+ for i=1,#collected do
+ local v = collected[i]
+ if all then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ else
+ local vmn = v.metadata and v.metadata.name
+ if hash[vmn] then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ end
+ end
+ end
+ elseif criterium == v_current then
+ local collectedsections = sections.collected
+ for i=1,#collected do
+ local v = collected[i]
+ local sectionnumber = collectedsections[v.references.section]
+ if sectionnumber then
+ local cnumbers = sectionnumber.numbers
+ if prevmode then
+ if (all or hash[v.metadata.name]) and #cnumbers >= depth then -- is the = ok for lists as well?
+ local ok = true
+ for d=1,depth do
+ if not (cnumbers[d] == numbers[d]) then -- no zero test
+ ok = false
+ break
+ end
+ end
+ if ok then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ end
+ end
+ else
+ if (all or hash[v.metadata.name]) and #cnumbers > depth then
+ local ok = true
+ for d=1,depth do
+ local cnd = cnumbers[d]
+ if not (cnd == 0 or cnd == numbers[d]) then
+ ok = false
+ break
+ end
+ end
+ if ok then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ end
+ end
+ end
+ end
+ end
+ elseif criterium == v_previous then
+ local collectedsections = sections.collected
+ for i=1,#collected do
+ local v = collected[i]
+ local sectionnumber = collectedsections[v.references.section]
+ if sectionnumber then
+ local cnumbers = sectionnumber.numbers
+ if (all or hash[v.metadata.name]) and #cnumbers >= depth then
+ local ok = true
+ if prevmode then
+ for d=1,depth do
+ if not (cnumbers[d] == numbers[d]) then
+ ok = false
+ break
+ end
+ end
+ else
+ for d=1,depth do
+ local cnd = cnumbers[d]
+ if not (cnd == 0 or cnd == numbers[d]) then
+ ok = false
+ break
+ end
+ end
+ end
+ if ok then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ end
+ end
+ end
+ end
+ elseif criterium == variables["local"] then
+ if sections.autodepth(data.numbers) == 0 then
+ return filtercollected(names,v_all,number,collected,prevmode)
+ else
+ return filtercollected(names,v_current,number,collected,prevmode)
+ end
+ else -- sectionname, number
+ -- beware, this works ok for registers
+ -- to be redone with reference instead
+ local depth = sections.getlevel(criterium)
+ local number = tonumber(number) or numberatdepth(depth) or 0
+ if trace_registers then
+ detail = format("depth: %s, number: %s, numbers: %s, startset: %s",depth,number,concat(sections.numbers(),".",1,depth),#collected)
+ end
+ if number > 0 then
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r then
+ local sectionnumber = sections.collected[r.section]
+ if sectionnumber then
+ local metadata = v.metadata
+ local cnumbers = sectionnumber.numbers
+ if cnumbers then
+ if (all or hash[metadata.name or false]) and #cnumbers >= depth and matchingtilldepth(depth,cnumbers) then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ if trace_registers then
+ if detail then
+ report_registers("criterium %a, detail %a, found %a",criterium,detail,#result)
+ else
+ report_registers("criterium %a, detail %a, found %a",criterium,nil,#result)
+ end
+ end
+ return result
+end
+
+local tobesaved = allocate()
+local collected = allocate()
+
+registers.collected = collected
+registers.tobesaved = tobesaved
+registers.filtercollected = filtercollected
+
+-- we follow a different strategy than by lists, where we have a global
+-- result table; we might do that here as well but since sorting code is
+-- older we delay that decision
+
+-- maybe store the specification in the format (although we predefine only
+-- saved registers)
+
+local function checker(t,k)
+ local v = {
+ metadata = {
+ language = 'en',
+ sorted = false,
+ class = class,
+ },
+ entries = { },
+ }
+ t[k] = v
+ return v
+end
+
+local function initializer()
+ tobesaved = registers.tobesaved
+ collected = registers.collected
+ setmetatableindex(tobesaved,checker)
+ setmetatableindex(collected,checker)
+ local usedinternals = references.usedinternals
+ for name, list in next, collected do
+ local entries = list.entries
+ if not list.metadata.notsaved then
+ for e=1,#entries do
+ local entry = entries[e]
+ local r = entry.references
+ if r then
+ local internal = r and r.internal
+ if internal then
+ internalreferences[internal] = entry
+ usedinternals[internal] = r.used
+ end
+ end
+ end
+ end
+ end
+end
+
+local function finalizer()
+ local flaginternals = references.flaginternals
+ for k, v in next, tobesaved do
+ local entries = v.entries
+ if entries then
+ for i=1,#entries do
+ local r = entries[i].references
+ if r and flaginternals[r.internal] then
+ r.used = true
+ end
+ end
+ end
+ end
+end
+
+job.register('structures.registers.collected', tobesaved, initializer, finalizer)
+
+setmetatableindex(tobesaved,checker)
+setmetatableindex(collected,checker)
+
+local function defineregister(class,method)
+ local d = tobesaved[class]
+ if method == v_forward then
+ d.metadata.notsaved = true
+ end
+end
+
+registers.define = defineregister -- 4 times is somewhat over the top but we want consistency
+registers.setmethod = defineregister -- and we might have a difference some day
+
+implement {
+ name = "defineregister",
+ actions = defineregister,
+ arguments = { "string", "string" }
+}
+
+implement {
+ name = "setregistermethod",
+ actions = defineregister, -- duplicate use
+ arguments = { "string", "string" }
+}
+
+local entrysplitter = lpeg.tsplitat('+') -- & obsolete in mkiv
+
+local tagged = { }
+
+-- this whole splitting is an inheritance of mkii
+
+local function preprocessentries(rawdata)
+ local entries = rawdata.entries
+ if entries then
+ --
+ -- local e = entries[1] or ""
+ -- local k = entries[2] or ""
+ -- local et, kt, entryproc, pageproc
+ -- if type(e) == "table" then
+ -- et = e
+ -- else
+ -- entryproc, e = splitprocessor(e)
+ -- et = lpegmatch(entrysplitter,e)
+ -- end
+ -- if type(k) == "table" then
+ -- kt = k
+ -- else
+ -- pageproc, k = splitprocessor(k)
+ -- kt = lpegmatch(entrysplitter,k)
+ -- end
+ --
+ local processors = rawdata.processors
+ local et = entries.entries
+ local kt = entries.keys
+ local entryproc = processors and processors.entry
+ local pageproc = processors and processors.page
+ if entryproc == "" then
+ entryproc = nil
+ end
+ if pageproc == "" then
+ pageproc = nil
+ end
+ if not et then
+ local p, e = splitprocessor(entries.entry or "")
+ if p then
+ entryproc = p
+ end
+ et = lpegmatch(entrysplitter,e)
+ end
+ if not kt then
+ local p, k = splitprocessor(entries.key or "")
+ if p then
+ pageproc = p
+ end
+ kt = lpegmatch(entrysplitter,k)
+ end
+ --
+ entries = { }
+ local ok = false
+ for k=#et,1,-1 do
+ local etk = et[k]
+ local ktk = kt[k]
+ if not ok and etk == "" then
+ entries[k] = nil
+ else
+ entries[k] = { etk or "", ktk ~= "" and ktk or nil }
+ ok = true
+ end
+ end
+ rawdata.list = entries
+ if pageproc or entryproc then
+ rawdata.processors = { entryproc, pageproc } -- old way: indexed .. will be keys
+ end
+ rawdata.entries = nil
+ end
+ local seeword = rawdata.seeword
+ if seeword then
+ seeword.processor, seeword.text = splitprocessor(seeword.text or "")
+ end
+end
+
+local function storeregister(rawdata) -- metadata, references, entries
+ local references = rawdata.references
+ local metadata = rawdata.metadata
+ -- checking
+ if not metadata then
+ metadata = { }
+ rawdata.metadata = metadata
+ end
+ --
+ if not metadata.kind then
+ metadata.kind = "entry"
+ end
+ --
+ if not metadata.catcodes then
+ metadata.catcodes = tex.catcodetable -- get
+ end
+ --
+ local name = metadata.name
+ local notsaved = tobesaved[name].metadata.notsaved
+ --
+ if not references then
+ references = { }
+ rawdata.references = references
+ end
+ --
+ local internal = references.internal
+ if not internal then
+ internal = texgetcount("locationcount") -- we assume that it has been set
+ references.internal = internal
+ end
+ --
+ if notsaved then
+ usedinternals[internal] = true -- todo view (we assume that forward references index entries are used)
+ end
+ --
+ if not references.realpage then
+ references.realpage = 0 -- just to be sure as it can be refered to
+ end
+ --
+ local userdata = rawdata.userdata
+ if userdata then
+ rawdata.userdata = touserdata(userdata)
+ end
+ --
+ references.section = currentid()
+ metadata.level = currentlevel()
+ --
+ local data = notsaved and collected[name] or tobesaved[name]
+ local entries = data.entries
+ internalreferences[internal] = rawdata
+ preprocessentries(rawdata)
+ entries[#entries+1] = rawdata
+ local label = references.label
+ if label and label ~= "" then
+ tagged[label] = #entries
+ else
+ references.label = nil
+ end
+ return #entries
+end
+
+registers.store = storeregister
+
+function registers.enhance(name,n)
+ local data = tobesaved[name].metadata.notsaved and collected[name] or tobesaved[name]
+ local entry = data.entries[n]
+ if entry then
+ entry.references.realpage = texgetcount("realpageno")
+ end
+end
+
+function registers.extend(name,tag,rawdata) -- maybe do lastsection internally
+ if type(tag) == "string" then
+ tag = tagged[tag]
+ end
+ if tag then
+ local data = tobesaved[name].metadata.notsaved and collected[name] or tobesaved[name]
+ local entry = data.entries[tag]
+ if entry then
+ local references = entry.references
+ references.lastrealpage = texgetcount("realpageno")
+ references.lastsection = currentid()
+ if rawdata then
+ local userdata = rawdata.userdata
+ if userdata then
+ rawdata.userdata = touserdata(userdata)
+ end
+ if rawdata.entries then
+ preprocessentries(rawdata)
+ end
+ local metadata = rawdata.metadata
+ if metadata and not metadata.catcodes then
+ metadata.catcodes = tex.catcodetable -- get
+ end
+ for k, v in next, rawdata do
+ local rk = references[k]
+ if not rk then
+ references[k] = v
+ else
+ for kk, vv in next, v do
+ if type(vv) == "table" then
+ if next(vv) then
+ rk[kk] = vv
+ end
+ elseif vv ~= "" then
+ rk[kk] = vv
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function registers.get(tag,n)
+ local list = tobesaved[tag]
+ return list and list.entries[n]
+end
+
+implement {
+ name = "enhanceregister",
+ actions = registers.enhance,
+ arguments = { "string", "integer" }
+}
+
+implement {
+ name = "extendregister",
+ actions = registers.extend,
+ arguments = { "string", "string" }
+}
+
+implement {
+ name = "storeregister",
+ actions = function(rawdata)
+ local nofentries = storeregister(rawdata)
+ setinternalreference { internal = rawdata.references.internal }
+ context(nofentries)
+ end,
+ arguments = {
+ {
+ { "metadata", {
+ { "kind" },
+ { "name" },
+ { "coding" },
+ { "level", "integer" },
+ { "catcodes", "integer" },
+ { "own" },
+ { "xmlroot" },
+ { "xmlsetup" }
+ }
+ },
+ { "entries", {
+ { "entries", "list" },
+ { "keys", "list" },
+ { "entry" },
+ { "key" }
+ }
+ },
+ { "references", {
+ { "internal", "integer" },
+ { "section", "integer" },
+ { "label" }
+ }
+ },
+ { "seeword", {
+ { "text" }
+ }
+ },
+ { "processors", {
+ { "entry" },
+ { "key" },
+ { "page" }
+ }
+ },
+ { "userdata" },
+ }
+ }
+}
+
+-- sorting and rendering
+
+local compare = sorters.comparers.basic
+
+function registers.compare(a,b)
+ local result = compare(a,b)
+ if result ~= 0 then
+ return result
+ else
+ local ka = a.metadata.kind
+ local kb = b.metadata.kind
+ if ka == kb then
+ local page_a, page_b = a.references.realpage, b.references.realpage
+ if not page_a or not page_b then
+ return 0
+ elseif page_a < page_b then
+ return -1
+ elseif page_a > page_b then
+ return 1
+ end
+ elseif ka == "see" then
+ return 1
+ elseif kb == "see" then
+ return -1
+ end
+ end
+ return 0
+end
+
+function registers.filter(data,options)
+ data.result = registers.filtercollected(nil,options.criterium,options.number,data.entries,true)
+end
+
+local seeindex = 0
+
+-- meerdere loops, seewords, dan words, anders seewords
+
+local function crosslinkseewords(result) -- all words
+ -- collect all seewords
+ local seewords = { }
+ for i=1,#result do
+ local data = result[i]
+ local seeword = data.seeword
+ if seeword then
+ local seetext = seeword.text
+ if seetext and not seewords[seetext] then
+ seeindex = seeindex + 1
+ seewords[seetext] = seeindex
+ if trace_registers then
+ report_registers("see word %03i: %s",seeindex,seetext)
+ end
+ end
+ end
+ end
+ -- mark seeparents
+ local seeparents = { }
+ for i=1,#result do
+ local data = result[i]
+ local word = data.list[1]
+ word = word and word[1]
+ if word then
+ local seeindex = seewords[word]
+ if seeindex then
+ seeparents[word] = data
+ data.references.seeparent = seeindex
+ if trace_registers then
+ report_registers("see parent %03i: %s",seeindex,word)
+ end
+ end
+ end
+ end
+ -- mark seewords and extend sort list
+ for i=1,#result do
+ local data = result[i]
+ local seeword = data.seeword
+ if seeword then
+ local text = seeword.text
+ if text then
+ local seeparent = seeparents[text]
+ if seeparent then
+ local seeindex = seewords[text]
+ local s, ns, d, w, l = { }, 0, data.split, seeparent.split, data.list
+ -- trick: we influence sorting by adding fake subentries
+ for i=1,#d do
+ ns = ns + 1
+ s[ns] = d[i] -- parent
+ end
+ for i=1,#w do
+ ns = ns + 1
+ s[ns] = w[i] -- see
+ end
+ data.split = s
+ -- we also register a fake extra list entry so that the
+ -- collapser works okay
+ l[#l+1] = { text, "" }
+ data.references.seeindex = seeindex
+ if trace_registers then
+ report_registers("see crosslink %03i: %s",seeindex,text)
+ end
+ end
+ end
+ end
+ end
+end
+
+local function removeemptyentries(result)
+ local i, n, m = 1, #result, 0
+ while i <= n do
+ local entry = result[i]
+ if #entry.list == 0 or #entry.split == 0 then
+ remove(result,i)
+ n = n - 1
+ m = m + 1
+ else
+ i = i + 1
+ end
+ end
+ if m > 0 then
+ report_registers("%s empty entries removed in register",m)
+ end
+end
+
+function registers.prepare(data)
+ -- data has 'list' table
+ local strip = sorters.strip
+ local splitter = sorters.splitters.utf
+ local result = data.result
+ if result then
+ for i=1, #result do
+ local entry = result[i]
+ local split = { }
+ local list = entry.list
+ if list then
+ for l=1,#list do
+ local ll = list[l]
+ local word = ll[1]
+ local key = ll[2]
+ if not key or key == "" then
+ key = word
+ end
+ split[l] = splitter(strip(key))
+ end
+ end
+ entry.split = split
+ end
+ removeemptyentries(result)
+ crosslinkseewords(result)
+ end
+end
+
+function registers.sort(data,options)
+ -- if options.pagenumber == false then
+ -- sorters.sort(data.result,compare)
+ -- else
+ sorters.sort(data.result,registers.compare)
+ -- end
+end
+
+function registers.unique(data,options)
+ local result, nofresult, prev = { }, 0, nil
+ local dataresult = data.result
+ for k=1,#dataresult do
+ local v = dataresult[k]
+ if prev then
+ local vr = v.references
+ local pr = prev.references
+ if not equal(prev.list,v.list) then
+ -- ok
+ elseif pr.realpage ~= vr.realpage then
+ -- ok
+ else
+ local pl, vl = pr.lastrealpage, vr.lastrealpage
+ if pl or vl then
+ if not vl then
+ -- ok
+ elseif not pl then
+ -- ok
+ elseif pl ~= vl then
+ -- ok
+ else
+ v = nil
+ end
+ else
+ v = nil
+ end
+ end
+ end
+ if v then
+ nofresult = nofresult + 1
+ result[nofresult] = v
+ prev = v
+ end
+ end
+ data.result = result
+end
+
+function registers.finalize(data,options) -- maps character to index (order)
+ local result = data.result
+ data.metadata.nofsorted = #result
+ local split, nofsplit, lasttag, done, nofdone = { }, 0, nil, nil, 0
+ local firstofsplit = sorters.firstofsplit
+ for k=1,#result do
+ local v = result[k]
+ local entry, tag = firstofsplit(v)
+ if tag ~= lasttag then
+ if trace_registers then
+ report_registers("splitting at %a",tag)
+ end
+ done = { }
+ nofdone = 0
+ nofsplit = nofsplit + 1
+ lasttag = tag
+ split[nofsplit] = { tag = tag, data = done }
+ end
+ nofdone = nofdone + 1
+ done[nofdone] = v
+ end
+ data.result = split
+end
+
+local function analyzeregister(class,options)
+ local data = collected[class]
+ if data and data.entries then
+ options = options or { }
+ sorters.setlanguage(options.language,options.method,options.numberorder)
+ registers.filter(data,options) -- filter entries into results (criteria)
+ registers.prepare(data,options) -- adds split table parallel to list table
+ registers.sort(data,options) -- sorts results
+ registers.unique(data,options) -- get rid of duplicates
+ registers.finalize(data,options) -- split result in ranges
+ data.metadata.sorted = true
+ return data.metadata.nofsorted or 0
+ else
+ return 0
+ end
+end
+
+registers.analyze = analyzeregister
+
+implement {
+ name = "analyzeregister",
+ actions = { analyzeregister, context },
+ arguments = {
+ "string",
+ {
+ { "language" },
+ { "method" },
+ { "numberorder" },
+ { "compress" },
+ { "criterium" },
+ { "pagenumber", "boolean" },
+ }
+ }
+}
+
+-- todo take conversion from index
+
+function registers.userdata(index,name)
+ local data = references.internals[tonumber(index)]
+ return data and data.userdata and data.userdata[name] or nil
+end
+
+implement {
+ name = "registeruserdata",
+ actions = { registers.userdata, context },
+ arguments = { "integer", "string" }
+}
+
+-- todo: ownnumber
+
+local function pagerange(f_entry,t_entry,is_last,prefixspec,pagespec)
+ local fer, ter = f_entry.references, t_entry.references
+ ctx_registerpagerange(
+ f_entry.processors and f_entry.processors[2] or "",
+ fer.internal or 0,
+ fer.realpage or 0,
+ function()
+ h_prefixpage(f_entry,prefixspec,pagespec)
+ end,
+ ter.internal or 0,
+ ter.lastrealpage or ter.realpage or 0,
+ function()
+ if is_last then
+ h_prefixlastpage(t_entry,prefixspec,pagespec) -- swaps page and realpage keys
+ else
+ h_prefixpage (t_entry,prefixspec,pagespec)
+ end
+ end
+ )
+end
+
+local function pagenumber(entry,prefixspec,pagespec)
+ local er = entry.references
+ ctx_registeronepage(
+ entry.processors and entry.processors[2] or "",
+ er.internal or 0,
+ er.realpage or 0,
+ function() h_prefixpage(entry,prefixspec,pagespec) end
+ )
+end
+
+local function collapsedpage(pages)
+ for i=2,#pages do
+ local first, second = pages[i-1], pages[i]
+ local first_first, first_last, second_first, second_last = first[1], first[2], second[1], second[2]
+ local first_last_pn = first_last .references.realpage
+ local second_first_pn = second_first.references.realpage
+ local second_last_pn = second_last .references.realpage
+ local first_last_last = first_last .references.lastrealpage
+ local second_first_last = second_first.references.lastrealpage
+ if first_last_last then
+ first_last_pn = first_last_last
+ if second_first == second_last and second_first_pn <= first_last_pn then
+ -- 2=8, 5 -> 12=8
+ remove(pages,i)
+ return true
+ elseif second_first == second_last and second_first_pn > first_last_pn then
+ -- 2=8, 9 -> 2-9
+ pages[i-1] = { first_first, second_last }
+ remove(pages,i)
+ return true
+ elseif second_last_pn < first_last_pn then
+ -- 2=8, 3-4 -> 2=8
+ remove(pages,i)
+ return true
+ elseif first_last_pn < second_last_pn then
+ -- 2=8, 3-9 -> 2-9
+ pages[i-1] = { first_first, second_last }
+ remove(pages,i)
+ return true
+ elseif first_last_pn + 1 == second_first_pn and second_last_pn > first_last_pn then
+ -- 2=8, 9-11 -> 2-11
+ pages[i-1] = { first_first, second_last }
+ remove(pages,i)
+ return true
+ elseif second_first.references.lastrealpage then
+ -- 2=8, 9=11 -> 2-11
+ pages[i-1] = { first_first, second_last }
+ remove(pages,i)
+ return true
+ end
+ elseif second_first_last then
+ second_first_pn = second_first_last
+ if first_last_pn == second_first_pn then
+ -- 2-4, 5=9 -> 2-9
+ pages[i-1] = { first_first, second_last }
+ remove(pages,i)
+ return true
+ end
+ elseif first_last_pn == second_first_pn then
+ -- 2-3, 3-4 -> 2-4
+ pages[i-1] = { first_last, second_last }
+ remove(pages,i)
+ return true
+ end
+ end
+ return false
+end
+
+local function collapsepages(pages)
+ while collapsedpage(pages) do end
+ return #pages
+end
+
+function registers.flush(data,options,prefixspec,pagespec)
+ local collapse_singles = options.compress == v_yes
+ local collapse_ranges = options.compress == v_all
+ local show_page_number = options.pagenumber ~= false -- true or false
+ local result = data.result
+ local maxlevel = 0
+ --
+ for i=1,#result do
+ local data = result[i].data
+ for d=1,#data do
+ local m = #data[d].list
+ if m > maxlevel then
+ maxlevel = m
+ end
+ end
+ end
+ if maxlevel > absmaxlevel then
+ maxlevel = absmaxlevel
+ report_registers("limiting level to %a",maxlevel)
+ end
+ --
+ ctx_startregisteroutput()
+ local done = { }
+ local started = false
+ for i=1,#result do
+ -- ranges need checking !
+ local sublist = result[i]
+ -- local done = { false, false, false, false }
+ for i=1,maxlevel do
+ done[i] = false
+ end
+ local data = sublist.data
+ local d, n = 0, 0
+ ctx_startregistersection(sublist.tag)
+ for d=1,#data do
+ local entry = data[d]
+ if entry.metadata.kind == "see" then
+ local list = entry.list
+ if #list > 1 then
+ list[#list] = nil
+ else
+ -- we have an \seeindex{Foo}{Bar} without Foo being defined anywhere .. somehow this message is wrong
+ -- report_registers("invalid see entry in register %a, reference %a",entry.metadata.name,list[1][1])
+ end
+ end
+ end
+ -- ok, this is tricky: we use e[i] delayed so we need it to be local
+ -- but we don't want to allocate too many entries so there we go
+ while d < #data do
+ d = d + 1
+ local entry = data[d]
+ local metadata = entry.metadata
+ local kind = metadata.kind
+ local list = entry.list
+ local e = { false, false, false }
+ for i=3,maxlevel do
+ e[i] = false
+ end
+ for i=1,maxlevel do
+ if list[i] then
+ e[i] = list[i][1]
+ end
+ if e[i] == done[i] then
+ -- skip
+ elseif not e[i] then
+ -- see ends up here
+ -- can't happen any more
+ done[i] = false
+ for j=i+1,maxlevel do
+ done[j] = false
+ end
+ elseif e[i] == "" then
+ done[i] = false
+ for j=i+1,maxlevel do
+ done[j] = false
+ end
+ else
+ done[i] = e[i]
+ for j=i+1,maxlevel do
+ done[j] = false
+ end
+ if started then
+ ctx_stopregisterentry()
+ started = false
+ end
+ if n == i then
+-- ctx_stopregisterentries()
+-- ctx_startregisterentries(n)
+ else
+ while n > i do
+ n = n - 1
+ ctx_stopregisterentries()
+ end
+ while n < i do
+ n = n + 1
+ ctx_startregisterentries(n)
+ end
+ end
+ local references = entry.references
+ local processors = entry.processors
+ local internal = references.internal or 0
+ local seeparent = references.seeparent or ""
+ local processor = processors and processors[1] or ""
+ -- so, we need to keep e as is (local), or we need local title = e[i] ... which might be
+ -- more of a problem
+ ctx_startregisterentry(0) -- will become a counter
+ started = true
+ if metadata then
+ ctx_registerentry(processor,internal,seeparent,function() h_title(e[i],metadata) end)
+ else
+ -- can this happen?
+ ctx_registerentry(processor,internal,seeindex,e[i])
+ end
+ end
+ end
+ if kind == 'entry' then
+ if show_page_number then
+ ctx_startregisterpages()
+ if collapse_singles or collapse_ranges then
+ -- we collapse ranges and keep existing ranges as they are
+ -- so we get prebuilt as well as built ranges
+ local first, last, prev, pages, dd, nofpages = entry, nil, entry, { }, d, 0
+ while dd < #data do
+ dd = dd + 1
+ local next = data[dd]
+ if next and next.metadata.kind == "see" then
+ dd = dd - 1
+ break
+ else
+ local el, nl = entry.list, next.list
+ if not equal(el,nl) then
+ dd = dd - 1
+ --~ first = nil
+ break
+ elseif next.references.lastrealpage then
+ nofpages = nofpages + 1
+ pages[nofpages] = first and { first, last or first } or { entry, entry }
+ nofpages = nofpages + 1
+ pages[nofpages] = { next, next }
+ first, last, prev = nil, nil, nil
+ elseif not first then
+ first, prev = next, next
+ elseif next.references.realpage - prev.references.realpage == 1 then -- 1 ?
+ last, prev = next, next
+ else
+ nofpages = nofpages + 1
+ pages[nofpages] = { first, last or first }
+ first, last, prev = next, nil, next
+ end
+ end
+ end
+ if first then
+ nofpages = nofpages + 1
+ pages[nofpages] = { first, last or first }
+ end
+ if collapse_ranges and nofpages > 1 then
+ nofpages = collapsepages(pages)
+ end
+ if nofpages > 0 then -- or 0
+ d = dd
+ for p=1,nofpages do
+ local first, last = pages[p][1], pages[p][2]
+ if first == last then
+ if first.references.lastrealpage then
+ pagerange(first,first,true,prefixspec,pagespec)
+ else
+ pagenumber(first,prefixspec,pagespec)
+ end
+ elseif last.references.lastrealpage then
+ pagerange(first,last,true,prefixspec,pagespec)
+ else
+ pagerange(first,last,false,prefixspec,pagespec)
+ end
+ end
+ elseif entry.references.lastrealpage then
+ pagerange(entry,entry,true,prefixspec,pagespec)
+ else
+ pagenumber(entry,prefixspec,pagespec)
+ end
+ else
+ while true do
+ if entry.references.lastrealpage then
+ pagerange(entry,entry,true,prefixspec,pagespec)
+ else
+ pagenumber(entry,prefixspec,pagespec)
+ end
+ if d == #data then
+ break
+ else
+ d = d + 1
+ local next = data[d]
+ if next.metadata.kind == "see" or not equal(entry.list,next.list) then
+ d = d - 1
+ break
+ else
+ entry = next
+ end
+ end
+ end
+ end
+ ctx_stopregisterpages()
+ end
+ elseif kind == 'see' then
+ local t, nt = { }, 0
+ while true do
+ nt = nt + 1
+ t[nt] = entry
+ if d == #data then
+ break
+ else
+ d = d + 1
+ local next = data[d]
+ if next.metadata.kind ~= "see" or not equal(entry.list,next.list) then
+ d = d - 1
+ break
+ else
+ entry = next
+ end
+ end
+ end
+ ctx_startregisterseewords()
+ for i=1,nt do
+ local entry = t[i]
+ local seeword = entry.seeword
+ local seetext = seeword.text or ""
+ local processor = seeword.processor or (entry.processors and entry.processors[1]) or ""
+ local seeindex = entry.references.seeindex or ""
+ -- ctx_registerseeword(i,nt,processor,0,seeindex,seetext)
+ ctx_registerseeword(i,nt,processor,0,seeindex,function() h_title(seetext,metadata) end)
+ end
+ ctx_stopregisterseewords()
+ end
+ end
+ if started then
+ ctx_stopregisterentry()
+ started = false
+ end
+ while n > 0 do
+ ctx_stopregisterentries()
+ n = n - 1
+ end
+ ctx_stopregistersection()
+ end
+ ctx_stopregisteroutput()
+ -- for now, maybe at some point we will do a multipass or so
+ data.result = nil
+ data.metadata.sorted = false
+ -- temp hack for luajittex :
+ local entries = data.entries
+ for i=1,#entries do
+ entries[i].split = nil
+ end
+ -- collectgarbage("collect")
+end
+
+function registers.process(class,...)
+ if analyzeregister(class,...) > 0 then
+ local data = collected[class]
+ registers.flush(data,...)
+ end
+end
+
+implement {
+ name = "processregister",
+ actions = registers.process,
+ arguments = {
+ "string",
+ {
+ { "language" },
+ { "method" },
+ { "numberorder" },
+ { "compress" },
+ { "criterium" },
+ { "pagenumber", "boolean" },
+ },
+ {
+ { "separatorset" },
+ { "conversionset" },
+ { "starter" },
+ { "stopper" },
+ { "set" },
+ { "segments" },
+ { "connector" },
+ },
+ {
+ { "prefix" },
+ { "separatorset" },
+ { "conversionset" },
+ { "starter" },
+ { "stopper" },
+ { "segments" },
+ }
+ }
+}