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