strc-doc.lua /size: 45 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['strc-doc'] = {
2    version   = 1.001,
3    comment   = "companion to strc-doc.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-- todo: associate counter with head
10-- we need to better split the lua/tex end
11-- we need to freeze and document this module
12
13-- keep this as is:
14--
15-- in section titles by default a zero aborts, so there we need: sectionset=bagger with \definestructureprefixset [bagger] [section-2,section-4] []
16-- in lists however zero's are ignored, so there numbersegments=2:4 gives result
17
18local next, type, tonumber, select = next, type, tonumber, select
19local find, match = string.find, string.match
20local concat, fastcopy, insert, remove = table.concat, table.fastcopy, table.insert, table.remove
21local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys
22local max, min = math.max, math.min
23local allocate, mark, accesstable = utilities.storage.allocate, utilities.storage.mark, utilities.tables.accesstable
24local setmetatableindex = table.setmetatableindex
25local lpegmatch, P, C = lpeg.match, lpeg.P, lpeg.C
26
27local catcodenumbers      = catcodes.numbers
28local ctxcatcodes         = catcodenumbers.ctxcatcodes
29local variables           = interfaces.variables
30
31local implement           = interfaces.implement
32
33local v_last              = variables.last
34local v_first             = variables.first
35local v_previous          = variables.previous
36local v_next              = variables.next
37local v_auto              = variables.auto
38local v_strict            = variables.strict
39local v_all               = variables.all
40local v_positive          = variables.positive
41local v_current           = variables.current
42
43local texgetcount         = tex.getcount
44
45local trace_sectioning    = false  trackers.register("structures.sectioning", function(v) trace_sectioning = v end)
46local trace_details       = false  trackers.register("structures.details",    function(v) trace_details    = v end)
47
48local report_structure    = logs.reporter("structure","sectioning")
49local report_used         = logs.reporter("structure")
50
51local context             = context
52local commands            = commands
53local ctx_doifelse        = commands.doifelse
54
55local structures          = structures
56local helpers             = structures.helpers
57local documents           = structures.documents
58local sections            = structures.sections
59local lists               = structures.lists
60local counters            = structures.counters
61local sets                = structures.sets
62local tags                = structures.tags
63
64local processors          = typesetters.processors
65local applyprocessor      = processors.apply
66local startapplyprocessor = processors.startapply
67local stopapplyprocessor  = processors.stopapply
68local strippedprocessor   = processors.stripped
69
70local convertnumber       = converters.convert
71
72local ctx_convertnumber   = context.convertnumber
73local ctx_sprint          = context.sprint
74local ctx_finalizeauto    = context.finalizeautostructurelevel
75
76-- -- -- document -- -- --
77
78local data -- the current state
79
80function documents.initialize()
81    data = allocate { -- whole data is marked
82        numbers    = { },
83        forced     = { },
84        ownnumbers = { },
85        status     = { },
86        checkers   = { },
87        depth      = 0,
88        blocks     = { },
89        block      = "",
90    }
91    documents.data = data
92end
93
94function documents.reset()
95    data.numbers    = { }
96    data.forced     = { }
97    data.ownnumbers = { }
98    data.status     = { }
99 -- data.checkers   = { }
100    data.depth      = 0
101end
102
103documents.initialize()
104
105-- -- -- components -- -- --
106
107function documents.preset(numbers)
108    local nofnumbers = #numbers
109    local ownnumbers = { }
110    data.numbers     = numbers
111    data.ownnumbers  = ownnumbers
112    data.depth       = nofnumbers
113    for i=1,nofnumbers do
114        ownnumbers[i] = ""
115    end
116    sections.setnumber(nofnumbers,"-1")
117end
118
119-- -- -- sections -- -- --
120
121-- This is just a quick way to have access to prefixes and the numbers (section entry in a ref)
122-- is not the list entry. An alternative is to  use the list index of the last numbered section. In
123-- that case we should check a buse of the current structure.
124
125local collected  = allocate()
126local tobesaved  = allocate()
127
128sections.collected  = collected
129sections.tobesaved  = tobesaved
130
131-- We have to save this mostly redundant list because we can have (rare)
132-- cases with own numbers that don't end up in the list so we get out of
133-- sync when we use (*).
134
135local function initializer()
136    collected = sections.collected
137    tobesaved = sections.tobesaved
138end
139
140job.register('structures.sections.collected', tobesaved, initializer)
141
142local registered    = sections.registered or allocate()
143sections.registered = registered
144
145storage.register("structures/sections/registered", registered, "structures.sections.registered")
146
147local function update(name,level,section)
148    for k, v in next, registered do
149        if k ~= name and v.coupling == name then
150            report_structure("updating section level %a to level of %a",k,name)
151            context.doredefinehead(k,name)
152            update(k,level,section)
153        end
154    end
155end
156
157function sections.register(name,specification)
158    registered[name] = specification
159    local level   = specification.level
160    local section = specification.section
161    update(name,level,section)
162end
163
164function sections.currentid()
165    return #tobesaved
166end
167
168local lastsaved = 0
169
170function sections.save(sectiondata)
171    local sectiondata = helpers.simplify(sectiondata) -- maybe done earlier
172    local numberdata  = sectiondata.numberdata
173    local ntobesaved  = #tobesaved
174    if not numberdata or sectiondata.metadata.nolist then
175        -- stay
176    else
177        ntobesaved = ntobesaved + 1
178        tobesaved[ntobesaved] = numberdata
179        if not collected[ntobesaved] then
180            collected[ntobesaved] = numberdata
181        end
182    end
183    lastsaved = ntobesaved
184    return ntobesaved
185end
186
187function sections.currentsectionindex()
188    return lastsaved -- only for special controlled situations
189end
190
191-- See comment above (*). We cannot use the following space optimization:
192--
193-- function sections.load()
194--     setmetatableindex(collected,nil)
195--     local lists = lists.collected
196--     for i=1,#lists do
197--         local list = lists[i]
198--         local metadata = list.metadata
199--         if metadata and metadata.kind == "section" and not metadata.nolist then
200--             local numberdata = list.numberdata
201--             if numberdata then
202--                 collected[#collected+1] = numberdata
203--             end
204--         end
205--     end
206--     sections.load = functions.dummy
207-- end
208--
209-- table.setmetatableindex(collected, function(t,i)
210--     sections.load()
211--     return collected[i] or { }
212-- end)
213
214sections.verbose          = true
215
216local sectionblockdata    = sections.sectionblockdata or { }
217sections.sectionblockdata = sectionblockdata
218
219local levelmap            = sections.levelmap or { }
220sections.levelmap         = levelmap
221levelmap.block            = -1
222
223storage.register("structures/sections/levelmap", sections.levelmap, "structures.sections.levelmap")
224
225function sections.setlevel(name,level) -- level can be number or parent (=string)
226    local l = tonumber(level)
227    if not l then
228        l = levelmap[level]
229    end
230    if l and l > 0 then
231        levelmap[name] = l
232    else
233        -- error
234    end
235end
236
237function sections.getlevel(name)
238    return levelmap[name] or 0
239end
240
241table.setmetatableindex(sectionblockdata,"table")
242
243function sections.setblock(name,settings)
244    local block = name or data.block or "unknown" -- can be used to set the default
245    data.block = block
246    sectionblockdata[block] = settings
247    return block
248end
249
250local jobvariables = job.variables
251local pushed_order = { }
252local pushed_done  = { }
253
254jobvariables.tobesaved.sectionblockorder = pushed_order
255
256-- function sections.order()
257--     return jobvariables.collected.sectionblockorder or pushed_order -- so we have a first pass list too
258-- end
259
260function sections.setinitialblock(default)
261    local order = jobvariables.collected.sectionblockorder or pushed_order
262    local name  = #order > 0 and order[1] or default or "bodypart"
263    context.setsectionblock { name }
264 -- interfaces.setmacro("currentsectionblock",name)
265 -- sections.setblock(name,{})
266end
267
268function sections.pushblock(name,settings)
269    counters.check(0) -- we assume sane usage of \page between blocks
270    local block = name or data.block
271    insert(data.blocks,block)
272    data.block = block
273    sectionblockdata[block] = settings
274    documents.reset()
275    if not pushed_done[name] then
276        pushed_done[name] = true
277        local nofpushed = #pushed_order + 1
278        pushed_order[nofpushed] = name
279    end
280    return block
281end
282
283function sections.popblock()
284    local block = remove(data.blocks) or data.block
285    data.block = block
286    documents.reset()
287    return block
288end
289
290local function getcurrentblock()
291    return data.block or data.blocks[#data.blocks] or "unknown"
292end
293
294sections.currentblock = getcurrentblock
295
296function sections.currentlevel()
297    return data.depth
298end
299
300function sections.getcurrentlevel()
301    context(data.depth)
302end
303
304local saveset = { } -- experiment, see sections/tricky-001.tex
305
306function sections.setentry(given)
307    -- old number
308    local numbers     = data.numbers
309    --
310    local metadata    = given.metadata
311    local numberdata  = given.numberdata
312    local references  = given.references
313    local directives  = given.directives
314    local userdata    = given.userdata
315
316    if not metadata then
317        metadata       = { }
318        given.metadata = metadata
319    end
320    if not numberdata then
321        numberdata = { }
322        given.numberdata = numberdata
323    end
324    if not references then
325        references       = { }
326        given.references = references
327    end
328
329    local ownnumbers  = data.ownnumbers
330    local forced      = data.forced
331    local status      = data.status
332    local olddepth    = data.depth
333    local givenname   = metadata.name
334    local mappedlevel = levelmap[givenname]
335    local newdepth    = tonumber(mappedlevel or (olddepth > 0 and olddepth) or 1) -- hm, levelmap only works for section-*
336    local resetset    = directives and directives.resetset or ""
337 -- local resetter    = sets.getall("structure:resets",data.block,resetset)
338    -- a trick to permit userdata to overload title, ownnumber and reference
339    -- normally these are passed as argument but nowadays we provide several
340    -- interfaces (we need this because we want to be compatible)
341    if trace_details then
342        report_structure("name %a, mapped level %a, old depth %a, new depth %a, reset set %a",
343            givenname,mappedlevel,olddepth,newdepth,resetset)
344    end
345    if userdata then
346        -- kind of obsolete as we can pass them directly anyway ... NEEDS CHECKING !
347        if userdata.reference and userdata.reference ~= "" then given.metadata.reference   = userdata.reference ; userdata.reference = nil end
348        if userdata.ownnumber and userdata.ownnumber ~= "" then given.numberdata.ownnumber = userdata.ownnumber ; userdata.ownnumber = nil end
349        if userdata.title     and userdata.title     ~= "" then given.titledata.title      = userdata.title     ; userdata.title     = nil end
350        if userdata.bookmark  and userdata.bookmark  ~= "" then given.titledata.bookmark   = userdata.bookmark  ; userdata.bookmark  = nil end
351        if userdata.label     and userdata.label     ~= "" then given.titledata.label      = userdata.label     ; userdata.label     = nil end
352    end
353    -- so far for the trick
354    if saveset then
355        saveset[newdepth] = (resetset ~= "" and resetset) or saveset[newdepth] or ""
356    end
357    if newdepth > olddepth then
358        for i=olddepth+1,newdepth do
359            local s = tonumber(sets.get("structure:resets",data.block,saveset and saveset[i] or resetset,i))
360            if trace_details then
361                report_structure("new depth %s, old depth %s, reset set %a, reset value %a, current %a",olddepth,newdepth,resetset,s,numbers[i])
362            end
363            if not s or s == 0 then
364                numbers[i] = numbers[i] or 0
365                ownnumbers[i] = ownnumbers[i] or ""
366            else
367                numbers[i] = s - 1
368                ownnumbers[i] = ""
369            end
370            status[i] = { }
371        end
372    elseif newdepth < olddepth then
373        for i=olddepth,newdepth+1,-1 do
374            local s = tonumber(sets.get("structure:resets",data.block,saveset and saveset[i] or resetset,i))
375            if trace_details then
376                report_structure("new depth %s, old depth %s, reset set %a, reset value %a, current %a",olddepth,newdepth,resetset,s,numbers[i])
377            end
378            if not s or s == 0 then
379                numbers[i] = numbers[i] or 0
380                ownnumbers[i] = ownnumbers[i] or ""
381            else
382                numbers[i] = s - 1
383                ownnumbers[i] = ""
384            end
385            status[i] = nil
386        end
387    end
388    counters.check(newdepth)
389    ownnumbers[newdepth] = numberdata.ownnumber or ""
390    numberdata.ownnumber = nil
391    data.depth = newdepth
392    -- new number
393    olddepth = newdepth
394    if metadata.increment then
395        local oldn = numbers[newdepth] or 0
396        local newn = 0
397        local fd   = forced[newdepth]
398        if fd then
399            if fd[1] == "add" then
400                newn = oldn + fd[2] + 1
401            else
402                newn = fd[2] + 1
403            end
404            if newn < 0 then
405                newn = 1 -- maybe zero is nicer
406            end
407            forced[newdepth] = nil
408            if trace_details then
409                report_structure("old depth %a, new depth %a, old n %a, new n %a, forced %t",olddepth,newdepth,oldn,newn,fd)
410            end
411        else
412            newn = oldn + 1
413            if trace_details then
414                report_structure("old depth %a, new depth %a, old n %a, new n %a, increment",olddepth,newdepth,oldn,newn)
415            end
416        end
417        numbers[newdepth] = newn
418    end
419    status[newdepth] = given or { }
420    for k, v in next, data.checkers do
421        if v[1] == newdepth and v[2] then
422            v[2](k)
423        end
424    end
425    numberdata.numbers = { unpack(numbers,1,newdepth) }
426    if not numberdata.block then
427        numberdata.block = getcurrentblock() -- also in references
428    end
429    if #ownnumbers > 0 then
430        numberdata.ownnumbers = fastcopy(ownnumbers) -- { unpack(ownnumbers) }
431    end
432    if trace_details then
433        report_structure("name %a, numbers % a, own numbers % a",givenname,numberdata.numbers,numberdata.ownnumbers)
434    end
435    if not references.block then
436        references.block = getcurrentblock() -- also in numberdata
437    end
438    local tag = references.tag or tags.getid(metadata.kind,metadata.name)
439    if tag and tag ~= "" and tag ~= "?" then
440        references.tag = tag
441    end
442    local setcomponent = structures.references.setcomponent
443    if setcomponent then
444        setcomponent(given) -- might move to the tex end
445    end
446    references.section = sections.save(given)
447 -- given.numberdata = nil
448end
449
450function sections.reportstructure()
451    if sections.verbose then
452        local numbers    = data.numbers
453        local ownnumbers = data.ownnumbers
454        local status     = data.status
455        local depth      = data.depth
456        local d = status[depth]
457        local o = concat(ownnumbers,".",1,depth)
458        local n = (numbers and concat(numbers,".",1,min(depth,#numbers))) or 0
459        local t = d.titledata.title
460        local l = t or ""
461        local t = (l ~= "" and l) or t or "[no title]"
462        local m = d.metadata.name
463        if o and not find(o,"^%.*$") then
464            report_structure("%s @ level %i : (%s) %s -> %s",m,depth,n,o,t)
465        elseif d.directives and d.directives.hidenumber then
466            report_structure("%s @ level %i : (%s) -> %s",m,depth,n,t)
467        else
468            report_structure("%s @ level %i : %s -> %s",m,depth,n,t)
469        end
470    end
471end
472
473-- function sections.setnumber(depth,n)
474--     local forced, depth, new = data.forced, depth or data.depth, tonumber(n) or 0
475--     if type(n) == "string" then
476--         if find(n,"^[%+%-]") then
477--             forced[depth] = { "add", new }
478--         else
479--             forced[depth] = { "set", new }
480--         end
481--     else
482--         forced[depth] = { "set", new }
483--     end
484-- end
485
486function sections.setnumber(depth,n)
487    data.forced[depth or data.depth] = {
488        type(n) == "string" and find(n,"^[%+%-]") and "add" or "set",
489        tonumber(n) or 0
490    }
491end
492
493function sections.numberatdepth(depth)
494    return data.numbers[tonumber(depth) or sections.getlevel(depth) or 0] or 0
495end
496
497function sections.numbers()
498    return data.numbers
499end
500
501function sections.matchingtilldepth(depth,numbers,parentnumbers)
502    local dn = parentnumbers or data.numbers
503    local ok = false
504    for i=1,depth do
505        if dn[i] == numbers[i] then
506            ok = true
507        else
508            return false
509        end
510    end
511    return ok
512end
513
514function sections.getnumber(depth) -- redefined later ...
515    context(data.numbers[depth] or 0)
516end
517
518function sections.set(key,value)
519    data.status[data.depth][key] = value -- may be nil for a reset
520end
521
522function sections.cct()
523    local metadata = data.status[data.depth].metadata
524    context(metadata and metadata.catcodes or ctxcatcodes)
525end
526
527-- this one will become: return catcode, d (etc)
528
529-- function sections.structuredata(depth,key,default,honorcatcodetable) -- todo: spec table and then also depth
530--     if depth then
531--         depth = levelmap[depth] or tonumber(depth)
532--     end
533--     if not depth or depth == 0 then
534--         depth = data.depth
535--     end
536--     local data = data.status[depth]
537--     local d
538--     if data then
539--         if find(key,".",1,true) then
540--             d = accesstable(key,data)
541--         else
542--             d = data.titledata
543--             d = d and d[key]
544--         end
545--     end
546--     if d and type(d) ~= "table" then
547--         if honorcatcodetable == true or honorcatcodetable == v_auto then
548--             local metadata = data.metadata
549--             local catcodes = metadata and metadata.catcodes
550--             if catcodes then
551--                 ctx_sprint(catcodes,d)
552--             else
553--                 context(d)
554--             end
555--         elseif not honorcatcodetable or honorcatcodetable == "" then
556--             context(d)
557--         else
558--             local catcodes = catcodenumbers[honorcatcodetable]
559--             if catcodes then
560--                 ctx_sprint(catcodes,d)
561--             else
562--                 context(d)
563--             end
564--         end
565--     elseif default then
566--         context(default)
567--     end
568-- end
569
570function sections.structuredata(depth,key,default,honorcatcodetable) -- todo: spec table and then also depth
571    local detail = false
572    if type(depth) == "string" then
573        depth, detail = string.splitup(depth,":")
574    end
575    if depth then
576        depth = levelmap[depth] or tonumber(depth)
577    end
578    if not depth or depth == 0 then
579        depth = data.depth
580    end
581    local useddata
582    if detail == "+" then
583        useddata = structures.lists.collected[#structures.lists.tobesaved+1]
584    else
585        useddata = data.status[depth]
586    end
587    local d
588    if useddata then
589        if find(key,".",1,true) then
590            d = accesstable(key,useddata)
591        else
592            d = useddata.titledata
593            d = d and d[key]
594        end
595    end
596    if d and type(d) ~= "table" then
597        if honorcatcodetable == true or honorcatcodetable == v_auto then
598            local metadata = useddata.metadata
599            local catcodes = metadata and metadata.catcodes
600            if catcodes then
601                ctx_sprint(catcodes,d)
602            else
603                context(d)
604            end
605        elseif not honorcatcodetable or honorcatcodetable == "" then
606            context(d)
607        else
608            local catcodes = catcodenumbers[honorcatcodetable]
609            if catcodes then
610                ctx_sprint(catcodes,d)
611            else
612                context(d)
613            end
614        end
615    elseif default then
616        context(default)
617    end
618end
619
620-- function sections.userdata(depth,key,default)
621--     if depth then
622--         depth = levelmap[depth] or tonumber(depth)
623--     end
624--     if not depth or depth == 0 then
625--         depth = data.depth
626--     end
627--     if depth > 0 then
628--         local userdata = data.status[depth]
629--         userdata = userdata and userdata.userdata
630--         userdata = (userdata and userdata[key]) or default
631--         if userdata then
632--             context(userdata)
633--         end
634--     end
635-- end
636
637function sections.userdata(depth,key,default)
638    local detail = false
639    if type(depth) == "string" then
640        depth, detail = string.splitup(depth,":")
641    end
642    if depth then
643        depth = levelmap[depth]
644    end
645    if not depth then
646        depth = tonumber(depth)
647    end
648    if not depth or depth == 0 then
649        depth = data.depth
650    end
651    local userdata
652    if detail == "+" then
653        userdata = structures.lists.collected[#structures.lists.tobesaved+1]
654        if userdata then
655            userdata = userdata.userdata
656        end
657    elseif depth > 0 then
658        userdata = data.status[depth]
659        userdata = userdata and userdata.userdata
660    end
661    userdata = (userdata and userdata[key]) or default
662    if userdata then
663        context(userdata)
664    end
665end
666
667function sections.setchecker(name,level,command) -- hm, checkers are not saved
668    data.checkers[name] = (name and command and level >= 0 and { level, command }) or nil
669end
670
671function sections.current()
672    return data.status[data.depth]
673end
674
675local function depthnumber(n)
676    local depth = data.depth
677    if not n or n == 0 then
678        n = depth
679    elseif n < 0 then
680        n = depth + n
681    end
682    return data.numbers[n] or 0
683end
684
685sections.depthnumber = depthnumber
686
687function sections.autodepth(numbers)
688    for i=#numbers,1,-1 do
689        if numbers[i] ~= 0 then
690            return i
691        end
692    end
693    return 0
694end
695
696--
697
698function structures.currentsectionnumber() -- brr, namespace wrong
699    local sc = sections.current()
700    return sc and sc.numberdata
701end
702
703-- \dorecurse{3} {
704--     \chapter{Blabla}                 \subsection{bla 1 1} \subsection{bla 1 2}
705--                      \section{bla 2} \subsection{bla 2 1} \subsection{bla 2 2}
706-- }
707
708-- sign=all      => also zero and negative
709-- sign=positive => also zero
710-- sign=hang     => llap sign
711
712-- this can be a local function
713
714local function process(index,numbers,ownnumbers,criterium,separatorset,conversion,conversionset,entry,result,preceding,done,language)
715    -- todo: too much (100 steps)
716    local number = numbers and (numbers[index] or 0)
717    local ownnumber = ownnumbers and ownnumbers[index] or ""
718    if number > criterium or (ownnumber ~= "") then
719        local block = (entry.block ~= "" and entry.block) or sections.currentblock() -- added
720        if preceding then
721            local separator = sets.get("structure:separators",block,separatorset,preceding,".")
722            if separator then
723                if result then
724                    result[#result+1] = strippedprocessor(separator)
725                else
726                    applyprocessor(separator)
727                end
728            end
729            preceding = false
730        end
731        if result then
732            if ownnumber ~= "" then
733                result[#result+1] = ownnumber
734            elseif conversion and conversion ~= "" then -- traditional (e.g. used in itemgroups) .. inherited!
735                result[#result+1] = convertnumber(conversion,number,language)
736            else
737                local theconversion = sets.get("structure:conversions",block,conversionset,index,"numbers")
738                result[#result+1] = convertnumber(theconversion,number,language)
739            end
740        else
741            if ownnumber ~= "" then
742                applyprocessor(ownnumber)
743            elseif conversion and conversion ~= "" then -- traditional (e.g. used in itemgroups)
744                ctx_convertnumber(conversion,number)
745            else
746                local theconversion = sets.get("structure:conversions",block,conversionset,index,"numbers")
747                local data = startapplyprocessor(theconversion)
748                ctx_convertnumber(data or "numbers",number)
749                stopapplyprocessor()
750            end
751        end
752        return index, true
753    else
754        return preceding or false, done
755    end
756end
757
758-- kind : section number prefix
759
760function sections.typesetnumber(entry,kind,...)
761    --
762    -- Maybe the hiding becomes an option .. after all this test was there
763    -- for a reason, but for now we have this:
764    --
765 -- if entry and entry.hidenumber ~= true then
766    if entry then
767        local separatorset  = ""
768        local conversionset = ""
769        local conversion    = ""
770        local groupsuffix   = ""
771        local stopper       = ""
772        local starter       = ""
773        local connector     = ""
774        local set           = ""
775        local segments      = ""
776        local criterium     = ""
777        local language      = ""
778        for d=1,select("#",...) do
779            local data = select(d,...) -- can be multiple parametersets
780            if data then
781                if separatorset  == "" then separatorset  = data.separatorset  or "" end
782                if conversionset == "" then conversionset = data.conversionset or "" end
783                if conversion    == "" then conversion    = data.conversion    or "" end
784                if groupsuffix   == "" then groupsuffix   = data.groupsuffix   or "" end
785                if stopper       == "" then stopper       = data.stopper       or "" end
786                if starter       == "" then starter       = data.starter       or "" end
787                if connector     == "" then connector     = data.connector     or "" end
788                if set           == "" then set           = data.set           or "" end
789                if segments      == "" then segments      = data.segments      or "" end
790                if criterium     == "" then criterium     = data.criterium     or "" end
791                if language      == "" then language      = data.language      or "" end
792            end
793        end
794        if separatorset  == "" then separatorset  = "default"  end
795        if conversionset == "" then conversionset = "default"  end -- not used
796        if conversion    == "" then conversion    = nil        end
797        if groupsuffix   == "" then groupsuffix   = nil        end
798        if stopper       == "" then stopper       = nil        end
799        if starter       == "" then starter       = nil        end
800        if connector     == "" then connector     = nil        end
801        if set           == "" then set           = "default"  end
802        if segments      == "" then segments      = nil        end
803        if language      == "" then language      = nil        end
804        --
805        if criterium == v_strict then
806            criterium = 0
807        elseif criterium == v_positive then
808            criterium = -1
809        elseif criterium == v_all then
810            criterium = -1000000
811        else
812            criterium = 0
813        end
814        --
815        local firstprefix =  0
816        local lastprefix  = 16 -- too much, could max found level
817        if segments == v_current then
818            firstprefix = data.depth
819            lastprefix  = firstprefix
820        elseif segments then
821            local f, l = match(tostring(segments),"^(.-):(.+)$")
822            if l == "*" or l == v_all then
823                l = 100 -- new
824            end
825            if f and l then
826                -- 0:100, chapter:subsubsection
827                firstprefix = tonumber(f) or sections.getlevel(f) or 0
828                lastprefix  = tonumber(l) or sections.getlevel(l) or 100
829            else
830                -- 3, section
831                local fl = tonumber(segments) or sections.getlevel(segments) -- generalize
832                if fl then
833                    firstprefix = fl
834                    lastprefix  = fl
835                end
836            end
837        end
838        --
839        local numbers    = entry.numbers
840        local ownnumbers = entry.ownnumbers
841        if numbers then
842            local done      = false
843            local preceding = false
844            --
845            local result = kind == "direct" and { }
846            if result then
847                connector = false
848            end
849            --
850            local prefixlist = set and sets.getall("structure:prefixes","",set) -- "" == block
851            if starter then
852                if result then
853                    result[#result+1] = strippedprocessor(starter)
854                else
855                    applyprocessor(starter)
856                end
857            end
858-- inspect(entry)
859            if prefixlist and (kind == "section" or kind == "prefix" or kind == "direct") then
860                -- find valid set (problem: for sectionnumber we should pass the level)
861                -- no holes
862                local b  = 1
863                local e  = #prefixlist
864                local bb = 0
865                local ee = 0
866                -- find last valid number
867-- print("index >>",b,e)
868-- inspect(prefixlist)
869                for k=e,b,-1 do
870                    local prefix = prefixlist[k]
871                    local index  = sections.getlevel(prefix) or k
872                    if index >= firstprefix and index <= lastprefix then
873                        local number = numbers and numbers[index]
874                        if number then
875                            local ownnumber = ownnumbers and ownnumbers[index] or ""
876                            if number > 0 or (ownnumber ~= "") then
877                                break
878                            else
879                                e = k -1
880                            end
881                        end
882                    end
883                end
884                -- find valid range
885                for k=b,e do
886                    local prefix = prefixlist[k]
887                    local index  = sections.getlevel(prefix) or k
888                    if index >= firstprefix and index <= lastprefix then
889                        local number = numbers and numbers[index]
890                        if number then
891                            local ownnumber = ownnumbers and ownnumbers[index] or ""
892                            if number > 0 or (ownnumber ~= "") then
893                                if bb == 0 then
894                                    bb = k
895                                end
896                                ee = k
897                            elseif criterium >= 0 then
898                                bb = 0
899                                ee = 0
900                            end
901                        else
902                            break
903                        end
904                    end
905                end
906                -- print valid range
907                for k=bb,ee do
908                    local prefix = prefixlist[k]
909                    local index = sections.getlevel(prefix) or k
910                    if index >= firstprefix and index <= lastprefix then
911                        preceding, done = process(index,numbers,ownnumbers,criterium,separatorset,conversion,conversionset,entry,result,preceding,done,language)
912                    end
913                end
914            else
915                -- also holes check
916                for index=firstprefix,lastprefix do
917                    preceding, done = process(index,numbers,ownnumbers,criterium,separatorset,conversion,conversionset,entry,result,preceding,done,language)
918                end
919            end
920            --
921            if done then
922                if connector and kind == 'prefix' then
923                    if result then
924                        -- can't happen as we're in 'direct'
925                    else
926                        applyprocessor(connector)
927                    end
928                else
929                    if groupsuffix and kind ~= "prefix" then
930                        if result then
931                            result[#result+1] = strippedprocessor(groupsuffix)
932                        else
933                           applyprocessor(groupsuffix)
934                        end
935                    end
936                    if stopper then
937                        if result then
938                            result[#result+1] = strippedprocessor(stopper)
939                        else
940                            applyprocessor(stopper)
941                        end
942                    end
943                end
944            end
945            return result -- a table !
946        else
947        --  report_structure("error: no numbers")
948        end
949    end
950end
951
952function sections.title()
953    local sc = sections.current()
954    if sc then
955        helpers.title(sc.titledata.title,sc.metadata)
956    end
957end
958
959function sections.findnumber(depth,what) -- needs checking (looks wrong and slow too)
960    local data = data.status[depth or data.depth]
961    if not data then
962        return
963    end
964    local references = data.references
965    if not references then
966        return
967    end
968    local index       = references.section
969    local collected   = sections.collected
970    local sectiondata = collected[index]
971    if sectiondata and sectiondata.hidenumber ~= true then -- can be nil
972        local quit = what == v_previous or what == v_next
973        if what == v_first or what == v_previous then
974            for i=index-1,1,-1 do
975                local s = collected[i]
976                if s then
977                    local n = s.numbers
978                    if #n == depth and n[depth] and n[depth] ~= 0 then
979                        sectiondata = s
980                        if quit then
981                            break
982                        end
983                    elseif #n < depth then
984                        break
985                    end
986                end
987            end
988        elseif what == v_last or what == v_next then
989            for i=index+1,#collected do
990                local s = collected[i]
991                if s then
992                    local n = s.numbers
993                    if #n == depth and n[depth] and n[depth] ~= 0 then
994                        sectiondata = s
995                        if quit then
996                            break
997                        end
998                    elseif #n < depth then
999                        break
1000                    end
1001                end
1002            end
1003        end
1004        return sectiondata
1005    end
1006end
1007
1008function sections.finddata(depth,what)
1009    local data = data.status[depth or data.depth]
1010    if not data then
1011        return
1012    end
1013    local references = data.references
1014    if not references then
1015        return
1016    end
1017    local index = references.listindex
1018    if not index then
1019        return
1020    end
1021    local collected = structures.lists.collected
1022    local quit      = what == v_previous or what == v_next
1023    if what == v_first or what == v_previous then
1024        for i=index-1,1,-1 do
1025            local s = collected[i]
1026            if not s then
1027                break
1028            elseif s.metadata.kind == "section" then -- maybe check on name
1029                local n = s.numberdata.numbers
1030                if #n == depth and n[depth] and n[depth] ~= 0 then
1031                    data = s
1032                    if quit then
1033                        break
1034                    end
1035                elseif #n < depth then
1036                    break
1037                end
1038            end
1039        end
1040    elseif what == v_last or what == v_next then
1041        for i=index+1,#collected do
1042            local s = collected[i]
1043            if not s then
1044                break
1045            elseif s.metadata.kind == "section" then -- maybe check on name
1046                local n = s.numberdata.numbers
1047                if #n == depth and n[depth] and n[depth] ~= 0 then
1048                    data = s
1049                    if quit then
1050                        break
1051                    end
1052                elseif #n < depth then
1053                    break
1054                end
1055            end
1056        end
1057    end
1058    return data
1059end
1060
1061function sections.internalreference(sectionname,what) -- to be used in pagebuilder (no marks used)
1062    local r = type(sectionname) == "number" and sectionname or registered[sectionname]
1063    if r then
1064        local data = sections.finddata(r.level,what)
1065        return data and data.references and data.references.internal
1066    end
1067end
1068
1069function sections.fullnumber(depth,what)
1070    local sectiondata = sections.findnumber(depth,what)
1071    if sectiondata then
1072        sections.typesetnumber(sectiondata,'section',sectiondata)
1073    end
1074end
1075
1076function sections.getnumber(depth,what) -- redefined here
1077    local sectiondata = sections.findnumber(depth,what)
1078    local askednumber = 0
1079    if sectiondata then
1080        local numbers = sectiondata.numbers
1081        if numbers then
1082            askednumber = numbers[depth] or 0
1083        end
1084    end
1085    context(askednumber)
1086end
1087
1088-- maybe handy
1089
1090function sections.showstructure()
1091
1092    local tobesaved = structures.lists.tobesaved
1093
1094    if not tobesaved then
1095        return
1096    end
1097
1098    local levels = setmetatableindex("table")
1099    local names  = setmetatableindex("table")
1100
1101    report_used()
1102    report_used("sections")
1103    for i=1,#tobesaved do
1104        local si = tobesaved[i]
1105        local md = si.metadata
1106        if md and md.kind == "section" then
1107            local level   = md.level
1108            local name    = md.name
1109            local numbers = si.numberdata.numbers
1110            local  title  = si.titledata.title
1111            report_used("  %i : %-10s %-20s %s",level,concat(numbers,"."),name,title)
1112            levels[level][name] = true
1113            names[name][level]  = true
1114        end
1115    end
1116    report_used()
1117    report_used("levels")
1118    for level, list in sortedhash(levels) do
1119        report_used("  %s : % t",level,sortedkeys(list))
1120    end
1121    report_used()
1122    report_used("names")
1123    for name, list in sortedhash(names) do
1124        report_used("  %-10s : % t",name,sortedkeys(list))
1125    end
1126    report_used()
1127end
1128
1129-- experimental
1130
1131local levels = { }
1132
1133local function autonextstructurelevel(level)
1134    if level > #levels then
1135        for i=#levels+1,level do
1136            levels[i] = false
1137        end
1138    else
1139        for i=level,#levels do
1140            if levels[i] then
1141                ctx_finalizeauto()
1142                levels[i] = false
1143            end
1144        end
1145    end
1146    levels[level] = true
1147end
1148
1149local function autofinishstructurelevels()
1150    for i=1,#levels do
1151        if levels[i] then
1152            ctx_finalizeauto()
1153        end
1154    end
1155    levels = { }
1156end
1157
1158implement {
1159    name      = "autonextstructurelevel",
1160    actions   = autonextstructurelevel,
1161    arguments = "integer",
1162}
1163
1164implement {
1165    name      = "autofinishstructurelevels",
1166    actions   = autofinishstructurelevels,
1167}
1168
1169-- interface (some are actually already commands, like sections.fullnumber)
1170
1171implement {
1172    name     = "depthnumber",
1173    actions  = { depthnumber, context },
1174    arguments = "integer",
1175}
1176
1177implement { name = "structurenumber",            actions = sections.fullnumber }
1178implement { name = "structuretitle",             actions = sections.title }
1179
1180implement { name = "structurevariable",          actions = sections.structuredata, arguments = { false, "string" } }
1181implement { name = "structureuservariable",      actions = sections.userdata,      arguments = { false, "string" } }
1182implement { name = "structurecatcodedget",       actions = sections.structuredata, arguments = { false, "string", false, true } }
1183implement { name = "structuregivencatcodedget",  actions = sections.structuredata, arguments = { false, "string", false, "integer" } }
1184implement { name = "structureautocatcodedget",   actions = sections.structuredata, arguments = { false, "string", false, "string" } }
1185
1186implement { name = "namedstructurevariable",     actions = sections.structuredata, arguments = "2 strings" }
1187implement { name = "namedstructureuservariable", actions = sections.userdata,      arguments = "2 strings" }
1188
1189implement { name = "setstructurelevel",          actions = sections.setlevel,        arguments = "2 strings" }
1190implement { name = "getstructurelevel",          actions = sections.getcurrentlevel, arguments = "string" }
1191implement { name = "setstructurenumber",         actions = sections.setnumber,       arguments = { "integer", "string" } } -- string as we support +-
1192implement { name = "getstructurenumber",         actions = sections.getnumber,       arguments = "integer" }
1193implement { name = "getsomestructurenumber",     actions = sections.getnumber,       arguments = { "integer", "string" } }
1194implement { name = "getfullstructurenumber",     actions = sections.fullnumber,      arguments = "integer" }
1195implement { name = "getsomefullstructurenumber", actions = sections.fullnumber,      arguments = { "integer", "string" } }
1196implement { name = "getspecificstructuretitle",  actions = sections.structuredata,   arguments = { "string", "'titledata.title'",false,"string" } }
1197
1198implement { name = "reportstructure",            actions = sections.reportstructure }
1199implement { name = "showstructure",              actions = sections.showstructure }
1200
1201implement {
1202    name      = "registersection",
1203    actions   = sections.register,
1204    arguments = {
1205        "string",
1206        {
1207            { "coupling" },
1208            { "section" },
1209            { "level", "integer" },
1210            { "parent" },
1211        }
1212    }
1213}
1214
1215implement {
1216    name      = "setsectionentry",
1217    actions   = sections.setentry,
1218    arguments = {
1219        {
1220            { "references", {
1221                    { "internal", "integer" },
1222                    { "block" },
1223                    { "backreference" },
1224                    { "prefix" },
1225                    { "reference" },
1226                }
1227            },
1228            { "directives", {
1229                    { "resetset" }
1230                }
1231            },
1232            { "metadata", {
1233                    { "kind" },
1234                    { "name" },
1235                    { "catcodes", "integer" },
1236                    { "coding" },
1237                    { "xmlroot" },
1238                    { "xmlsetup" },
1239                    { "nolist", "boolean" },
1240                    { "increment" },
1241                }
1242            },
1243            { "titledata", {
1244                    { "label" },
1245                    { "title" },
1246                    { "bookmark" },
1247                    { "marking" },
1248                    { "list" },
1249                    { "reference" },
1250                }
1251            },
1252            { "numberdata", {
1253                    { "block" },
1254                    { "hidenumber", "boolean" },
1255                    { "separatorset" },
1256                    { "conversionset" },
1257                    { "conversion" },
1258                    { "starter" },
1259                    { "stopper" },
1260                    { "set" },
1261                    { "segments" },
1262                    { "ownnumber" },
1263                    { "language" },
1264                    { "criterium" },
1265                },
1266            },
1267            { "userdata" },
1268        }
1269    }
1270}
1271
1272-- os.exit()
1273
1274implement {
1275    name      = "setsectionblock",
1276    actions   = sections.setblock,
1277    arguments = { "string", { { "bookmark" } } }
1278}
1279
1280implement {
1281    name      = "setinitialsectionblock",
1282    actions   = sections.setinitialblock,
1283    arguments = "string",
1284 -- onlyonce  = true,
1285}
1286
1287implement {
1288    name      = "pushsectionblock",
1289    actions   = sections.pushblock,
1290    arguments = { "string", { { "bookmark" } } }
1291}
1292
1293implement {
1294    name      = "popsectionblock",
1295    actions   = sections.popblock,
1296}
1297
1298interfaces.implement {
1299    name      = "doifelsefirstsectionpage",
1300    arguments = "1 argument",
1301    public    = true,
1302    protected = true,
1303    actions   = function(name)
1304        local found = false
1305     -- local list  = structures.lists.collected
1306        local list  = lists.collected
1307        if list then
1308            local realpage = texgetcount("realpageno")
1309            for i=1,#list do
1310                local listdata = list[i]
1311                local metadata = listdata.metadata
1312                if metadata and metadata.kind == "section" and metadata.name == name then
1313                 -- local current = structures.documents.data.status[metadata.level]
1314                    local current = data.status[metadata.level]
1315                    if current and current.references.internal == listdata.references.internal then
1316                        found = listdata.references.realpage == realpage
1317                        break
1318                    end
1319                end
1320            end
1321        end
1322        ctx_doifelse(found)
1323    end,
1324}
1325
1326-- could be faster (in huge lists)
1327
1328-- local firstpages = table.setmetatableindex(function(t,name)
1329--  -- local list  = structures.lists.collected
1330--     local list  = lists.collected
1331--     local pages = { }
1332--     if list then
1333--         for i=1,#list do
1334--             local listdata = list[i]
1335--             local metadata = listdata.metadata
1336--             if metadata and metadata.kind == "section" and metadata.name == name then
1337--                 local references = listdata.references
1338--                 if references then
1339--                     pages[references.internal] = listdata
1340--                 end
1341--             end
1342--         end
1343--     end
1344--     t[name] = pages
1345--     return pages
1346-- end)
1347--
1348-- interfaces.implement {
1349--     name      = "doifelsefirstsectionpage",
1350--     arguments = "1 argument",
1351--     public    = true,
1352--     protected = true,
1353--     actions   = function(name)
1354--         local found = firstpages[name]
1355--         if found then
1356--             local level = structures.sections.levelmap[name]
1357--             if level then
1358--              -- local current = structures.documents.data.status[level]
1359--                 local current = data.status[level]
1360--                 if current then
1361--                     local realpage = texgetcount("realpageno")
1362--                     found = found[current.references.internal]
1363--                     found = found and found.references.realpage == realpage
1364--                 else
1365--                     found = false
1366--                 end
1367--             else
1368--                 found = false
1369--             end
1370--         end
1371--         ctx_doifelse(found)
1372--     end,
1373-- }
1374