strc-lst.lua /size: 43 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['strc-lst'] = {
2    version   = 1.001,
3    comment   = "companion to strc-lst.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
9-- when all datastructures are stable a packer will be added which will
10-- bring down memory consumption a bit; we can use for instance a pagenumber,
11-- section, metadata cache (internal then has to move up one level) or a
12-- shared cache [we can use a fast and stupid serializer]
13
14-- todo: tag entry in list is crap
15--
16-- move more to commands
17
18local tonumber, type, next = tonumber, type, next
19local concat, insert, remove, sort = table.concat, table.insert, table.remove, table.sort
20local lpegmatch = lpeg.match
21
22local setmetatableindex = table.setmetatableindex
23local sortedkeys        = table.sortedkeys
24
25local settings_to_set   = utilities.parsers.settings_to_set
26local allocate          = utilities.storage.allocate
27local checked           = utilities.storage.checked
28
29local trace_lists       = false  trackers.register("structures.lists", function(v) trace_lists = v end)
30
31local report_lists      = logs.reporter("structure","lists")
32
33local context           = context
34local commands          = commands
35local implement         = interfaces.implement
36local conditionals      = tex.conditionals
37
38local ctx_latelua       = context.latelua
39
40local cheat = true
41
42local structures        = structures
43local lists             = structures.lists
44local sections          = structures.sections
45local helpers           = structures.helpers
46local documents         = structures.documents
47local tags              = structures.tags
48local counters          = structures.counters
49local references        = structures.references
50
51local collected         = allocate()
52local tobesaved         = allocate()
53local cached            = allocate()
54local pushed            = allocate()
55local kinds             = allocate()
56local names             = allocate()
57
58lists.collected         = collected
59lists.tobesaved         = tobesaved
60
61lists.enhancers         = lists.enhancers or { }
62-----.internals         = allocate(lists.internals or { }) -- to be checked
63lists.ordered           = allocate(lists.ordered   or { }) -- to be checked
64lists.cached            = cached
65lists.pushed            = pushed
66lists.kinds             = kinds
67lists.names             = names
68
69local sorters           = sorters
70local sortstripper      = sorters.strip
71local sortsplitter      = sorters.splitters.utf
72local sortcomparer      = sorters.comparers.basic
73
74local sectionblocks     = allocate()
75lists.sectionblocks     = sectionblocks
76
77references.specials     = references.specials or { }
78
79local matchingtilldepth = sections.matchingtilldepth
80local numberatdepth     = sections.numberatdepth
81local getsectionlevel   = sections.getlevel
82local typesetnumber     = sections.typesetnumber
83local autosectiondepth  = sections.autodepth
84
85local variables         = interfaces.variables
86
87local v_all             = variables.all
88local v_reference       = variables.reference
89local v_title           = variables.title
90local v_command         = variables.command
91local v_text            = variables.text
92local v_current         = variables.current
93local v_previous        = variables.previous
94local v_intro           = variables.intro
95local v_here            = variables.here
96local v_component       = variables.component
97local v_reference       = variables.reference
98local v_local           = variables["local"]
99local v_default         = variables.default
100
101local cheats = {
102    [variables.fit]   = true,
103    [variables.tight] = true,
104}
105
106local function zerostrippedconcat(t,separator)
107    local f = 1
108    local l = #t
109    for i=f,l do
110        if t[i] == 0 then
111            f = f + 1
112        end
113    end
114    for i=l,f,-1 do
115        if t[i] == 0 then
116            l = l - 1
117        end
118    end
119    return concat(t,separator,f,l)
120end
121
122-- -- -- -- -- --
123
124local function initializer()
125    -- create a cross reference between internal references
126    -- and list entries
127    local collected     = lists.collected
128    local internals     = checked(references.internals)
129    local ordered       = lists.ordered
130    local usedinternals = references.usedinternals
131    local blockdone     = { }
132    local lastblock     = nil
133    for i=1,#collected do
134        local c = collected[i]
135        local m = c.metadata
136        local r = c.references
137        if m then
138            -- access by internal reference
139            if r then
140                local internal = r.internal
141                if internal then
142                    internals[internal] = c
143                    usedinternals[internal] = r.used
144                end
145                local block = r.block
146                if not block then
147                    -- shouldn't happen
148                elseif lastblock == block then
149                    -- we're okay
150                elseif lastblock then
151                    if blockdone[block] then
152                        report_lists("out of order sectionsblocks, maybe use \\setsectionblock")
153                    else
154                        blockdone[block] = true
155                        sectionblocks[#sectionblocks+1] = block
156                    end
157                    lastblock = block
158                elseif not blockdone[block] then
159                    blockdone[block] = true
160                    sectionblocks[#sectionblocks+1] = block
161                    lastblock = block
162                end
163            end
164            -- access by order in list
165            local kind = m.kind
166            local name = m.name
167            if kind and name then
168                local ok = ordered[kind]
169                if ok then
170                    local on = ok[name]
171                    if on then
172                        on[#on+1] = c
173                    else
174                        ok[name] = { c }
175                    end
176                else
177                    ordered[kind] = { [name] = { c } }
178                end
179                kinds[kind] = true
180                names[name] = true
181            elseif kind then
182                kinds[kind] = true
183            elseif name then
184                names[name] = true
185            end
186        end
187        if r then
188            r.listindex = i -- handy to have
189        end
190    end
191end
192
193local function finalizer()
194    local flaginternals = references.flaginternals
195    local usedviews     = references.usedviews
196    for i=1,#tobesaved do
197        local r = tobesaved[i].references
198        if r then
199            local i = r.internal
200            local f = flaginternals[i]
201            local v = usedviews[i]
202            if cheat and v and cheats[v] then -- cheats check added, to be tested by RKB
203                -- this permits runs=2 with interactivity
204                r.view = v
205            end
206            if f then
207                r.used = v or true
208            end
209        end
210    end
211end
212
213job.register('structures.lists.collected', tobesaved, initializer, finalizer)
214
215local groupindices = setmetatableindex("table")
216
217function lists.groupindex(name,group)
218    local groupindex = groupindices[name]
219    return groupindex and groupindex[group] or 0
220end
221
222-- we could use t (as hash key) in order to check for dup entries
223
224function lists.addto(t) -- maybe more more here (saves parsing at the tex end)
225    local metadata   = t.metadata
226    local userdata   = t.userdata
227    local numberdata = t.numberdata
228    if userdata and type(userdata) == "string" then
229        t.userdata = helpers.touserdata(userdata)
230    end
231    if not metadata.level then
232        metadata.level = structures.sections.currentlevel() -- this is not used so it will go away
233    end
234    --
235 -- if not conditionals.inlinelefttoright then
236 --     metadata.idir = "r2l"
237 -- end
238 -- if not conditionals.displaylefttoright then
239 --     metadata.ddir = "r2l"
240 -- end
241    --
242    if numberdata then
243        local numbers = numberdata.numbers
244        if type(numbers) == "string" then
245            counters.compact(numberdata,numbers,numberdata.level)
246        end
247    end
248    local group = numberdata and numberdata.group
249    local name  = metadata.name
250    local kind  = metadata.kind
251    if not group then
252        -- forget about it
253    elseif group == "" then
254        group, numberdata.group = nil, nil
255    else
256        local groupindex = groupindices[name][group]
257        if groupindex then
258            numberdata.numbers = cached[groupindex].numberdata.numbers
259        end
260    end
261    local setcomponent = references.setcomponent
262    if setcomponent then
263        setcomponent(t) -- can be inlined
264    end
265    local r = t.references
266    if r and not r.section then
267        r.section = structures.sections.currentid()
268    end
269    local b = r and t.block
270    if r and not b then
271        local s = r.section
272        if s then
273            s = structures.sections.tobesaved[s]
274            r.block = s and s.block or nil
275        end
276    end
277    local i = r and r.internal or 0 -- brrr
278    if r and kind and name then
279        local tag = tags.getid(kind,name)
280        if tag and tag ~= "?" then
281            r.tag = tag -- todo: use internal ... is unique enough
282        end
283    end
284    local p = pushed[i]
285    if not p then
286        p = #cached + 1
287        cached[p] = helpers.simplify(t)
288        pushed[i] = p
289        if r then
290            r.listindex = p
291        end
292    end
293    if group then
294        groupindices[name][group] = p
295    end
296    if trace_lists then
297        report_lists("added %a, internal %a",name,p)
298    end
299    return p
300end
301
302function lists.discard(n)
303    n = tonumber(n)
304    if not n then
305        -- maybe an error message
306    elseif n == #cached then
307        cached[n] = nil
308        n = n - 1
309        while n > 0 and cached[n] == false do
310            cached[n] = nil -- collect garbage
311            n = n - 1
312        end
313    else
314        cached[n] = false
315    end
316end
317
318function lists.iscached(n)
319    return cached[tonumber(n)]
320end
321
322-- this is the main pagenumber enhancer
323
324local enhanced = { }
325
326local synchronizepage = function(r)  -- bah ... will move
327    synchronizepage = references.synchronizepage
328    return synchronizepage(r)
329end
330
331local function enhancelist(specification)
332    local n = specification.n
333    local l = cached[n]
334    if not l then
335        report_lists("enhancing %a, unknown internal",n)
336    elseif enhanced[n] then
337        if trace_lists then
338            report_lists("enhancing %a, name %a, duplicate ignored",n,name)
339        end
340    else
341        local metadata   = l.metadata
342        local references = l.references
343        --
344        l.directives = nil -- might change
345        -- save in the right order (happens at shipout)
346        lists.tobesaved[#lists.tobesaved+1] = l
347        -- default enhancer (cross referencing)
348        synchronizepage(references)
349        -- tags
350        local kind = metadata.kind
351        local name = metadata.name
352        if trace_lists then
353            report_lists("enhancing %a, name %a, page %a",n,name,references.realpage or 0)
354        end
355--         if references then
356--             -- is this used ?
357--             local tag = tags.getid(kind,name)
358--             if tag and tag ~= "?" then
359--                 references.tag = tag
360--             end
361--         end
362        -- specific enhancer (kind of obsolete)
363        local enhancer = kind and lists.enhancers[kind]
364        if enhancer then
365            enhancer(l)
366        end
367        --
368        enhanced[n] = true
369        return l
370    end
371end
372
373lists.enhance = enhancelist
374
375-- we can use level instead but we can also decide to remove level from the metadata
376
377local nesting = { }
378
379function lists.pushnesting(i)
380    local parent     = lists.result[i]
381    local name       = parent.metadata.name
382    local numberdata = parent and parent.numberdata
383    local numbers    = numberdata and numberdata.numbers
384    local number     = numbers and numbers[getsectionlevel(name)] or 0
385    insert(nesting, {
386        number = number,
387        name   = name,
388        result = lists.result,
389        parent = parent
390    })
391end
392
393function lists.popnesting()
394    local old = remove(nesting)
395    if old then
396        lists.result = old.result
397    else
398        report_lists("nesting error")
399    end
400end
401
402-- Historically we had blocks but in the mkiv approach that could as well be a level
403-- which would simplify things a bit.
404
405local splitter = lpeg.splitat(":") -- maybe also :: or have a block parameter
406
407local listsorters = {
408    [v_command] = function(a,b)
409        if a.metadata.kind == "command" or b.metadata.kind == "command" then
410            return a.references.internal < b.references.internal
411        else
412            return a.references.order < b.references.order
413        end
414    end,
415    [v_all] = function(a,b)
416        return a.references.internal < b.references.internal
417    end,
418    [v_title] = function(a,b)
419        local da = a.titledata
420        local db = b.titledata
421        if da and db then
422            local ta = da.title
423            local tb = db.title
424            if ta and tb then
425                local sa = da.split
426                if not sa then
427                    sa = sortsplitter(sortstripper(ta))
428                    da.split = sa
429                end
430                local sb = db.split
431                if not sb then
432                    sb = sortsplitter(sortstripper(tb))
433                    db.split = sb
434                end
435                return sortcomparer(da,db) == -1
436            end
437        end
438        return a.references.internal < b.references.internal
439    end
440}
441
442-- was: names, criterium, number, collected, forced, nested, sortorder
443
444local filters = setmetatableindex(function(t,k) return t[v_default] end)
445
446local function filtercollected(specification)
447    --
448    local names     = specification.names     or { }
449    local criterium = specification.criterium or v_default
450    local number    = 0 -- specification.number
451    local reference = specification.reference or ""
452    local collected = specification.collected or lists.collected
453    local forced    = specification.forced    or { }
454    local nested    = specification.nested    or false
455    local sortorder = specification.sortorder or specification.order
456    --
457    local numbers   = documents.data.numbers
458    local depth     = documents.data.depth
459    local block     = false -- all
460    local wantedblock, wantedcriterium = lpegmatch(splitter,criterium) -- block:criterium
461    if wantedblock == "" or wantedblock == v_all or wantedblock == v_text then
462        criterium = wantedcriterium ~= "" and wantedcriterium or criterium
463    elseif not wantedcriterium then
464        block = documents.data.block
465    else
466        block     = wantedblock
467        criterium = wantedcriterium
468    end
469    if block == "" then
470        block = false
471    end
472    if type(names) == "string" then
473        names = settings_to_set(names)
474    end
475    local all = not next(names) or names[v_all] or false
476    --
477    specification.names     = names
478    specification.criterium = criterium
479    specification.number    = 0 -- obsolete
480    specification.reference = reference -- new
481    specification.collected = collected
482    specification.forced    = forced -- todo: also on other branched, for the moment only needed for bookmarks
483    specification.nested    = nested
484    specification.sortorder = sortorder
485    specification.numbers   = numbers
486    specification.depth     = depth
487    specification.block     = block
488    specification.all       = all
489    --
490    if specification.atmost then
491        criterium = v_text
492    end
493    --
494    if trace_lists then
495        report_lists("filtering names %,t, criterium %a, block %a",sortedkeys(names), criterium, block or "*")
496    end
497    local result = filters[criterium](specification)
498    if trace_lists then
499        report_lists("criterium %a, block %a, found %a",specification.criterium, specification.block or "*", #result)
500    end
501    --
502    local levels = tonumber(specification.levels)
503    if levels then
504        local minlevel  = 1000
505        local found     = result
506        local nofresult = #result
507        for i=1,nofresult do
508            local v = found[i]
509            local l = v.metadata.level or 1
510            if l < minlevel then
511                minlevel = l
512            end
513        end
514        local maxlevel = minlevel + levels - 1
515        result    = { }
516        nofresult = 0
517        for i=1,#found do
518            local v = found[i]
519            local l = v.metadata.level or 1
520            if l >= minlevel and l <= maxlevel then
521                nofresult = nofresult + 1
522                result[nofresult] = v
523            end
524        end
525    end
526    --
527    if sortorder then -- experiment
528        local sorter = listsorters[sortorder]
529        if sorter then
530            if trace_lists then
531                report_lists("sorting list using method %a",sortorder)
532            end
533            for i=1,#result do
534                result[i].references.order = i
535            end
536            sort(result,sorter)
537        end
538    end
539    --
540    return result
541end
542
543filters[v_intro] = function(specification)
544    local collected = specification.collected
545    local result    = { }
546    local nofresult = 0
547    local all       = specification.all
548    local names     = specification.names
549    for i=1,#collected do
550        local v = collected[i]
551        local metadata = v.metadata
552        if metadata and (all or names[metadata.name or false]) then
553            local r = v.references
554            if r and r.section == 0 then
555                nofresult = nofresult + 1
556                result[nofresult] = v
557            end
558        end
559    end
560    return result
561end
562
563filters[v_reference] = function(specification)
564    local collected = specification.collected
565    local result    = { }
566    local nofresult = 0
567    local names     = specification.names
568    local sections  = sections.collected
569    local reference = specification.reference
570    if reference ~= "" then
571        local prefix, rest = lpegmatch(references.prefixsplitter,reference) -- p::r
572        local r = prefix and rest and references.derived[prefix][rest] or references.derived[""][reference]
573        local s = r and r.numberdata -- table ref !
574        if s then
575            local depth   = getsectionlevel(r.metadata.name)
576            local numbers = s.numbers
577            for i=1,#collected do
578                local v = collected[i]
579                local r = v.references
580                if r and (not block or not r.block or block == r.block) then
581                    local metadata = v.metadata
582                    if metadata and names[metadata.name or false] then
583                        local sectionnumber = (r.section == 0) or sections[r.section]
584                        if sectionnumber then
585                            if matchingtilldepth(depth,numbers,sectionnumber.numbers) then
586                                nofresult = nofresult + 1
587                                result[nofresult] = v
588                            end
589                        end
590                    end
591                end
592            end
593        else
594            report_lists("unknown reference %a specified",reference)
595        end
596    else
597        report_lists("no reference specified")
598    end
599    return result
600end
601
602filters[v_all] = function(specification)
603    local collected = specification.collected
604    local result    = { }
605    local nofresult = 0
606    local block     = specification.block
607    local all       = specification.all
608    local forced    = specification.forced
609    local names     = specification.names
610    local sections  = sections.collected
611    for i=1,#collected do
612        local v = collected[i]
613        local r = v.references
614        if r and (not block or not r.block or block == r.block) then
615            local metadata = v.metadata
616            if metadata then
617                local name  = metadata.name  or false
618                local sectionnumber = (r.section == 0) or sections[r.section]
619                if forced[name] or (sectionnumber and not metadata.nolist and (all or names[name])) then -- and not sectionnumber.hidenumber then
620                    nofresult = nofresult + 1
621                    result[nofresult] = v
622                end
623            end
624        end
625    end
626    return result
627end
628
629filters[v_text] = filters[v_all]
630
631filters[v_current] = function(specification)
632    if specification.depth == 0 then
633        specification.nested    = false
634        specification.criterium = v_intro
635        return filters[v_intro](specification)
636    end
637    local collected = specification.collected
638    local result    = { }
639    local nofresult = 0
640    local depth     = specification.depth
641    local block     = specification.block
642    local all       = specification.all
643    local names     = specification.names
644    local numbers   = specification.numbers
645    local sections  = sections.collected
646    for i=1,#collected do
647        local v = collected[i]
648        local r = v.references
649        if r and (not block or not r.block or block == r.block) then
650            local sectionnumber = sections[r.section]
651            if sectionnumber then -- and not sectionnumber.hidenumber then
652                local cnumbers = sectionnumber.numbers
653                local metadata = v.metadata
654                if cnumbers then
655                    if metadata and not metadata.nolist and (all or names[metadata.name or false]) and #cnumbers > depth then
656                        local ok = true
657                        for d=1,depth do
658                            local cnd = cnumbers[d]
659                            if not (cnd == 0 or cnd == numbers[d]) then
660                                ok = false
661                                break
662                            end
663                        end
664                        if ok then
665                            nofresult = nofresult + 1
666                            result[nofresult] = v
667                        end
668                    end
669                end
670            end
671        end
672    end
673    return result
674end
675
676filters[v_here] = function(specification)
677    -- this is quite dirty ... as cnumbers is not sparse we can misuse #cnumbers
678    if specification.depth == 0 then
679        specification.nested    = false
680        specification.criterium = v_intro
681        return filters[v_intro](specification)
682    end
683    local collected = specification.collected
684    local result    = { }
685    local nofresult = 0
686    local depth     = specification.depth
687    local block     = specification.block
688    local all       = specification.all
689    local names     = specification.names
690    local numbers   = specification.numbers
691    local sections  = sections.collected
692    for i=1,#collected do
693        local v = collected[i]
694        local r = v.references
695        if r then -- and (not block or not r.block or block == r.block) then
696            local sectionnumber = sections[r.section]
697            if sectionnumber then -- and not sectionnumber.hidenumber then
698                local cnumbers = sectionnumber.numbers
699                local metadata = v.metadata
700                if cnumbers then
701                    if metadata and not metadata.nolist and (all or names[metadata.name or false]) and #cnumbers >= depth then
702                        local ok = true
703                        for d=1,depth do
704                            local cnd = cnumbers[d]
705                            if not (cnd == 0 or cnd == numbers[d]) then
706                                ok = false
707                                break
708                            end
709                        end
710                        if ok then
711                            nofresult = nofresult + 1
712                            result[nofresult] = v
713                        end
714                    end
715                end
716            end
717        end
718    end
719    return result
720end
721
722filters[v_previous] = function(specification)
723    if specification.depth == 0 then
724        specification.nested    = false
725        specification.criterium = v_intro
726        return filters[v_intro](specification)
727    end
728    local collected = specification.collected
729    local result    = { }
730    local nofresult = 0
731    local block     = specification.block
732    local all       = specification.all
733    local names     = specification.names
734    local numbers   = specification.numbers
735    local sections  = sections.collected
736    local depth     = specification.depth
737    for i=1,#collected do
738        local v = collected[i]
739        local r = v.references
740        if r and (not block or not r.block or block == r.block) then
741            local sectionnumber = sections[r.section]
742            if sectionnumber then -- and not sectionnumber.hidenumber then
743                local cnumbers = sectionnumber.numbers
744                local metadata = v.metadata
745                if cnumbers then
746                    if metadata and not metadata.nolist and (all or names[metadata.name or false]) and #cnumbers >= depth then
747                        local ok = true
748                        for d=1,depth-1 do
749                            local cnd = cnumbers[d]
750                            if not (cnd == 0 or cnd == numbers[d]) then
751                                ok = false
752                                break
753                            end
754                        end
755                        if ok then
756                            nofresult = nofresult + 1
757                            result[nofresult] = v
758                        end
759                    end
760                end
761            end
762        end
763    end
764    return result
765end
766
767filters[v_local] = function(specification)
768    local numbers = specification.numbers
769    local nested  = nesting[#nesting]
770    if nested then
771        return filtercollected {
772            names     = specification.names,
773            criterium = nested.name,
774            collected = specification.collected,
775            forced    = specification.forced,
776            nested    = nested,
777            sortorder = specification.sortorder,
778        }
779    else
780        specification.criterium = autosectiondepth(numbers) == 0 and v_all or v_current
781        specification.nested    = false
782        return filtercollected(specification) -- rechecks, so better (for determining all)
783    end
784end
785
786
787filters[v_component] = function(specification)
788    -- special case, no structure yet
789    local collected = specification.collected
790    local result    = { }
791    local nofresult = 0
792    local all       = specification.all
793    local names     = specification.names
794    local component = resolvers.jobs.currentcomponent() or ""
795    if component ~= "" then
796        for i=1,#collected do
797            local v = collected[i]
798            local r = v.references
799            local m = v.metadata
800            if r and r.component == component and (m and names[m.name] or all) then
801                nofresult = nofresult + 1
802                result[nofresult] = v
803            end
804        end
805    end
806    return result
807end
808
809-- local number = tonumber(number) or numberatdepth(depth) or 0
810-- if number > 0 then
811--     ...
812-- end
813
814filters[v_default] = function(specification) -- is named
815    local collected = specification.collected
816    local result    = { }
817    local nofresult = 0
818    ----- depth     = specification.depth
819    local block     = specification.block
820    local criterium = specification.criterium
821    local all       = specification.all
822    local names     = specification.names
823    local numbers   = specification.numbers
824    local sections  = sections.collected
825    local reference = specification.reference
826    local nested    = specification.nested
827    --
828    if reference then
829        reference = tonumber(reference)
830    end
831    --
832    local depth     = getsectionlevel(criterium)
833    local pnumbers  = nil
834    local pblock    = block
835    local parent    = nested and nested.parent
836    --
837    if parent then
838        pnumbers = parent.numberdata.numbers or pnumbers -- so local as well as nested
839        pblock   = parent.references.block or pblock
840        if trace_lists then
841            report_lists("filtering by block %a and section %a",pblock,criterium)
842        end
843    end
844    --
845    for i=1,#collected do
846        local v = collected[i]
847        local r = v.references
848-- inspect(v)
849        if r and (not block or not r.block or pblock == r.block) then
850            local sectionnumber = sections[r.section]
851            if sectionnumber then
852                local metadata = v.metadata
853                local cnumbers = sectionnumber.numbers
854                if cnumbers then
855                    if all or names[metadata.name or false] then
856                        if reference then
857                            -- filter by number
858                            if reference == cnumbers[depth] then
859                                nofresult = nofresult + 1
860                                result[nofresult] = v
861                            end
862                        else
863                            if #cnumbers >= depth and matchingtilldepth(depth,cnumbers,pnumbers) then
864                                nofresult = nofresult + 1
865                                result[nofresult] = v
866                            end
867                        end
868                    end
869                end
870            end
871        end
872    end
873    return result
874end
875
876-- names, criterium, number, collected, forced, nested, sortorder) -- names is hash or string
877
878lists.filter = filtercollected
879
880lists.result = { }
881
882function lists.getresult(r)
883    return lists.result[r]
884end
885
886function lists.process(specification)
887    local result = filtercollected(specification)
888    local total  = #result
889    lists.result = result
890    if total > 0 then
891        local usedinternals = references.usedinternals
892        local usedviews     = references.usedviews
893        local specials = settings_to_set(specification.extras or "")
894              specials = next(specials) and specials or nil
895        for i=1,total do
896            local listentry  = result[i]
897            local metadata   = listentry.metadata
898            local numberdata = listentry.numberdata
899            local references = listentry.references
900            local special    = specials and numberdata and specials[zerostrippedconcat(numberdata.numbers,".")] or ""
901            if cheat and references then
902                -- this permits runs=2 with interactivity
903                local internal = references.internal
904                usedinternals[internal] = true
905                usedviews    [internal] = references.view
906            end
907            context.strclistsentryprocess(metadata.name,metadata.kind,i,special)
908        end
909    end
910end
911
912function lists.analyze(specification)
913    lists.result = filtercollected(specification)
914end
915
916function lists.userdata(name,r,tag) -- to tex (todo: xml)
917    local result = lists.result[r]
918    if result then
919        local userdata = result.userdata
920        local str = userdata and userdata[tag]
921        if str then
922            return str, result.metadata
923        end
924    end
925end
926
927function lists.uservalue(name,r,tag,default) -- to lua
928    local str = lists.result[r]
929    if str then
930        str = str.userdata
931    end
932    if str then
933        str = str[tag]
934    end
935    return str or default
936end
937
938function lists.size()
939    return #lists.result
940end
941
942function lists.location(n)
943    local l = lists.result[n]
944    return l and l.references.internal or n
945end
946
947function lists.label(n,default)
948    local l = lists.result[n]
949    local t = l.titledata
950    return t and t.label or default or ""
951end
952
953function lists.sectionnumber(name,n,spec)
954    local data = lists.result[n]
955    local sectiondata = sections.collected[data.references.section]
956    -- hm, prefixnumber?
957    typesetnumber(sectiondata,"prefix",spec,sectiondata) -- data happens to contain the spec too
958end
959
960-- some basics (todo: helpers for pages)
961
962function lists.title(name,n,tag) -- tag becomes obsolete
963    local data = lists.result[n]
964    if data then
965        local titledata = data.titledata
966        if titledata then
967            helpers.title(titledata[tag] or titledata.list or titledata.title or "",data.metadata)
968        end
969    end
970end
971
972function lists.hastitledata(name,n,tag)
973    local data = cached[tonumber(n)]
974    if data then
975        local titledata = data.titledata
976        if titledata then
977            return (titledata[tag] or titledata.title or "") ~= ""
978        end
979    end
980    return false
981end
982
983function lists.haspagedata(name,n)
984    local data = lists.result[n]
985    if data then
986        local references = data.references
987        if references and references.realpage then -- or references.pagedata
988            return true
989        end
990    end
991    return false
992end
993
994function lists.hasnumberdata(name,n)
995    local data = lists.result[n]
996    if data then
997        local numberdata = data.numberdata
998        if numberdata and not numberdata.hidenumber then -- the hide number is true
999            return true
1000        end
1001    end
1002    return false
1003end
1004
1005function lists.rawnumber(n,name)
1006    local data = lists.result[n]
1007    if data then
1008        local numberdata = data.numberdata
1009        if numberdata then
1010            numberdata = numberdata.numbers
1011            return numberdata and numberdata[getsectionlevel(name)] or numberdata[name] or 0
1012        end
1013    end
1014    return 0
1015end
1016
1017function lists.prefix(name,n,spec)
1018    helpers.prefix(lists.result[n],spec)
1019end
1020
1021function lists.page(name,n,pagespec)
1022    helpers.page(lists.result[n],pagespec)
1023end
1024
1025function lists.prefixedpage(name,n,prefixspec,pagespec)
1026    helpers.prefixpage(lists.result[n],prefixspec,pagespec)
1027end
1028
1029function lists.realpage(name,n)
1030    local data = lists.result[n]
1031    if data then
1032        local references = data.references
1033        return references and references.realpage or 0
1034    else
1035        return 0
1036    end
1037end
1038
1039-- numbers stored in entry.numberdata + entry.numberprefix
1040
1041function lists.number(name,n,spec)
1042    local data = lists.result[n]
1043    if data then
1044        local numberdata = data.numberdata
1045        if numberdata then
1046            typesetnumber(numberdata,"number",spec or false,numberdata or false)
1047        end
1048    end
1049end
1050
1051function lists.prefixednumber(name,n,prefixspec,numberspec,forceddata)
1052    local data = lists.result[n]
1053    if data then
1054        helpers.prefix(data,prefixspec)
1055        local numberdata = data.numberdata or forceddata
1056        if numberdata then
1057            typesetnumber(numberdata,"number",numberspec or false,numberdata or false)
1058        end
1059    end
1060end
1061
1062-- todo, do this in references namespace ordered instead (this is an experiment)
1063--
1064-- also see lpdf-ano (maybe move this there)
1065
1066local splitter = lpeg.splitat(":")
1067
1068function references.specials.order(var,actions) -- references.specials !
1069    local operation = var.operation
1070    if operation then
1071        local kind, name, n = lpegmatch(splitter,operation)
1072        local order = lists.ordered[kind]
1073        order = order and order[name]
1074        local v = order[tonumber(n)]
1075        local r = v and v.references.realpage
1076        if r then
1077            actions.realpage = r
1078            var.operation = r -- brrr, but test anyway
1079            return references.specials.page(var,actions)
1080        end
1081    end
1082end
1083
1084-- interface (maybe strclistpush etc)
1085
1086if not lists.reordered then
1087    function lists.reordered(data)
1088        return data.numberdata
1089    end
1090end
1091
1092implement { name = "pushlist", actions = lists.pushnesting, arguments = "integer" }
1093implement { name = "poplist",  actions = lists.popnesting  }
1094
1095implement {
1096    name      = "addtolist",
1097    actions   = { lists.addto, context },
1098    arguments = {
1099        {
1100            { "references", {
1101                    { "internal", "integer" },
1102                    { "block" },
1103                    { "section", "integer" },
1104                    { "location" },
1105                    { "prefix" },
1106                    { "reference" },
1107                    { "view" },
1108                    { "order", "integer" },
1109                }
1110            },
1111            { "metadata", {
1112                    { "kind" },
1113                    { "name" },
1114                    { "level", "integer" },
1115                    { "catcodes", "integer" },
1116                    { "coding" },
1117                    { "xmlroot" },
1118                    { "setup" },
1119                }
1120            },
1121            { "userdata" },
1122            { "titledata", {
1123                    { "label" },
1124                    { "title" },
1125                    { "bookmark" },
1126                    { "marking" },
1127                    { "list" },
1128                    { "reference" },
1129                }
1130            },
1131            { "prefixdata", {
1132                    { "prefix" },
1133                    { "separatorset" },
1134                    { "conversionset" },
1135                    { "conversion" },
1136                    { "set" },
1137                    { "segments" },
1138                    { "connector" },
1139                }
1140            },
1141            { "numberdata", {
1142                    { "level", "integer" },
1143                    { "numbers" },
1144                    { "groupsuffix" },
1145                    { "group" },
1146                    { "counter" },
1147                    { "separatorset" },
1148                    { "conversionset" },
1149                    { "conversion" },
1150                    { "starter" },
1151                    { "stopper" },
1152                    { "segments" },
1153                }
1154            }
1155        }
1156    }
1157}
1158
1159implement {
1160    name      = "enhancelist",
1161    arguments = "integer",
1162    actions   = function(n)
1163        enhancelist { n = n }
1164    end
1165}
1166
1167implement {
1168    name      = "deferredenhancelist",
1169    arguments = "integer",
1170    protected = true, -- for now, pre 1.09
1171    actions   = function(n)
1172        ctx_latelua { action = enhancelist, n = n }
1173    end,
1174}
1175
1176implement {
1177    name      = "processlist",
1178    actions   = lists.process,
1179    arguments = {
1180        {
1181            { "names" },
1182            { "criterium" },
1183            { "reference" },
1184            { "extras" },
1185            { "order" },
1186            { "levels" },
1187        }
1188    }
1189}
1190
1191implement {
1192    name      = "analyzelist",
1193    actions   = lists.analyze,
1194    arguments = {
1195        {
1196            { "names" },
1197            { "criterium" },
1198            { "reference" },
1199        }
1200    }
1201}
1202
1203implement {
1204    name      = "listtitle",
1205    actions   = lists.title,
1206    arguments = { "string", "integer" }
1207}
1208
1209implement {
1210    name      = "listprefixednumber",
1211    actions   = lists.prefixednumber,
1212    arguments = {
1213        "string",
1214        "integer",
1215        {
1216            { "prefix" },
1217            { "separatorset" },
1218            { "conversionset" },
1219            { "starter" },
1220            { "stopper" },
1221            { "set" },
1222            { "segments" },
1223            { "connector" },
1224        },
1225        {
1226            { "separatorset" },
1227            { "conversionset" },
1228            { "starter" },
1229            { "stopper" },
1230            { "segments" },
1231        }
1232    }
1233}
1234
1235implement {
1236    name      = "listprefixedpage",
1237    actions   = lists.prefixedpage,
1238    arguments = {
1239        "string",
1240        "integer",
1241        {
1242            { "separatorset" },
1243            { "conversionset" },
1244            { "set" },
1245            { "segments" },
1246            { "connector" },
1247        },
1248        {
1249            { "prefix" },
1250            { "conversionset" },
1251            { "starter" },
1252            { "stopper" },
1253        }
1254    }
1255}
1256
1257implement { name = "listsize",       actions = { lists.size, context } }
1258implement { name = "listlocation",   actions = { lists.location, context }, arguments = "integer" }
1259implement { name = "listlabel",      actions = { lists.label, context }, arguments = { "integer", "string" } }
1260implement { name = "listrealpage",   actions = { lists.realpage, context }, arguments = { "string", "integer" } }
1261implement { name = "listgroupindex", actions = { lists.groupindex, context }, arguments = "2 strings", }
1262
1263implement {
1264    name    = "currentsectiontolist",
1265    actions = { sections.current, lists.addto, context }
1266}
1267
1268local function userdata(name,r,tag)
1269    local str, metadata = lists.userdata(name,r,tag)
1270    if str then
1271     -- local catcodes = metadata and metadata.catcodes
1272     -- if catcodes then
1273     --     context.sprint(catcodes,str)
1274     -- else
1275     --     context(str)
1276     -- end
1277        helpers.title(str,metadata)
1278    end
1279end
1280
1281implement {
1282    name      = "listuserdata",
1283    actions   = userdata,
1284    arguments = { "string", "integer", "string" }
1285}
1286
1287-- we could also set variables .. names will change (when this module is done)
1288-- maybe strc_lists_savedtitle etc
1289
1290implement { name = "doifelselisthastitle",  actions = { lists.hastitledata,  commands.doifelse }, arguments = { "string", "integer" } }
1291implement { name = "doifelselisthaspage",   actions = { lists.haspagedata,   commands.doifelse }, arguments = { "string", "integer" } }
1292implement { name = "doifelselisthasnumber", actions = { lists.hasnumberdata, commands.doifelse }, arguments = { "string", "integer" } }
1293implement { name = "doifelselisthasentry",  actions = { lists.iscached,      commands.doifelse }, arguments = "integer" }
1294
1295local function savedlisttitle(name,n,tag)
1296    local data = cached[tonumber(n)]
1297    if data then
1298        local titledata = data.titledata
1299        if titledata then
1300            helpers.title(titledata[tag] or titledata.title or "",data.metadata)
1301        end
1302    end
1303end
1304
1305local function savedlistnumber(name,n)
1306    local data = cached[tonumber(n)]
1307    if data then
1308        local numberdata = data.numberdata
1309        if numberdata then
1310            typesetnumber(numberdata,"number",numberdata or false)
1311        end
1312    end
1313end
1314
1315local function savedlistprefixednumber(name,n)
1316    local data = cached[tonumber(n)]
1317    if data then
1318        local numberdata = lists.reordered(data)
1319        if numberdata then
1320            helpers.prefix(data,data.prefixdata)
1321            typesetnumber(numberdata,"number",numberdata or false)
1322        end
1323    end
1324end
1325
1326lists.savedlisttitle          = savedlisttitle
1327lists.savedlistnumber         = savedlistnumber
1328lists.savedlistprefixednumber = savedlistprefixednumber
1329
1330implement {
1331    name      = "savedlistnumber",
1332    actions   = savedlistnumber,
1333    arguments = { "string", "integer" }
1334}
1335
1336implement {
1337    name      = "savedlisttitle",
1338    actions   = savedlisttitle,
1339    arguments = { "string", "integer" }
1340}
1341
1342implement {
1343    name      = "savedlistprefixednumber",
1344    actions   = savedlistprefixednumber,
1345    arguments = { "string", "integer" }
1346}
1347
1348implement {
1349    name      = "discardfromlist",
1350    actions   = lists.discard,
1351    arguments = "integer"
1352}
1353
1354implement {
1355    name      = "rawlistnumber",
1356    actions   = { lists.rawnumber, context },
1357    arguments = { "integer", "string" },
1358}
1359
1360-- new and experimental and therefore off by default
1361
1362lists.autoreorder = false -- true
1363
1364local function addlevel(t,k)
1365    local v = { }
1366    setmetatableindex(v,function(t,k)
1367        local v = { }
1368        t[k] = v
1369        return v
1370    end)
1371    t[k] = v
1372    return v
1373end
1374
1375local internals = setmetatableindex({ }, function(t,k)
1376
1377    local sublists = setmetatableindex({ },addlevel)
1378
1379    local collected = lists.collected or { }
1380
1381    for i=1,#collected do
1382        local entry = collected[i]
1383        local numberdata = entry.numberdata
1384        if numberdata then
1385            local metadata = entry.metadata
1386            if metadata then
1387                local references = entry.references
1388                if references then
1389                    local kind = metadata.kind
1390                    local name = numberdata.counter or metadata.name
1391                    local internal = references.internal
1392                    if kind and name and internal then
1393                        local sublist = sublists[kind][name]
1394                        sublist[#sublist + 1] = { internal, numberdata }
1395                    end
1396                end
1397            end
1398        end
1399    end
1400
1401    for k, v in next, sublists do
1402        for k, v in next, v do
1403            local tmp = { }
1404            for i=1,#v do
1405                tmp[i] = v[i]
1406            end
1407            sort(v,function(a,b) return a[1] < b[1] end)
1408            for i=1,#v do
1409                t[v[i][1]] = tmp[i][2]
1410            end
1411        end
1412    end
1413
1414    setmetatableindex(t,nil)
1415
1416    return t[k]
1417
1418end)
1419
1420function lists.reordered(entry)
1421    local numberdata = entry.numberdata
1422    if lists.autoreorder then
1423        if numberdata then
1424            local metadata = entry.metadata
1425            if metadata then
1426                local references = entry.references
1427                if references then
1428                    local kind = metadata.kind
1429                    local name = numberdata.counter or metadata.name
1430                    local internal = references.internal
1431                    if kind and name and internal then
1432                        return internals[internal] or numberdata
1433                    end
1434                end
1435            end
1436        end
1437    else
1438        function lists.reordered(entry)
1439            return entry.numberdata
1440        end
1441    end
1442    return numberdata
1443end
1444