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