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