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