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