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