Skip to content

Commit 57b3b7c

Browse files
Omikhleiaalerque
authored andcommitted
feat(packages): Keep track of cited bibliography entries
This should even be the default when generating a bibliography.
1 parent df92f0e commit 57b3b7c

3 files changed

Lines changed: 122 additions & 30 deletions

File tree

csl/core/engine.lua

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
--
1414
-- THINGS NOT DONE
1515
-- - disambiguation logic (not done at all)
16+
-- - collapse logic in citations (not done at all)
1617
-- - other FIXME/TODOs in the code on specific features
1718
--
1819
-- luacheck: no unused args
@@ -875,7 +876,7 @@ function CslEngine:_names (options, content, entry)
875876
local name_delimiter = name_node.options.delimiter or inherited_opts["names-delimiter"]
876877
-- local delimiter_precedes_et_al = name_node.options["delimiter-precedes-et-al"] -- TODO NOT IMPLEMENTED
877878

878-
if not self.cache[name_delimiter] then
879+
if name_delimiter and not self.cache[name_delimiter] then
879880
name_delimiter = self:_xmlEscape(name_delimiter)
880881
self.cache[name_delimiter] = name_delimiter
881882
end
@@ -885,7 +886,7 @@ function CslEngine:_names (options, content, entry)
885886
et_al_min = et_al_min,
886887
et_al_use_first = et_al_use_first,
887888
and_word = and_word,
888-
name_delimiter = self.cache[name_delimiter],
889+
name_delimiter = name_delimiter and self.cache[name_delimiter],
889890
is_label_first = is_label_first,
890891
label_opts = label_opts,
891892
et_al_opts = et_al_opts,
@@ -1083,9 +1084,10 @@ function CslEngine:_key (options, content, entry)
10831084
end
10841085

10851086
-- FIXME: A bit ugly: When implementing SU.collatedSort, I didn't consider
1086-
-- sorting structured tables, so I need to go low level here.
1087+
-- sorting structured tables, so we need to go low level here.
10871088
-- Moreover, I made icu.compare return a boolean, so we have to pay twice
10881089
-- the comparison cost to check equality...
1090+
-- See PR #2105
10891091
local icu = require("justenoughicu")
10901092

10911093
function CslEngine:_sort (options, content, entries)
@@ -1116,14 +1118,32 @@ function CslEngine:_sort (options, content, entries)
11161118
local lang = self.locale.lang
11171119
local collator = icu.collation_create(lang, {})
11181120
table.sort(entries, function (a, b)
1121+
if a["citation-key"] == b["citation-key"] then
1122+
-- Lua can invoke the comparison function with the same entry.
1123+
-- Really! Due to the way it handles it pivot on partitioning.
1124+
-- Shortcut the inner keys comparison in that case.
1125+
return false
1126+
end
1127+
-- NOT IMPLEMENTED (not bothering for now):
1128+
-- "Items with an empty sort key value are placed at the end of the sort,
1129+
-- both for ascending and descending sorts."
11191130
local ak = a._keys
11201131
local bk = b._keys
11211132
for i = 1, math.min(#ak, #bk) do
1122-
if ak[i] ~= bk[i] then -- See comment, ugly inequality check)
1123-
return icu.compare(collator, ak[i], bk[i])
1133+
if ak[i] ~= bk[i] then -- HACK: See comment above, ugly inequality check
1134+
local cmp = icu.compare(collator, ak[i], bk[i])
1135+
if type(cmp) == "number" then
1136+
return cmp < 0 -- To keep on working whenever PR #2105 lands
1137+
end
1138+
return cmp
11241139
end
11251140
end
1126-
return false
1141+
-- If we reach this point, the keys are equal.
1142+
-- Probably unlikely in real life, and not mentioned in the CSL spec
1143+
-- unless I missed it. Let's fallback to the citation order, so at
1144+
-- least cited entries are ordered predictably.
1145+
SU.warn("CSL sort keys are equal for " .. a["citation-key"] .. " and " .. b["citation-key"])
1146+
return a["citation-number"] < b["citation-number"]
11271147
end)
11281148
icu.collation_destroy(collator)
11291149
end
@@ -1212,6 +1232,28 @@ function CslEngine:_process (entries, mode)
12121232
self.sorting = true
12131233
self:_sort(sort.options, sort, entries)
12141234
self.sorting = false
1235+
else
1236+
-- The CSL specification says:
1237+
-- "In the absence of cs:sort, cites and bibliographic entries appear in
1238+
-- the order in which they are cited."
1239+
-- The wording is ambiguous!
1240+
-- We tracked the first citation number in 'citation-number', so for
1241+
-- the bibliography, using it makes sense.
1242+
-- For citations, we use the exact order of the input. Consider a cite
1243+
-- (work1, work2) and a subsequent cite (work2, work1). The order of
1244+
-- the bibliography should be (work1, work2), but the order of the cites
1245+
-- should be (work1, work2) and (work2, work1) respectively.
1246+
-- It seeems to be the case: Some styles (ex. American Chemical Society)
1247+
-- have an explicit sort by 'citation-number' in the citations section,
1248+
-- which would be useless if that order was impplied.
1249+
if mode == "bibliography" then
1250+
table.sort(entries, function (e1, e2)
1251+
if not e1["citation-number"] or not e2["citation-number"] then
1252+
return false -- Safeguard?
1253+
end
1254+
return e1["citation-number"] < e2["citation-number"]
1255+
end)
1256+
end
12151257
end
12161258

12171259
local res = self:_render_children(ast, entries)

packages/bibtex/init.lua

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ local function parseBibtex (fn, biblio)
195195
for i = 1, #t do
196196
if t[i].id == "entry" then
197197
local ent = t[i][1]
198-
local entry = { type = ent.type, attributes = ent[1] }
198+
local entry = { type = ent.type, label = ent.label, attributes = ent[1] }
199199
if biblio[ent.label] then
200200
SU.warn("Duplicate entry key '" .. ent.label .. "', picking the last one")
201201
end
@@ -301,7 +301,7 @@ end
301301

302302
function package:_init ()
303303
base._init(self)
304-
SILE.scratch.bibtex = { bib = {} }
304+
SILE.scratch.bibtex = { bib = {}, cited = { keys = {}, citnums = {} } }
305305
Bibliography = require("packages.bibtex.bibliography")
306306
-- For DOI, PMID, PMCID and URL support.
307307
self:loadPackage("url")
@@ -457,8 +457,6 @@ function package:registerCommands ()
457457
-- - multiple citation keys
458458
if not SILE.scratch.bibtex.engine then
459459
SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
460-
-- SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-fullnote-bibliography" })
461-
-- SILE.call("bibliographystyle", { lang = "en-US", style = "apa" })
462460
end
463461
local engine = SILE.scratch.bibtex.engine
464462
if not options.key then
@@ -469,7 +467,12 @@ function package:registerCommands ()
469467
return
470468
end
471469

472-
local csljson = bib2csl(entry)
470+
-- Keep track of cited entries
471+
table.insert(SILE.scratch.bibtex.cited.keys, options.key)
472+
local citnum = #SILE.scratch.bibtex.cited.keys
473+
SILE.scratch.bibtex.cited.citnums[options.key] = citnum
474+
475+
local csljson = bib2csl(entry, citnum)
473476
-- csljson.locator = { -- EXPERIMENTAL
474477
-- label = "page",
475478
-- value = "123-125"
@@ -482,8 +485,6 @@ function package:registerCommands ()
482485
self:registerCommand("csl:reference", function (options, content)
483486
if not SILE.scratch.bibtex.engine then
484487
SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
485-
-- SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-fullnote-bibliography" })
486-
-- SILE.call("bibliographystyle", { lang = "en-US", style = "apa" })
487488
end
488489
local engine = SILE.scratch.bibtex.engine
489490
if not options.key then
@@ -494,34 +495,65 @@ function package:registerCommands ()
494495
return
495496
end
496497

497-
local cslentry = bib2csl(entry)
498+
local citnum = SILE.scratch.bibtex.cited.citnums[options.key]
499+
if not citnum then
500+
SU.warn("Reference to a non-cited entry " .. options.key)
501+
-- Make it cited
502+
table.insert(SILE.scratch.bibtex.cited.keys, options.key)
503+
citnum = #SILE.scratch.bibtex.cited.keys
504+
SILE.scratch.bibtex.cited.citnums[options.key] = citnum
505+
end
506+
local cslentry = bib2csl(entry, citnum)
498507
local cite = engine:reference(cslentry)
499508

500509
SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
501510
end)
502511

503-
self:registerCommand("printbibliography", function (_, _)
512+
self:registerCommand("printbibliography", function (options, _)
504513
if not SILE.scratch.bibtex.engine then
505514
SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
506-
-- SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-fullnote-bibliography" })
507-
-- SILE.call("bibliographystyle", { lang = "en-US", style = "apa" })
508515
end
509516
local engine = SILE.scratch.bibtex.engine
510517

511-
local bib = SILE.scratch.bibtex.bib
518+
local bib
519+
if SU.boolean(options.cited, true) then
520+
bib = {}
521+
for _, key in ipairs(SILE.scratch.bibtex.cited.keys) do
522+
bib[key] = SILE.scratch.bibtex.bib[key]
523+
end
524+
else
525+
bib = SILE.scratch.bibtex.bib
526+
end
527+
512528
local entries = {}
513-
for _, entry in pairs(bib) do
529+
local ncites = #SILE.scratch.bibtex.cited.keys
530+
for key, entry in pairs(bib) do
514531
if entry.type ~= "xdata" then
515532
crossrefAndXDataResolve(bib, entry)
516533
if entry then
517-
local cslentry = bib2csl(entry)
534+
local citnum = SILE.scratch.bibtex.cited.citnums[key]
535+
if not citnum then
536+
-- This is just to make happy CSL styles that require a citation number
537+
-- However, table order is not guaranteed in Lua so the output may be
538+
-- inconsistent across runs with styles that use this number for sorting.
539+
-- This may only happen for non-cited entries in the bibliography, and it
540+
-- would be a bad practive to use such a style to print the full bibliography,
541+
-- so I don't see a strong need to fix this at the expense of performance.
542+
-- (and we can't really, some styles might have several sorting criteria
543+
-- leading to impredictable order anyway).
544+
ncites = ncites + 1
545+
citnum = ncites
546+
end
547+
local cslentry = bib2csl(entry, citnum)
518548
table.insert(entries, cslentry)
519549
end
520550
end
521551
end
522552
print("<bibliography: " .. #entries .. " entries>")
523553
local cite = engine:reference(entries)
524554
SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
555+
556+
SILE.scratch.bibtex.cited = { keys = {}, citnums = {} }
525557
end)
526558
end
527559

@@ -531,7 +563,6 @@ BibTeX is a citation management system.
531563
It was originally designed for TeX but has since been integrated into a variety of situations.
532564
533565
This experimental package allows SILE to read and process BibTeX \code{.bib} files and output citations and full text references.
534-
(It doesn’t currently produce full bibliography listings.)
535566
536567
To load a BibTeX file, issue the command \autodoc:command{\loadbibliography[file=<whatever.bib>]}
537568
@@ -544,9 +575,9 @@ To load a BibTeX file, issue the command \autodoc:command{\loadbibliography[file
544575
To produce an inline citation, call \autodoc:command{\cite{<key>}}, which will typeset something like “Jones 1982”.
545576
If you want to cite a particular page number, use \autodoc:command{\cite[page=22]{<key>}}.
546577
547-
To produce a full reference, use \autodoc:command{\reference{<key>}}.
578+
To produce a bibliographic reference, use \autodoc:command{\reference{<key>}}.
548579
549-
Currently, the only supported bibliography style is Chicago referencing.
580+
This implementation doesn’t currently produce full bibliography listings, and the only supported bibliography style is Chicago referencing.
550581
551582
\smallskip
552583
\noindent
@@ -556,7 +587,7 @@ Currently, the only supported bibliography style is Chicago referencing.
556587
\indent
557588
While an experimental work-in-progress, the CSL (Citation Style Language) implementation is more powerful and flexible than the legacy commands.
558589
559-
You must first invoke \autodoc:command{\bibliographystyle[style=<style>, lang=<lang>]}, where \autodoc:parameter{style} is the name of the CSL style file (without the \code{.csl} extension), and \autodoc:parameter{lang} is the language code of the CSL locale to use (e.g., \code{en-US}).
590+
You should first invoke \autodoc:command{\bibliographystyle[style=<style>, lang=<lang>]}, where \autodoc:parameter{style} is the name of the CSL style file (without the \code{.csl} extension), and \autodoc:parameter{lang} is the language code of the CSL locale to use (e.g., \code{en-US}).
560591
561592
The command accepts a few additional options:
562593
@@ -572,11 +603,13 @@ If you don’t specify a style or locale, the author-date style and the \code{en
572603
573604
To produce an inline citation, call \autodoc:command{\csl:cite{<key>}}, which will typeset something like “(Jones 1982)”.
574605
575-
To produce a full reference, use \autodoc:command{\csl:reference{<key>}}.
606+
To produce a bibliography of cited references, use \autodoc:command{\printbibliography}.
607+
After printing the bibliography, the list of cited entries will be cleared. This allows you to start fresh for subsequent uses (e.g., in a different chapter).
608+
If you want to include all entries in the bibliography, not just those that have been cited, set the option \autodoc:parameter{cited} to false.
576609
577-
To produce a complete bibliography, use \autodoc:command{\printbibliography}.
578-
As of yet, this command is for testing purposes only.
579-
It does not handle filtering of the bibliography.
610+
To produce a bibliographic reference, use \autodoc:command{\csl:reference{<key>}}.
611+
Note that this command is not intended for actual use, but for testing purposes.
612+
It may be removed in the future.
580613
581614
\smallskip
582615
\noindent

packages/bibtex/support/bib2csl.lua

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ end
7878

7979
--- Convert a BibTeX entry to a CSL item.
8080
-- @tparam table entry The BibTeX entry
81+
-- @tparam number citnum The citation number (computed, not from BibTeX but from actual citation order)
8182
-- @treturn table The CSL item
82-
local function bib2csl (entry)
83+
local function bib2csl (entry, citnum)
8384
local csl = {}
8485
local bibtex = entry.attributes
8586
local bibtype = entry.type:lower()
@@ -88,6 +89,17 @@ local function bib2csl (entry)
8889
local t = BIBTEX2CSL_TYPES[bibtype] or "document"
8990
csl.type = t
9091

92+
-- Citation key may be wanted by some styles
93+
csl["citation-key"] = entry.label
94+
-- Citation number is used by some styles such as ACS
95+
csl["citation-number"] = citnum
96+
97+
-- BibLaTeX label / shorthand
98+
-- The label "provides a substitute for any missing data"
99+
-- it relates to shorthand, which overrides the label.
100+
-- Some CSL styles such as USITC use citation-label in priority over author, etc.
101+
csl["citation-label"] = bibtex.shorthand or bibtex.label
102+
91103
-- BibTeX address / BibLaTeX location
92104
if bibtex.location then
93105
csl["event-place"] = bibtex.location
@@ -105,7 +117,12 @@ local function bib2csl (entry)
105117

106118
-- BibTex editor
107119
csl.editor = bibtex.editor
108-
csl["collection-editor"] = bibtex.editor
120+
-- N.B. BibLaTeX does not have a "collection-editor".
121+
-- Using some editora and editoratype hint is sometimes mentioned on forums
122+
-- but it's ad hoc and not part of any 'standard' recommendation.
123+
-- Biber would allow to define extra fields (e.g. serieseditor) but the issue
124+
-- is the same: lack of standardization.
125+
-- csl["collection-editor"] = bibtex.editoratype == "serieseditor" and bibtex.editora
109126

110127
-- BibLaTeX date / BibTeX year and month
111128
local date = bibtex.date and bibtex.date or toDate(bibtex.year, bibtex.month)

0 commit comments

Comments
 (0)