publ-ini.lua /size: 124 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['publ-ini'] = {
2    version   = 1.001,
3    comment   = "this module part of publication support",
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-- bah .. this 200 locals limit again ... so we need to split it as adding more
10-- do ... ends makes it messier
11
12-- plug the list sorted in the list mechanism (specification.sortorder)
13
14-- If we define two datasets with the same bib file we can consider
15-- sharing the data but that means that we need to have a parent which
16-- in turn makes things messy if we start manipulating entries in
17-- different ways (future) .. not worth the trouble as we will seldom
18-- load big bib files many times and even then ... fonts are larger.
19
20-- A potential optimization is to work with current_dataset, current_tag when
21-- fetching fields but the code become real messy that way (many currents). The
22-- gain is not that large anyway because not much publication stuff is flushed.
23
24local next, rawget, type, tostring, tonumber = next, rawget, type, tostring, tonumber
25local match, find, gsub = string.match, string.find, string.gsub
26local concat, sort, tohash = table.concat, table.sort, table.tohash
27local mod = math.mod
28local formatters = string.formatters
29local allocate = utilities.storage.allocate
30local settings_to_array, settings_to_set = utilities.parsers.settings_to_array, utilities.parsers.settings_to_set
31local sortedkeys, sortedhash = table.sortedkeys, table.sortedhash
32local setmetatableindex = table.setmetatableindex
33local lpegmatch = lpeg.match
34local P, S, C, Ct, Cs, R, Carg = lpeg.P, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.R, lpeg.Carg
35local upper, lower = characters.upper, characters.lower
36
37local report             = logs.reporter("publications")
38local report_cite        = logs.reporter("publications","cite")
39local report_list        = logs.reporter("publications","list")
40local report_suffix      = logs.reporter("publications","suffix")
41
42local trace              = false  trackers.register("publications",                 function(v) trace            = v end)
43local trace_cite         = false  trackers.register("publications.cite",            function(v) trace_cite       = v end)
44local trace_missing      = false  trackers.register("publications.cite.missing",    function(v) trace_missing    = v end)
45local trace_references   = false  trackers.register("publications.cite.references", function(v) trace_references = v end)
46local trace_details      = false  trackers.register("publications.details",         function(v) trace_details    = v end)
47local trace_suffixes     = false  trackers.register("publications.suffixes",        function(v) trace_suffixes   = v end)
48
49publications             = publications or { }
50local datasets           = publications.datasets
51local writers            = publications.writers
52local casters            = publications.casters
53local detailed           = publications.detailed
54local enhancer           = publications.enhancer
55local enhancers          = publications.enhancers
56
57if not publications.btx then publications.btx = { } end -- user space
58
59local tracers            = publications.tracers or { }
60publications.tracers     = tracers
61
62local setmacro           = interfaces.setmacro   -- todo
63local setcounter         = tex.setcounter        -- todo
64local variables          = interfaces.variables
65
66local v_local            = variables["local"]
67local v_global           = variables["global"]
68
69local v_force            = variables.force
70local v_normal           = variables.normal
71local v_reverse          = variables.reverse
72local v_none             = variables.none
73local v_yes              = variables.yes
74local v_no               = variables.no
75local v_all              = variables.all
76local v_always           = variables.always
77local v_text             = variables.text
78local v_doublesided      = variables.doublesided
79local v_default          = variables.default
80local v_dataset          = variables.dataset
81local v_label            = variables.label
82
83local conditionals       = tex.conditionals
84
85local isdefined          = tex.isdefined
86
87----- basicsorter        = sorters.basicsorter -- (a,b)
88----- sortstripper       = sorters.strip
89----- sortsplitter       = sorters.splitters.utf
90
91local manipulators       = typesetters.manipulators
92local splitmanipulation  = manipulators.splitspecification
93local applymanipulation  = manipulators.applyspecification
94local manipulatormethods = manipulators.methods
95
96-- this might move elsewhere
97
98manipulatormethods.Word  = converters.Word
99manipulatormethods.WORD  = converters.WORD
100manipulatormethods.Words = converters.Words
101manipulatormethods.WORDS = converters.WORDS
102
103local context                     = context
104local commands                    = commands
105local implement                   = interfaces.implement
106
107local ctx_doifelse                = commands.doifelse
108local ctx_doif                    = commands.doif
109local ctx_doifnot                 = commands.doifnot
110local ctx_gobbletwoarguments      = context.gobbletwoarguments
111
112local ctx_btxhandlelistentry      = context.btxhandlelistentry
113local ctx_btxhandlecombientry     = context.btxhandlecombientry
114local ctx_btxchecklistentry       = context.btxchecklistentry
115
116local ctx_btxsetdataset           = context.btxsetdataset
117local ctx_btxsettag               = context.btxsettag
118local ctx_btxsetnumber            = context.btxsetnumber
119local ctx_btxsetlanguage          = context.btxsetlanguage
120local ctx_btxsetcombis            = context.btxsetcombis
121local ctx_btxsetcategory          = context.btxsetcategory
122local ctx_btxsetfirst             = context.btxsetfirst
123local ctx_btxsetsecond            = context.btxsetsecond
124local ctx_btxsetsuffix            = context.btxsetsuffix
125local ctx_btxsetinternal          = context.btxsetinternal
126local ctx_btxsetlefttext          = context.btxsetlefttext
127local ctx_btxsetrighttext         = context.btxsetrighttext
128local ctx_btxsetbefore            = context.btxsetbefore
129local ctx_btxsetafter             = context.btxsetafter
130local ctx_btxsetbacklink          = context.btxsetbacklink
131local ctx_btxsetfirstinternal     = context.btxsetfirstinternal
132local ctx_btxsetlastinternal      = context.btxsetlastinternal
133local ctx_btxsetauthorfield       = context.btxsetauthorfield
134
135-- local ctx_btxsetdataset           = function(s) setmacro("currentbtxdataset",       s) end -- context.btxsetdataset
136-- local ctx_btxsettag               = function(s) setmacro("currentbtxtag",           s) end -- context.btxsettag
137-- local ctx_btxsetnumber            = function(s) setmacro("currentbtxnumber",        s) end -- context.btxsetnumber
138-- local ctx_btxsetlanguage          = function(s) setmacro("currentbtxlanguage",      s) end -- context.btxsetlanguage
139-- local ctx_btxsetcombis            = function(s) setmacro("currentbtxcombis",        s) end -- context.btxsetcombis
140-- local ctx_btxsetcategory          = function(s) setmacro("currentbtxcategory",      s) end -- context.btxsetcategory
141-- local ctx_btxsetfirst             = function(s) setmacro("currentbtxfirst",         s) end -- context.btxsetfirst
142-- local ctx_btxsetsecond            = function(s) setmacro("currentbtxsecond",        s) end -- context.btxsetsecond
143-- local ctx_btxsetsuffix            = function(s) setmacro("currentbtxsuffix",        s) end -- context.btxsetsuffix
144-- local ctx_btxsetinternal          = function(s) setmacro("currentbtxinternal",      s) end -- context.btxsetinternal
145-- local ctx_btxsetlefttext          = function(s) setmacro("currentbtxlefttext",      s) end -- context.btxsetlefttext
146-- local ctx_btxsetrighttext         = function(s) setmacro("currentbtxrighttext",     s) end -- context.btxsetrighttext
147-- local ctx_btxsetbefore            = function(s) setmacro("currentbtxbefore",        s) end -- context.btxsetbefore
148-- local ctx_btxsetafter             = function(s) setmacro("currentbtxafter",         s) end -- context.btxsetafter
149-- local ctx_btxsetbacklink          = function(s) setmacro("currentbtxbacklink",      s) end -- context.btxsetbacklink
150-- local ctx_btxsetfirstinternal     = function(s) setmacro("currentbtxfirstinternal", s) end -- context.btxsetfirstinternal
151-- local ctx_btxsetlastinternal      = function(s) setmacro("currentbtxlastinternal",  s) end -- context.btxsetlastinternal
152
153local ctx_btxsetfirstpage         = context.btxsetfirstpage
154local ctx_btxsetlastpage          = context.btxsetlastpage
155
156local ctx_btxstartcite            = context.btxstartcite
157local ctx_btxstopcite             = context.btxstopcite
158local ctx_btxstartciteauthor      = context.btxstartciteauthor
159local ctx_btxstopciteauthor       = context.btxstopciteauthor
160local ctx_btxstartsubcite         = context.btxstartsubcite
161local ctx_btxstopsubcite          = context.btxstopsubcite
162local ctx_btxstartlistentry       = context.btxstartlistentry
163local ctx_btxstoplistentry        = context.btxstoplistentry
164local ctx_btxstartcombientry      = context.btxstartcombientry
165local ctx_btxstopcombientry       = context.btxstopcombientry
166
167local ctx_btxflushauthor          = context.btxflushauthor
168
169local ctx_btxsetnoflistentries    = context.btxsetnoflistentries
170local ctx_btxsetcurrentlistentry  = context.btxsetcurrentlistentry
171local ctx_btxsetcurrentlistindex  = context.btxsetcurrentlistindex
172
173local ctx_btxsetcount             = context.btxsetcount
174local ctx_btxsetconcat            = context.btxsetconcat
175
176local ctx_btxcitesetup            = context.btxcitesetup
177local ctx_btxsubcitesetup         = context.btxsubcitesetup
178local ctx_btxnumberingsetup       = context.btxnumberingsetup
179local ctx_btxpagesetup            = context.btxpagesetup
180local ctx_btxlistsetup            = context.btxlistsetup
181
182local trialtypesetting            = context.trialtypesetting
183
184languages.data                    = languages.data       or { }
185local data                        = languages.data
186
187local specifications              = publications.specifications
188local currentspecification        = specifications[false]
189local ignoredfields               = { }
190publications.currentspecification = currentspecification
191
192local function setspecification(name)
193    currentspecification = specifications[name]
194    if trace then
195        report("setting specification %a",type(name) == "string" and name or "anything")
196    end
197    publications.currentspecification = currentspecification
198end
199
200publications.setspecification = setspecification
201
202implement {
203    name      = "btxsetspecification",
204    actions   = setspecification,
205    arguments = "string",
206}
207
208local optionalspace  = lpeg.patterns.whitespace^0
209local prefixsplitter = optionalspace * lpeg.splitat(optionalspace * P("::") * optionalspace)
210
211statistics.register("publications load time", function()
212    local publicationsstats = publications.statistics
213    local nofbytes = publicationsstats.nofbytes
214    if nofbytes > 0 then
215        return string.format("%s seconds, %s bytes, %s definitions, %s shortcuts",
216            statistics.elapsedtime(publications),
217            nofbytes,
218            publicationsstats.nofdefinitions or 0,
219            publicationsstats.nofshortcuts or 0
220        )
221    else
222        return nil
223    end
224end)
225
226logs.registerfinalactions(function()
227    local done    = false
228    local unknown = false
229    for name, dataset in sortedhash(datasets) do
230        for command, n in sortedhash(dataset.commands) do
231            if not done then
232                logs.startfilelogging(report,"used btx commands")
233                done = true
234            end
235            if isdefined(command) then
236                report("%-20s %-20s % 5i %s",name,command,n,"known")
237            elseif isdefined(upper(command)) then
238                report("%-20s %-20s % 5i %s",name,command,n,"KNOWN")
239            else
240                report("%-20s %-20s % 5i %s",name,command,n,"unknown")
241                unknown = true
242            end
243        end
244    end
245    if done then
246        logs.stopfilelogging()
247    end
248    if unknown and logs.loggingerrors() then
249        logs.starterrorlogging(report,"unknown btx commands")
250        for name, dataset in sortedhash(datasets) do
251            for command, n in sortedhash(dataset.commands) do
252                if not isdefined(command) and not isdefined(upper(command)) then
253                    report("%-20s %-20s % 5i %s",name,command,n,"unknown")
254                end
255            end
256        end
257        logs.stoperrorlogging()
258    end
259end)
260
261-- multipass, we need to sort because hashing is random per run and not per
262-- version (not the best changed feature of lua)
263
264local collected = allocate()
265local tobesaved = allocate()
266
267do
268
269    local function serialize(t)
270        local f_key_table  = formatters[" [%q] = {"]
271        local f_key_string = formatters["  %s = %q,"]
272        local r = { "return {" }
273        local m = 1
274        for tag, entry in sortedhash(t) do
275            m = m + 1
276            r[m] = f_key_table(tag)
277            local s = sortedkeys(entry)
278            for i=1,#s do
279                local k = s[i]
280                m = m + 1
281                r[m] = f_key_string(k,entry[k])
282            end
283            m = m + 1
284            r[m] = " },"
285        end
286        r[m] = "}"
287        return concat(r,"\n")
288    end
289
290    local function finalizer()
291        local prefix = tex.jobname -- or environment.jobname
292        local setnames = sortedkeys(datasets)
293        for i=1,#setnames do
294            local name     = setnames[i]
295            local dataset  = datasets[name]
296            local userdata = dataset.userdata
297            local checksum = nil
298            local username = file.addsuffix(file.robustname(formatters["%s-btx-%s"](prefix,name)),"lua")
299            if userdata and next(userdata) then
300                if environment.currentrun == 1 then
301             -- if job.passes.first then
302                    local newdata = serialize(userdata)
303                    checksum = md5.HEX(newdata)
304                    io.savedata(username,newdata)
305                end
306            else
307                os.remove(username)
308                username = nil
309            end
310            local loaded  = dataset.loaded
311            local sources = dataset.sources
312            local used    = { }
313            for i=1,#sources do
314                local source = sources[i]
315             -- if loaded[source.filename] ~= "previous" then -- needs checking
316                if loaded[source.filename] ~= "previous" or loaded[source.filename] == "current" then
317                    used[#used+1] = source
318                end
319            end
320            tobesaved[name] = {
321                usersource = {
322                    filename = username,
323                    checksum = checksum,
324                },
325                datasources = used,
326            }
327        end
328    end
329
330    local function initializer()
331        statistics.starttiming(publications)
332        for name, state in sortedhash(collected) do
333            local dataset     = datasets[name]
334            local datasources = state.datasources
335            local usersource  = state.usersource
336            if datasources then
337                for i=1,#datasources do
338                    local filename = datasources[i].filename
339                    publications.load {
340                        dataset  = dataset,
341                        filename = filename,
342                        kind     = "previous"
343                    }
344                end
345            end
346            if usersource then
347                dataset.userdata = table.load(usersource.filename) or { }
348            end
349        end
350        statistics.stoptiming(publications)
351        function initializer() end -- will go, for now, runtime loaded
352    end
353
354    job.register('publications.collected',tobesaved,initializer,finalizer)
355
356end
357
358-- we want to minimize references as there can be many (at least
359-- when testing)
360
361local nofcitations = 0
362local usedentries  = nil
363local citetolist   = nil
364local listtocite   = nil
365local listtolist   = nil
366
367do
368
369    local initialize = nil -- we delay
370
371    initialize = function(t)
372        usedentries     = allocate { }
373        citetolist      = allocate { }
374        listtocite      = allocate { }
375        listtolist      = allocate { }
376        local names     = { }
377        local p_collect = (C(R("09")^1) * Carg(1) / function(s,entry) listtocite[tonumber(s)] = entry end + P(1))^0
378        local nofunique = 0
379        local nofreused = 0
380     -- local internals = references.sortedinternals -- todo: when we need it more than once
381     -- for i=1,#internals do                        -- but currently we don't do this when not
382     --     local entry = internals[i]               -- needed anyway so ...
383        local internals = structures.references.internals
384        for i, entry in sortedhash(internals) do
385            local metadata = entry.metadata
386            if metadata then
387                local kind = metadata.kind
388                if kind == "full" then
389                    -- reference (in list)
390                    local userdata = entry.userdata
391                    if userdata then
392                        local tag = userdata.btxref
393                        if tag then
394                            local set = userdata.btxset or v_default
395                            local s = usedentries[set]
396                            if s then
397                                local u = s[tag]
398                                if u then
399                                    u[#u+1] = entry
400                                else
401                                    s[tag] = { entry }
402                                end
403                                nofreused = nofreused + 1
404                            else
405                                usedentries[set] = { [tag] = { entry } }
406                                nofunique = nofunique + 1
407                            end
408                            -- alternative: collect prev in group
409                            local int = tonumber(userdata.btxint)
410                            if int then
411                                listtocite[int] = entry
412                            end
413                            local detail = datasets[set].details[tag]
414                            -- todo: these have to be pluggable
415                            if detail then
416                                local author = detail.author
417                                if author then
418                                    for i=1,#author do
419                                        local a = author[i]
420                                        local s = a.surnames
421                                        if s then
422                                            local c = concat(s,"+")
423                                            local n = names[c]
424                                            if n then
425                                                n[#n+1] = a
426                                                break
427                                            else
428                                                names[c] = { a }
429                                            end
430                                        end
431                                    end
432                                end
433                            end
434                        end
435                    end
436                elseif kind == "btx" then
437                    -- list entry (each cite)
438                    local userdata = entry.userdata
439                    if userdata then
440                        local int = tonumber(userdata.btxint)
441                        if int then
442                            citetolist[int] = entry
443                        end
444                    end
445                end
446            end
447        end
448        for k, v in sortedhash(names) do
449            local n = #v
450            if n > 1 then
451                local original = v[1].original
452                for i=2,n do
453                    if original ~= v[i].original then
454                        report("potential clash in name %a",k)
455                        for i=1,n do
456                            v[i].state = 1
457                        end
458                        break
459                    end
460                end
461            end
462        end
463        if trace_details then
464            report("%s unique references, %s reused entries",nofunique,nofreused)
465        end
466        initialize = nil
467    end
468
469    usedentries = setmetatableindex(function(_,k) if initialize then initialize() end return usedentries[k] end)
470    citetolist  = setmetatableindex(function(_,k) if initialize then initialize() end return citetolist [k] end)
471    listtocite  = setmetatableindex(function(_,k) if initialize then initialize() end return listtocite [k] end)
472    listtolist  = setmetatableindex(function(_,k) if initialize then initialize() end return listtolist [k] end)
473
474    function publications.usedentries()
475        if initialize then
476            initialize()
477        end
478        return usedentries
479    end
480
481end
482
483-- match:
484--
485-- [current|previous|following] section
486-- [current|previous|following] block
487-- [current|previous|following] component
488--
489-- by prefix
490-- by dataset
491
492local findallused do
493
494    local reported = { }
495    ----- finder   = publications.finder
496
497    findallused = function(dataset,reference,internal,forcethem)
498        local current  = datasets[dataset]
499        local finder   = publications.finder -- for the moment, not yet in all betas
500        local find     = finder and finder(current,reference)
501        local tags     = not find and settings_to_array(reference)
502        local todo     = { }
503        local okay     = { } -- only if mark
504        local allused  = usedentries[dataset] or { }
505     -- local allused  = usedentries[dataset] -- only test
506        local luadata  = current.luadata
507        local details  = current.details
508        local ordered  = current.ordered
509        if allused then -- always true
510            local registered = { }
511            local function register(tag)
512                local entry = forcethem and luadata[tag]
513                if entry then
514                    if registered[tag] then
515                        if trace_cite then
516                            report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"already cited (1)")
517                        end
518                        return
519                    elseif trace_cite then
520                        report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"okay")
521                    end
522                    okay[#okay+1] = entry
523                 -- todo[tag] = true
524                    registered[tag] = true
525                    return tag
526                end
527                entry = allused[tag]
528                if trace_cite then
529                    report_cite("dataset: %s, tag: %s, used: % t",dataset,tag,table.sortedkeys(allused))
530                end
531                if not entry then
532                    local parent = details[tag].parent
533                    if parent then
534                        entry = allused[parent]
535                    end
536                    if entry then
537                        report("using reference of parent %a for %a",parent,tag)
538                        tag = parent
539                    elseif trace_cite then
540                        report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"not used")
541                    end
542                elseif trace_cite then
543                    report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"used")
544                end
545                if registered[tag] then
546                    if trace_cite then
547                        report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"already cited (2)")
548                    end
549                    return
550                end
551                if entry then
552                    -- only once in a list but at some point we can have more (if we
553                    -- decide to duplicate)
554                    if #entry == 1 then
555                        entry = entry[1]
556                    else
557                        -- same block and section
558                        local done = false
559                        if internal and internal > 0 then
560                            -- first following in list
561                            for i=1,#entry do
562                                local e = entry[i]
563                                if e.references.internal > internal then
564                                    done = e
565                                    break
566                                end
567                            end
568                            if not done then
569                                -- last preceding in list
570                                for i=1,#entry do
571                                    local e = entry[i]
572                                    if e.references.internal < internal then
573                                        done = e
574                                    else
575                                        break
576                                    end
577                                end
578                            end
579                        end
580                        if done then
581                            entry = done
582                        else
583                            entry = entry[1]
584                        end
585                    end
586                    if not entry then
587                        report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"no entry (1)")
588                    elseif trace_cite then
589                        report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"new entry")
590                    end
591                    okay[#okay+1] = entry
592                elseif not entry then
593                    if trace_cite then
594                        report_cite("dataset: %s, tag: %s, state: %s",dataset,tag,"no entry (2)")
595                    end
596        -- okay[#okay+1] = luadata[tag]
597                end
598                todo[tag] = true
599                registered[tag] = true
600                return tag
601            end
602            if reference == "*" then
603                tags = { }
604                for i=1,#ordered do
605                    local tag = ordered[i].tag
606                    tag = register(tag)
607                    tags[#tags+1] = tag
608                end
609            elseif find then
610                tags = { }
611                for i=1,#ordered do
612                    local entry = ordered[i]
613                    if find(entry) then
614                        local tag = entry.tag
615                        tag = register(tag)
616                        tags[#tags+1] = tag
617                    end
618                end
619                if #tags == 0 and not reported[reference] then
620                    tags[1] = reference
621                    reported[reference] = true
622                end
623            else
624                for i=1,#tags do
625                    local tag = tags[i]
626                    if luadata[tag] then
627                        tag = register(tag)
628                        tags[i] = tag
629                    elseif not reported[tag] then
630                        reported[tag] = true
631                        report_cite("non-existent entry %a in %a",tag,dataset)
632                    end
633                end
634            end
635        elseif find then
636            tags = { }
637            for i=1,#ordered do
638                local entry = ordered[i]
639                if find(entry) then
640                    local tag    = entry.tag
641                    local parent = details[tag].parent
642                    if parent then
643                        tag = parent
644                    end
645                    tags[#tags+1] = tag
646                    todo[tag] = true
647                 -- okay[#okay+1] = entry -- only test
648                end
649            end
650            if #tags == 0 and not reported[reference] then
651                tags[1] = reference
652                reported[reference] = true
653            end
654        else
655            for i=1,#tags do
656                local tag    = tags[i]
657                local parent = details[tag].parent
658                if parent then
659                    tag = parent
660                    tags[i] = tag
661                end
662                local entry = luadata[tag]
663                if entry then
664                    todo[tag] = true
665                 -- okay[#okay+1] = entry -- only test
666                elseif not reported[tag] then
667                    reported[tag] = true
668                    report_cite("non-existent entry %a in %a",tag,dataset)
669                end
670            end
671        end
672        return okay, todo, tags
673    end
674
675    local firstoftwoarguments  = context.firstoftwoarguments
676    local secondoftwoarguments = context.secondoftwoarguments
677
678    implement {
679        name      = "btxdoifelsematches",
680        arguments = "3 strings",
681        actions   = function(dataset,tag,expression)
682            local find = publications.finder(dataset,expression)
683            local okay = false
684            if find then
685                local d = datasets[dataset]
686                if d then
687                    local e = d.luadata[tag]
688                    if e and find(e) then
689                        firstoftwoarguments()
690                        return
691                    end
692                end
693            end
694            secondoftwoarguments()
695        end
696    }
697
698    implement {
699        name      = "btxmissing",
700        arguments = "2 strings",
701        actions   = function(dataset,tag)
702            local dataset = datasets[dataset]
703            if dataset then
704                local missing = dataset.missing
705                local message = missing[tag]
706                if message == nil then
707                    local luadata = dataset.luadata
708                    local entry   = luadata[tag]
709                    if not entry then
710                        local t = lower(tag)
711                        if luadata[t] then
712                            message = t
713                        else
714                            t = upper(tag)
715                            if luadata[t] then
716                                message = t
717                            else
718                                for k, v in next, luadata do
719                                    if t == upper(k) then
720                                        message = k
721                                        break
722                                    end
723                                end
724                            end
725                        end
726                    end
727                    if not message then
728                        message = false
729                    end
730                    missing[tag] = message
731                end
732                if message then
733                    context("%s vs %s",tag,message)
734                    return
735                end
736            end
737            context(tag)
738        end
739    }
740
741end
742
743local function unknowncite(reference)
744    ctx_btxsettag(reference)
745    if trace_details then
746        report("expanding %a cite setup %a","unknown","unknown")
747    end
748    ctx_btxcitesetup("unknown")
749end
750
751local concatstate = publications.concatstate
752
753-- hidden : mark for list, don't show in text
754-- list   : mark for list, show in text only when in list
755-- text   : not to list, show in text
756-- always : mark for list, show in text
757
758local marked_todo    = false -- keeps track or not yet flushed
759local marked_dataset = false
760local marked_list    = false -- the sequential list (we flush in order, not by unordered hash)
761local marked_method  = false
762
763local function marknocite(dataset,tag,nofcitations,setup)
764    ctx_btxstartcite()
765    ctx_btxsetdataset(dataset)
766    ctx_btxsettag(tag)
767    ctx_btxsetbacklink(nofcitations)
768    if trace_details then
769        report("expanding cite setup %a",setup)
770    end
771    ctx_btxcitesetup(setup)
772    ctx_btxstopcite()
773end
774
775local function markcite(dataset,tag,flush)
776    if not marked_todo then
777        return 0
778    end
779    local citation = marked_todo[tag]
780    if not citation then
781        return 0
782    end
783    if citation == true then
784        nofcitations = nofcitations + 1
785        if trace_cite then
786            report_cite("mark, dataset: %s, tag: %s, number: %s, state: %s",dataset,tag,nofcitations,"cited")
787        end
788        if flush then
789            marknocite(dataset,tag,nofcitations,"nocite")
790        end
791        marked_todo[tag] = nofcitations -- signal that it's marked
792        return nofcitations
793    else
794        return citation
795    end
796end
797
798local function btxflushmarked()
799    if marked_list and marked_todo then
800        for i=1,#marked_list do
801            -- keep order
802            local tag = marked_list[i]
803            local tbm = marked_todo[tag]
804            if tbm == true or not tbm then
805                nofcitations = nofcitations + 1
806                local setup = (tbm or marked_method == v_always) and "nocite" or "invalid"
807                marknocite(marked_dataset,tag,nofcitations,setup)
808                if trace_cite then
809                    report_cite("mark, dataset: %s, tag: %s, number: %s, setup: %s",marked_dataset,tag,nofcitations,setup)
810                end
811            else
812                -- a number signaling being marked
813            end
814        end
815    end
816    marked_todo    = false
817    marked_dataset = false
818    marked_list    = false
819    marked_method  = false
820end
821
822implement { name = "btxflushmarked", actions = btxflushmarked }
823
824-- basic access
825
826local function getfield(dataset,tag,name) -- for the moment quick and dirty
827    local d = datasets[dataset].luadata[tag]
828    return d and d[name]
829end
830
831local function getdetail(dataset,tag,name) -- for the moment quick and dirty
832    local d = datasets[dataset].details[tag]
833    return d and d[name]
834end
835
836local function getcasted(dataset,tag,field,specification)
837    local current = datasets[dataset]
838    if current then
839        local data = current.luadata[tag]
840        if data then
841            local category = data.category
842            if not specification then
843                specification = currentspecification
844            end
845            local catspec = specification.categories[category]
846            if not catspec then
847                return false
848            end
849            local fields = catspec.fields
850            if fields then
851                local sets = catspec.sets
852                if sets then
853                    local set = sets[field]
854                    if set then
855                        for i=1,#set do
856                            local field = set[i]
857                            local value = fields[field] and data[field] -- redundant check
858                            if value then
859                                local kind = specification.types[field]
860                                return detailed[kind][value], field, kind
861                            end
862                        end
863                    end
864                end
865                local value = fields[field] and data[field] -- redundant check
866                if value then
867                    local kind = specification.types[field]
868                    return detailed[kind][value], field, kind
869                end
870            end
871            local data = current.details[tag]
872            if data then
873                local kind = specification.types[field]
874                return data[field], field, kind -- no check
875            end
876        end
877    end
878end
879
880local function getfaster(current,data,details,field,categories,types)
881    local category = data.category
882    local catspec  = categories[category]
883    if not catspec then
884        return false
885    end
886    local fields = catspec.fields
887    if fields then
888        local sets = catspec.sets
889        if sets then
890            local set = sets[field]
891            if set then
892                for i=1,#set do
893                    local field = set[i]
894                    local value = fields[field] and data[field] -- redundant check
895                    if value then
896                        local kind = types[field]
897                        return detailed[kind][value], field, kind
898                    end
899                end
900            end
901        end
902        local value = fields[field] and data[field] -- redundant check
903        if value then
904            local kind = types[field]
905            return detailed[kind][value]
906        end
907    end
908    if details then
909        local kind = types[field]
910        return details[field]
911    end
912end
913
914local function getdirect(dataset,data,field,catspec) -- no field check, no dataset check
915    local catspec = (catspec or currentspecification).categories[data.category]
916    if not catspec then
917        return false
918    end
919    local fields = catspec.fields
920    if fields then
921        local sets = catspec.sets
922        if sets then
923            local set = sets[field]
924            if set then
925                for i=1,#set do
926                    local field = set[i]
927                    local value = fields[field] and data[field] -- redundant check
928                    if value then
929                        return value
930                    end
931                end
932            end
933        end
934        return fields[field] and data[field] or nil -- redundant check
935    end
936end
937
938local function getfuzzy(data,field,categories) -- no field check, no dataset check
939    local catspec
940    if categories then
941        local category = data.category
942        if category then
943            catspec = categories[data.category]
944        end
945    end
946    if not field then
947        return
948    elseif not catspec then
949        return data[field]
950    end
951    local fields = catspec.fields
952    if fields then
953        local sets = catspec.sets
954        if sets then
955            local set = sets[field]
956            if set then
957                for i=1,#set do
958                    local field = set[i]
959                    local value = fields[field] and data[field] -- redundant check
960                    if value then
961                        return value
962                    end
963                end
964            end
965        end
966        return fields[field] and data[field] or nil -- redundant check
967    end
968end
969
970publications.getfield  = getfield
971publications.getdetail = getdetail
972publications.getcasted = getcasted
973publications.getfaster = getfaster
974publications.getdirect = getdirect
975publications.getfuzzy  = getfuzzy
976
977-- this needs to be checked: a specific type should have a checker
978
979-- author pagenumber keyword url
980
981-- function commands.btxsingularorplural(dataset,tag,name)
982--     local d = getcasted(dataset,tag,name)
983--     if type(d) == "table" then
984--         d = #d <= 1
985--     else
986--         d = true
987--     end
988--     ctx_doifelse(d)
989-- end
990
991-- function commands.oneorrange(dataset,tag,name)
992--     local d = datasets[dataset].luadata[tag] -- details ?
993--     if d then
994--         d = d[name]
995--     end
996--     if type(d) == "string" then
997--         d = find(d,"%-")
998--     else
999--         d = false
1000--     end
1001--     ctx_doifelse(not d) -- so singular is default
1002-- end
1003
1004-- function commands.firstofrange(dataset,tag,name)
1005--     local d = datasets[dataset].luadata[tag] -- details ?
1006--     if d then
1007--         d = d[name]
1008--     end
1009--     if type(d) == "string" then
1010--         context(match(d,"([^%-]+)"))
1011--     end
1012-- end
1013
1014local inspectors   = allocate()
1015local nofmultiple  = allocate()
1016local firstandlast = allocate()
1017
1018publications.inspectors = inspectors
1019inspectors.nofmultiple  = nofmultiple
1020inspectors.firstandlast = firstandlast
1021
1022function nofmultiple.author(d)
1023    return type(d) == "table" and #d or 0
1024end
1025
1026function publications.singularorplural(dataset,tag,name)
1027    local data, field, kind = getcasted(dataset,tag,name)
1028    if data then
1029        local test = nofmultiple[kind]
1030        if test then
1031            local n = test(data)
1032            return not n or n < 2
1033        end
1034    end
1035    return true
1036end
1037
1038function firstandlast.range(d)
1039    if type(d) == "table" then
1040        return d[1], d[2]
1041    end
1042end
1043
1044firstandlast.pagenumber = firstandlast.range
1045
1046function publications.oneorrange(dataset,tag,name)
1047    local data, field, kind = getcasted(dataset,tag,name)
1048    if data then
1049        local test = firstandlast[kind]
1050        if test then
1051            local first, last = test(data)
1052            return not (first and last)
1053        end
1054    end
1055    return nil -- nothing at all
1056end
1057
1058function publications.firstofrange(dataset,tag,name)
1059    local data, field, kind = getcasted(dataset,tag,name)
1060    if data then
1061        local test = firstandlast[kind]
1062        if test then
1063            local first = test(data)
1064            if first then
1065                return first
1066            end
1067        end
1068    end
1069end
1070
1071function publications.lastofrange(dataset,tag,name)
1072    local data, field, kind = getcasted(dataset,tag,name)
1073    if data then
1074        local test = firstandlast[kind]
1075        if test then
1076            local first, last = test(data)
1077            if last then
1078                return last
1079            end
1080        end
1081    end
1082end
1083
1084implement {
1085    name      = "btxsingularorplural",
1086    actions   = { publications.singularorplural, ctx_doifelse },
1087    arguments = "3 strings"
1088}
1089
1090implement {
1091    name      = "btxoneorrange",
1092    actions   = { publications.oneorrange, function(b) if b == nil then ctx_gobbletwoarguments() else ctx_doifelse(b) end end },
1093    arguments = "3 strings"
1094}
1095
1096implement {
1097    name      = "btxfirstofrange",
1098    actions   = { publications.firstofrange, context },
1099    arguments = "3 strings"
1100}
1101
1102implement {
1103    name      = "btxlastofrange",
1104    actions   = { publications.lastofrange, context },
1105    arguments = "3 strings"
1106}
1107
1108-- basic loading
1109
1110function publications.usedataset(specification)
1111    specification.kind = "current"
1112    publications.load(specification)
1113end
1114
1115implement {
1116    name      = "btxusedataset",
1117    actions   = publications.usedataset,
1118    arguments = {
1119        {
1120            { "specification" },
1121            { "dataset" },
1122            { "filename" },
1123        }
1124    }
1125}
1126
1127implement {
1128    name       = "convertbtxdatasettoxml",
1129    arguments  = { "string", true },
1130    actions    = publications.converttoxml
1131}
1132
1133-- enhancing
1134
1135do
1136
1137    -- maybe not redo when already done
1138
1139    local function shortsorter(a,b)
1140        local ay = a[2] -- year
1141        local by = b[2] -- year
1142        if ay ~= by then
1143            return ay < by
1144        end
1145        local ay = a[3] -- suffix
1146        local by = b[3] -- suffix
1147        if ay ~= by then
1148            -- bah, bah, bah
1149            local an = tonumber(ay)
1150            local bn = tonumber(by)
1151            if an and bn then
1152                return an < bn
1153            else
1154                return ay < by
1155            end
1156        end
1157        return a[4] < b[4]
1158    end
1159
1160    -- We could avoid loops by combining enhancers but that makes it only
1161    -- more messy and for documents that use publications the few extra milli
1162    -- seconds are irrelevant (there is for sure more to gain by proper coding
1163    -- of the source and or style).
1164
1165    local f_short = formatters["%s%02i"]
1166
1167    function publications.enhancers.suffixes(dataset)
1168        if not dataset then
1169            return -- bad news
1170        else
1171            report("analyzing previous publication run for %a",dataset.name)
1172        end
1173        dataset.suffixed = true
1174        --
1175        local used = usedentries[dataset.name]
1176        if not used then
1177            return -- probably a first run
1178        end
1179        local luadata  = dataset.luadata
1180        local details  = dataset.details
1181        local ordered  = dataset.ordered
1182        if not luadata or not details or not ordered then
1183            report("nothing to be analyzed in %a",dataset.name)
1184            return -- also bad news
1185        end
1186        -- we have two suffixes: author (dependent of type) and short
1187        local kind    = dataset.authorconversion or "name"
1188        local fields  = { "author", "editor" } -- will be entry in data definition
1189        local shorts  = { }
1190        local authors = { }
1191        local hasher  = publications.authorhashers[kind]
1192        local shorter = publications.authorhashers.short
1193        for i=1,#ordered do
1194            local entry = ordered[i]
1195            if entry then
1196                local tag = entry.tag
1197                if tag then
1198                    local use = used[tag]
1199                    if use then
1200                        -- use is a table of used list entries (so there can be more) and we just look at
1201                        -- the first one for btx properties
1202                        local listentry = use[1]
1203                        local userdata  = listentry.userdata
1204                        local btxspc    = userdata and userdata.btxspc
1205                        if btxspc then
1206                            -- we could act on the 3rd arg returned by getcasted but in general any string will do
1207                            -- so we deal with it in the author hashers ... maybe some day ...
1208                            local done = false
1209                            for i=1,#fields do
1210                                local field = fields[i]
1211                                local author = getcasted(dataset,tag,field,specifications[btxspc])
1212                                local kind   = type(author)
1213                                if kind == "table" or kind == "string" then
1214                                    if u then
1215                                        u = listentry.entries.text -- hm
1216                                    else
1217                                        u = "0"
1218                                    end
1219                                    local year  = tonumber(entry.year) or 9999
1220                                    local data  = { tag, year, u, i }
1221                                    -- authors
1222                                    local hash  = hasher(author)
1223                                    local found = authors[hash]
1224                                    if not found then
1225                                        authors[hash] = { data }
1226                                    else
1227                                        found[#found+1] = data
1228                                    end
1229                                    -- shorts
1230                                    local hash  = shorter(author)
1231                                    local short = f_short(hash,mod(year,100))
1232                                    local found = shorts[short]
1233                                    if not found then
1234                                        shorts[short] = { data }
1235                                    else
1236                                        found[#found+1] = data
1237                                    end
1238                                    done = true
1239                                    break
1240                                end
1241                            end
1242                            if not done then
1243                                report("unable to create short for %a, needs one of [%,t]",tag,fields)
1244                            end
1245                        else
1246                            --- no spec so let's forget about it
1247                        end
1248                    end
1249                end
1250            end
1251        end
1252        local function addsuffix(hashed,key,suffixkey)
1253            for hash, tags in sortedhash(hashed) do -- ordered ?
1254                local n = #tags
1255                if n == 0 then
1256                    -- skip
1257                elseif n == 1 then
1258                    local tagdata = tags[1]
1259                    local tag     = tagdata[1]
1260                    local detail  = details[tag]
1261                    local entry   = luadata[tag]
1262                    local year    = entry.year
1263                    detail[key]   = hash
1264                elseif n > 1 then
1265                    sort(tags,shortsorter) -- or take first -- todo: proper utf sorter
1266                    local lastyear = nil
1267                    local suffix   = nil
1268                    local previous = nil
1269                    for i=1,n do
1270                        local tagdata = tags[i]
1271                        local tag     = tagdata[1]
1272                        local detail  = details[tag]
1273                        local entry   = luadata[tag]
1274                        local year    = entry.year
1275                        detail[key]   = hash
1276                        if not year or year ~= lastyear then
1277                            lastyear = year
1278                            suffix = 1
1279                        else
1280                            if previous and suffix == 1 then
1281                                previous[suffixkey] = suffix
1282                            end
1283                            suffix = suffix + 1
1284                            detail[suffixkey] = suffix
1285                        end
1286                        previous = detail
1287                    end
1288                end
1289                if trace_suffixes then
1290                    for i=1,n do
1291                        local tag    = tags[i][1]
1292                        local year   = luadata[tag].year
1293                        local suffix = details[tag].suffix
1294                        if suffix then
1295                            report_suffix("%s: tag %a, hash %a, year %a, suffix %a",key,tag,hash,year or '',suffix or '')
1296                        else
1297                            report_suffix("%s: tag %a, hash %a, year %a",key,tag,hash,year or '')
1298                        end
1299                    end
1300                end
1301            end
1302        end
1303        addsuffix(shorts, "shorthash", "shortsuffix") -- todo: shorthash
1304        addsuffix(authors,"authorhash","authorsuffix")
1305    end
1306
1307 -- utilities.sequencers.appendaction(enhancer,"system","publications.enhancers.suffixes")
1308
1309end
1310
1311implement {
1312    name      = "btxaddentry",
1313    arguments = "3 strings",
1314    actions   = function(name,settings,content)
1315        local dataset = datasets[name]
1316        if dataset then
1317            publications.addtexentry(dataset,settings,content)
1318        end
1319    end,
1320}
1321
1322function publications.checkeddataset(name,default)
1323    local dataset = rawget(datasets,name)
1324    if dataset then
1325        return name
1326    elseif default and default ~= "" then
1327        return default
1328    else
1329        report("unknown dataset %a, forcing %a",name,v_default)
1330        return v_default
1331    end
1332end
1333
1334implement {
1335    name      = "btxsetdataset",
1336    arguments = "2 strings",
1337    actions   = { publications.checkeddataset, context },
1338}
1339
1340implement {
1341    name      = "btxsetentry",
1342    arguments = "2 strings",
1343    actions   = function(name,tag)
1344        local dataset = rawget(datasets,name)
1345        if dataset then
1346            if dataset.luadata[tag] then
1347                context(tag)
1348            else
1349                report("unknown tag %a in dataset %a",tag,name)
1350            end
1351        else
1352            report("unknown dataset %a",name)
1353        end
1354    end,
1355}
1356
1357-- rendering of fields
1358
1359do
1360
1361    local typesetters        = { }
1362    publications.typesetters = typesetters
1363
1364    local function defaulttypesetter(field,value,manipulator)
1365        if value and value ~= "" then
1366            value = tostring(value)
1367            context(manipulator and applymanipulation(manipulator,value) or value)
1368        end
1369    end
1370
1371    setmetatableindex(typesetters,function(t,k)
1372        local v = defaulttypesetter
1373        t[k] = v
1374        return v
1375    end)
1376
1377    function typesetters.string(field,value,manipulator)
1378        if value and value ~= "" then
1379            context(manipulator and applymanipulation(manipulator,value) or value)
1380        end
1381    end
1382
1383    function typesetters.author(field,value,manipulator)
1384        ctx_btxflushauthor(field)
1385    end
1386
1387 -- function typesetters.url(field,value,manipulator)
1388 --     ....
1389 -- end
1390
1391    -- if there is no specification then we're in trouble but there is
1392    -- always a default anyway
1393    --
1394    -- there's also always a fields table but it can be empty due to
1395    -- lack of specifications
1396    --
1397    -- then there can be cases where we have no specification for instance
1398    -- when we have a special kind of database
1399
1400    local splitter = lpeg.splitat(":")
1401
1402    local function permitted(category,field)
1403        local catspec = currentspecification.categories[category]
1404        if not catspec then
1405            report("invalid category %a, %s",category,"no specification") -- can't happen
1406            return false
1407        end
1408        local fields = catspec.fields
1409        if not fields then
1410            report("invalid category %a, %s",category,"no fields") -- can't happen
1411            return false
1412        end
1413        if ignoredfields and ignoredfields[field] then
1414            return false
1415        end
1416        local virtualfields = currentspecification.virtualfields
1417        if virtualfields and virtualfields[field] then
1418            return true
1419        end
1420        local sets = catspec.sets
1421        if sets then
1422            local set = sets[field]
1423            if set then
1424                return set
1425            end
1426        end
1427        if fields[field] then
1428            return true
1429        end
1430        local f, l = lpegmatch(splitter,field)
1431        if f and l and fields[f] then
1432            return true -- language specific one
1433        end
1434    end
1435
1436    local function found(dataset,tag,field,valid,fields)
1437        if valid == true then
1438         -- local fields = dataset.luadata[tag]
1439            local okay = fields[field]
1440            if okay then
1441                return field, okay
1442            end
1443            local details = dataset.details[tag]
1444            local value = details[field]
1445            if value then
1446                return field, value
1447            end
1448        elseif valid then
1449         -- local fields = dataset.luadata[tag]
1450            for i=1,#valid do
1451                local field = valid[i]
1452                local value = fields[field]
1453                if value then
1454                    return field, value
1455                end
1456            end
1457            local details = dataset.details[tag]
1458            for i=1,#valid do
1459                local value = details[field]
1460                if value then
1461                    return field, value
1462                end
1463            end
1464        end
1465    end
1466
1467    local function get(dataset,tag,field,what,check,catspec) -- somewhat more extensive
1468        local current = rawget(datasets,dataset)
1469        if current then
1470            local data = current.luadata[tag]
1471            if data then
1472                local category = data.category
1473                local catspec  = (catspec or currentspecification).categories[category]
1474                if not catspec then
1475                    return false
1476                end
1477                local fields = catspec.fields
1478                if fields then
1479                    local sets = catspec.sets
1480                    if sets then
1481                        local set = sets[field]
1482                        if set then
1483                            if check then
1484                                for i=1,#set do
1485                                    local field = set[i]
1486                                    local kind  = (not check or data[field]) and fields[field]
1487                                    if kind then
1488                                        return what and kind or field
1489                                    end
1490                                end
1491                            elseif what then
1492                                local t = { }
1493                                for i=1,#set do
1494                                    t[i] = fields[set[i]] or "unknown"
1495                                end
1496                                return concat(t,",")
1497                            else
1498                                return concat(set,",")
1499                            end
1500                        end
1501                    end
1502                    local kind = (not check or data[field]) and fields[field]
1503                    if kind then
1504                        return what and kind or field
1505                    end
1506                end
1507            end
1508        end
1509        return ""
1510    end
1511
1512    publications.permitted = permitted
1513    publications.found     = found
1514    publications.get       = get
1515
1516    local function btxflush(name,tag,field)
1517        local dataset = rawget(datasets,name)
1518        if dataset then
1519            local fields = dataset.luadata[tag]
1520            if fields then
1521                local manipulator, field = splitmanipulation(field)
1522                local category = fields.category
1523                local valid    = permitted(category,field)
1524                if valid then
1525                    local name, value = found(dataset,tag,field,valid,fields)
1526                    if value then
1527                        typesetters[currentspecification.types[name]](field,value,manipulator)
1528                    elseif trace_details then
1529                        report("%s %s %a in category %a for tag %a in dataset %a","unknown","entry",field,category,tag,name)
1530                    end
1531                elseif trace_details then
1532                    report("%s %s %a in category %a for tag %a in dataset %a","invalid","entry",field,category,tag,name)
1533                end
1534            else
1535                report("unknown tag %a in dataset %a",tag,name)
1536            end
1537        else
1538            report("unknown dataset %a",name)
1539        end
1540    end
1541
1542    local function btxfield(name,tag,field)
1543        local dataset = rawget(datasets,name)
1544        if dataset then
1545            local fields = dataset.luadata[tag]
1546            if fields then
1547                local category = fields.category
1548                local manipulator, field = splitmanipulation(field)
1549                if permitted(category,field) then
1550                    local value = fields[field]
1551                    if value then
1552                        typesetters[currentspecification.types[field]](field,value,manipulator)
1553                    elseif trace_details then
1554                        report("%s %s %a in category %a for tag %a in dataset %a","unknown","field",field,category,tag,name)
1555                    end
1556                elseif trace_details then
1557                    report("%s %s %a in category %a for tag %a in dataset %a","invalid","field",field,category,tag,name)
1558                end
1559            else
1560                report("unknown tag %a in dataset %a",tag,name)
1561            end
1562        else
1563            report("unknown dataset %a",name)
1564        end
1565    end
1566
1567    local function btxdetail(name,tag,field)
1568        local dataset = rawget(datasets,name)
1569        if dataset then
1570            local fields = dataset.luadata[tag]
1571            if fields then
1572                local details = dataset.details[tag]
1573                if details then
1574                    local category = fields.category
1575                    local manipulator, field = splitmanipulation(field)
1576                    if permitted(category,field) then
1577                        local value = details[field]
1578                        if value then
1579                            typesetters[currentspecification.types[field]](field,value,manipulator)
1580                        elseif trace_details then
1581                            report("%s %s %a in category %a for tag %a in dataset %a","unknown","detail",field,category,tag,name)
1582                        end
1583                    elseif trace_details then
1584                        report("%s %s %a in category %a for tag %a in dataset %a","invalid","detail",field,category,tag,name)
1585                    end
1586                else
1587                    report("no details for tag %a in dataset %a",tag,name)
1588                end
1589            else
1590                report("unknown tag %a in dataset %a",tag,name)
1591            end
1592        else
1593            report("unknown dataset %a",name)
1594        end
1595    end
1596
1597    local function btxdirect(name,tag,field)
1598        local dataset = rawget(datasets,name)
1599        if dataset then
1600            local fields = dataset.luadata[tag]
1601            if fields then
1602                local manipulator, field = splitmanipulation(field)
1603                local value = fields[field]
1604                if value then
1605                    context(typesetters.default(field,value,manipulator))
1606                elseif trace_details then
1607                    report("field %a of tag %a in dataset %a has no value",field,tag,name)
1608                end
1609            else
1610                report("unknown tag %a in dataset %a",tag,name)
1611            end
1612        else
1613            report("unknown dataset %a",name)
1614        end
1615    end
1616
1617    local function okay(name,tag,field)
1618        local dataset = rawget(datasets,name)
1619        if dataset then
1620            local fields = dataset.luadata[tag]
1621            if fields then
1622                local category = fields.category
1623                local valid    = permitted(category,field)
1624                if valid then
1625                    local value, field = found(dataset,tag,field,valid,fields)
1626                    return value and value ~= ""
1627                end
1628            end
1629        end
1630    end
1631
1632    publications.okay = okay
1633
1634    implement { name = "btxfield",     actions = btxfield,  arguments = "3 strings" }
1635    implement { name = "btxdetail",    actions = btxdetail, arguments = "3 strings" }
1636    implement { name = "btxflush",     actions = btxflush,  arguments = "3 strings" }
1637    implement { name = "btxdirect",    actions = btxdirect, arguments = "3 strings" }
1638
1639    implement { name = "btxfieldname", actions = { get, context }, arguments = { "string", "string", "string", false, false } }
1640    implement { name = "btxfieldtype", actions = { get, context }, arguments = { "string", "string", "string", true,  false } }
1641    implement { name = "btxfoundname", actions = { get, context }, arguments = { "string", "string", "string", false, true  } }
1642    implement { name = "btxfoundtype", actions = { get, context }, arguments = { "string", "string", "string", true,  true  } }
1643
1644    implement { name = "btxdoifelse",  actions = { okay, ctx_doifelse }, arguments = "3 strings" }
1645    implement { name = "btxdoif",      actions = { okay, ctx_doif     }, arguments = "3 strings" }
1646    implement { name = "btxdoifnot",   actions = { okay, ctx_doifnot  }, arguments = "3 strings" }
1647
1648end
1649
1650-- -- alternative approach: keep data at the tex end
1651
1652function publications.singularorplural(singular,plural)
1653    if lastconcatsize and lastconcatsize > 1 then
1654        context(plural)
1655    else
1656        context(singular)
1657    end
1658end
1659
1660-- loading
1661
1662do
1663
1664    local patterns = {
1665        CONTEXTLMTXMODE > 0 and "symb-imp-%s.mklx" or "",
1666        CONTEXTLMTXMODE > 0 and "symb-imp-%s.mkxl" or "",
1667        "publ-imp-%s.mkvi",
1668        "publ-imp-%s.mkiv",
1669        "publ-imp-%s.tex",
1670    }
1671
1672    local function failure(name)
1673        report("unknown library %a",name)
1674    end
1675
1676    local function action(name,foundname)
1677        context.loadfoundpublicationfile(name,foundname)
1678    end
1679
1680    function publications.loaddefinitionfile(name) -- a more specific name
1681        resolvers.uselibrary {
1682            name     = gsub(name,"^publ%-",""),
1683            patterns = patterns,
1684            action   = action,
1685            failure  = failure,
1686            onlyonce = true,
1687        }
1688    end
1689
1690    local patterns = {
1691        "publ-imp-%s.lua",
1692    }
1693
1694    function publications.loadreplacementfile(name) -- a more specific name
1695        resolvers.uselibrary {
1696            name     = gsub(name,"^publ%-",""),
1697            patterns = patterns,
1698            action   = publications.loaders.registercleaner,
1699            failure  = failure,
1700            onlyonce = true,
1701        }
1702    end
1703
1704    implement { name = "btxloaddefinitionfile",  actions = publications.loaddefinitionfile,  arguments = "string" }
1705    implement { name = "btxloadreplacementfile", actions = publications.loadreplacementfile, arguments = "string" }
1706
1707end
1708
1709-- lists
1710
1711local renderings = { } --- per dataset
1712
1713do
1714
1715    publications.lists = publications.lists or { }
1716    local lists        = publications.lists
1717
1718    local context     = context
1719    local structures  = structures
1720
1721    local references  = structures.references
1722    local sections    = structures.sections
1723
1724    -- per rendering
1725
1726    setmetatableindex(renderings,function(t,k)
1727        local v = {
1728            list         = { },
1729            done         = { },
1730            alldone      = { },
1731            used         = { },
1732            registered   = { },
1733            ordered      = { },
1734            shorts       = { },
1735            method       = v_none,
1736            texts        = setmetatableindex("table"),
1737            currentindex = 0,
1738        }
1739        t[k] = v
1740        return v
1741    end)
1742
1743    -- helper
1744
1745    function lists.register(dataset,tag,short) -- needs checking now that we split
1746        local r = renderings[dataset]
1747        if not short or short == "" then
1748            short = tag
1749        end
1750        if trace then
1751            report("registering publication entry %a with shortcut %a",tag,short)
1752        end
1753        local top = #r.registered + 1
1754        -- do we really need these
1755        r.registered[top] = tag
1756        r.ordered   [tag] = top
1757        r.shorts    [tag] = short
1758    end
1759
1760    function lists.nofregistered(dataset)
1761        return #renderings[dataset].registered
1762    end
1763
1764    local function validkeyword(dataset,tag,keyword,specification) -- todo: pass specification
1765        local kw = getcasted(dataset,tag,"keywords",specification)
1766        if kw then
1767            for i=1,#kw do
1768                if keyword[kw[i]] then
1769                    return true
1770                end
1771            end
1772        end
1773    end
1774
1775    local function registerpage(pages,tag,result,listindex)
1776        local p = pages[tag]
1777        local r = result[listindex].references
1778        if p then
1779            local last = p[#p][2]
1780            local real = last.realpage
1781            if real ~= r.realpage then
1782                p[#p+1] = { listindex, r }
1783            end
1784        else
1785            pages[tag] = { { listindex, r } }
1786        end
1787    end
1788
1789    -- tag | listindex | reference | userdata | dataindex
1790
1791    local methods = { }
1792    lists.methods = methods
1793
1794    methods[v_dataset] = function(dataset,rendering,keyword)
1795        local current = datasets[dataset]
1796        local luadata = current.luadata
1797        local list    = rendering.list
1798        for tag, data in sortedhash(luadata) do
1799            if not keyword or validkeyword(dataset,tag,keyword) then
1800                local index = data.index or 0
1801                list[#list+1] = { tag, index, 0, false, index }
1802            end
1803        end
1804    end
1805
1806    methods[v_label] = function(dataset,rendering,keyword)
1807        if type(keyword) == "table" then
1808            local current = datasets[dataset]
1809            local luadata = current.luadata
1810            local list    = rendering.list
1811            for tag in next, keyword do
1812                local data = luadata[tag]
1813                if data then
1814                    local index = data.index or 0
1815                    list[#list+1] = { tag, index, 0, false, index }
1816                end
1817            end
1818        end
1819    end
1820
1821    -- todo: names = { "btx" }
1822
1823    local function collectresult(rendering)
1824        return structures.lists.filter(rendering.specifications) or { }
1825    end
1826
1827    methods[v_force] = function (dataset,rendering,keyword)
1828        -- only for checking, can have duplicates, todo: collapse page numbers, although
1829        -- we then also needs deferred writes
1830        local result  = collectresult(rendering)
1831        local list    = rendering.list
1832        local current = datasets[dataset]
1833        local luadata = current.luadata
1834        for listindex=1,#result do
1835            local r = result[listindex]
1836            local u = r.userdata -- better check on metadata.kind == "btx"
1837            if u then
1838                local set = u.btxset or v_default
1839                if set == dataset then
1840                    local tag = u.btxref
1841                    if tag and (not keyword or validkeyword(dataset,tag,keyword)) then
1842                        local data = luadata[tag]
1843                        list[#list+1] = { tag, listindex, 0, u, data and data.index or 0 }
1844                    end
1845                end
1846            end
1847        end
1848        lists.result = result
1849    end
1850
1851    -- local  : if tag and                      done[tag] ~= section then ...
1852    -- global : if tag and not alldone[tag] and done[tag] ~= section then ...
1853
1854    methods[v_local] = function(dataset,rendering,keyword)
1855        local result    = collectresult(rendering)
1856        local section   = sections.currentid()
1857        local list      = rendering.list
1858        local repeated  = rendering.repeated == v_yes
1859        local r_done    = rendering.done
1860        local r_alldone = rendering.alldone
1861        local done      = repeated and { } or r_done
1862        local alldone   = repeated and { } or r_alldone
1863        local doglobal  = rendering.method == v_global
1864        local traced    = { } -- todo: only if interactive (backlinks) or when tracing
1865        local pages     = { }
1866        local current   = datasets[dataset]
1867        local luadata   = current.luadata
1868        -- handy for tracing :
1869        rendering.result = result
1870        --
1871        for listindex=1,#result do
1872            local r = result[listindex]
1873            local u = r.userdata
1874            if u then -- better check on metadata.kind == "btx"
1875                local set = u.btxset or v_default
1876                if set == dataset then
1877-- inspect(structures.references.internals[tonumber(u.btxint)])
1878                    local tag = u.btxref
1879                    if not tag then
1880                        -- problem
1881                    elseif done[tag] == section then -- a bit messy for global and all and so
1882                        -- skip
1883                    elseif doglobal and alldone[tag] then
1884                        -- skip
1885                    elseif not keyword or validkeyword(dataset,tag,keyword) then
1886                        if traced then
1887                            local l = traced[tag]
1888                            if l then
1889                                l[#l+1] = u.btxint -- tonumber ?
1890                            else
1891                                local data = luadata[tag]
1892                                local l = { tag, listindex, 0, u, data and data.index or 0 }
1893                                list[#list+1] = l
1894                                traced[tag] = l
1895                            end
1896                        else
1897                            done[tag]    = section
1898                            alldone[tag] = true
1899                            local data = luadata[tag]
1900                            list[#list+1] = { tag, listindex, 0, u, data and data.index or 0 }
1901                        end
1902                    end
1903                    if tag then
1904                        registerpage(pages,tag,result,listindex)
1905                    end
1906                end
1907            end
1908        end
1909        if traced then
1910            for tag in next, traced do
1911                done[tag]    = section
1912                alldone[tag] = true
1913            end
1914        end
1915        lists.result = result
1916        structures.lists.result = result
1917        rendering.pages = pages -- or list.pages
1918    end
1919
1920    methods[v_global] = methods[v_local]
1921
1922    function lists.collectentries(specification)
1923        local dataset = specification.dataset
1924        if not dataset then
1925            return
1926        end
1927        local rendering = renderings[dataset]
1928        if not rendering then
1929            return
1930        end
1931        local method             = specification.method or v_none
1932        local ignored            = specification.ignored or ""
1933        local filter             = specification.filter or ""
1934        rendering.method         = method
1935        rendering.ignored        = ignored ~= "" and settings_to_set(ignored) or nil
1936        rendering.list           = { }
1937        rendering.done           = { }
1938        rendering.sorttype       = specification.sorttype or v_default
1939        rendering.criterium      = specification.criterium or v_none
1940        rendering.repeated       = specification.repeated or v_no
1941        rendering.group          = specification.group or ""
1942        rendering.specifications = specification
1943        rendering.collected      = false
1944        local filtermethod       = methods[method]
1945        if not filtermethod then
1946            report_list("invalid method %a",method or "")
1947            return
1948        end
1949        report_list("collecting entries using method %a and sort order %a",method,rendering.sorttype)
1950        lists.result  = { } -- kind of reset
1951        local keyword = specification.keyword
1952        if keyword and keyword ~= "" then
1953            keyword = settings_to_set(keyword)
1954        else
1955            keyword = nil
1956        end
1957        local filename = specification.filename
1958        if filename and filename ~= "" then
1959            local utilitydata = job.loadother(filename)
1960            local lists = utilitydata and utilitydata.structures.lists
1961            if lists then
1962                rendering.collected = lists.collected
1963            else
1964                return
1965            end
1966        end
1967        filtermethod(dataset,rendering,keyword)
1968        local list = rendering.list
1969        if list and filter ~= "" then
1970            local find = publications.finder(dataset,filter)
1971            if find then
1972                local luadata = datasets[dataset].luadata
1973                local matched = 0
1974                for i=1,#list do
1975                    local found = list[i]
1976                    local entry = luadata[found[1]]
1977                    if find(entry) then
1978                        matched = matched + 1
1979                        list[matched] = found
1980                    end
1981                end
1982                for i=#list,matched + 1,-1 do
1983                    list[i] = nil
1984                end
1985            end
1986        end
1987        ctx_btxsetnoflistentries(list and #list or 0)
1988    end
1989
1990    -- for determining width
1991
1992    local groups = setmetatableindex("number")
1993
1994    function lists.prepareentries(dataset)
1995        local rendering = renderings[dataset]
1996        local list      = rendering.list
1997        local used      = rendering.used
1998        local forceall  = rendering.criterium == v_all
1999        local repeated  = rendering.repeated == v_yes
2000        local sorttype  = rendering.sorttype or v_default
2001        local group     = rendering.group or ""
2002        local sorter    = lists.sorters[sorttype]
2003        local current   = datasets[dataset]
2004        local luadata   = current.luadata
2005        local details   = current.details
2006        local newlist   = { }
2007        local lastreferencenumber = groups[group] -- current.lastreferencenumber or 0
2008        for i=1,#list do
2009            local li    = list[i]
2010            local tag   = li[1]
2011            local entry = luadata[tag]
2012            if entry then
2013                if forceall or repeated or not used[tag] then
2014                    newlist[#newlist+1] = li
2015                    -- already here:
2016                    if not repeated then
2017                        used[tag] = true -- beware we keep the old state (one can always use criterium=all)
2018                    end
2019                end
2020            end
2021        end
2022        if type(sorter) == "function" then
2023            list = sorter(dataset,rendering,newlist,sorttype) or newlist
2024        else
2025            list = newlist
2026        end
2027        local newlist        = { }
2028        local tagtolistindex = { }
2029        rendering.tagtolistindex = tagtolistindex
2030-- if rendering.criterium == v_label then
2031-- else
2032        for i=1,#list do
2033            local li    = list[i]
2034            local tag   = li[1]
2035            local entry = luadata[tag]
2036            if entry then
2037                local detail = details[tag]
2038                if not detail then
2039                    -- fatal error
2040                    report("fatal error, missing details for tag %a in dataset %a (enhanced: %s)",tag,dataset,current.enhanced and "yes" or "no")
2041                 -- lastreferencenumber = lastreferencenumber + 1
2042                 -- details[tag] = { referencenumber = lastreferencenumber }
2043                 -- li[3] = lastreferencenumber
2044                 -- tagtolistindex[tag] = i
2045                 -- newlist[#newlist+1] = li
2046                elseif detail.parent then
2047                    -- skip this one
2048                else
2049                    local referencenumber = detail.referencenumber
2050                    if not referencenumber then
2051                        lastreferencenumber    = lastreferencenumber + 1
2052                        referencenumber        = lastreferencenumber
2053                        detail.referencenumber = lastreferencenumber
2054                    end
2055                    li[3] = referencenumber
2056                    tagtolistindex[tag] = i
2057                    newlist[#newlist+1] = li
2058                end
2059            end
2060        end
2061        groups[group] = lastreferencenumber
2062        rendering.list = newlist
2063-- end
2064    end
2065
2066    function lists.fetchentries(dataset)
2067        local rendering = renderings[dataset]
2068        local list      = rendering.list
2069        if list then
2070            for i=1,#list do
2071                local li = list[i]
2072                ctx_btxsettag(li[1])
2073                ctx_btxsetnumber(li[3])
2074                ctx_btxchecklistentry()
2075            end
2076        end
2077    end
2078
2079    -- for rendering
2080
2081    -- setspecification
2082
2083    local function btxflushpages(dataset,tag)
2084        -- todo: interaction
2085        local rendering = renderings[dataset]
2086        local pages     = rendering.pages
2087        if not pages then
2088            return
2089        else
2090            pages = pages[tag]
2091        end
2092        if not pages then
2093            return
2094        end
2095        local nofpages = #pages
2096        if nofpages == 0 then
2097            return
2098        end
2099        local first_p = nil
2100        local first_r = nil
2101        local last_p  = nil
2102        local last_r  = nil
2103        local ranges  = { }
2104        local nofdone = 0
2105        local function flush()
2106            if last_r and first_r ~= last_r then
2107                ranges[#ranges+1] = { first_p, last_p }
2108            else
2109                ranges[#ranges+1] = { first_p }
2110            end
2111        end
2112        for i=1,nofpages do
2113            local next_p = pages[i]
2114            local next_r = next_p[2].realpage
2115            if not first_r then
2116                first_p = next_p
2117                first_r = next_r
2118            elseif last_r + 1 == next_r then
2119                -- continue
2120            elseif first_r then
2121                flush()
2122                first_p = next_p
2123                first_r = next_r
2124            end
2125            last_p = next_p
2126            last_r = next_r
2127        end
2128        if first_r then
2129            flush()
2130        end
2131        local nofranges = #ranges
2132        local interactive = not rendering.collected
2133        for i=1,nofranges do
2134            local r = ranges[i]
2135            ctx_btxsetconcat(concatstate(i,nofranges))
2136            local first = r[1]
2137            local last  = r[2]
2138            if interactive then
2139                ctx_btxsetfirstinternal(first[2].internal)
2140            end
2141            ctx_btxsetfirstpage(first[1])
2142            if last then
2143                if interactive then
2144                    ctx_btxsetlastinternal(last[2].internal)
2145                end
2146                ctx_btxsetlastpage(last[1])
2147            end
2148            if trace_details then
2149                report("expanding page setup")
2150            end
2151            ctx_btxpagesetup("") -- nothing yet
2152        end
2153    end
2154
2155    implement {
2156        name      = "btxflushpages",
2157        arguments = "2 strings",
2158        actions   = btxflushpages,
2159    }
2160
2161    local function identical(a,b)
2162        local na = #a
2163        local nb = #b
2164        if na ~= nb then
2165            return false
2166        end
2167        if na > 0 then
2168            for i=1,na do
2169                if not identical(a[i],b[i]) then
2170                    return false
2171                end
2172            end
2173            return true
2174        end
2175        local ha = a.hash
2176        local hb = b.hash
2177        if ha then
2178            return ha == hb
2179        end
2180        for k, v in next, a do
2181            if k == "original" or k == "snippets" then
2182                -- skip diagnostic info
2183            elseif v ~= b[k] then
2184                return false
2185            end
2186        end
2187        return true
2188    end
2189
2190    function lists.sameasprevious(dataset,i,name,order,method)
2191        local rendering = renderings[dataset]
2192        local list      = rendering.list
2193        local n         = tonumber(i)
2194        if n and n > 1 and n <= #list then
2195            local luadata   = datasets[dataset].luadata
2196            local p_index   = list[n-1][1]
2197            local c_index   = list[n  ][1]
2198            local previous  = getdirect(dataset,luadata[p_index],name)
2199            local current   = getdirect(dataset,luadata[c_index],name)
2200
2201            -- authors are a special case
2202
2203          -- if not order then
2204          --    order = gettexcounter("c_btx_list_reference")
2205          -- end
2206            if order and order > 0 and (method == v_always or method == v_doublesided) then
2207                local clist = listtolist[order]
2208                local plist = listtolist[order-1]
2209                if clist and plist then
2210                    local crealpage = clist.references.realpage
2211                    local prealpage = plist.references.realpage
2212                    if crealpage ~= prealpage then
2213                        if method == v_always or not conditionals.layoutisdoublesided then
2214                            if trace_details then
2215                                report("previous %a, current %a, different page",previous,current)
2216                            end
2217                            return false
2218                        elseif crealpage % 2 == 0 then
2219                            if trace_details then
2220                                report("previous %a, current %a, different page",previous,current)
2221                            end
2222                            return false
2223                        end
2224                    end
2225                end
2226            end
2227            local sameentry = false
2228            if current and current == previous then
2229                sameentry = true
2230            else
2231                local p_casted = getcasted(dataset,p_index,name)
2232                local c_casted = getcasted(dataset,c_index,name)
2233                if c_casted and c_casted == p_casted then
2234                    sameentry = true
2235                elseif type(c_casted) == "table" and type(p_casted) == "table" then
2236                    sameentry = identical(c_casted,p_casted)
2237                end
2238            end
2239            if trace_details then
2240                if sameentry then
2241                    report("previous %a, current %a, same entry",previous,current)
2242                else
2243                    report("previous %a, current %a, different entry",previous,current)
2244                end
2245           end
2246            return sameentry
2247        else
2248            return false
2249        end
2250    end
2251
2252    function lists.combiinlist(dataset,tag)
2253        local rendering = renderings[dataset]
2254     -- local list      = rendering.list
2255        local toindex   = rendering.tagtolistindex
2256        return toindex and toindex[tag]
2257    end
2258
2259    function lists.flushcombi(dataset,tag)
2260        local rendering = renderings[dataset]
2261        local toindex   = rendering.tagtolistindex
2262        local listindex = toindex and toindex[tag]
2263        if listindex then
2264            local list = rendering.list
2265            local li   = list[listindex]
2266            if li then
2267                local data      = datasets[dataset]
2268                local luadata   = data.luadata
2269                local details   = data.details
2270                local tag       = li[1]
2271                local listindex = li[2]
2272                local n         = li[3]
2273                local entry     = luadata[tag]
2274                local detail    = details[tag]
2275                ctx_btxstartcombientry()
2276                ctx_btxsetcurrentlistindex(listindex)
2277                ctx_btxsetcategory(entry.category or "unknown")
2278                ctx_btxsettag(tag)
2279                ctx_btxsetnumber(n)
2280                local language = entry.language
2281                if language then
2282                    ctx_btxsetlanguage(language)
2283                end
2284                local authorsuffix = detail.authorsuffix
2285                if authorsuffix then
2286                    ctx_btxsetsuffix(authorsuffix)
2287                end
2288                ctx_btxhandlecombientry()
2289                ctx_btxstopcombientry()
2290            end
2291        end
2292    end
2293
2294    function lists.flushtag(dataset,i)
2295        local li = renderings[dataset].list[i]
2296        ctx_btxsettag(li and li[1] or "")
2297    end
2298
2299    function lists.flushentry(dataset,i)
2300        local rendering = renderings[dataset]
2301        local list      = rendering.list
2302        local li        = list[i]
2303        if li then
2304            local data        = datasets[dataset]
2305            local luadata     = data.luadata
2306            local details     = data.details
2307            local tag         = li[1]
2308            local listindex   = li[2]
2309            local n           = li[3]
2310            local entry       = luadata[tag]
2311            local detail      = details[tag]
2312            --
2313            local interactive = not rendering.collected
2314            --
2315            ctx_btxstartlistentry()
2316            ctx_btxsetcurrentlistentry(i) -- redundant
2317            ctx_btxsetcurrentlistindex(listindex or 0)
2318            local children = detail.children
2319            local language = entry.language
2320            if children then
2321                ctx_btxsetcombis(concat(children,","))
2322            end
2323            ctx_btxsetcategory(entry.category or "unknown")
2324            ctx_btxsettag(tag)
2325            ctx_btxsetnumber(n)
2326            --
2327            if interactive then
2328                local citation = citetolist[listindex] -- was wrong (sort wise): local citation = citetolist[n]
2329                if citation then
2330                    local references = citation.references
2331                    if references then
2332                        local internal = references.internal
2333                        if internal and internal > 0 then
2334                            ctx_btxsetinternal(internal)
2335                        end
2336                    end
2337                end
2338            end
2339            --
2340            if language then
2341                ctx_btxsetlanguage(language)
2342            end
2343            local userdata = li[4]
2344            if userdata then
2345                local b = userdata.btxbtx
2346                local a = userdata.btxatx
2347                if b then
2348                    ctx_btxsetbefore(b)
2349                end
2350                if a then
2351                    ctx_btxsetafter(a)
2352                end
2353                local bl = tonumber(userdata.btxint)
2354                if bl then
2355                    ctx_btxsetbacklink(bl)
2356                    bl = listtocite[bl]
2357                end
2358            end
2359            local authorsuffix = detail.authorsuffix
2360            if authorsuffix then
2361                ctx_btxsetsuffix(authorsuffix)
2362            end
2363            rendering.userdata = userdata
2364            ctx_btxhandlelistentry()
2365            ctx_btxstoplistentry()
2366            --
2367         -- context(function()
2368         --     -- wrapup
2369         --     rendering.ignoredfields = nil
2370         -- end)
2371        end
2372    end
2373
2374    local function getuserdata(dataset,key)
2375        local rendering = renderings[dataset]
2376        if rendering then
2377            local userdata = rendering.userdata
2378            if userdata then
2379                local value = userdata[key]
2380                if value and value ~= "" then
2381                    return value
2382                end
2383            end
2384        end
2385    end
2386
2387    lists.uservariable = getuserdata
2388
2389    function lists.filterall(dataset)
2390        local r = renderings[dataset]
2391        local list = r.list
2392        local registered = r.registered
2393        for i=1,#registered do
2394            list[i] = { registered[i], i, 0, false, false }
2395        end
2396    end
2397
2398    implement {
2399        name      = "btxuservariable",
2400        arguments = "2 strings",
2401        actions   = { getuserdata, context },
2402    }
2403
2404    implement {
2405        name      = "btxdoifelseuservariable",
2406        arguments = "2 strings",
2407        actions   = { getuserdata, ctx_doifelse },
2408    }
2409
2410 -- implement {
2411 --     name      = "btxresolvelistreference",
2412 --     arguments = "2 strings",
2413 --     actions   = lists.resolve,
2414 -- }
2415
2416    implement {
2417        name      = "btxcollectlistentries",
2418        actions   = lists.collectentries,
2419        arguments = {
2420            {
2421                { "names" },
2422                { "criterium" },
2423                { "reference" },
2424                { "method" },
2425                { "dataset" },
2426                { "keyword" },
2427                { "sorttype" },
2428                { "repeated" },
2429                { "ignored" },
2430                { "group" },
2431                { "filter" },
2432                { "filename" },
2433            }
2434        }
2435    }
2436
2437    implement {
2438        name      = "btxpreparelistentries",
2439        arguments = "string",
2440        actions   = lists.prepareentries,
2441    }
2442
2443    implement {
2444        name      = "btxfetchlistentries",
2445        arguments = "string",
2446        actions   = lists.fetchentries,
2447    }
2448
2449    implement {
2450        name      = "btxflushlistentry",
2451        arguments = { "string", "integer" },
2452        actions   = lists.flushentry,
2453    }
2454
2455    implement {
2456        name      = "btxflushlisttag",
2457        arguments = { "string", "integer" },
2458        actions   = lists.flushtag,
2459    }
2460
2461    implement {
2462        name      = "btxflushlistcombi",
2463        arguments = "2 strings",
2464        actions   = lists.flushcombi,
2465    }
2466
2467    implement {
2468        name      = "btxdoifelsesameasprevious",
2469        actions   = { lists.sameasprevious, ctx_doifelse },
2470        arguments = { "string", "integer", "string", "integer", "string" }
2471    }
2472
2473    implement {
2474        name      = "btxdoifelsecombiinlist",
2475        arguments = "2 strings",
2476        actions   = { lists.combiinlist, ctx_doifelse },
2477    }
2478
2479end
2480
2481do
2482
2483    local citevariants        = { }
2484    publications.citevariants = citevariants
2485
2486    local function btxvalidcitevariant(dataset,variant)
2487        local citevariant = rawget(citevariants,variant)
2488        if citevariant then
2489            return citevariant
2490        end
2491        local s = datasets[dataset]
2492        if s then
2493            s = s.specifications
2494        end
2495        if s then
2496            for k, v in sortedhash(s) do
2497                s = k
2498                break
2499            end
2500            -- weird
2501            if type(s) == "table" then
2502                return citevariants.default
2503            end
2504        end
2505        if s then
2506            s = specifications[s]
2507        end
2508        if s then
2509            s = s.types
2510        end
2511        if s then
2512            variant = s[variant]
2513            if variant then
2514                citevariant = rawget(citevariants,variant)
2515            end
2516            if citevariant then
2517                return citevariant
2518            end
2519        end
2520        return citevariants.default
2521    end
2522
2523    local function btxhandlecite(specification)
2524        local dataset   = specification.dataset or v_default
2525        local reference = specification.reference
2526        local variant   = specification.variant
2527        --
2528        if not variant or variant == "" then
2529            variant = "default"
2530        end
2531        if not reference or reference == "" then
2532            return
2533        end
2534        --
2535        local data = datasets[dataset]
2536        if not data.suffixed then
2537            data.authorconversion = specification.authorconversion
2538            publications.enhancers.suffixes(data)
2539        end
2540        --
2541        specification.variant   = variant
2542        specification.compress  = specification.compress
2543        specification.markentry = specification.markentry ~= false
2544        --
2545        if specification.sorttype == v_yes then
2546            specification.sorttype = v_normal
2547        end
2548        --
2549        local prefix, rest = lpegmatch(prefixsplitter,reference)
2550        if prefix and rest then
2551            dataset = prefix
2552            specification.dataset   = prefix
2553            specification.reference = rest
2554        end
2555        --
2556        if trace_cite then
2557            report_cite("inject, dataset: %s, tag: %s, variant: %s, compressed",
2558                specification.dataset or "-",
2559                specification.reference,
2560                specification.variant
2561            )
2562        end
2563        --
2564        ctx_btxsetdataset(dataset)
2565        --
2566        local citevariant = btxvalidcitevariant(dataset,variant)
2567        --
2568        citevariant(specification) -- we always fall back on default
2569    end
2570
2571    local function btxhandlenocite(specification)
2572        if trialtypesetting() then
2573            return
2574        end
2575        local dataset   = specification.dataset or v_default
2576        local reference = specification.reference
2577        if not reference or reference == "" then
2578            return
2579        end
2580        --
2581        local method   = specification.method
2582        local internal = specification.internal or 0
2583        --
2584        local prefix, rest = lpegmatch(prefixsplitter,reference)
2585        if rest then
2586            dataset   = prefix
2587            reference = rest
2588        end
2589        --
2590        if trace_cite then
2591            report_cite("mark, dataset: %s, tags: %s",dataset or "-",reference)
2592        end
2593        --
2594        local reference = publications.parenttag(dataset,reference)
2595        --
2596        local found, todo, list = findallused(dataset,reference,internal)
2597        --
2598        if todo then
2599            marked_todo    = todo
2600            marked_dataset = dataset
2601            marked_list    = list
2602            marked_method  = method
2603            btxflushmarked() -- here (could also be done in caller)
2604        else
2605            marked_todo = false
2606        end
2607    end
2608
2609    implement {
2610        name      = "btxhandlecite",
2611        actions   = btxhandlecite,
2612        arguments = {
2613            {
2614                { "dataset" },
2615                { "reference" },
2616                { "method" },
2617                { "variant" },
2618                { "sorttype" },
2619                { "compress" },
2620                { "authorconversion" },
2621                { "author" },
2622                { "lefttext" },
2623                { "righttext" },
2624                { "before" },
2625                { "after" },
2626            }
2627        }
2628    }
2629
2630    implement {
2631        name      = "btxhandlenocite",
2632        actions   = btxhandlenocite,
2633        arguments = {
2634            {
2635                { "dataset" },
2636                { "reference" },
2637                { "method" },
2638            }
2639        }
2640    }
2641
2642    -- sorter
2643
2644    local keysorter = function(a,b)
2645        local ak = a.sortkey
2646        local bk = b.sortkey
2647        if ak == bk then
2648            local as = a.suffix -- numeric
2649            local bs = b.suffix -- numeric
2650            if as and bs then
2651                return (as or 0) < (bs or 0)
2652            else
2653                return false
2654            end
2655        else
2656            return ak < bk
2657        end
2658    end
2659
2660    local revsorter = function(a,b)
2661        return keysorter(b,a)
2662    end
2663
2664    local function compresslist(source,specification)
2665        if specification.sorttype == v_normal then
2666            sort(source,keysorter)
2667        elseif specification.sorttype == v_reverse then
2668            sort(source,revsorter)
2669        end
2670        if specification and specification.compress == v_yes and specification.numeric then
2671            local first, last, firstr, lastr
2672            local target, noftarget, tags = { }, 0, { }
2673            local oldvalue = nil
2674            local function flushrange()
2675                noftarget = noftarget + 1
2676                if last > first + 1 then
2677                    target[noftarget] = {
2678                        first = firstr,
2679                        last  = lastr,
2680                        tags  = tags,
2681                    }
2682                else
2683                    target[noftarget] = firstr
2684                    if last > first then
2685                        noftarget = noftarget + 1
2686                        target[noftarget] = lastr
2687                    end
2688                end
2689                tags = { }
2690            end
2691            for i=1,#source do
2692                local entry   = source[i]
2693                local current = entry.sortkey -- so we need a sortkey !
2694                if type(current) == "number" then
2695                    if entry.suffix then
2696                        if not first then
2697                            first, last, firstr, lastr = current, current, entry, entry
2698                        else
2699                            flushrange()
2700                            first, last, firstr, lastr = current, current, entry, entry
2701                        end
2702                    else
2703                        if not first then
2704                            first, last, firstr, lastr = current, current, entry, entry
2705                        elseif current == last + 1 then
2706                            last, lastr = current, entry
2707                        else
2708                            flushrange()
2709                            first, last, firstr, lastr = current, current, entry, entry
2710                        end
2711                    end
2712                    tags[#tags+1] = entry.tag
2713                end
2714            end
2715            if first and last then
2716                flushrange()
2717            end
2718            return target
2719        else
2720            local target, noftarget = { }, 0
2721            for i=1,#source do
2722                local entry = source[i]
2723                noftarget   = noftarget + 1
2724                target[noftarget] = {
2725                    first = entry,
2726                    tags  = { entry.tag },
2727                }
2728            end
2729            return target
2730        end
2731    end
2732
2733    -- local source = {
2734    --     { tag = "one",   internal = 1, value = "foo", page = 1 },
2735    --     { tag = "two",   internal = 2, value = "bar", page = 2 },
2736    --     { tag = "three", internal = 3, value = "gnu", page = 3 },
2737    -- }
2738    --
2739    -- local target = compresslist(source)
2740
2741    local numberonly = R("09")^1 / tonumber + P(1)^0
2742    local f_missing  = formatters["<%s>"]
2743
2744    -- maybe also sparse (e.g. pages)
2745
2746    -- a bit redundant access to datasets
2747
2748    local creported = setmetatableindex("table")
2749
2750    local function processcite(presets,specification)
2751        --
2752        if specification then
2753            setmetatableindex(specification,presets)
2754        else
2755            specification = presets
2756        end
2757        --
2758        local dataset    = specification.dataset
2759        local reference  = specification.reference
2760        local internal   = specification.internal
2761        local setup      = specification.variant
2762        local compress   = specification.compress
2763        local sorttype   = specification.sorttype
2764        local getter     = specification.getter
2765        local setter     = specification.setter
2766        local compressor = specification.compressor
2767        local method     = specification.method
2768        local varfield   = specification.varfield
2769        --
2770        local reference  = publications.parenttag(dataset,reference)
2771        --
2772        local found, todo, list = findallused(dataset,reference,internal,method == v_text or method == v_always) -- also when not in list
2773        --
2774        if not found or #found == 0 then
2775--         if not list or #list == 0 then
2776            if not creported[dataset][reference] then
2777                report("no entry %a found in dataset %a",reference,dataset)
2778                creported[dataset][reference] = true
2779            end
2780        elseif not setup then
2781            if not creported[""][reference] then
2782                report("invalid reference for %a",reference)
2783                creported[""][reference] = true
2784            end
2785        else
2786            if trace_cite then
2787                report("processing reference %a",reference)
2788            end
2789            local source  = { }
2790            local luadata = datasets[dataset].luadata
2791            for i=1,#found do
2792                local entry      = found[i]
2793                local userdata   = entry.userdata
2794                local references = entry.references
2795                local tag        = userdata and userdata.btxref or entry.tag -- no need for userdata
2796                if tag then
2797                    local ldata = luadata[tag]
2798                    local data  = {
2799                        internal  = references and references.internal,
2800                        language  = ldata.language,
2801                        dataset   = dataset,
2802                        tag       = tag,
2803                        varfield  = varfield,
2804                     -- combis    = entry.userdata.btxcom,
2805                     -- luadata   = ldata,
2806                    }
2807                    setter(data,dataset,tag,entry)
2808                    if type(data) == "table" then
2809                        source[#source+1] = data
2810                    else
2811                        report("error in cite rendering %a",setup or "?")
2812                    end
2813                end
2814            end
2815
2816            local lefttext  = specification.lefttext
2817            local righttext = specification.righttext
2818            local before    = specification.before
2819            local after     = specification.after
2820
2821            if lefttext  and lefttext  ~= "" then lefttext  = settings_to_array(lefttext)  end
2822            if righttext and righttext ~= "" then righttext = settings_to_array(righttext) end
2823            if before    and before    ~= "" then before    = settings_to_array(before)    end
2824            if after     and after     ~= "" then after     = settings_to_array(after)     end
2825
2826            local function flush(i,n,entry,last)
2827                local tag = entry.tag
2828                local currentcitation = markcite(dataset,tag)
2829                --
2830                ctx_btxstartcite()
2831                ctx_btxsettag(tag)
2832                ctx_btxsetcategory(entry.category or "unknown")
2833                --
2834                local language = entry.language
2835                if language then
2836                    ctx_btxsetlanguage(language)
2837                end
2838                --
2839                if lefttext  then local text = lefttext [i] ; if text and text ~= "" then ctx_btxsetlefttext (text) end end
2840                if righttext then local text = righttext[i] ; if text and text ~= "" then ctx_btxsetrighttext(text) end end
2841                if before    then local text = before   [i] ; if text and text ~= "" then ctx_btxsetbefore   (text) end end
2842                if after     then local text = after    [i] ; if text and text ~= "" then ctx_btxsetafter    (text) end end
2843                --
2844                if method ~= v_text then
2845                    ctx_btxsetbacklink(currentcitation)
2846                    local bl = listtocite[currentcitation]
2847                    if bl then
2848                        -- we refer to a coming list entry
2849                        bl = bl.references.internal
2850                    else
2851                        -- we refer to a previous list entry
2852                        bl = entry.internal
2853                    end
2854                    -- no check for external (yet)
2855                    ctx_btxsetinternal(bl and bl > 0 and bl or "")
2856                end
2857                local language = entry.language
2858                if language then
2859                    ctx_btxsetlanguage(language)
2860                end
2861             -- local combis = entry.combis
2862             -- if combis then
2863             --     ctx_btxsetcombis(combis)
2864             -- end
2865                if not getter(entry,last,nil,specification) then
2866                    ctx_btxsetfirst("") -- (f_missing(tag))
2867                end
2868                ctx_btxsetconcat(concatstate(i,n))
2869                if trace_details then
2870                    report("expanding cite setup %a",setup)
2871                end
2872                ctx_btxcitesetup(setup)
2873                ctx_btxstopcite()
2874            end
2875            if sorttype == v_normal or sorttype == v_reverse then
2876                local target = (compressor or compresslist)(source,specification)
2877                local nofcollected = #target
2878                if nofcollected == 0 then
2879                    local nofcollected = #source
2880                    if nofcollected == 0 then
2881                        unknowncite(reference)
2882                    else
2883                        for i=1,nofcollected do
2884                            flush(i,nofcollected,source[i])
2885                        end
2886                    end
2887                else
2888                    for i=1,nofcollected do
2889                        local entry = target[i]
2890                        local first = entry.first
2891                        if first then
2892                            flush(i,nofcollected,first,entry.last)
2893                        else
2894                            flush(i,nofcollected,entry)
2895                        end
2896                    end
2897                end
2898            else
2899                local nofcollected = #source
2900                if nofcollected == 0 then
2901                    unknowncite(reference)
2902                else
2903                    for i=1,nofcollected do
2904                        flush(i,nofcollected,source[i])
2905                    end
2906                end
2907            end
2908        end
2909        if trialtypesetting() then
2910            marked_todo = false
2911        elseif method ~= v_text then
2912            marked_todo    = todo
2913            marked_dataset = dataset
2914            marked_list    = list
2915            marked_method  = method
2916            btxflushmarked() -- here (could also be done in caller)
2917        else
2918            marked_todo = false
2919        end
2920    end
2921
2922    --
2923
2924    local function simplegetter(first,last,field,specification)
2925        local value = first[field]
2926        if value then
2927            if type(value) == "string" then
2928                ctx_btxsetfirst(value)
2929                if last then
2930                    ctx_btxsetsecond(last[field])
2931                end
2932                return true
2933            else
2934                report("missing data type definition for %a",field)
2935            end
2936        end
2937    end
2938
2939    local setters = setmetatableindex({},function(t,k)
2940        local v = function(data,dataset,tag,entry)
2941            local value  = getcasted(dataset,tag,k)
2942            data.value   = value -- not really needed
2943            data[k]      = value
2944            data.sortkey = value
2945            data.sortfld = k
2946        end
2947        t[k] = v
2948        return v
2949    end)
2950
2951    local getters = setmetatableindex({},function(t,k)
2952        local v = function(first,last,_,specification)
2953            return simplegetter(first,last,k,specification) -- maybe _ or k
2954        end
2955        t[k] = v
2956        return v
2957    end)
2958
2959    setmetatableindex(citevariants,function(t,k)
2960        local p = defaultvariant or "default"
2961        local v = rawget(t,p)
2962        report_cite("variant %a falls back on %a setter and getter with setup %a",k,p,k)
2963        t[k] = v
2964        return v
2965    end)
2966
2967    function citevariants.default(presets)
2968        local variant = presets.variant
2969        processcite(presets,{
2970            setup   = variant,
2971            setter  = setters[variant],
2972            getter  = getters[variant],
2973        })
2974    end
2975
2976    -- category
2977
2978    do
2979
2980        local function setter(data,dataset,tag,entry)
2981            data.category = getfield(dataset,tag,"category")
2982        end
2983
2984        local function getter(first,last,_,specification)
2985            return simplegetter(first,last,"category",specification)
2986        end
2987
2988        function citevariants.category(presets)
2989            processcite(presets,{
2990                setter  = setter,
2991                getter  = getter,
2992            })
2993        end
2994
2995    end
2996
2997
2998    -- entry (we could provide a generic one)
2999
3000    do
3001
3002        local function setter(data,dataset,tag,entry)
3003            -- nothing
3004        end
3005
3006        local function getter(first,last,_,specification) -- last not used
3007            ctx_btxsetfirst(first.tag)
3008        end
3009
3010        function citevariants.entry(presets)
3011            processcite(presets,{
3012                compress = false,
3013                setter   = setter,
3014                getter   = getter,
3015            })
3016        end
3017
3018    end
3019
3020    -- short
3021
3022    do
3023
3024        local function setter(data,dataset,tag,entry)
3025            local short  = getdetail(dataset,tag,"shorthash")
3026            local suffix = getdetail(dataset,tag,"shortsuffix")
3027            data.short   = short
3028            data.sortkey = short
3029            data.suffix  = suffix
3030        end
3031
3032        local function getter(first,last,_,specification) -- last not used
3033            local short = first.short
3034            if short then
3035                local suffix = first.suffix
3036                ctx_btxsetfirst(short)
3037                if suffix then
3038                    ctx_btxsetsuffix(suffix) -- watch out: third
3039                end
3040                return true
3041            end
3042        end
3043
3044        function citevariants.short(presets)
3045            processcite(presets,{
3046                setter = setter,
3047                getter = getter,
3048            })
3049        end
3050
3051    end
3052
3053    -- pages (no compress)
3054
3055    do
3056
3057        local function setter(data,dataset,tag,entry)
3058            data.pages = getcasted(dataset,tag,"pages")
3059        end
3060
3061        local function getter(first,last,_,specification)
3062            local pages = first.pages
3063            if pages then
3064                if type(pages) == "table" then
3065                    ctx_btxsetfirst(pages[1])
3066                    ctx_btxsetsecond(pages[2])
3067                else
3068                    ctx_btxsetfirst(pages)
3069                end
3070                return true
3071            end
3072        end
3073
3074        function citevariants.page(presets)
3075            processcite(presets,{
3076                setter = setter,
3077                getter = getter,
3078            })
3079        end
3080
3081    end
3082
3083    -- num
3084
3085    do
3086
3087        local function setter(data,dataset,tag,entry)
3088            local entries = entry.entries
3089            local text    = entries and entries.text or "?"
3090            data.num      = text
3091            data.sortkey  = tonumber(text) or text
3092        end
3093
3094        local function getter(first,last,tag,specification)
3095            return simplegetter(first,last,"num",specification)
3096        end
3097
3098        function citevariants.num(presets)
3099            processcite(presets,{
3100                numeric = true,
3101                setter  = setter,
3102                getter  = getter,
3103            })
3104        end
3105
3106        citevariants.textnum = citevariants.num -- should not be needed
3107
3108    end
3109
3110    -- year
3111
3112    do
3113
3114        local function setter(data,dataset,tag,entry)
3115            local year   = getfield (dataset,tag,"year")
3116            local suffix = getdetail(dataset,tag,"authorsuffix")
3117            data.year    = year
3118            data.suffix  = suffix
3119            data.sortkey = tonumber(year) or 9999
3120        end
3121
3122        local function getter(first,last,_,specification)
3123            return simplegetter(first,last,"year",specification)
3124        end
3125
3126        function citevariants.year(presets)
3127            processcite(presets,{
3128                numeric = true,
3129                setter  = setter,
3130                getter  = getter,
3131            })
3132        end
3133
3134    end
3135
3136    -- index
3137
3138    do
3139
3140        local function setter(data,dataset,tag,entry)
3141            local index  = getfield(dataset,tag,"index")
3142            data.index   = index
3143            data.sortkey = index
3144        end
3145
3146        local function getter(first,last,_,specification)
3147            return simplegetter(first,last,"index",specification)
3148        end
3149
3150        function citevariants.index(presets)
3151            processcite(presets,{
3152                setter  = setter,
3153                getter  = getter,
3154                numeric = true,
3155            })
3156        end
3157
3158    end
3159
3160    -- tag
3161
3162    do
3163
3164        local function setter(data,dataset,tag,entry)
3165            data.tag     = tag
3166            data.sortkey = tag
3167        end
3168
3169        local function getter(first,last,_,specification)
3170            return simplegetter(first,last,"tag",specification)
3171        end
3172
3173        function citevariants.tag(presets)
3174            return processcite(presets,{
3175                setter = setter,
3176                getter = getter,
3177            })
3178        end
3179
3180    end
3181
3182    -- keyword
3183
3184    do
3185
3186        local function listof(list)
3187            local size = type(list) == "table" and #list or 0
3188            if size > 0 then
3189                return function()
3190                    for i=1,size do
3191                        ctx_btxsetfirst(list[i])
3192                        ctx_btxsetconcat(concatstate(i,size))
3193                        ctx_btxcitesetup("listelement")
3194                    end
3195                    return true
3196                end
3197            else
3198                return "?" -- unknown
3199            end
3200        end
3201
3202        local function setter(data,dataset,tag,entry)
3203            data.keywords = getcasted(dataset,tag,"keywords")
3204        end
3205
3206        local function getter(first,last,_,specification)
3207            context(listof(first.keywords))
3208        end
3209
3210        function citevariants.keywords(presets)
3211            return processcite(presets,{
3212                variant = "keywords",
3213                setter  = setter,
3214                getter  = getter,
3215            })
3216        end
3217
3218    end
3219
3220    -- authors
3221
3222    do
3223
3224        -- is this good enough?
3225
3226        local keysorter = function(a,b)
3227            local ak = a.authorhash
3228            local bk = b.authorhash
3229            if ak == bk then
3230                local as = a.authorsuffix -- numeric
3231                local bs = b.authorsuffix -- numeric
3232                if as and bs then
3233                    return (as or 0) < (bs or 0)
3234                else
3235                    return false
3236                end
3237            elseif ak and bk then
3238                return ak < bk
3239            else
3240                return false
3241            end
3242        end
3243
3244        local revsorter = function(a,b)
3245            return keysorter(b,a)
3246        end
3247
3248        local function authorcompressor(found,specification)
3249            -- HERE
3250            if specification.sorttype == v_normal then
3251                sort(found,keysorter)
3252            elseif specification.sorttype == v_reverse then
3253                sort(found,revsorter)
3254            end
3255            local result  = { }
3256            local entries = { }
3257            for i=1,#found do
3258                local entry  = found[i]
3259                local author = entry.authorhash
3260                if author then
3261                    local aentries = entries[author]
3262                    if aentries then
3263                        aentries[#aentries+1] = entry
3264                    else
3265                        entries[author] = { entry }
3266                    end
3267                end
3268            end
3269            -- beware: we use tables as hash so we get a cycle when inspecting (unless we start
3270            -- hashing with strings)
3271            for i=1,#found do
3272                local entry  = found[i]
3273                local author = entry.authorhash
3274                if author then
3275                    local aentries = entries[author]
3276                    if not aentries then
3277                        result[#result+1] = entry
3278                    elseif aentries == true then
3279                        -- already done
3280                    else
3281                        result[#result+1] = entry
3282                        entry.entries = aentries
3283                        entries[author] = true
3284                    end
3285                end
3286            end
3287            return result
3288        end
3289
3290        local function authorconcat(target,key,setup)
3291            ctx_btxstartsubcite(setup)
3292            local nofcollected = #target
3293            if nofcollected == 0 then
3294                unknowncite(tag)
3295            else
3296                for i=1,nofcollected do
3297                    local entry = target[i]
3298                    local first = entry.first
3299                    local tag   = entry.tag
3300                    local currentcitation = markcite(entry.dataset,tag)
3301                    ctx_btxstartciteauthor()
3302                    ctx_btxsettag(tag)
3303                    ctx_btxsetbacklink(currentcitation)
3304                    local bl = listtocite[tonumber(currentcitation)]
3305                    if first then
3306                        ctx_btxsetfirst(first[key] or "") -- f_missing(first.tag))
3307                        local suffix = entry.suffix
3308                        local last   = entry.last
3309                        local value  = last and last[key]
3310                        if value then
3311                            ctx_btxsetsecond(value)
3312                        end
3313                        if suffix then
3314                            ctx_btxsetsuffix(suffix)
3315                        end
3316                    else
3317                        local suffix = entry.suffix
3318                        local value  = entry[key] or "" -- f_missing(tag)
3319                        ctx_btxsetfirst(value)
3320                        if suffix then
3321                            ctx_btxsetsuffix(suffix)
3322                        end
3323                    end
3324                    ctx_btxsetconcat(concatstate(i,nofcollected))
3325                    if trace_details then
3326                        report("expanding %a cite setup %a","multiple author",setup)
3327                    end
3328                    ctx_btxsubcitesetup(setup)
3329                    ctx_btxstopciteauthor()
3330                end
3331            end
3332            ctx_btxstopsubcite()
3333        end
3334
3335        local function authorsingle(entry,key,setup)
3336            ctx_btxstartsubcite(setup)
3337            ctx_btxstartciteauthor()
3338            local tag = entry.tag
3339            ctx_btxsettag(tag)
3340            ctx_btxsetfirst(entry[key] or "") -- f_missing(tag)
3341            if suffix then
3342                ctx_btxsetsuffix(entry.suffix)
3343            end
3344            if trace_details then
3345                report("expanding %a cite setup %a","single author",setup)
3346            end
3347            ctx_btxcitesetup(setup)
3348            ctx_btxstopciteauthor()
3349            ctx_btxstopsubcite()
3350        end
3351
3352        local partialinteractive = false
3353
3354        local currentbtxciteauthor = function()
3355            context.currentbtxciteauthorbyfield()
3356            return true -- needed?
3357        end
3358
3359        local function authorgetter(first,last,key,specification) -- only first
3360            ctx_btxsetauthorfield(first.varfield or "author")
3361            if first.type == "author" then
3362                ctx_btxsetfirst(currentbtxciteauthor) -- formatter (much slower)
3363            else
3364                ctx_btxsetfirst(first.author)         -- unformatted
3365            end
3366            local entries = first.entries
3367            -- alternatively we can use a concat with one ... so that we can only make the
3368            -- year interactive, as with the concat
3369            if partialinteractive and not entries then
3370                entries = { first }
3371            end
3372            if entries then
3373                -- happens with year
3374                local c = compresslist(entries,specification)
3375                local f = function() authorconcat(c,key,specification.setup or "author") return true end -- indeed return true?
3376                ctx_btxsetcount(#c)
3377                ctx_btxsetsecond(f)
3378            elseif first then
3379                -- happens with num
3380                local f = function() authorsingle(first,key,specification.setup or "author") return true end -- indeed return true?
3381                ctx_btxsetcount(0)
3382                ctx_btxsetsecond(f)
3383            end
3384            return true
3385        end
3386
3387        -- author (the varfield hack is for editor and translator i.e author type)
3388
3389        local function setter(data,dataset,tag,entry)
3390            data.author, data.field, data.type = getcasted(dataset,tag,data.varfield or "author")
3391            data.sortkey = text and lpegmatch(numberonly,text)
3392            data.authorhash = getdetail(dataset,tag,"authorhash") -- todo let getcasted return
3393        end
3394
3395        local function getter(first,last,_,specification)
3396            ctx_btxsetauthorfield(specification.varfield or "author")
3397            if first.type == "author" then
3398                ctx_btxsetfirst(currentbtxciteauthor) -- formatter (much slower)
3399            else
3400                ctx_btxsetfirst(first.author)         -- unformatted
3401            end
3402            return true
3403        end
3404
3405        function citevariants.author(presets)
3406            processcite(presets,{
3407                variant    = "author",
3408                setup      = "author",
3409                setter     = setter,
3410                getter     = getter,
3411                varfield   = presets.variant or "author",
3412                compressor = authorcompressor,
3413            })
3414        end
3415
3416        -- authornum
3417
3418        local function setter(data,dataset,tag,entry)
3419            local entries = entry.entries
3420            local text    = entries and entries.text or "?"
3421            data.author, data.field, data.type = getcasted(dataset,tag,"author")
3422            data.authorhash = getdetail(dataset,tag,"authorhash") -- todo let getcasted return
3423            data.num     = text
3424            data.sortkey = text and lpegmatch(numberonly,text)
3425        end
3426
3427        local function getter(first,last,_,specification)
3428            authorgetter(first,last,"num",specification)
3429            return true
3430        end
3431
3432        function citevariants.authornum(presets)
3433            processcite(presets,{
3434                variant    = "authornum",
3435                setup      = "author:num",
3436                numeric    = true,
3437                setter     = setter,
3438                getter     = getter,
3439                compressor = authorcompressor,
3440            })
3441        end
3442
3443        -- authoryear | authoryears
3444
3445        local function setter(data,dataset,tag,entry)
3446            data.author, data.field, data.type = getcasted(dataset,tag,"author")
3447            data.authorhash = getdetail(dataset,tag,"authorhash") -- todo let getcasted return
3448            local year   = getfield (dataset,tag,"year")
3449            local suffix = getdetail(dataset,tag,"authorsuffix")
3450            data.year    = year
3451            data.suffix  = suffix
3452            data.sortkey = tonumber(year) or 9999
3453        end
3454
3455        local function getter(first,last,_,specification)
3456            authorgetter(first,last,"year",specification)
3457            return true
3458        end
3459
3460        function citevariants.authoryear(presets)
3461            processcite(presets,{
3462                variant    = "authoryear",
3463                setup      = "author:year",
3464                numeric    = true,
3465                setter     = setter,
3466                getter     = getter,
3467                compressor = authorcompressor,
3468            })
3469        end
3470
3471        local function getter(first,last,_,specification)
3472            authorgetter(first,last,"year",specification)
3473            return true
3474        end
3475
3476        function citevariants.authoryears(presets)
3477            processcite(presets,{
3478                variant    = "authoryears",
3479                setup      = "author:years",
3480                numeric    = true,
3481                setter     = setter,
3482                getter     = getter,
3483                compressor = authorcompressor,
3484            })
3485        end
3486
3487    end
3488
3489end
3490
3491-- List variants
3492
3493do
3494
3495    local listvariants        = { }
3496    publications.listvariants = listvariants
3497
3498    local function btxlistvariant(dataset,block,tag,variant,listindex)
3499        local action = listvariants[variant] or listvariants.default
3500        if action then
3501            listindex = tonumber(listindex)
3502            if listindex then
3503                action(dataset,block,tag,variant,listindex)
3504            end
3505        end
3506    end
3507
3508    implement {
3509        name      = "btxlistvariant",
3510        arguments = "5 strings",
3511        actions   = btxlistvariant,
3512    }
3513
3514    function listvariants.default(dataset,block,tag,variant)
3515        ctx_btxsetfirst("?")
3516        if trace_details then
3517            report("expanding %a list setup %a","default",variant)
3518        end
3519        ctx_btxnumberingsetup("default")
3520    end
3521
3522    function listvariants.num(dataset,block,tag,variant,listindex)
3523        ctx_btxsetfirst(listindex)
3524        if trace_details then
3525            report("expanding %a list setup %a","num",variant)
3526        end
3527        ctx_btxnumberingsetup(variant or "num")
3528    end
3529
3530 -- listvariants[v_yes] = listvariants.num
3531
3532    function listvariants.index(dataset,block,tag,variant,listindex)
3533        local index = getdetail(dataset,tag,"index")
3534        ctx_btxsetfirst(index or "?")
3535        if trace_details then
3536            report("expanding %a list setup %a","index",variant)
3537        end
3538        ctx_btxnumberingsetup(variant or "index")
3539    end
3540
3541    function listvariants.tag(dataset,block,tag,variant,listindex)
3542        ctx_btxsetfirst(tag)
3543        if trace_details then
3544            report("expanding %a list setup %a","tag",variant)
3545        end
3546        ctx_btxnumberingsetup(variant or "tag")
3547    end
3548
3549    function listvariants.short(dataset,block,tag,variant,listindex)
3550        local short  = getdetail(dataset,tag,"shorthash")
3551        local suffix = getdetail(dataset,tag,"shortsuffix")
3552        if short then
3553            ctx_btxsetfirst(short)
3554        end
3555        if suffix then
3556            ctx_btxsetsuffix(suffix)
3557        end
3558        if trace_details then
3559            report("expanding %a list setup %a","short",variant)
3560        end
3561        ctx_btxnumberingsetup(variant or "short")
3562    end
3563
3564end
3565
3566-- a helper
3567
3568do
3569
3570 -- local context   = context
3571 -- local lpegmatch = lpeg.match
3572    local splitter  = lpeg.tsplitat(":")
3573
3574    implement {
3575        name      = "checkinterfacechain",
3576        arguments = "2 strings",
3577        actions   = function(str,command)
3578            local chain = lpegmatch(splitter,str)
3579            if #chain > 0 then
3580                local command = context[command]
3581                local parent  = ""
3582                local child   = chain[1]
3583                command(child,parent)
3584                for i=2,#chain do
3585                    parent = child
3586                    child  = child .. ":" .. chain[i]
3587                    command(child,parent)
3588                end
3589            end
3590        end
3591    }
3592
3593end
3594
3595do
3596
3597    local btxstring = ""
3598
3599    implement {
3600        name    = "btxcmdstring",
3601        actions = function() if btxstring ~= "" then context(btxstring) end end,
3602    }
3603
3604    function publications.prerollcmdstring(str)
3605        btxstring = str or ""
3606        tex.runlocal("t_btx_cmd")
3607        return nodes.toutf(tex.getbox("b_btx_cmd").list) or str
3608    end
3609
3610end
3611
3612do
3613
3614    -- no caching for now
3615
3616    interfaces.implement { -- shared with mkiv so no public
3617        name      = "btxdoifelsecitedone",
3618        protected = true,
3619     -- public    = true,
3620     -- arguments = "2 arguments",
3621        arguments = "2 strings",
3622        actions   = function(dataset,tag)
3623            -- dataset ignored
3624            local list = structures.lists.tobesaved
3625            local done = false
3626            for i=1,#list do
3627                local l = list[i]
3628                local m = l.metadata
3629                if m and m.kind == "btx" then
3630                    local u = l.userdata
3631                    if u and u.btxref == tag then
3632                        done = true
3633                        break
3634                    end
3635                end
3636            end
3637            ctx_doifelse(done)
3638        end
3639    }
3640
3641end
3642