strc-doc.lua /size: 45 Kb    last modification: 2025-02-21 11:03
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 depth  = data.depth
453        local status = data.status
454        local d      = status[depth]
455        if d then
456            local numbers    = data.numbers
457            local ownnumbers = data.ownnumbers
458            local o = concat(ownnumbers,".",1,depth)
459            local n = (numbers and concat(numbers,".",1,min(depth,#numbers))) or 0
460            local t = d.titledata.title
461            local l = t or ""
462            local t = (l ~= "" and l) or t or "[no title]"
463            local m = d.metadata.name
464            if o and not find(o,"^%.*$") then
465                report_structure("%s @ level %i : (%s) %s -> %s",m,depth,n,o,t)
466            elseif d.directives and d.directives.hidenumber then
467                report_structure("%s @ level %i : (%s) -> %s",m,depth,n,t)
468            else
469                report_structure("%s @ level %i : %s -> %s",m,depth,n,t)
470            end
471        else
472            report_structure("invalid structure @ depth %S",depth)
473        end
474    end
475end
476
477-- function sections.setnumber(depth,n)
478--     local forced, depth, new = data.forced, depth or data.depth, tonumber(n) or 0
479--     if type(n) == "string" then
480--         if find(n,"^[%+%-]") then
481--             forced[depth] = { "add", new }
482--         else
483--             forced[depth] = { "set", new }
484--         end
485--     else
486--         forced[depth] = { "set", new }
487--     end
488-- end
489
490function sections.setnumber(depth,n)
491    data.forced[depth or data.depth] = {
492        type(n) == "string" and find(n,"^[%+%-]") and "add" or "set",
493        tonumber(n) or 0
494    }
495end
496
497function sections.numberatdepth(depth)
498    return data.numbers[tonumber(depth) or sections.getlevel(depth) or 0] or 0
499end
500
501function sections.numbers()
502    return data.numbers
503end
504
505function sections.matchingtilldepth(depth,numbers,parentnumbers)
506    local dn = parentnumbers or data.numbers
507    local ok = false
508    for i=1,depth do
509        if dn[i] == numbers[i] then
510            ok = true
511        else
512            return false
513        end
514    end
515    return ok
516end
517
518function sections.getnumber(depth) -- redefined later ...
519    context(data.numbers[depth] or 0)
520end
521
522function sections.set(key,value)
523    data.status[data.depth][key] = value -- may be nil for a reset
524end
525
526function sections.cct()
527    local metadata = data.status[data.depth].metadata
528    context(metadata and metadata.catcodes or ctxcatcodes)
529end
530
531-- this one will become: return catcode, d (etc)
532
533-- function sections.structuredata(depth,key,default,honorcatcodetable) -- todo: spec table and then also depth
534--     if depth then
535--         depth = levelmap[depth] or tonumber(depth)
536--     end
537--     if not depth or depth == 0 then
538--         depth = data.depth
539--     end
540--     local data = data.status[depth]
541--     local d
542--     if data then
543--         if find(key,".",1,true) then
544--             d = accesstable(key,data)
545--         else
546--             d = data.titledata
547--             d = d and d[key]
548--         end
549--     end
550--     if d and type(d) ~= "table" then
551--         if honorcatcodetable == true or honorcatcodetable == v_auto then
552--             local metadata = data.metadata
553--             local catcodes = metadata and metadata.catcodes
554--             if catcodes then
555--                 ctx_sprint(catcodes,d)
556--             else
557--                 context(d)
558--             end
559--         elseif not honorcatcodetable or honorcatcodetable == "" then
560--             context(d)
561--         else
562--             local catcodes = catcodenumbers[honorcatcodetable]
563--             if catcodes then
564--                 ctx_sprint(catcodes,d)
565--             else
566--                 context(d)
567--             end
568--         end
569--     elseif default then
570--         context(default)
571--     end
572-- end
573
574function sections.structuredata(depth,key,default,honorcatcodetable) -- todo: spec table and then also depth
575    local detail = false
576    if type(depth) == "string" then
577        depth, detail = string.splitup(depth,":")
578    end
579    if depth then
580        depth = levelmap[depth] or tonumber(depth)
581    end
582    if not depth or depth == 0 then
583        depth = data.depth
584    end
585    local useddata
586    if detail == "+" then
587        useddata = structures.lists.collected[#structures.lists.tobesaved+1]
588    else
589        useddata = data.status[depth]
590    end
591    local d
592    if useddata then
593        if find(key,".",1,true) then
594            d = accesstable(key,useddata)
595        else
596            d = useddata.titledata
597            d = d and d[key]
598        end
599    end
600    if d and type(d) ~= "table" then
601        if honorcatcodetable == true or honorcatcodetable == v_auto then
602            local metadata = useddata.metadata
603            local catcodes = metadata and metadata.catcodes
604            if catcodes then
605                ctx_sprint(catcodes,d)
606            else
607                context(d)
608            end
609        elseif not honorcatcodetable or honorcatcodetable == "" then
610            context(d)
611        else
612            local catcodes = catcodenumbers[honorcatcodetable]
613            if catcodes then
614                ctx_sprint(catcodes,d)
615            else
616                context(d)
617            end
618        end
619    elseif default then
620        context(default)
621    end
622end
623
624-- function sections.userdata(depth,key,default)
625--     if depth then
626--         depth = levelmap[depth] or tonumber(depth)
627--     end
628--     if not depth or depth == 0 then
629--         depth = data.depth
630--     end
631--     if depth > 0 then
632--         local userdata = data.status[depth]
633--         userdata = userdata and userdata.userdata
634--         userdata = (userdata and userdata[key]) or default
635--         if userdata then
636--             context(userdata)
637--         end
638--     end
639-- end
640
641function sections.userdata(depth,key,default)
642    local detail = false
643    if type(depth) == "string" then
644        depth, detail = string.splitup(depth,":")
645    end
646    if depth then
647        depth = levelmap[depth]
648    end
649    if not depth then
650        depth = tonumber(depth)
651    end
652    if not depth or depth == 0 then
653        depth = data.depth
654    end
655    local userdata
656    if detail == "+" then
657        userdata = structures.lists.collected[#structures.lists.tobesaved+1]
658        if userdata then
659            userdata = userdata.userdata
660        end
661    elseif depth > 0 then
662        userdata = data.status[depth]
663        userdata = userdata and userdata.userdata
664    end
665    userdata = (userdata and userdata[key]) or default
666    if userdata then
667        context(userdata)
668    end
669end
670
671function sections.setchecker(name,level,command) -- hm, checkers are not saved
672    data.checkers[name] = (name and command and level >= 0 and { level, command }) or nil
673end
674
675function sections.current()
676    return data.status[data.depth]
677end
678
679local function depthnumber(n)
680    local depth = data.depth
681    if not n or n == 0 then
682        n = depth
683    elseif n < 0 then
684        n = depth + n
685    end
686    return data.numbers[n] or 0
687end
688
689sections.depthnumber = depthnumber
690
691function sections.autodepth(numbers)
692    for i=#numbers,1,-1 do
693        if numbers[i] ~= 0 then
694            return i
695        end
696    end
697    return 0
698end
699
700--
701
702function structures.currentsectionnumber() -- brr, namespace wrong
703    local sc = sections.current()
704    return sc and sc.numberdata
705end
706
707-- \dorecurse{3} {
708--     \chapter{Blabla}                 \subsection{bla 1 1} \subsection{bla 1 2}
709--                      \section{bla 2} \subsection{bla 2 1} \subsection{bla 2 2}
710-- }
711
712-- sign=all      => also zero and negative
713-- sign=positive => also zero
714-- sign=hang     => llap sign
715
716-- this can be a local function
717
718local function process(index,numbers,ownnumbers,criterium,separatorset,conversion,conversionset,entry,result,preceding,done,language)
719    -- todo: too much (100 steps)
720    local number = numbers and (numbers[index] or 0)
721    local ownnumber = ownnumbers and ownnumbers[index] or ""
722    if number > criterium or (ownnumber ~= "") then
723        local block = (entry.block ~= "" and entry.block) or sections.currentblock() -- added
724        if preceding then
725            local separator = sets.get("structure:separators",block,separatorset,preceding,".")
726            if separator then
727                if result then
728                    result[#result+1] = strippedprocessor(separator)
729                else
730                    applyprocessor(separator)
731                end
732            end
733            preceding = false
734        end
735        if result then
736            if ownnumber ~= "" then
737                result[#result+1] = ownnumber
738            elseif conversion and conversion ~= "" then -- traditional (e.g. used in itemgroups) .. inherited!
739                result[#result+1] = convertnumber(conversion,number,language)
740            else
741                local theconversion = sets.get("structure:conversions",block,conversionset,index,"numbers")
742                result[#result+1] = convertnumber(theconversion,number,language)
743            end
744        else
745            if ownnumber ~= "" then
746                applyprocessor(ownnumber)
747            elseif conversion and conversion ~= "" then -- traditional (e.g. used in itemgroups)
748                ctx_convertnumber(conversion,number)
749            else
750                local theconversion = sets.get("structure:conversions",block,conversionset,index,"numbers")
751                local data = startapplyprocessor(theconversion)
752                ctx_convertnumber(data or "numbers",number)
753                stopapplyprocessor()
754            end
755        end
756        return index, true
757    else
758        return preceding or false, done
759    end
760end
761
762-- kind : section number prefix
763
764function sections.typesetnumber(entry,kind,...)
765    --
766    -- Maybe the hiding becomes an option .. after all this test was there
767    -- for a reason, but for now we have this:
768    --
769 -- if entry and entry.hidenumber ~= true then
770    if entry then
771        local separatorset  = ""
772        local conversionset = ""
773        local conversion    = ""
774        local groupsuffix   = ""
775        local stopper       = ""
776        local starter       = ""
777        local connector     = ""
778        local set           = ""
779        local segments      = ""
780        local criterium     = ""
781        local language      = ""
782        for d=1,select("#",...) do
783            local data = select(d,...) -- can be multiple parametersets
784            if data then
785                if separatorset  == "" then separatorset  = data.separatorset  or "" end
786                if conversionset == "" then conversionset = data.conversionset or "" end
787                if conversion    == "" then conversion    = data.conversion    or "" end
788                if groupsuffix   == "" then groupsuffix   = data.groupsuffix   or "" end
789                if stopper       == "" then stopper       = data.stopper       or "" end
790                if starter       == "" then starter       = data.starter       or "" end
791                if connector     == "" then connector     = data.connector     or "" end
792                if set           == "" then set           = data.set           or "" end
793                if segments      == "" then segments      = data.segments      or "" end
794                if criterium     == "" then criterium     = data.criterium     or "" end
795                if language      == "" then language      = data.language      or "" end
796            end
797        end
798        if separatorset  == "" then separatorset  = "default"  end
799        if conversionset == "" then conversionset = "default"  end -- not used
800        if conversion    == "" then conversion    = nil        end
801        if groupsuffix   == "" then groupsuffix   = nil        end
802        if stopper       == "" then stopper       = nil        end
803        if starter       == "" then starter       = nil        end
804        if connector     == "" then connector     = nil        end
805        if set           == "" then set           = "default"  end
806        if segments      == "" then segments      = nil        end
807        if language      == "" then language      = nil        end
808        --
809        if criterium == v_strict then
810            criterium = 0
811        elseif criterium == v_positive then
812            criterium = -1
813        elseif criterium == v_all then
814            criterium = -1000000
815        else
816            criterium = 0
817        end
818        --
819        local firstprefix =  0
820        local lastprefix  = 16 -- too much, could max found level
821        if segments == v_current then
822            firstprefix = data.depth
823            lastprefix  = firstprefix
824        elseif segments then
825            local f, l = match(tostring(segments),"^(.-):(.+)$")
826            if l == "*" or l == v_all then
827                l = 100 -- new
828            end
829            if f and l then
830                -- 0:100, chapter:subsubsection
831                firstprefix = tonumber(f) or sections.getlevel(f) or 0
832                lastprefix  = tonumber(l) or sections.getlevel(l) or 100
833            else
834                -- 3, section
835                local fl = tonumber(segments) or sections.getlevel(segments) -- generalize
836                if fl then
837                    firstprefix = fl
838                    lastprefix  = fl
839                end
840            end
841        end
842        --
843        local numbers    = entry.numbers
844        local ownnumbers = entry.ownnumbers
845        if numbers then
846            local done      = false
847            local preceding = false
848            --
849            local result = kind == "direct" and { }
850            if result then
851                connector = false
852            end
853            --
854            local prefixlist = set and sets.getall("structure:prefixes","",set) -- "" == block
855            if starter then
856                if result then
857                    result[#result+1] = strippedprocessor(starter)
858                else
859                    applyprocessor(starter)
860                end
861            end
862-- inspect(entry)
863            if prefixlist and (kind == "section" or kind == "prefix" or kind == "direct") then
864                -- find valid set (problem: for sectionnumber we should pass the level)
865                -- no holes
866                local b  = 1
867                local e  = #prefixlist
868                local bb = 0
869                local ee = 0
870                -- find last valid number
871-- print("index >>",b,e)
872-- inspect(prefixlist)
873                for k=e,b,-1 do
874                    local prefix = prefixlist[k]
875                    local index  = sections.getlevel(prefix) or k
876                    if index >= firstprefix and index <= lastprefix then
877                        local number = numbers and numbers[index]
878                        if number then
879                            local ownnumber = ownnumbers and ownnumbers[index] or ""
880                            if number > 0 or (ownnumber ~= "") then
881                                break
882                            else
883                                e = k -1
884                            end
885                        end
886                    end
887                end
888                -- find valid range
889                for k=b,e do
890                    local prefix = prefixlist[k]
891                    local index  = sections.getlevel(prefix) or k
892                    if index >= firstprefix and index <= lastprefix then
893                        local number = numbers and numbers[index]
894                        if number then
895                            local ownnumber = ownnumbers and ownnumbers[index] or ""
896                            if number > 0 or (ownnumber ~= "") then
897                                if bb == 0 then
898                                    bb = k
899                                end
900                                ee = k
901                            elseif criterium >= 0 then
902                                bb = 0
903                                ee = 0
904                            end
905                        else
906                            break
907                        end
908                    end
909                end
910                -- print valid range
911                for k=bb,ee do
912                    local prefix = prefixlist[k]
913                    local index = sections.getlevel(prefix) or k
914                    if index >= firstprefix and index <= lastprefix then
915                        preceding, done = process(index,numbers,ownnumbers,criterium,separatorset,conversion,conversionset,entry,result,preceding,done,language)
916                    end
917                end
918            else
919                -- also holes check
920                for index=firstprefix,lastprefix do
921                    preceding, done = process(index,numbers,ownnumbers,criterium,separatorset,conversion,conversionset,entry,result,preceding,done,language)
922                end
923            end
924            --
925            if done then
926                if connector and kind == 'prefix' then
927                    if result then
928                        -- can't happen as we're in 'direct'
929                    else
930                        applyprocessor(connector)
931                    end
932                else
933                    if groupsuffix and kind ~= "prefix" then
934                        if result then
935                            result[#result+1] = strippedprocessor(groupsuffix)
936                        else
937                           applyprocessor(groupsuffix)
938                        end
939                    end
940                    if stopper then
941                        if result then
942                            result[#result+1] = strippedprocessor(stopper)
943                        else
944                            applyprocessor(stopper)
945                        end
946                    end
947                end
948            end
949            return result -- a table !
950        else
951        --  report_structure("error: no numbers")
952        end
953    end
954end
955
956function sections.title()
957    local sc = sections.current()
958    if sc then
959        helpers.title(sc.titledata.title,sc.metadata)
960    end
961end
962
963function sections.findnumber(depth,what) -- needs checking (looks wrong and slow too)
964    local data = data.status[depth or data.depth]
965    if not data then
966        return
967    end
968    local references = data.references
969    if not references then
970        return
971    end
972    local index       = references.section
973    local collected   = sections.collected
974    local sectiondata = collected[index]
975    if sectiondata and sectiondata.hidenumber ~= true then -- can be nil
976        local quit = what == v_previous or what == v_next
977        if what == v_first or what == v_previous then
978            for i=index-1,1,-1 do
979                local s = collected[i]
980                if s then
981                    local n = s.numbers
982                    if #n == depth and n[depth] and n[depth] ~= 0 then
983                        sectiondata = s
984                        if quit then
985                            break
986                        end
987                    elseif #n < depth then
988                        break
989                    end
990                end
991            end
992        elseif what == v_last or what == v_next then
993            for i=index+1,#collected do
994                local s = collected[i]
995                if s then
996                    local n = s.numbers
997                    if #n == depth and n[depth] and n[depth] ~= 0 then
998                        sectiondata = s
999                        if quit then
1000                            break
1001                        end
1002                    elseif #n < depth then
1003                        break
1004                    end
1005                end
1006            end
1007        end
1008        return sectiondata
1009    end
1010end
1011
1012function sections.finddata(depth,what)
1013    local data = data.status[depth or data.depth]
1014    if not data then
1015        return
1016    end
1017    local references = data.references
1018    if not references then
1019        return
1020    end
1021    local index = references.listindex
1022    if not index then
1023        return
1024    end
1025    local collected = structures.lists.collected
1026    local quit      = what == v_previous or what == v_next
1027    if what == v_first or what == v_previous then
1028        for i=index-1,1,-1 do
1029            local s = collected[i]
1030            if not s then
1031                break
1032            elseif s.metadata.kind == "section" then -- maybe check on name
1033                local n = s.numberdata.numbers
1034                if #n == depth and n[depth] and n[depth] ~= 0 then
1035                    data = s
1036                    if quit then
1037                        break
1038                    end
1039                elseif #n < depth then
1040                    break
1041                end
1042            end
1043        end
1044    elseif what == v_last or what == v_next then
1045        for i=index+1,#collected do
1046            local s = collected[i]
1047            if not s then
1048                break
1049            elseif s.metadata.kind == "section" then -- maybe check on name
1050                local n = s.numberdata.numbers
1051                if #n == depth and n[depth] and n[depth] ~= 0 then
1052                    data = s
1053                    if quit then
1054                        break
1055                    end
1056                elseif #n < depth then
1057                    break
1058                end
1059            end
1060        end
1061    end
1062    return data
1063end
1064
1065function sections.internalreference(sectionname,what) -- to be used in pagebuilder (no marks used)
1066    local r = type(sectionname) == "number" and sectionname or registered[sectionname]
1067    if r then
1068        local data = sections.finddata(r.level,what)
1069        return data and data.references and data.references.internal
1070    end
1071end
1072
1073function sections.fullnumber(depth,what)
1074    local sectiondata = sections.findnumber(depth,what)
1075    if sectiondata then
1076        sections.typesetnumber(sectiondata,'section',sectiondata)
1077    end
1078end
1079
1080function sections.getnumber(depth,what) -- redefined here
1081    local sectiondata = sections.findnumber(depth,what)
1082    local askednumber = 0
1083    if sectiondata then
1084        local numbers = sectiondata.numbers
1085        if numbers then
1086            askednumber = numbers[depth] or 0
1087        end
1088    end
1089    context(askednumber)
1090end
1091
1092-- maybe handy
1093
1094function sections.showstructure()
1095
1096    local tobesaved = structures.lists.tobesaved
1097
1098    if not tobesaved then
1099        return
1100    end
1101
1102    local levels = setmetatableindex("table")
1103    local names  = setmetatableindex("table")
1104
1105    report_used()
1106    report_used("sections")
1107    for i=1,#tobesaved do
1108        local si = tobesaved[i]
1109        local md = si.metadata
1110        if md and md.kind == "section" then
1111            local level   = md.level
1112            local name    = md.name
1113            local numbers = si.numberdata.numbers
1114            local  title  = si.titledata.title
1115            report_used("  %i : %-10s %-20s %s",level,concat(numbers,"."),name,title)
1116            levels[level][name] = true
1117            names[name][level]  = true
1118        end
1119    end
1120    report_used()
1121    report_used("levels")
1122    for level, list in sortedhash(levels) do
1123        report_used("  %s : % t",level,sortedkeys(list))
1124    end
1125    report_used()
1126    report_used("names")
1127    for name, list in sortedhash(names) do
1128        report_used("  %-10s : % t",name,sortedkeys(list))
1129    end
1130    report_used()
1131end
1132
1133-- experimental
1134
1135local levels = { }
1136
1137local function autonextstructurelevel(level)
1138    if level > #levels then
1139        for i=#levels+1,level do
1140            levels[i] = false
1141        end
1142    else
1143        for i=level,#levels do
1144            if levels[i] then
1145                ctx_finalizeauto()
1146                levels[i] = false
1147            end
1148        end
1149    end
1150    levels[level] = true
1151end
1152
1153local function autofinishstructurelevels()
1154    for i=1,#levels do
1155        if levels[i] then
1156            ctx_finalizeauto()
1157        end
1158    end
1159    levels = { }
1160end
1161
1162implement {
1163    name      = "autonextstructurelevel",
1164    actions   = autonextstructurelevel,
1165    arguments = "integer",
1166}
1167
1168implement {
1169    name      = "autofinishstructurelevels",
1170    actions   = autofinishstructurelevels,
1171}
1172
1173-- interface (some are actually already commands, like sections.fullnumber)
1174
1175implement {
1176    name     = "depthnumber",
1177    actions  = { depthnumber, context },
1178    arguments = "integer",
1179}
1180
1181implement { name = "structurenumber",            actions = sections.fullnumber }
1182implement { name = "structuretitle",             actions = sections.title }
1183
1184implement { name = "structurevariable",          actions = sections.structuredata, arguments = { false, "string" } }
1185implement { name = "structureuservariable",      actions = sections.userdata,      arguments = { false, "string" } }
1186implement { name = "structurecatcodedget",       actions = sections.structuredata, arguments = { false, "string", false, true } }
1187implement { name = "structuregivencatcodedget",  actions = sections.structuredata, arguments = { false, "string", false, "integer" } }
1188implement { name = "structureautocatcodedget",   actions = sections.structuredata, arguments = { false, "string", false, "string" } }
1189
1190implement { name = "namedstructurevariable",     actions = sections.structuredata, arguments = "2 strings" }
1191implement { name = "namedstructureuservariable", actions = sections.userdata,      arguments = "2 strings" }
1192
1193implement { name = "setstructurelevel",          actions = sections.setlevel,        arguments = "2 strings" }
1194implement { name = "getstructurelevel",          actions = sections.getcurrentlevel, arguments = "string" }
1195implement { name = "setstructurenumber",         actions = sections.setnumber,       arguments = { "integer", "string" } } -- string as we support +-
1196implement { name = "getstructurenumber",         actions = sections.getnumber,       arguments = "integer" }
1197implement { name = "getsomestructurenumber",     actions = sections.getnumber,       arguments = { "integer", "string" } }
1198implement { name = "getfullstructurenumber",     actions = sections.fullnumber,      arguments = "integer" }
1199implement { name = "getsomefullstructurenumber", actions = sections.fullnumber,      arguments = { "integer", "string" } }
1200implement { name = "getspecificstructuretitle",  actions = sections.structuredata,   arguments = { "string", "'titledata.title'",false,"string" } }
1201
1202implement { name = "reportstructure",            actions = sections.reportstructure }
1203implement { name = "showstructure",              actions = sections.showstructure }
1204
1205implement {
1206    name      = "registersection",
1207    actions   = sections.register,
1208    arguments = {
1209        "string",
1210        {
1211            { "coupling" },
1212            { "section" },
1213            { "level", "integer" },
1214            { "parent" },
1215        }
1216    }
1217}
1218
1219implement {
1220    name      = "setsectionentry",
1221    actions   = sections.setentry,
1222    arguments = {
1223        {
1224            { "references", {
1225                    { "internal", "integer" },
1226                    { "block" },
1227                    { "backreference" },
1228                    { "prefix" },
1229                    { "reference" },
1230                }
1231            },
1232            { "directives", {
1233                    { "resetset" }
1234                }
1235            },
1236            { "metadata", {
1237                    { "kind" },
1238                    { "name" },
1239                    { "catcodes", "integer" },
1240                    { "coding" },
1241                    { "xmlroot" },
1242                    { "xmlsetup" },
1243                    { "nolist", "boolean" },
1244                    { "increment" },
1245                }
1246            },
1247            { "titledata", {
1248                    { "label" },
1249                    { "title" },
1250                    { "bookmark" },
1251                    { "marking" },
1252                    { "list" },
1253                    { "reference" },
1254                }
1255            },
1256            { "numberdata", {
1257                    { "block" },
1258                    { "hidenumber", "boolean" },
1259                    { "separatorset" },
1260                    { "conversionset" },
1261                    { "conversion" },
1262                    { "starter" },
1263                    { "stopper" },
1264                    { "set" },
1265                    { "segments" },
1266                    { "ownnumber" },
1267                    { "language" },
1268                    { "criterium" },
1269                },
1270            },
1271            { "userdata" },
1272        }
1273    }
1274}
1275
1276-- os.exit()
1277
1278implement {
1279    name      = "setsectionblock",
1280    actions   = sections.setblock,
1281    arguments = { "string", { { "bookmark" } } }
1282}
1283
1284implement {
1285    name      = "setinitialsectionblock",
1286    actions   = sections.setinitialblock,
1287    arguments = "string",
1288 -- onlyonce  = true,
1289}
1290
1291implement {
1292    name      = "pushsectionblock",
1293    actions   = sections.pushblock,
1294    arguments = { "string", { { "bookmark" } } }
1295}
1296
1297implement {
1298    name      = "popsectionblock",
1299    actions   = sections.popblock,
1300}
1301
1302interfaces.implement {
1303    name      = "doifelsefirstsectionpage",
1304    arguments = "1 argument",
1305    public    = true,
1306    protected = true,
1307    actions   = function(name)
1308        local found = false
1309     -- local list  = structures.lists.collected
1310        local list  = lists.collected
1311        if list then
1312            local realpage = texgetcount("realpageno")
1313            for i=1,#list do
1314                local listdata = list[i]
1315                local metadata = listdata.metadata
1316                if metadata and metadata.kind == "section" and metadata.name == name then
1317                 -- local current = structures.documents.data.status[metadata.level]
1318                    local current = data.status[metadata.level]
1319                    if current and current.references.internal == listdata.references.internal then
1320                        found = listdata.references.realpage == realpage
1321                        break
1322                    end
1323                end
1324            end
1325        end
1326        ctx_doifelse(found)
1327    end,
1328}
1329
1330-- could be faster (in huge lists)
1331
1332-- local firstpages = table.setmetatableindex(function(t,name)
1333--  -- local list  = structures.lists.collected
1334--     local list  = lists.collected
1335--     local pages = { }
1336--     if list then
1337--         for i=1,#list do
1338--             local listdata = list[i]
1339--             local metadata = listdata.metadata
1340--             if metadata and metadata.kind == "section" and metadata.name == name then
1341--                 local references = listdata.references
1342--                 if references then
1343--                     pages[references.internal] = listdata
1344--                 end
1345--             end
1346--         end
1347--     end
1348--     t[name] = pages
1349--     return pages
1350-- end)
1351--
1352-- interfaces.implement {
1353--     name      = "doifelsefirstsectionpage",
1354--     arguments = "1 argument",
1355--     public    = true,
1356--     protected = true,
1357--     actions   = function(name)
1358--         local found = firstpages[name]
1359--         if found then
1360--             local level = structures.sections.levelmap[name]
1361--             if level then
1362--              -- local current = structures.documents.data.status[level]
1363--                 local current = data.status[level]
1364--                 if current then
1365--                     local realpage = texgetcount("realpageno")
1366--                     found = found[current.references.internal]
1367--                     found = found and found.references.realpage == realpage
1368--                 else
1369--                     found = false
1370--                 end
1371--             else
1372--                 found = false
1373--             end
1374--         end
1375--         ctx_doifelse(found)
1376--     end,
1377-- }
1378