strc-reg.lmt /size: 60 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['strc-reg'] = {
2    version   = 1.001,
3    comment   = "companion to strc-reg.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local next, type, tonumber, rawget = next, type, tonumber, rawget
10local char, format, gmatch = string.char, string.format, string.gmatch
11local equal, concat, remove = table.are_equal, table.concat, table.remove
12local lpegmatch, lpegpatterns, P, C, Ct, Cs = lpeg.match, lpeg.patterns, lpeg.P, lpeg.C, lpeg.Ct, lpeg.Cs
13local allocate = utilities.storage.allocate
14
15local trace_registers    = false  trackers.register("structures.registers", function(v) trace_registers = v end)
16
17local report_registers     = logs.reporter("structure","registers")
18
19local structures           = structures
20local registers            = structures.registers
21local helpers              = structures.helpers
22local sections             = structures.sections
23local documents            = structures.documents
24local pages                = structures.pages
25local references           = structures.references
26
27local usedinternals        = references.usedinternals
28
29local mappings             = sorters.mappings
30local entries              = sorters.entries
31local replacements         = sorters.replacements
32
33local processors           = typesetters.processors
34local splitprocessor       = processors.split
35
36local texgetcount          = tex.getcount
37local texiscount           = tex.iscount
38
39local variables            = interfaces.variables
40local v_forward            = variables.forward
41local v_all                = variables.all
42local v_no                 = variables.no
43local v_yes                = variables.yes
44local v_packed             = variables.packed
45local v_current            = variables.current
46local v_previous           = variables.previous
47local v_first              = variables.first
48local v_last               = variables.last
49local v_text               = variables.text
50local v_section            = variables.section
51
52local context              = context
53local ctx_latelua          = context.latelua
54
55local implement            = interfaces.implement
56
57local matchingtilldepth    = sections.matchingtilldepth
58local numberatdepth        = sections.numberatdepth
59local currentlevel         = sections.currentlevel
60local currentid            = sections.currentid
61
62local touserdata           = helpers.touserdata
63
64local internalreferences   = references.internals
65local setinternalreference = references.setinternalreference
66
67local setmetatableindex    = table.setmetatableindex
68
69local absmaxlevel          = 5 -- \c_strc_registers_maxlevel
70
71local c_realpageno         = texiscount("realpageno")
72local c_locationcount      = texiscount("locationcount")
73
74local h_prefixpage              = helpers.prefixpage
75local h_prefixlastpage          = helpers.prefixlastpage
76local h_title                   = helpers.title
77local h_prefix                  = helpers.prefix
78
79local ctx_startregisteroutput   = context.startregisteroutput
80local ctx_stopregisteroutput    = context.stopregisteroutput
81local ctx_startregistersection  = context.startregistersection
82local ctx_stopregistersection   = context.stopregistersection
83local ctx_startregisterentries  = context.startregisterentries
84local ctx_stopregisterentries   = context.stopregisterentries
85local ctx_startregisterentry    = context.startregisterentry
86local ctx_stopregisterentry     = context.stopregisterentry
87local ctx_startregisterpages    = context.startregisterpages
88local ctx_stopregisterpages     = context.stopregisterpages
89local ctx_startregisterseewords = context.startregisterseewords
90local ctx_stopregisterseewords  = context.stopregisterseewords
91
92local ctx_registerentry         = context.registerentry
93local ctx_registerseeword       = context.registerseeword
94local ctx_registerpagerange     = context.registerpagerange
95local ctx_registeronepage       = context.registeronepage
96local ctx_registersection       = context.registersection
97local ctx_registerpacked        = context.registerpacked
98
99-- possible export, but ugly code (overloads)
100--
101-- local output, section, entries, nofentries, pages, words, rawtext
102--
103-- h_title = function(a,b) rawtext = a end
104--
105-- local function ctx_startregisteroutput()
106--     output     = { }
107--     section    = nil
108--     entries    = nil
109--     nofentries = nil
110--     pages      = nil
111--     words      = nil
112--     rawtext    = nil
113-- end
114-- local function ctx_stopregisteroutput()
115--     inspect(output)
116--     output     = nil
117--     section    = nil
118--     entries    = nil
119--     nofentries = nil
120--     pages      = nil
121--     words      = nil
122--     rawtext    = nil
123-- end
124-- local function ctx_startregistersection(tag)
125--     section = { }
126--     output[#output+1] = {
127--         section = section,
128--         tag     = tag,
129--     }
130-- end
131-- local function ctx_stopregistersection()
132-- end
133-- local function ctx_startregisterentries(n)
134--     entries = { }
135--     nofentries = 0
136--     section[#section+1] = entries
137-- end
138-- local function ctx_stopregisterentries()
139-- end
140-- local function ctx_startregisterentry(n) -- or subentries (nested?)
141--     nofentries = nofentries + 1
142--     entry = { }
143--     entries[nofentries] = entry
144-- end
145-- local function ctx_stopregisterentry()
146--     nofentries = nofentries - 1
147--     entry = entries[nofentries]
148-- end
149-- local function ctx_startregisterpages()
150--     pages = { }
151--     entry.pages = pages
152-- end
153-- local function ctx_stopregisterpages()
154-- end
155-- local function ctx_startregisterseewords()
156--     words = { }
157--     entry.words = words
158-- end
159-- local function ctx_stopregisterseewords()
160-- end
161-- local function ctx_registerentry(processor,internal,seeparent,text)
162--     text()
163--     entry.text = {
164--         processor = processor,
165--         internal  = internal,
166--         seeparent = seeparent,
167--         text      = rawtext,
168--     }
169-- end
170-- local function ctx_registerseeword(i,n,processor,internal,seeindex,seetext)
171--     seetext()
172--     entry.words[i] = {
173--         processor = processor,
174--         internal  = internal,
175--         seeparent = seeparent,
176--         seetext   = rawtext,
177--     }
178-- end
179-- local function ctx_registerpagerange(fprocessor,finternal,frealpage,lprocessor,linternal,lrealpage)
180--     pages[#pages+1] = {
181--         first = {
182--             processor = fprocessor,
183--             internal  = finternal,
184--             realpage  = frealpage,
185--         },
186--         last = {
187--             processor = lprocessor,
188--             internal  = linternal,
189--             realpage  = lrealpage,
190--         },
191--     }
192-- end
193-- local function ctx_registeronepage(processor,internal,realpage)
194--     pages[#pages+1] = {
195--         processor = processor,
196--         internal  = internal,
197--         realpage  = realpage,
198--     }
199-- end
200
201-- some day we will share registers and lists (although there are some conceptual
202-- differences in the application of keywords)
203
204local function filtercollected(names,criterium,number,collected,prevmode)
205    if not criterium or criterium == "" then
206        criterium = v_all
207    end
208    local data      = documents.data
209    local numbers   = data.numbers
210    local depth     = data.depth
211    local hash      = { }
212    local result    = { }
213    local nofresult = 0
214    local all       = not names or names == "" or names == v_all
215    local detail    = nil
216    if not all then
217        for s in gmatch(names,"[^, ]+") do
218            hash[s] = true
219        end
220    end
221    if criterium == v_all or criterium == v_text then
222        for i=1,#collected do
223            local v = collected[i]
224            if all then
225                nofresult = nofresult + 1
226                result[nofresult] = v
227            else
228                local vmn = v.metadata and v.metadata.name
229                if hash[vmn] then
230                    nofresult = nofresult + 1
231                    result[nofresult] = v
232                end
233            end
234        end
235    elseif criterium == v_current then
236        local collectedsections = sections.collected
237        for i=1,#collected do
238            local v = collected[i]
239            local sectionnumber = collectedsections[v.references.section]
240            if sectionnumber then
241                local cnumbers = sectionnumber.numbers
242                if prevmode then
243                    if (all or hash[v.metadata.name]) and #cnumbers >= depth then -- is the = ok for lists as well?
244                        local ok = true
245                        for d=1,depth do
246                            if not (cnumbers[d] == numbers[d]) then -- no zero test
247                                ok = false
248                                break
249                            end
250                        end
251                        if ok then
252                            nofresult = nofresult + 1
253                            result[nofresult] = v
254                        end
255                    end
256                else
257                    if (all or hash[v.metadata.name]) and #cnumbers > depth then
258                        local ok = true
259                        for d=1,depth do
260                            local cnd = cnumbers[d]
261                            if not (cnd == 0 or cnd == numbers[d]) then
262                                ok = false
263                                break
264                            end
265                        end
266                        if ok then
267                            nofresult = nofresult + 1
268                            result[nofresult] = v
269                        end
270                    end
271                end
272            end
273        end
274    elseif criterium == v_previous then
275        local collectedsections = sections.collected
276        for i=1,#collected do
277            local v = collected[i]
278            local sectionnumber = collectedsections[v.references.section]
279            if sectionnumber then
280                local cnumbers = sectionnumber.numbers
281                if (all or hash[v.metadata.name]) and #cnumbers >= depth then
282                    local ok = true
283                    if prevmode then
284                        for d=1,depth do
285                            if not (cnumbers[d] == numbers[d]) then
286                                ok = false
287                                break
288                            end
289                        end
290                    else
291                        for d=1,depth do
292                            local cnd = cnumbers[d]
293                            if not (cnd == 0 or cnd == numbers[d]) then
294                                ok = false
295                                break
296                            end
297                        end
298                    end
299                    if ok then
300                        nofresult = nofresult + 1
301                        result[nofresult] = v
302                    end
303                end
304            end
305        end
306    elseif criterium == variables["local"] then
307        if sections.autodepth(data.numbers) == 0 then
308            return filtercollected(names,v_all,number,collected,prevmode)
309        else
310            return filtercollected(names,v_current,number,collected,prevmode)
311        end
312    else -- sectionname, number
313        -- beware, this works ok for registers
314        -- to be redone with reference instead
315        local depth = sections.getlevel(criterium)
316        local number = tonumber(number) or numberatdepth(depth) or 0
317        if trace_registers then
318            detail = format("depth: %s, number: %s, numbers: %s, startset: %s",depth,number,concat(sections.numbers(),".",1,depth),#collected)
319        end
320        if number > 0 then
321            for i=1,#collected do
322                local v = collected[i]
323                local r = v.references
324                if r then
325                    local sectionnumber = sections.collected[r.section]
326                    if sectionnumber then
327                        local metadata = v.metadata
328                        local cnumbers = sectionnumber.numbers
329                        if cnumbers then
330                            if (all or hash[metadata.name or false]) and #cnumbers >= depth and matchingtilldepth(depth,cnumbers) then
331                                nofresult = nofresult + 1
332                                result[nofresult] = v
333                            end
334                        end
335                    end
336                end
337            end
338        end
339    end
340    if trace_registers then
341        if detail then
342            report_registers("criterium %a, detail %a, found %a",criterium,detail,#result)
343        else
344            report_registers("criterium %a, detail %a, found %a",criterium,nil,#result)
345        end
346    end
347    return result
348end
349
350local tobesaved           = allocate()
351local collected           = allocate()
352
353registers.collected       = collected
354registers.tobesaved       = tobesaved
355registers.filtercollected = filtercollected
356
357-- we follow a different strategy than by lists, where we have a global
358-- result table; we might do that here as well but since sorting code is
359-- older we delay that decision
360
361-- maybe store the specification in the format (although we predefine only
362-- saved registers)
363
364local function checker(t,k)
365    local v = {
366        metadata = {
367            language = 'en',
368            sorted   = false,
369            class    = class,
370        },
371        entries  = { },
372    }
373    t[k] = v
374    return v
375end
376
377local function initializer()
378    tobesaved = registers.tobesaved
379    collected = registers.collected
380    setmetatableindex(tobesaved,checker)
381    setmetatableindex(collected,checker)
382    local usedinternals = references.usedinternals
383    for name, list in next, collected do
384        local entries = list.entries
385        if not list.metadata.notsaved then
386            for e=1,#entries do
387                local entry = entries[e]
388                local r = entry.references
389                if r then
390                    local internal = r and r.internal
391                    if internal then
392                        internalreferences[internal] = entry
393                        usedinternals[internal] = r.used
394                    end
395                end
396            end
397        end
398    end
399 -- references.sortedinternals = sortedkeys(internalreferences) -- todo: when we need it more than once
400end
401
402local function finalizer()
403    local flaginternals = references.flaginternals
404    local usedviews     = references.usedviews
405    for k, v in next, tobesaved do
406        local entries = v.entries
407        if entries then
408            for i=1,#entries do
409                local r = entries[i].references
410                if r then
411                    local i = r.internal
412                    local f = flaginternals[i]
413                    if f then
414                        r.used = usedviews[i] or true
415                    end
416                end
417            end
418        end
419    end
420end
421
422job.register('structures.registers.collected', tobesaved, initializer, finalizer)
423
424setmetatableindex(tobesaved,checker)
425setmetatableindex(collected,checker)
426
427local function defineregister(class,method)
428    local d = tobesaved[class]
429    if method == v_forward then
430        d.metadata.notsaved = true
431    end
432end
433
434registers.define           = defineregister -- 4 times is somewhat over the top but we want consistency
435registers.setmethod        = defineregister -- and we might have a difference some day
436
437implement {
438    name      = "defineregister",
439    actions   = defineregister,
440    arguments = "2 strings",
441}
442
443implement {
444    name      = "setregistermethod",
445    actions   = defineregister, -- duplicate use
446    arguments = "2 strings",
447}
448
449local p_s = P("+") -- & obsolete in mkiv
450local p_e = P("&") * (1-P(";"))^0 * P(";")
451local p_r = C((p_e + (1-p_s))^0)
452
453local p_t = Cs ( (
454    lpegpatterns.nestedbraces
455--+ lpegpatterns.nestedbrackets
456  + lpegpatterns.nestedparents
457  + P("$") * (1-P("$"))^1 * P("$")
458  + (1-p_s)
459  )^1)
460
461local entrysplitter_xml = Ct(p_r * (p_s * p_r)^0) -- bah
462----- entrysplitter_tex = lpeg.tsplitat('+')
463local entrysplitter_tex = Ct((p_t * p_s^-1)^0)
464
465local tagged = { }
466
467-- this whole splitting is an inheritance of mkii
468
469local function preprocessentries(rawdata)
470    local entries = rawdata.entries
471    if entries then
472        local processors = rawdata.processors
473        local et         = entries.entries
474        local kt         = entries.keys
475        local pt         = entries.processors
476        local entryproc  = processors and processors.entry
477        local pageproc   = processors and processors.page
478        local coding     = rawdata.metadata.coding
479        if entryproc == "" then
480            entryproc = nil
481        end
482        if pageproc == "" then
483            pageproc = nil
484        end
485        if not et then
486            local p, e = splitprocessor(entries.entry or "")
487            if p then
488                entryproc = p
489            end
490            et = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,e)
491        end
492        if not kt then
493            local p, k = splitprocessor(entries.key or "")
494            if p then
495                pageproc = p
496            end
497            kt = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,k)
498        end
499        if not pt then
500            pt = { }
501        end
502        --
503        entries = { }
504        local ok = false
505        for k=#et,1,-1 do
506            local etk = et[k]
507            local ktk = kt[k]
508            local ptk = pt[k]
509            if not ok and etk == "" then
510                entries[k] = nil
511            else
512                if not etk then
513                    etk = ""
514                end
515                -- we save some space by pruning
516                if ptk == "" then
517                    ptk = nil
518                end
519                -- we save even more space by pruning
520                if ktk == "" then
521                    if ptk then
522                        ktk = false
523                    else
524                        ktk = nil
525                    end
526                end
527                entries[k] = { etk, ktk, ptk }
528                ok = true
529            end
530        end
531        rawdata.list = entries
532        if pageproc or entryproc then
533            rawdata.processors = { entryproc, pageproc } -- old way: indexed .. will be keys
534        end
535        rawdata.entries = nil
536    end
537    local seeword = rawdata.seeword
538    if seeword then
539        local text = seeword.text or ""
540        local sp, st = splitprocessor(text)
541        seeword.text = text
542     -- seeword.list = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,st)
543        if sp then
544            seeword.processor = sp
545        end
546    end
547end
548
549local function storeregister(rawdata) -- metadata, references, entries
550    local references = rawdata.references
551    local metadata   = rawdata.metadata
552    -- checking
553    if not metadata then
554        metadata = { }
555        rawdata.metadata = metadata
556    end
557    --
558    if not metadata.kind then
559        metadata.kind = "entry"
560    end
561    --
562    --
563    if not metadata.catcodes then
564        metadata.catcodes = tex.catcodetable -- get
565    end
566    --
567    local name     = metadata.name
568    local notsaved = tobesaved[name].metadata.notsaved
569    --
570    if not references then
571        references         = { }
572        rawdata.references = references
573    end
574    --
575    local internal = references.internal
576    if not internal then
577        internal = texgetcount(c_locationcount) -- we assume that it has been set
578        references.internal = internal
579    end
580    --
581    if notsaved then
582        usedinternals[internal] = references.used -- todo view (we assume that forward references index entries are used)
583    end
584    --
585    if not references.realpage then
586        references.realpage = 0 -- just to be sure as it can be refered to
587    end
588    --
589    local userdata = rawdata.userdata
590    if userdata then
591        rawdata.userdata = touserdata(userdata)
592    end
593    --
594    references.block     = structures.sections.currentblock()
595    references.structure = resolvers.jobs.currentstructure()
596    references.section   = currentid()
597    metadata.level       = currentlevel()
598    --
599    local data     = notsaved and collected[name] or tobesaved[name]
600    local entries  = data.entries
601    --
602    internalreferences[internal] = rawdata
603    preprocessentries(rawdata)
604    entries[#entries+1] = rawdata
605    local label = references.label
606    if label and label ~= "" then
607        tagged[label] = #entries
608    else
609        references.label = nil
610    end
611    return #entries
612end
613
614local function enhanceregister(specification)
615    local name  = specification.name
616    local n     = specification.n
617    local saved = tobesaved[name]
618    local data  = saved.metadata.notsaved and collected[name] or saved
619    local entry = data.entries[n]
620    if entry then
621        entry.references.realpage = texgetcount(c_realpageno)
622    end
623end
624
625local function extendregister(rawdata) -- this is probably not okay in mkiv
626    local references = rawdata.references
627    local metadata   = rawdata.metadata
628    if references and metadata then
629        local name = rawdata.metadata.name
630        local tag  = references.label
631        if type(tag) == "string" then
632            tag = tagged[tag]
633        end
634        if tag then
635            local data = tobesaved[name].metadata.notsaved and collected[name] or tobesaved[name]
636            local entry = data.entries[tag]
637            if entry then
638                references = entry.references
639                references.lastrealpage = texgetcount(c_realpageno)
640                references.lastsection = currentid()
641                if rawdata then
642                    local userdata = rawdata.userdata
643                    if userdata and userdata ~= "" then
644                        entry.userdata = table.merged(entry.userdata or { },touserdata(userdata))
645                    end
646                    if rawdata.entries then
647                        preprocessentries(rawdata)
648                        if next(rawdata.list) then
649                            entry.list = rawdata.list
650                        end
651                    end
652                    if metadata then
653                        entry.metadata = table.merged(entry.metadata or { },metadata)
654                    end
655               end
656           end
657        end
658    end
659end
660
661registers.store   = storeregister
662registers.enhance = enhanceregister
663registers.extend  = extendregister
664
665function registers.get(tag,n)
666    local list = tobesaved[tag]
667    return list and list.entries[n]
668end
669
670implement {
671    name      = "enhanceregister",
672    arguments = { "string", "integer" },
673    actions   = function(name,n)
674        enhanceregister { name = name, n = n } -- todo: move to scanner
675    end,
676}
677
678implement {
679    name      = "deferredenhanceregister",
680    arguments = { "string", "integer" },
681    protected = true,
682    actions   = function(name,n)
683        ctx_latelua { action = enhanceregister, name = name, n = n }
684    end,
685}
686
687implement {
688    name      = "extendregister",
689    actions   = extendregister,
690    arguments = {
691        {
692            { "metadata", {
693                    { "name" },
694                    { "coding" },
695                    { "catcodes", "integer" },
696                    { "own" },
697                }
698            },
699            { "entries", {
700                    { "entries", "list" },
701                    { "keys", "list" },
702                    { "processors", "list" },
703                    { "entry" },
704                    { "key" },
705                    { "processor" },
706                }
707            },
708            { "references", {
709                    { "label" },
710                }
711            },
712            { "userdata" },
713        }
714    }
715}
716
717implement {
718    name      = "extendregisterlate",
719    arguments = "2 strings",
720    actions   = function(name,label)
721        context.latelua(function()
722            extendregister {
723                metadata   = { name =  name },
724                references = { abel =  label },
725            }
726        end)
727    end,
728}
729
730-- commands.extendregister = extendregister
731
732implement {
733    name      = "storeregister",
734 -- actions   = function(rawdata)
735 --     local nofentries = storeregister(rawdata)
736 --     setinternalreference { internal = rawdata.references.internal }
737 --     context(nofentries)
738 -- end,
739    actions   = { storeregister, context },
740    arguments = {
741        {
742            { "metadata", {
743                    { "kind" },
744                    { "name" },
745                    { "coding" },
746                    { "level", "integer" },
747                    { "catcodes", "integer" },
748                    { "own" },
749                    { "xmlroot" },
750                    { "xmlsetup" },
751                }
752            },
753            { "entries", {
754                    { "entries", "list" },
755                    { "keys", "list" },
756                    { "processors", "list" },
757                    { "entry" },
758                    { "key" },
759                    { "processor" },
760                }
761            },
762            { "references", {
763                    { "internal", "integer" },
764                    { "section", "integer" },
765                    { "view" },
766                    { "label" },
767                }
768            },
769            { "seeword", {
770                    { "text" },
771                }
772            },
773            { "processors", {
774                    { "entry" },
775                    { "key" },
776                    { "page" },
777                }
778            },
779            { "userdata" },
780        }
781    }
782}
783
784-- sorting and rendering
785
786local compare = sorters.comparers.basic
787
788function registers.compare(a,b)
789    local result = compare(a,b)
790    if result ~= 0 then
791        return result
792    else
793        local ka = a.metadata.kind
794        local kb = b.metadata.kind
795        if ka == kb then
796            local ra = a.references
797            local rb = b.references
798            local pa = ra.realpage
799            local pb = rb.realpage
800            if not pa or not pb then
801                return 0
802            elseif pa < pb then
803                return -1
804            elseif pa > pb then
805                return  1
806            else
807                -- new, we need to pick the right one of multiple and
808                -- we want to prevent oscillation in the tuc file so:
809                local ia = ra.internal
810                local ib = rb.internal
811                if not ia or not ib then
812                    return 0
813                elseif ia < ib then
814                    return -1
815                elseif ia > ib then
816                    return  1
817                else
818                    return 0
819                end
820            end
821        elseif ka == "see" then
822            return 1
823        elseif kb == "see" then
824            return -1
825        end
826    end
827    return 0
828end
829
830function registers.filter(data,options)
831    data.result = registers.filtercollected(nil,options.criterium,options.number,data.entries,true)
832end
833
834local seeindex = 0
835
836-- meerdere loops, seewords, dan words, anders seewords
837
838-- todo: split seeword
839
840local function crosslinkseewords(result,check) -- all words
841    -- collect all seewords
842    local seewords = { }
843    for i=1,#result do
844        local data = result[i]
845        local seeword = data.seeword
846        if seeword then
847            local seetext = seeword.text
848            if seetext and not seewords[seetext] then
849                seeindex = seeindex + 1
850                seewords[seetext] = seeindex
851                if trace_registers then
852                    report_registers("see word %03i: %s",seeindex,seetext)
853                end
854            end
855        end
856    end
857
858    -- mark seeparents
859
860 -- local seeparents = { }
861 -- for i=1,#result do
862 --     local data = result[i]
863 --     local word = data.list[1]
864 --     local okay = word and word[1]
865 --     if okay then
866 --         local seeindex = seewords[okay]
867 --         if seeindex then
868 --             seeparents[okay] = data
869 --             data.references.seeparent = seeindex
870 --             if trace_registers then
871 --                 report_registers("see parent %03i: %s",seeindex,okay)
872 --             end
873 --         end
874 --     end
875 -- end
876
877    local entries    = { }
878    local keywords   = { }
879    local seeparents = { }
880    for i=1,#result do
881        local data = result[i]
882        local word = data.list
883        local size = #word
884        if data.seeword then
885            -- beware: a seeword has an extra entry for sorting purposes
886            size = size - 1
887        end
888        for i=1,size do
889            local w = word[i]
890            local e = w[1]
891            local k = w[2] or e
892            entries [i] = e
893            keywords[i] = k
894        end
895        -- first try the keys
896        local okay, seeindex
897        for n=size,1,-1 do
898            okay = concat(keywords,"+",1,n)
899            seeindex = seewords[okay]
900            -- first try the entries
901            if seeindex then
902                break
903            end
904            okay = concat(entries,"+",1,n)
905            seeindex = seewords[okay]
906            if seeindex then
907                break
908            end
909        end
910        if seeindex then
911            seeparents[okay] = data
912            data.references.seeparent = seeindex
913            if trace_registers then
914                report_registers("see parent %03i: %s",seeindex,okay)
915            end
916        end
917    end
918
919    -- mark seewords and extend sort list
920    for i=1,#result do
921        local data = result[i]
922        local seeword = data.seeword
923        if seeword then
924            local text = seeword.text
925            if text then
926                local seeparent = seeparents[text]
927                if seeparent then
928                    local seeindex = seewords[text]
929                    data.references.seeindex = seeindex
930                    if trace_registers then
931                        report_registers("see crosslink %03i: %s",seeindex,text)
932                    end
933                    seeword.valid = true
934                elseif check then
935                    report_registers("invalid crosslink : %s, %s",text,"ignored")
936                    seeword.valid = false
937                else
938                    report_registers("invalid crosslink : %s, %s",text,"passed")
939                    seeword.valid = true
940                end
941            end
942        end
943    end
944end
945
946local function removeemptyentries(result)
947    local i, n, m = 1, #result, 0
948    while i <= n do
949        local entry = result[i]
950        if #entry.list == 0 or #entry.split == 0 then
951            remove(result,i)
952            n = n - 1
953            m = m + 1
954        else
955            i = i + 1
956        end
957    end
958    if m > 0 then
959        report_registers("%s empty entries removed in register",m)
960    end
961end
962
963function registers.prepare(data,options)
964    -- data has 'list' table
965    local strip    = sorters.strip
966    local splitter = sorters.splitters.utf
967    local result   = data.result
968    if result then
969        local seeprefix = char(0)
970        for i=1, #result do
971            local entry = result[i]
972            local split = { }
973            local list  = entry.list
974            if list then
975                if entry.seeword then
976                    -- we can have multiple seewords, only two levels supported
977                    list[#list+1] = { seeprefix .. strip(entry.seeword.text) }
978                end
979                for l=1,#list do
980                    local ll   = list[l]
981                    local word = ll[1]
982                    local key  = ll[2]
983                    if not key or key == "" then
984                        key = word
985                    end
986                    split[l] = splitter(strip(key))
987                end
988            end
989            entry.split = split
990        end
991        removeemptyentries(result)
992        crosslinkseewords(result,options.check ~= v_no)
993    end
994end
995
996function registers.sort(data,options)
997 -- if options.pagenumber == false then
998 --     sorters.sort(data.result,compare)
999 -- else
1000        sorters.sort(data.result,registers.compare)
1001 -- end
1002end
1003
1004function registers.unique(data,options)
1005    local result     = { }
1006    local nofresult  = 0
1007    local prev       = nil
1008    local dataresult = data.result
1009    local bysection  = options.pagemethod == v_section -- normally page
1010    for k=1,#dataresult do
1011        local v = dataresult[k]
1012        if prev then
1013            local vr = v.references
1014            local pr = prev.references
1015            if not equal(prev.list,v.list) then
1016                -- ok
1017            elseif bysection and vr.section == pr.section then
1018                v = nil
1019                -- ok
1020            elseif pr.realpage ~= vr.realpage then
1021                -- ok
1022            else
1023                local pl = pr.lastrealpage
1024                local vl = vr.lastrealpage
1025                if pl or vl then
1026                    if not vl then
1027                        -- ok
1028                    elseif not pl then
1029                        -- ok
1030                    elseif pl ~= vl then
1031                        -- ok
1032                    else
1033                        v = nil
1034                    end
1035                else
1036                    v = nil
1037                end
1038            end
1039        end
1040        if v then
1041            nofresult = nofresult + 1
1042            result[nofresult] = v
1043            prev = v
1044        end
1045    end
1046    data.result = result
1047end
1048
1049function registers.finalize(data,options) -- maps character to index (order)
1050    local result = data.result
1051    data.metadata.nofsorted = #result
1052    local split    = { }
1053    local nofsplit = 0
1054    local lasttag  = nil
1055    local done     = nil
1056    local nofdone  = 0
1057    local firstofsplit = sorters.firstofsplit
1058    for k=1,#result do
1059        local v = result[k]
1060        local entry, tag = firstofsplit(v)
1061        if tag ~= lasttag then
1062            if trace_registers then
1063                report_registers("splitting at %a",tag)
1064            end
1065            done     = { }
1066            nofdone  = 0
1067            nofsplit = nofsplit + 1
1068            lasttag  = tag
1069            split[nofsplit] = { tag = tag, data = done }
1070        end
1071        nofdone = nofdone + 1
1072        done[nofdone] = v
1073    end
1074    data.result = split
1075end
1076
1077local used = { }
1078
1079function registers.use(tag,filename,class,prefix)
1080    used[tag] = {
1081        class    = class,
1082        filename = filename,
1083        data     = job.loadother(filename),
1084        prefix   = prefix or class,
1085        label    = prefix or class,
1086    }
1087end
1088
1089implement {
1090    name      = "useregister",
1091    arguments = "4 strings",
1092    actions   = registers.use,
1093}
1094
1095-- function registers.use(tag,specification)
1096--     local class    = specification.class
1097--     local filename = specification.filename
1098--     local prefix   = specification.prefix or class
1099--     local label    = specification.label  or prefix
1100--     if class and filename then
1101--         used[tag] = {
1102--             class      = class,
1103--             filename   = filename,
1104--             data       = job.loadother(filename),
1105--             prefix     = prefix,
1106--             label      = label,
1107--         }
1108--     end
1109-- end
1110
1111-- implement {
1112--     name      = "useregister",
1113--     actions   = registers.use,
1114--     arguments = {
1115--         "string",
1116--         {
1117--             { "filename" },
1118--             { "class" },
1119--             { "prefix" },
1120--             { "label" },
1121--         },
1122--     }
1123-- }
1124
1125implement {
1126    name      = "registerlabel",
1127    arguments = "string",
1128    actions   = function(tag)
1129        local u = used[tag]
1130        if u then
1131            context(u.label)
1132        end
1133    end
1134}
1135
1136function registers.tweak(data,options)
1137    -- Just in case it's needed.
1138end
1139
1140local function analyzeregister(class,options)
1141    local data = rawget(collected,class)
1142    if not data then
1143        local list       = utilities.parsers.settings_to_array(class)
1144        local entries    = { }
1145        local nofentries = 0
1146        local multiple   = false
1147        for i=1,#list do
1148            if used[list[i]] then
1149                multiple = true
1150                break
1151            end
1152        end
1153        for i=1,#list do
1154            local l = list[i]
1155            local u = used[l]
1156            local d = collected[l]
1157            if u then
1158                local collected = u.data.structures.registers.collected
1159                local class     = u.class
1160                if collected and class then
1161                    d = collected[class]
1162                end
1163            end
1164            if d then
1165                local e = d.entries
1166--                 local u = u and { u.prefix } or nil
1167local u = multiple and { string.formatters["%03i"](i) } or nil -- maybe prefix but then how about main
1168                for i=1,#e do
1169                    -- see has no pagedata
1170                    local ei = e[i]
1171                    local external = ei.external
1172if multiple and ei.metadata.kind == "see" and external then
1173    -- too messy, could be an option but useless
1174else
1175
1176                    nofentries = nofentries + 1
1177                    entries[nofentries] = ei
1178                    if u then
1179                        local eil = ei.list
1180                        eil[#eil+1] = u
1181if external then
1182                        ei.external = l -- this is the (current) abstract tag, used for prefix
1183end
1184                    end
1185end
1186                end
1187                if not metadata then
1188                    metadata = d.metadata
1189                end
1190            end
1191        end
1192        data = {
1193            metadata = metadata or { },
1194            multiple = multiple,
1195            entries  = entries,
1196        }
1197        collected[class] = data
1198        options.multiple = multiple
1199    end
1200    if data and data.entries then
1201        options = options or { }
1202        sorters.setlanguage(options.language,options.method,options.numberorder)
1203        registers.filter(data,options)  -- filter entries into results (criteria)
1204        registers.prepare(data,options) -- adds split table parallel to list table
1205        registers.sort(data,options)    -- sorts results
1206        registers.unique(data,options)  -- get rid of duplicates
1207        registers.tweak(data,options)   -- plugin
1208        registers.finalize(data,options)-- split result in ranges
1209        data.metadata.sorted = true
1210        return data.metadata.nofsorted or 0
1211    else
1212        return 0
1213    end
1214end
1215
1216registers.analyze = analyzeregister
1217
1218implement {
1219    name      = "analyzeregister",
1220    actions   = { analyzeregister, context },
1221    arguments = {
1222        "string",
1223        {
1224            { "language" },
1225            { "method" },
1226            { "numberorder" },
1227            { "compress" },
1228            { "criterium" },
1229            { "pagenumber", "boolean" },
1230        }
1231    }
1232}
1233
1234-- todo take conversion from index
1235
1236function registers.userdata(index,name)
1237    local data = references.internals[tonumber(index)]
1238    return data and data.userdata and data.userdata[name] or nil
1239end
1240
1241implement {
1242    name      = "registeruserdata",
1243    actions   = { registers.userdata, context },
1244    arguments = { "integer", "string" }
1245}
1246
1247-- todo: ownnumber
1248
1249local function pagerange(f_entry,t_entry,is_last,prefixspec,pagespec)
1250    local fer = f_entry.references
1251    local ter = t_entry.references
1252    ctx_registerpagerange(
1253        f_entry.metadata.name or "",
1254        f_entry.processors and f_entry.processors[2] or "",
1255        f_entry.external or fer.internal or 0,
1256        fer.realpage or 0,
1257        function()
1258            h_prefixpage(f_entry,prefixspec,pagespec)
1259        end,
1260        t_entry.external or ter.internal or 0,
1261        ter.lastrealpage or ter.realpage or 0,
1262        function()
1263            if is_last then
1264                h_prefixlastpage(t_entry,prefixspec,pagespec) -- swaps page and realpage keys
1265            else
1266                h_prefixpage    (t_entry,prefixspec,pagespec)
1267            end
1268        end
1269    )
1270end
1271
1272local function pagenumber(entry,prefixspec,pagespec)
1273    local er = entry.references
1274    ctx_registeronepage(
1275        entry.metadata.name or "",
1276        entry.processors and entry.processors[2] or "",
1277        entry.external or er.internal or 0,
1278        er.realpage or 0,
1279        function() h_prefixpage(entry,prefixspec,pagespec) end
1280    )
1281end
1282
1283local function packed(f_entry,t_entry)
1284    local fer = f_entry.references
1285    local ter = t_entry.references
1286-- todo: make sure we don't end up here with external
1287    ctx_registerpacked(
1288        f_entry.external or fer.internal or 0,
1289        t_entry.external or ter.internal or 0
1290    )
1291end
1292
1293local function collapsedpage(pages)
1294    for i=2,#pages do
1295        local first             = pages[i-1]
1296        local second            = pages[i]
1297        local first_first       = first[1]
1298        local first_last        = first[2]
1299        local second_first      = second[1]
1300        local second_last       = second[2]
1301        local first_last_pn     = first_last  .references.realpage
1302        local second_first_pn   = second_first.references.realpage
1303        local second_last_pn    = second_last .references.realpage
1304        local first_last_last   = first_last  .references.lastrealpage
1305        local second_first_last = second_first.references.lastrealpage
1306        if first_last_last then
1307            first_last_pn = first_last_last
1308            if first_last_pn == second_first_pn then
1309                -- 2-3, 3-9 -> 2-9
1310                pages[i-1] = { first_first, second_last }
1311                remove(pages,i)
1312                return true
1313            elseif second_first == second_last and second_first_pn <= first_last_pn then
1314                -- 2=8, 5 -> 12=8
1315                remove(pages,i)
1316                return true
1317            elseif second_first == second_last and second_first_pn > first_last_pn then
1318                -- 2=8, 9 -> 2-9
1319                pages[i-1] = { first_first, second_last }
1320                remove(pages,i)
1321                return true
1322            elseif second_last_pn < first_last_pn then
1323                -- 2=8, 3-4 -> 2=8
1324                remove(pages,i)
1325                return true
1326            elseif first_last_pn < second_last_pn then
1327                -- 2=8, 3-9 -> 2-9
1328                pages[i-1] = { first_first, second_last }
1329                remove(pages,i)
1330                return true
1331            elseif first_last_pn + 1 == second_first_pn and second_last_pn > first_last_pn then
1332                -- 2=8, 9-11 -> 2-11
1333                pages[i-1] = { first_first, second_last }
1334                remove(pages,i)
1335                return true
1336            elseif second_first.references.lastrealpage then
1337                -- 2=8, 9=11 -> 2-11
1338                pages[i-1] = { first_first, second_last }
1339                remove(pages,i)
1340                return true
1341            end
1342        elseif second_first_last then
1343            second_first_pn = second_first_last
1344            if first_last_pn == second_first_pn then
1345                -- 2-4, 5=9 -> 2-9
1346                pages[i-1] = { first_first, second_last }
1347                remove(pages,i)
1348                return true
1349            end
1350        elseif first_last_pn == second_first_pn then
1351            -- 2-3, 3-4 -> 2-4
1352            pages[i-1] = { first_last, second_last }
1353            remove(pages,i)
1354            return true
1355        end
1356    end
1357    return false
1358end
1359
1360local function collapsepages(pages)
1361    while collapsedpage(pages) do end
1362    return #pages
1363end
1364
1365-- todo: create an intermediate structure and flush that
1366
1367function registers.flush(data,options,prefixspec,pagespec)
1368    local compress         = options.compress
1369    local collapse_singles = compress == v_yes
1370    local collapse_ranges  = compress == v_all
1371    local collapse_packed  = compress == v_packed
1372    local show_page_number = options.pagenumber ~= false -- true or false
1373    local bysection        = options.pagemethod == v_section
1374    local result = data.result
1375    local maxlevel = 0
1376    --
1377    for i=1,#result do
1378        local data = result[i].data
1379        for d=1,#data do
1380            local m = #data[d].list
1381            if m > maxlevel then
1382                maxlevel = m
1383            end
1384        end
1385    end
1386    if maxlevel > absmaxlevel then
1387        maxlevel = absmaxlevel
1388        report_registers("limiting level to %a",maxlevel)
1389    end
1390    --
1391    ctx_startregisteroutput()
1392    local done    = { }
1393    local started = false
1394    for i=1,#result do
1395     -- ranges need checking !
1396        local sublist = result[i]
1397     -- local done = { false, false, false, false }
1398        for i=1,maxlevel do
1399            done[i] = false
1400        end
1401        local data = sublist.data
1402        local d    = 0
1403        local n    = 0
1404        ctx_startregistersection(sublist.tag)
1405        for d=1,#data do
1406            local entry = data[d]
1407            if entry.metadata.kind == "see" then
1408                local list = entry.list
1409                if #list > 1 then
1410                    list[#list] = nil
1411                else
1412                 -- we have an \seeindex{Foo}{Bar} without Foo being defined anywhere .. somehow this message is wrong
1413                 -- report_registers("invalid see entry in register %a, reference %a",entry.metadata.name,list[1][1])
1414                end
1415            end
1416-- move up ?
1417--             if entry.external then
1418            if options.multiple or entry.external then
1419                local list = entry.list
1420                list[#list] = nil
1421            end
1422        end
1423        -- ok, this is tricky: we use e[i] delayed so we need it to be local
1424        -- but we don't want to allocate too many entries so there we go
1425
1426        while d < #data do
1427            d = d + 1
1428            local entry    = data[d]
1429-- inspect(entry)
1430            local metadata = entry.metadata
1431            local kind     = metadata.kind
1432            local list     = entry.list
1433            local e = { false, false, false }
1434            for i=3,maxlevel do
1435                e[i] = false
1436            end
1437            for i=1,maxlevel do
1438                local li = list[i]
1439                if list[i] then
1440                    e[i] = li[1]
1441                end
1442                if e[i] == done[i] then
1443                    -- skip
1444                elseif not e[i] then
1445                    -- see ends up here
1446                    -- can't happen any more
1447                    done[i] = false
1448                    for j=i+1,maxlevel do
1449                        done[j] = false
1450                    end
1451                elseif e[i] == "" then
1452                    done[i] = false
1453                    for j=i+1,maxlevel do
1454                        done[j] = false
1455                    end
1456                else
1457                    done[i] = e[i]
1458                    for j=i+1,maxlevel do
1459                        done[j] = false
1460                    end
1461                    if started then
1462                        ctx_stopregisterentry()
1463                        started = false
1464                    end
1465                    if n == i then
1466                     -- ctx_stopregisterentries()
1467                     -- ctx_startregisterentries(n)
1468                    else
1469                        while n > i do
1470                            n = n - 1
1471                            ctx_stopregisterentries()
1472                        end
1473                        while n < i do
1474                            n = n + 1
1475                            ctx_startregisterentries(n)
1476                        end
1477                    end
1478                    local references = entry.references
1479                    local processors = entry.processors
1480                    local internal   = references.internal or 0
1481                    local seeparent  = references.seeparent or ""
1482                    local processor  = (li and li[3]) or (processors and processors[1]) or ""
1483                    -- so, we need to keep e as is (local), or we need local title = e[i] ... which might be
1484                    -- more of a problem
1485                    ctx_startregisterentry(0) -- will become a counter
1486                    started = true
1487                    if metadata then
1488                        ctx_registerentry(metadata.name or "",processor,internal,seeparent,function() h_title(e[i],metadata) end)
1489                    else
1490                        -- can this happen?
1491                        ctx_registerentry("",processor,internal,seeindex,e[i])
1492                    end
1493                end
1494            end
1495
1496            local function case_1()
1497                -- we collapse ranges and keep existing ranges as they are
1498                -- so we get prebuilt as well as built ranges
1499                local first, last, prev, pages, dd, nofpages = entry, nil, entry, { }, d, 0
1500                while dd < #data do
1501                    dd = dd + 1
1502                    local next = data[dd]
1503                    if next and next.metadata.kind == "see" then
1504                        dd = dd - 1
1505                        break
1506                    else
1507                        local el, nl = entry.list, next.list
1508                        if not equal(el,nl) then
1509                            dd = dd - 1
1510                        --~ first = nil
1511                            break
1512                        elseif next.references.lastrealpage then
1513                            nofpages = nofpages + 1
1514                            pages[nofpages] = first and { first, last or first } or { entry, entry }
1515                            nofpages = nofpages + 1
1516                            pages[nofpages] = { next, next }
1517                            first, last, prev = nil, nil, nil
1518                        elseif not first then
1519                            first, prev = next, next
1520                        elseif next.references.realpage - prev.references.realpage == 1 then -- 1 ?
1521                            last, prev = next, next
1522                        else
1523                            nofpages = nofpages + 1
1524                            pages[nofpages] = { first, last or first }
1525                            first, last, prev = next, nil, next
1526                        end
1527                    end
1528                end
1529                if first then
1530                    nofpages = nofpages + 1
1531                    pages[nofpages] = { first, last or first }
1532                end
1533                if collapse_ranges and nofpages > 1 then
1534                    nofpages = collapsepages(pages)
1535                end
1536                if nofpages > 0 then -- or 0
1537                    d = dd
1538                    for p=1,nofpages do
1539                        local page  = pages[p]
1540                        local first = page[1]
1541                        local last  = page[2]
1542                        if first == last then
1543                            if first.references.lastrealpage then
1544                                pagerange(first,first,true,prefixspec,pagespec)
1545                            else
1546                                pagenumber(first,prefixspec,pagespec)
1547                            end
1548                        elseif last.references.lastrealpage then
1549                            pagerange(first,last,true,prefixspec,pagespec)
1550                        else
1551                            pagerange(first,last,false,prefixspec,pagespec)
1552                        end
1553                    end
1554                elseif entry.references.lastrealpage then
1555                    pagerange(entry,entry,true,prefixspec,pagespec)
1556                else
1557                    pagenumber(entry,prefixspec,pagespec)
1558                end
1559            end
1560
1561            local function case_2()
1562                local first = nil
1563                local last  = nil
1564                while true do
1565                    if not first then
1566                        first = entry
1567                    end
1568                    last  = entry
1569                    if d == #data then
1570                        break
1571                    else
1572                        d = d + 1
1573                        local next = data[d]
1574                        if next.metadata.kind == "see" or not equal(entry.list,next.list) then
1575                            d = d - 1
1576                            break
1577                        else
1578                            entry = next
1579                        end
1580                    end
1581                end
1582                packed(first,last) -- returns internals
1583            end
1584
1585            local function case_3()
1586                while true do
1587                    if entry.references.lastrealpage then
1588                        pagerange(entry,entry,true,prefixspec,pagespec)
1589                    else
1590                        pagenumber(entry,prefixspec,pagespec)
1591                    end
1592                    if d == #data then
1593                        break
1594                    else
1595                        d = d + 1
1596                        local next = data[d]
1597                        if next.metadata.kind == "see" or not equal(entry.list,next.list) then
1598                            d = d - 1
1599                            break
1600                        else
1601                            entry = next
1602                        end
1603                    end
1604                end
1605            end
1606
1607            local function case_4()
1608                local t  = { }
1609                local nt = 0
1610                while true do
1611                    if entry.seeword and entry.seeword.valid then
1612                        nt = nt + 1
1613                        t[nt] = entry
1614                    end
1615                    if d == #data then
1616                        break
1617                    else
1618                        d = d + 1
1619                        local next = data[d]
1620                        if next.metadata.kind ~= "see" or not equal(entry.list,next.list) then
1621                            d = d - 1
1622                            break
1623                        else
1624                            entry = next
1625                        end
1626                    end
1627                end
1628                for i=1,nt do
1629                    local entry     = t[i]
1630                    local seeword   = entry.seeword
1631                    local seetext   = seeword.text or ""
1632                    local processor = seeword.processor or (entry.processors and entry.processors[1]) or ""
1633                    local seeindex  = entry.external or entry.references.seeindex or ""
1634                    local seelist   = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,seetext)
1635                    if #seelist > 1 then
1636                        seetext = concat(seelist,", ")
1637                    end
1638                    ctx_registerseeword(
1639                        metadata.name or "",
1640                        i,
1641                        nt,
1642                        processor,
1643                        0,
1644                        seeindex,
1645                        function() h_title(seetext,metadata) end
1646                    )
1647                end
1648            end
1649
1650            local function case_5()
1651                local first = d
1652                while true do
1653                    if d == #data then
1654                        break
1655                    else
1656                        d = d + 1
1657                        local next = data[d]
1658                        if next.metadata.kind == "see" or not equal(entry.list,next.list) then
1659                            d = d - 1
1660                            break
1661                        else
1662                            entry = next
1663                        end
1664                    end
1665                end
1666                local last      = d
1667                local n         = last - first + 1
1668                local i         = 0
1669                local name      = metadata.name or ""
1670                local processor = entry.processors and entry.processors[1] or ""
1671                for e=first,last do
1672                    local d = data[e]
1673                    local sectionindex = d.references.internal or 0
1674                    i = i + 1
1675                    ctx_registersection(
1676                        name,
1677                        i,
1678                        n,
1679                        processor,
1680                        0,
1681                        sectionindex,
1682                        function() h_prefix(d,prefixspec,true) end
1683                    )
1684                end
1685            end
1686
1687            if kind == "entry" then
1688                if show_page_number then
1689                    ctx_startregisterpages()
1690                    if bysection then
1691                        case_5()
1692                    elseif collapse_singles or collapse_ranges then
1693                        case_1()
1694                    elseif collapse_packed then
1695                        case_2()
1696                    else
1697                        case_3()
1698                    end
1699                    ctx_stopregisterpages()
1700                end
1701            elseif kind == "see" then
1702                ctx_startregisterseewords()
1703                case_4()
1704                ctx_stopregisterseewords()
1705            end
1706
1707        end
1708
1709        if started then
1710            ctx_stopregisterentry()
1711            started = false
1712        end
1713        while n > 0 do
1714            ctx_stopregisterentries()
1715            n = n - 1
1716        end
1717        ctx_stopregistersection()
1718    end
1719    ctx_stopregisteroutput()
1720    -- for now, maybe at some point we will do a multipass or so
1721    data.result = nil
1722    data.metadata.sorted = false
1723    -- temp hack for luajittex :
1724    local entries = data.entries
1725    for i=1,#entries do
1726        entries[i].split = nil
1727    end
1728 -- collectgarbage("collect")
1729end
1730
1731function registers.process(class,...)
1732    if analyzeregister(class,...) > 0 then
1733        local data = collected[class]
1734        registers.flush(data,...)
1735    end
1736end
1737
1738implement {
1739    name      = "processregister",
1740    actions   = registers.process,
1741    arguments = {
1742        "string",
1743        {
1744            { "language" },
1745            { "method" },
1746            { "numberorder" },
1747            { "compress" },
1748            { "criterium" },
1749            { "check" },
1750            { "pagemethod" },
1751            { "pagenumber", "boolean" },
1752        },
1753        {
1754            { "separatorset" },
1755            { "conversionset" },
1756            { "starter" },
1757            { "stopper" },
1758            { "set" },
1759            { "segments" },
1760            { "connector" },
1761        },
1762        {
1763            { "prefix" },
1764            { "separatorset" },
1765            { "conversionset" },
1766            { "starter" },
1767            { "stopper" },
1768            { "segments" },
1769        }
1770    }
1771}
1772
1773-- linked registers
1774
1775function registers.findinternal(tag,where,n)
1776 -- local collected = registers.collected
1777    local current   = collected[tag]
1778    if not current then
1779        return 0
1780    end
1781    local entries = current.entries
1782    if not entries then
1783        return 0
1784    end
1785    local entry = entries[n]
1786    if not entry then
1787        return 0
1788    end
1789    local list = entry.list
1790    local size = #list
1791    --
1792    local start, stop, step
1793    if where == v_previous then
1794        start = n - 1
1795        stop  = 1
1796        step  = -1
1797    elseif where == v_first then
1798        start = 1
1799        stop  = #entries
1800        step  = 1
1801    elseif where == v_last then
1802        start = #entries
1803        stop  = 1
1804        step  = -1
1805    else
1806        start = n + 1
1807        stop  = #entries
1808        step  = 1
1809    end
1810    --
1811    for i=start,stop,step do
1812        local r = entries[i]
1813        local l = r.list
1814        local s = #l
1815        if s == size then
1816            local ok = true
1817            for i=1,size do
1818                if list[i][1] ~= l[i][1] then
1819                    ok = false
1820                    break
1821                end
1822            end
1823            if ok then
1824                return r.references.internal or 0
1825            end
1826        end
1827    end
1828    return 0
1829end
1830
1831interfaces.implement {
1832    name      = "findregisterinternal",
1833    arguments = { "string", "string", "integer" },
1834    actions   = { registers.findinternal, context },
1835}
1836
1837-- external data
1838
1839function registers.integrate(utilitydata)
1840    local filename = utilitydata.comment.file
1841    if filename and filename ~= environment.jobname then
1842        local structures = utilitydata.structures
1843        if structures then
1844            local registers = structures.registers.collected or { }
1845            if registers then
1846                local sections = structures.sections.collected or { }
1847                local pages    = structures.pages   .collected or { }
1848                for class, register in next, registers do
1849                    local entries = register.entries
1850                    if entries then
1851                        for i=1,#entries do
1852                            local entry      = entries[i]
1853                            local references = entry.references
1854                            if references then
1855                                local section  = references.section
1856                                local realpage = references.realpage
1857                                -- maybe entry.* instead:
1858                                references.sectiondata = section  and sections[section]
1859                                references.pagedata    = realpage and pages[realpage]
1860                                -- get rid of these, maybe even crash as these are bogus
1861                             -- references.internal = nil -- or 0
1862                             -- references.realpage = nil -- or 0
1863                             -- references.section  = nil -- or 0
1864                                if references.x then
1865                                    references.x = nil
1866                                end
1867                                if references.y then
1868                                    references.y = nil
1869                                end
1870                                references.external = filename -- not really needed
1871                            end
1872                            entry.external = true
1873                        end
1874                    end
1875                end
1876            end
1877        end
1878    end
1879end
1880