strc-ini.lua /size: 12 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['strc-ini'] = {
2    version   = 1.001,
3    comment   = "companion to strc-ini.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--[[
10The restructuring is the (intermediate) result of quite some experiments. I started
11with the basic structure, followed by lists, numbers, enumerations, itemgroups
12and floats. All these have something in common, like pagenumbers and section
13prefixes. I played with some generic datastructure (in order to save space) but
14the code at both the lua and tex end then quickly becomes messy due to the fact
15that access to variables is too different. So, eventually I ended up with
16dedicated structures combined with sharing data. In lua this is quite efficient
17because tables are referenced. However, some precautions are to be taken in
18order to keep the utility file small. Utility data and process data share much
19but it does not make sense to store all processdata.
20
21]]--
22
23local lpegmatch = lpeg.match
24local type, next, tonumber, select = type, next, tonumber, select
25
26local formatters        = string.formatters
27local settings_to_array = utilities.parsers.settings_to_array
28local settings_to_hash  = utilities.parsers.settings_to_hash
29local allocate          = utilities.storage.allocate
30
31local catcodenumbers    = catcodes.numbers -- better use the context(...) way to switch
32
33local ctxcatcodes       = catcodenumbers.ctxcatcodes
34local xmlcatcodes       = catcodenumbers.xmlcatcodes
35local notcatcodes       = catcodenumbers.notcatcodes
36local txtcatcodes       = catcodenumbers.txtcatcodes
37
38local context           = context
39local commands          = commands
40
41local trace_processors  = false
42local report_processors = logs.reporter("processors","structure")
43
44trackers.register("typesetters.processors", function(v) trace_processors = v end)
45
46local xmlconvert = lxml.convert
47local xmlstore   = lxml.store
48
49local ctx_pushcatcodes     = context.pushcatcodes
50local ctx_popcatcodes      = context.popcatcodes
51local ctx_xmlsetup         = context.xmlsetup
52local ctx_xmlprocessbuffer = context.xmlprocessbuffer
53
54-- -- -- namespace -- -- --
55
56-- This is tricky: we have stored and initialized already some of
57-- the job.registered tables so we have a forward reference!
58
59structures       = structures or { }
60local structures = structures
61
62structures.blocks       = structures.blocks       or { }
63structures.sections     = structures.sections     or { }
64structures.pages        = structures.pages        or { }
65structures.registers    = structures.registers    or { }
66structures.references   = structures.references   or { }
67structures.lists        = structures.lists        or { }
68structures.helpers      = structures.helpers      or { }
69structures.documents    = structures.documents    or { }
70structures.notes        = structures.notes        or { }
71structures.descriptions = structures.descriptions or { }
72structures.itemgroups   = structures.itemgroups   or { }
73structures.specials     = structures.specials     or { }
74structures.counters     = structures.counters     or { }
75structures.tags         = structures.tags         or { }
76structures.formulas     = structures.formulas     or { } -- not used but reserved
77structures.sets         = structures.sets         or { }
78structures.marks        = structures.marks        or { }
79structures.floats       = structures.floats       or { }
80structures.synonyms     = structures.synonyms     or { }
81
82--~ table.print(structures)
83
84local processors        = typesetters.processors
85
86-- -- -- specials -- -- --
87
88-- we can store information and get back a reference; this permits
89-- us to store rather raw data in references
90
91local specials = structures.specials
92
93local collected    = allocate()
94local tobesaved    = allocate()
95
96specials.collected = collected
97specials.tobesaved = tobesaved
98
99local function initializer()
100    collected = specials.collected
101    tobesaved = specials.tobesaved
102end
103
104if job then
105    job.register('structures.specials.collected', tobesaved, initializer)
106end
107
108function specials.store(class,data)
109    if class and data then
110        local s = tobesaved[class]
111        if not s then
112            s = { }
113            tobesaved[class] = s
114        end
115        s[#s+1] = data
116        context(#s)
117    else
118        context(0)
119    end
120end
121
122function specials.retrieve(class,n)
123    if class and n then
124        local c = collected[class]
125        return c and c[n]
126    end
127end
128
129-- -- -- helpers -- -- --
130
131local helpers = structures.helpers
132
133-- function helpers.touserdata(str)
134--     local hash = str and str ~= "" and settings_to_hash(str)
135--     if hash and next(hash) then
136--         return hash
137--     end
138-- end
139
140function helpers.touserdata(data)
141    if type(data) == "string" then
142        if data == "" then
143            return nil
144        else
145            data = settings_to_hash(data)
146        end
147    end
148    if data and next(data) then
149        return data
150    end
151end
152
153local function simplify(d,nodefault)
154    if d then
155        local t = { }
156        for k, v in next, d do
157            local tv = type(v)
158            if tv == "table" then
159                if next(v) then
160                    t[k] = simplify(v)
161                end
162            elseif tv == "string" then
163                if v ~= "" then
164                    t[k] = v
165                end
166            elseif tv == "boolean" then
167                if v then
168                    t[k] = v
169                end
170            else
171                t[k] = v
172            end
173        end
174        return next(t) and t
175    elseif nodefault then
176        return nil
177    else
178        return { }
179    end
180end
181
182-- we only care about the tuc file so this would do too:
183--
184-- local function simplify(d,nodefault)
185--     if d then
186--         for k, v in next, d do
187--             local tv = type(v)
188--             if tv == "string" then
189--                 if v == "" or v == "default" then
190--                     d[k] = nil
191--                 end
192--             elseif tv == "table" then
193--                 if next(v) then
194--                     simplify(v)
195--                 end
196--             elseif tv == "boolean" then
197--                 if not v then
198--                     d[k] = nil
199--                 end
200--             end
201--         end
202--         return d
203--     elseif nodefault then
204--         return nil
205--     else
206--         return { }
207--     end
208-- end
209
210helpers.simplify = simplify
211
212function helpers.merged(...)
213    local t = { }
214    for k=1, select("#",...) do
215        local v = select(k,...)
216        if v and v ~= "" and not t[k] then
217            t[k] = v
218        end
219    end
220    return t
221end
222
223local tags = {
224    generic = "ctx:genericentry",
225    section = "ctx:sectionentry",
226    entry   = "ctx:registerentry",
227}
228
229--  We had the following but it overloads the main document so it's a no-go as we
230--  no longer push and pop. So now we use the tag as buffername, namespace and also
231--  (optionally) as a setups to be applied but keep in mind that document setups
232--  also get applied (when they use #1's).
233--
234--  local command = formatters["\\xmlprocessbuffer{%s}{%s}{}"](metadata.xmlroot or "main",tag)
235
236local overload_catcodes = true
237
238directives.register("typesetters.processors.overloadcatcodes",function(v)
239    -- number | true | false | string
240    overload_catcodes = v
241end)
242
243local experiment = true
244
245function helpers.title(title,metadata) -- coding is xml is rather old and not that much needed now
246    if title and title ~= "" then      -- so it might disappear
247        if metadata then
248            local xmlsetup = metadata.xmlsetup
249            if metadata.coding == "xml" then
250                -- title can contain raw xml
251                local tag = tags[metadata.kind] or tags.generic
252                local xmldata = formatters["<?xml version='1.0'?><%s>%s</%s>"](tag,title,tag)
253                if not experiment then
254                    buffers.assign(tag,xmldata)
255                end
256                if trace_processors then
257                    report_processors("putting xml data in buffer: %s",xmldata)
258                    report_processors("processing buffer with setup %a and tag %a",xmlsetup,tag)
259                end
260                if experiment then
261                    -- the question is: will this be forgotten ... better store in a via file
262                    local xmltable = xmlconvert("temp",xmldata or "")
263                    xmlstore("temp",xmltable)
264                    ctx_xmlsetup("temp",xmlsetup or "")
265                else
266                    ctx_xmlprocessbuffer("dummy",tag,xmlsetup or "")
267                end
268            elseif xmlsetup then -- title is reference to node (so \xmlraw should have been used)
269                if trace_processors then
270                    report_processors("feeding xmlsetup %a using node %a",xmlsetup,title)
271                end
272                ctx_xmlsetup(title,metadata.xmlsetup)
273            else
274                local catcodes = metadata.catcodes
275                if overload_catcodes == false then
276                    if trace_processors then
277                        report_processors("catcodetable %a, text %a",catcodes,title)
278                    end
279                    --
280                    -- context.sprint(catcodes,title)
281                    --
282                    -- doesn't work when a newline is in there \section{Test\ A} so we do
283                    -- it this way:
284                    --
285                    ctx_pushcatcodes(catcodes)
286                    context(title)
287                    ctx_popcatcodes()
288                elseif overload_catcodes == true then
289                    if catcodes == notcatcodes or catcodes == xmlcatcodes then
290                        -- when was this needed
291                        if trace_processors then
292                            report_processors("catcodetable %a, overloads %a, text %a",ctxcatcodes,catcodes,title)
293                        end
294                        context(title)
295                    else
296                        ctx_pushcatcodes(catcodes)
297                        context(title)
298                        ctx_popcatcodes()
299                    end
300                else
301                    if trace_processors then
302                        report_processors("catcodetable %a, overloads %a, text %a",catcodes,overload_catcodes,title)
303                    end
304                    ctx_pushcatcodes(overload_catcodes)
305                    context(title)
306                    ctx_popcatcodes()
307                end
308            end
309        else
310            -- no catcode switch, was: texsprint(title)
311            context(title)
312        end
313    end
314end
315
316-- -- -- sets -- -- --
317
318local sets = structures.sets
319
320sets.setlist = sets.setlist or { }
321
322storage.register("structures/sets/setlist", structures.sets.setlist, "structures.sets.setlist")
323
324local setlist = sets.setlist
325
326function sets.define(namespace,name,values,default,numbers)
327    local dn = setlist[namespace]
328    if not dn then
329        dn = { }
330        setlist[namespace] = dn
331    end
332    if values == "" then
333        dn[name] = { { }, default }
334    else
335        local split = settings_to_array(values)
336        if numbers then
337            -- convert to numbers (e.g. for reset)
338            for i=1,#split do
339                split[i] = tonumber(split[i]) or 0
340            end
341        end
342        dn[name] = { split, default }
343    end
344end
345
346function sets.getall(namespace,block,name)
347    local ds = setlist[namespace]
348    if not ds then
349        return { }
350    else
351        local dn
352        if block and block ~= "" then
353            dn = ds[block..":"..name] or ds[name] or ds[block] or ds.default
354        else
355            dn = ds[name] or ds.default
356        end
357        return (dn and dn[1]) or { }
358    end
359end
360
361-- messy (will be another keyword, fixedconversion) .. needs to be documented too
362-- maybe we should cache
363
364local splitter = lpeg.splitat("::")
365
366function sets.get(namespace,block,name,level,default) -- check if name is passed
367    --fixed::R:a: ...
368    local kind, rest = lpegmatch(splitter,name)
369    if rest and kind == "fixed" then -- fixed::n,a,i
370        local s = settings_to_array(rest)
371        return s[level] or s[#s] or default
372    end
373    --
374    local ds = setlist[namespace]
375    if not ds then
376        return default
377    end
378    local dn
379    if name and name ~= "" then
380        if block and block ~= "" then
381            dn = ds[block..":"..name] or ds[name] or ds[block] or ds.default
382        else
383            dn = ds[name] or ds.default
384        end
385    else
386        if block and block ~= "" then
387            dn = ds[block] or ds[block..":default"] or ds.default
388        else
389            dn = ds.default
390        end
391    end
392    if not dn then
393        return default
394    end
395    local dl = dn[1][level]
396    return dl or dn[2] or default
397end
398
399-- interface
400
401interfaces.implement {
402    name      = "definestructureset",
403    actions   = sets.define,
404    arguments = { "string", "string", "string", "string", "boolean" }
405}
406