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