diff options
Diffstat (limited to 'tex/context/base/strc-reg.lua')
-rw-r--r-- | tex/context/base/strc-reg.lua | 1724 |
1 files changed, 862 insertions, 862 deletions
diff --git a/tex/context/base/strc-reg.lua b/tex/context/base/strc-reg.lua index 40cd3455b..61d18a5d5 100644 --- a/tex/context/base/strc-reg.lua +++ b/tex/context/base/strc-reg.lua @@ -1,862 +1,862 @@ -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 texcount = tex.count -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 mappings = sorters.mappings -local entries = sorters.entries -local replacements = sorters.replacements - -local processors = typesetters.processors -local splitprocessor = processors.split - -local variables = interfaces.variables -local context = context - -local matchingtilldepth, numberatdepth = sections.matchingtilldepth, sections.numberatdepth - --- 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 = variables.all end - local data = documents.data - local numbers, depth = data.numbers, data.depth - local hash, result, nofresult, all, detail = { }, { }, 0, not names or names == "" or names == variables.all, nil - if not all then - for s in gmatch(names,"[^, ]+") do - hash[s] = true - end - end - if criterium == variables.all or criterium == variables.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 == variables.current then - for i=1,#collected do - local v = collected[i] - local sectionnumber = sections.collected[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 == variables.previous then - for i=1,#collected do - local v = collected[i] - local sectionnumber = sections.collected[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,variables.all,number,collected,prevmode) - else - return filtercollected(names,variables.current,number,collected,prevmode) - end - else -- sectionname, number - -- beware, this works ok for registers - 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 - -local function initializer() - tobesaved = registers.tobesaved - collected = registers.collected - local internals = references.internals - for name, list in next, collected do - local entries = list.entries - 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 - internals[internal] = entry - end - end - end - end -end - -job.register('structures.registers.collected', tobesaved, initializer) - -local function allocate(class) - local d = tobesaved[class] - if not d then - d = { - metadata = { - language = 'en', - sorted = false, - class = class - }, - entries = { }, - } - tobesaved[class] = d - end - return d -end - -registers.define = allocate - -local entrysplitter = lpeg.tsplitat('+') -- & obsolete in mkiv - -local tagged = { } - -local function preprocessentries(rawdata) - local entries = rawdata.entries - if entries then ---~ table.print(rawdata) - local e, k = entries[1] or "", 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 - entries = { } - for k=1,#et do - entries[k] = { et[k] or "", kt[k] or "" } - end - for k=#et,1,-1 do - if entries[k][1] ~= "" then - break - else - entries[k] = nil - end - end - rawdata.list = entries - if pageproc or entryproc then - rawdata.processors = { entryproc, pageproc } - end - rawdata.entries = nil - end - local seeword = rawdata.seeword - if seeword then - seeword.processor, seeword.text = splitprocessor(seeword.text or "") - end -end - -function registers.store(rawdata) -- metadata, references, entries - local data = allocate(rawdata.metadata.name).entries - local references = rawdata.references - references.realpage = references.realpage or 0 -- just to be sure as it can be refered to - preprocessentries(rawdata) - data[#data+1] = rawdata - local label = references.label - if label and label ~= "" then tagged[label] = #data end - context(#data) -end - -function registers.enhance(name,n) - local r = tobesaved[name].entries[n] - if r then - r.references.realpage = texcount.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 r = tobesaved[name].entries[tag] - if r then - local rr = r.references - rr.lastrealpage = texcount.realpageno - rr.lastsection = sections.currentid() - if rawdata then - if rawdata.entries then - preprocessentries(rawdata) - end - for k,v in next, rawdata do - if not r[k] then - r[k] = v - else - local rk = r[k] - 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 - --- 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, kb = a.metadata.kind, 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, an 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, split = result[i], { } - local list = entry.list - if list then - for l=1,#list do - local ll = list[l] - local word, key = ll[1], 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) - sorters.sort(data.result,registers.compare) -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 pr, vr = prev.references, v.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 - split[nofsplit] = { tag = tag, data = done } - lasttag = tag - end - nofdone = nofdone + 1 - done[nofdone] = v - end - data.result = split -end - -function registers.analyzed(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 - --- todo take conversion from index - -function registers.userdata(index,name) - local data = references.internals[tonumber(index)] - data = data and data.userdata and data.userdata[name] - if data then - context(data) - end -end - --- todo: ownnumber - -local function pagerange(f_entry,t_entry,is_last,prefixspec,pagespec) - local fer, ter = f_entry.references, t_entry.references - context.registerpagerange( - f_entry.processors and f_entry.processors[2] or "", - fer.internal or 0, - fer.realpage or 0, - function() - helpers.prefixpage(f_entry,prefixspec,pagespec) - end, - ter.internal or 0, - ter.lastrealpage or ter.realpage or 0, - function() - if is_last then - helpers.prefixlastpage(t_entry,prefixspec,pagespec) -- swaps page and realpage keys - else - helpers.prefixpage (t_entry,prefixspec,pagespec) - end - end - ) -end - -local function pagenumber(entry,prefixspec,pagespec) - local er = entry.references - context.registeronepage( - entry.processors and entry.processors[2] or "", - er.internal or 0, - er.realpage or 0, - function() helpers.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 == variables.yes - local collapse_ranges = options.compress == variables.all - local result = data.result - context.startregisteroutput() - for i=1,#result do - -- ranges need checking ! - local sublist = result[i] - local done = { false, false, false, false } - local data = sublist.data - local d, n = 0, 0 - context.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 - report_registers("invalid see entry in register %a, reference %a",entry.metadata.name,list[1][1]) - end - end - end - while d < #data do - d = d + 1 - local entry = data[d] - local e = { false, false, false, false } - local metadata = entry.metadata - local kind = metadata.kind - local list = entry.list - for i=1,4 do -- max 4 - if list[i] then - e[i] = list[i][1] - end - if e[i] ~= done[i] then - if e[i] and e[i] ~= "" then - done[i] = e[i] - if n == i then - context.stopregisterentries() - context.startregisterentries(n) - else - while n > i do - n = n - 1 - context.stopregisterentries() - end - while n < i do - n = n + 1 - context.startregisterentries(n) - end - end - local internal = entry.references.internal or 0 - local seeparent = entry.references.seeparent or "" - local processor = entry.processors and entry.processors[1] or "" - if metadata then - context.registerentry(processor,internal,seeparent,function() helpers.title(e[i],metadata) end) - else -- ? - context.registerentry(processor,internal,seeindex,e[i]) - end - else - done[i] = false - end - end - end - if kind == 'entry' then - context.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 - context.stopregisterpages() - 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 - context.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 "" - context.registerseeword(i,n,processor,0,seeindex,seetext) - end - context.stopregisterseewords() - end - end - while n > 0 do - context.stopregisterentries() - n = n - 1 - end - context.stopregistersection() - end - context.stopregisteroutput() - -- for now, maybe at some point we will do a multipass or so - data.result = nil - data.metadata.sorted = false -end - -function registers.analyze(class,options) - context(registers.analyzed(class,options)) -end - -function registers.process(class,...) - if registers.analyzed(class,...) > 0 then - registers.flush(collected[class],...) - end -end - +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 texcount = tex.count
+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 mappings = sorters.mappings
+local entries = sorters.entries
+local replacements = sorters.replacements
+
+local processors = typesetters.processors
+local splitprocessor = processors.split
+
+local variables = interfaces.variables
+local context = context
+
+local matchingtilldepth, numberatdepth = sections.matchingtilldepth, sections.numberatdepth
+
+-- 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 = variables.all end
+ local data = documents.data
+ local numbers, depth = data.numbers, data.depth
+ local hash, result, nofresult, all, detail = { }, { }, 0, not names or names == "" or names == variables.all, nil
+ if not all then
+ for s in gmatch(names,"[^, ]+") do
+ hash[s] = true
+ end
+ end
+ if criterium == variables.all or criterium == variables.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 == variables.current then
+ for i=1,#collected do
+ local v = collected[i]
+ local sectionnumber = sections.collected[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 == variables.previous then
+ for i=1,#collected do
+ local v = collected[i]
+ local sectionnumber = sections.collected[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,variables.all,number,collected,prevmode)
+ else
+ return filtercollected(names,variables.current,number,collected,prevmode)
+ end
+ else -- sectionname, number
+ -- beware, this works ok for registers
+ 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
+
+local function initializer()
+ tobesaved = registers.tobesaved
+ collected = registers.collected
+ local internals = references.internals
+ for name, list in next, collected do
+ local entries = list.entries
+ 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
+ internals[internal] = entry
+ end
+ end
+ end
+ end
+end
+
+job.register('structures.registers.collected', tobesaved, initializer)
+
+local function allocate(class)
+ local d = tobesaved[class]
+ if not d then
+ d = {
+ metadata = {
+ language = 'en',
+ sorted = false,
+ class = class
+ },
+ entries = { },
+ }
+ tobesaved[class] = d
+ end
+ return d
+end
+
+registers.define = allocate
+
+local entrysplitter = lpeg.tsplitat('+') -- & obsolete in mkiv
+
+local tagged = { }
+
+local function preprocessentries(rawdata)
+ local entries = rawdata.entries
+ if entries then
+--~ table.print(rawdata)
+ local e, k = entries[1] or "", 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
+ entries = { }
+ for k=1,#et do
+ entries[k] = { et[k] or "", kt[k] or "" }
+ end
+ for k=#et,1,-1 do
+ if entries[k][1] ~= "" then
+ break
+ else
+ entries[k] = nil
+ end
+ end
+ rawdata.list = entries
+ if pageproc or entryproc then
+ rawdata.processors = { entryproc, pageproc }
+ end
+ rawdata.entries = nil
+ end
+ local seeword = rawdata.seeword
+ if seeword then
+ seeword.processor, seeword.text = splitprocessor(seeword.text or "")
+ end
+end
+
+function registers.store(rawdata) -- metadata, references, entries
+ local data = allocate(rawdata.metadata.name).entries
+ local references = rawdata.references
+ references.realpage = references.realpage or 0 -- just to be sure as it can be refered to
+ preprocessentries(rawdata)
+ data[#data+1] = rawdata
+ local label = references.label
+ if label and label ~= "" then tagged[label] = #data end
+ context(#data)
+end
+
+function registers.enhance(name,n)
+ local r = tobesaved[name].entries[n]
+ if r then
+ r.references.realpage = texcount.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 r = tobesaved[name].entries[tag]
+ if r then
+ local rr = r.references
+ rr.lastrealpage = texcount.realpageno
+ rr.lastsection = sections.currentid()
+ if rawdata then
+ if rawdata.entries then
+ preprocessentries(rawdata)
+ end
+ for k,v in next, rawdata do
+ if not r[k] then
+ r[k] = v
+ else
+ local rk = r[k]
+ 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
+
+-- 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, kb = a.metadata.kind, 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, an 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, split = result[i], { }
+ local list = entry.list
+ if list then
+ for l=1,#list do
+ local ll = list[l]
+ local word, key = ll[1], 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)
+ sorters.sort(data.result,registers.compare)
+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 pr, vr = prev.references, v.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
+ split[nofsplit] = { tag = tag, data = done }
+ lasttag = tag
+ end
+ nofdone = nofdone + 1
+ done[nofdone] = v
+ end
+ data.result = split
+end
+
+function registers.analyzed(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
+
+-- todo take conversion from index
+
+function registers.userdata(index,name)
+ local data = references.internals[tonumber(index)]
+ data = data and data.userdata and data.userdata[name]
+ if data then
+ context(data)
+ end
+end
+
+-- todo: ownnumber
+
+local function pagerange(f_entry,t_entry,is_last,prefixspec,pagespec)
+ local fer, ter = f_entry.references, t_entry.references
+ context.registerpagerange(
+ f_entry.processors and f_entry.processors[2] or "",
+ fer.internal or 0,
+ fer.realpage or 0,
+ function()
+ helpers.prefixpage(f_entry,prefixspec,pagespec)
+ end,
+ ter.internal or 0,
+ ter.lastrealpage or ter.realpage or 0,
+ function()
+ if is_last then
+ helpers.prefixlastpage(t_entry,prefixspec,pagespec) -- swaps page and realpage keys
+ else
+ helpers.prefixpage (t_entry,prefixspec,pagespec)
+ end
+ end
+ )
+end
+
+local function pagenumber(entry,prefixspec,pagespec)
+ local er = entry.references
+ context.registeronepage(
+ entry.processors and entry.processors[2] or "",
+ er.internal or 0,
+ er.realpage or 0,
+ function() helpers.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 == variables.yes
+ local collapse_ranges = options.compress == variables.all
+ local result = data.result
+ context.startregisteroutput()
+ for i=1,#result do
+ -- ranges need checking !
+ local sublist = result[i]
+ local done = { false, false, false, false }
+ local data = sublist.data
+ local d, n = 0, 0
+ context.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
+ report_registers("invalid see entry in register %a, reference %a",entry.metadata.name,list[1][1])
+ end
+ end
+ end
+ while d < #data do
+ d = d + 1
+ local entry = data[d]
+ local e = { false, false, false, false }
+ local metadata = entry.metadata
+ local kind = metadata.kind
+ local list = entry.list
+ for i=1,4 do -- max 4
+ if list[i] then
+ e[i] = list[i][1]
+ end
+ if e[i] ~= done[i] then
+ if e[i] and e[i] ~= "" then
+ done[i] = e[i]
+ if n == i then
+ context.stopregisterentries()
+ context.startregisterentries(n)
+ else
+ while n > i do
+ n = n - 1
+ context.stopregisterentries()
+ end
+ while n < i do
+ n = n + 1
+ context.startregisterentries(n)
+ end
+ end
+ local internal = entry.references.internal or 0
+ local seeparent = entry.references.seeparent or ""
+ local processor = entry.processors and entry.processors[1] or ""
+ if metadata then
+ context.registerentry(processor,internal,seeparent,function() helpers.title(e[i],metadata) end)
+ else -- ?
+ context.registerentry(processor,internal,seeindex,e[i])
+ end
+ else
+ done[i] = false
+ end
+ end
+ end
+ if kind == 'entry' then
+ context.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
+ context.stopregisterpages()
+ 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
+ context.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 ""
+ context.registerseeword(i,n,processor,0,seeindex,seetext)
+ end
+ context.stopregisterseewords()
+ end
+ end
+ while n > 0 do
+ context.stopregisterentries()
+ n = n - 1
+ end
+ context.stopregistersection()
+ end
+ context.stopregisteroutput()
+ -- for now, maybe at some point we will do a multipass or so
+ data.result = nil
+ data.metadata.sorted = false
+end
+
+function registers.analyze(class,options)
+ context(registers.analyzed(class,options))
+end
+
+function registers.process(class,...)
+ if registers.analyzed(class,...) > 0 then
+ registers.flush(collected[class],...)
+ end
+end
+
|