• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 12272864087

11 Dec 2024 08:55AM UTC coverage: 29.607% (-41.0%) from 70.614%
12272864087

push

github

web-flow
Merge 95cccf286 into f394f608c

5834 of 19705 relevant lines covered (29.61%)

429.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/bibtex/init.lua
1
local base = require("packages.base")
×
2

3
local loadkit = require("loadkit")
×
4
local cslStyleLoader = loadkit.make_loader("csl")
×
5
local cslLocaleLoader = loadkit.make_loader("xml")
×
6

7
local CslLocale = require("packages.bibtex.csl.locale")
×
8
local CslStyle = require("packages.bibtex.csl.style")
×
9
local CslEngine = require("packages.bibtex.csl.engine")
×
10

11
local function loadCslLocale (name)
12
   local filename = SILE.resolveFile("packages/bibtex/csl/locales/locales-" .. name .. ".xml")
×
13
      or cslLocaleLoader("packages.bibtex.csl.locales.locales-" .. name)
×
14
   if not filename then
×
15
      SU.error("Could not find CSL locale '" .. name .. "'")
×
16
   end
17
   local locale, err = CslLocale.read(filename)
×
18
   if not locale then
×
19
      SU.error("Could not open CSL locale '" .. name .. "'': " .. err)
×
20
      return
×
21
   end
22
   return locale
×
23
end
24
local function loadCslStyle (name)
25
   local filename = SILE.resolveFile("packages/bibtex/csl/styles/" .. name .. ".csl")
×
26
      or cslStyleLoader("packages.bibtex.csl.styles." .. name)
×
27
   if not filename then
×
28
      SU.error("Could not find CSL style '" .. name .. "'")
×
29
   end
30
   local style, err = CslStyle.read(filename)
×
31
   if not style then
×
32
      SU.error("Could not open CSL style '" .. name .. "'': " .. err)
×
33
      return
×
34
   end
35
   return style
×
36
end
37

38
local package = pl.class(base)
×
39
package._name = "bibtex"
×
40

41
local epnf = require("epnf")
×
42
local nbibtex = require("packages.bibtex.support.nbibtex")
×
43
local namesplit, parse_name = nbibtex.namesplit, nbibtex.parse_name
×
44
local isodatetime = require("packages.bibtex.support.isodatetime")
×
45
local bib2csl = require("packages.bibtex.support.bib2csl")
×
46
local locators = require("packages.bibtex.support.locators")
×
47

48
local Bibliography
49

50
local nbsp = luautf8.char(0x00A0)
×
51
local function sanitize (str)
52
   local s = str
×
53
      -- TeX special characters:
54
      -- Backslash-escaped tilde is a tilde,
55
      -- but standalone tilde is a non-breaking space
56
      :gsub(
×
57
         "(.?)~",
58
         function (prev)
59
            if prev == "\\" then
×
60
               return "~"
×
61
            end
62
            return prev .. nbsp
×
63
         end
64
      )
65
      -- Other backslash-escaped characters are skipped
66
      -- TODO FIXME:
67
      -- This ok for \", \& etc. which we want to unescape,
68
      -- BUT what should we do with other TeX-like commands?
69
      :gsub(
×
70
         "\\",
71
         ""
72
      )
73
      -- We will wrap the content in <sile> tags so we need to XML-escape
74
      -- the input.
75
      :gsub("&", "&amp;")
×
76
      :gsub("<", "&lt;")
×
77
      :gsub(">", "&gt;")
×
78
   return s
×
79
end
80

81
-- luacheck: push ignore
82
-- stylua: ignore start
83
---@diagnostic disable: undefined-global, unused-local, lowercase-global
84
local bibtexparser = epnf.define(function (_ENV)
×
85
   local strings = {} -- Local store for @string entries
×
86

87
   local identifier = (SILE.parserBits.identifier + S":-")^1
×
88
   local balanced = C{ "{" * P" "^0 * C(((1 - S"{}") + V(1))^0) * "}" } / function (...) local t={...}; return t[2] end
×
89
   local quoted = C( P'"' * C(((1 - S'"\r\n\f\\') + (P'\\' * 1)) ^ 0) * '"' ) / function (...) local t={...}; return t[2] end
×
90
   local _ = WS^0
×
91
   local sep = S",;" * _
×
92
   local myID = C(identifier)
×
93
   local myStrID = myID / function (t) return strings[t] or t end
×
94
   local myTag = C(identifier) / function (t) return t:lower() end
×
95
   local pieces = balanced + quoted + myStrID
×
96
   local value = Ct(pieces * (WS * P("#") * WS * pieces)^0)
×
97
      / function (t) return table.concat(t) end / sanitize
×
98
   local pair = myTag * _ * "=" * _ * value * _ * sep^-1
×
99
      / function (...) local t= {...}; return t[1], t[#t] end
×
100
   local list = Cf(Ct("") * pair^0, rawset)
×
101
   local skippedType = Cmt(R("az", "AZ")^1, function(_, _, tag)
×
102
      -- ignore both @comment and @preamble
103
      local t = tag:lower()
×
104
      return t == "comment" or t == "preamble"
×
105
   end)
106

107
   START "document"
108
   document = (V"skipped" -- order important: skipped (@comment, @preamble) must be first
×
109
      + V"stringblock" -- order important: @string must be before @entry
×
110
      + V"entry")^1
×
111
      * (-1 + E("Unexpected character at end of input"))
×
112
   skipped  = WS + (V"blockskipped" + (1 - P"@")^1 ) / ""
×
113
   blockskipped = (P("@") * skippedType) + balanced / ""
×
114
   stringblock = Ct( P("@string") * _ * P("{") * pair * _ * P("}") * _ )
×
115
       / function (t)
×
116
          strings[t[1]] = t[2]
×
117
          return t end
×
118
   entry = Ct( P("@") * Cg(myTag, "type") * _ * P("{") * _ * Cg(myID, "label") * _ * sep * list * P("}") * _ )
×
119
end)
120
-- luacheck: pop
121
-- stylua: ignore end
122
---@diagnostic enable: undefined-global, unused-local, lowercase-global
123

124
local bibcompat = require("packages.bibtex.support.bibmaps")
×
125
local crossrefmap, fieldmap = bibcompat.crossrefmap, bibcompat.fieldmap
×
126
local months =
127
   { jan = 1, feb = 2, mar = 3, apr = 4, may = 5, jun = 6, jul = 7, aug = 8, sep = 9, oct = 10, nov = 11, dec = 12 }
×
128

129
local function consolidateEntry (entry, label)
130
   local consolidated = {}
×
131
   -- BibLaTeX aliases for legacy BibTeX fields
132
   for field, value in pairs(entry.attributes) do
×
133
      consolidated[field] = value
×
134
      local alias = fieldmap[field]
×
135
      if alias then
×
136
         if entry.attributes[alias] then
×
137
            SU.warn("Duplicate field '" .. field .. "' and alias '" .. alias .. "' in entry '" .. label .. "'")
×
138
         else
139
            consolidated[alias] = value
×
140
         end
141
      end
142
   end
143
   -- Names field split and parsed
144
   for _, field in ipairs({ "author", "editor", "translator", "shortauthor", "shorteditor", "holder" }) do
×
145
      if consolidated[field] then
×
146
         -- FIXME Check our corporate names behave, we are probably bad currently
147
         -- with nested braces !!!
148
         -- See biblatex manual v3.20 §2.3.3 Name Lists
149
         -- e.g. editor = {{National Aeronautics and Space Administration} and Doe, John}
150
         local names = namesplit(consolidated[field])
×
151
         for i = 1, #names do
×
152
            names[i] = parse_name(names[i])
×
153
         end
154
         consolidated[field] = names
×
155
      end
156
   end
157
   -- Month field in either number or string (3-letter code)
158
   if consolidated.month then
×
159
      local month = tonumber(consolidated.month) or months[consolidated.month:lower()]
×
160
      if month and (month >= 1 and month <= 12) then
×
161
         consolidated.month = month
×
162
      else
163
         SU.warn("Unrecognized month skipped in entry '" .. label .. "'")
×
164
         consolidated.month = nil
×
165
      end
166
   end
167
   -- Extended date fields
168
   for _, field in ipairs({ "date", "origdate", "eventdate", "urldate" }) do
×
169
      if consolidated[field] then
×
170
         local dt = isodatetime(consolidated[field])
×
171
         if dt then
×
172
            consolidated[field] = dt
×
173
         else
174
            SU.warn("Invalid '" .. field .. "' skipped in entry '" .. label .. "'")
×
175
            consolidated[field] = nil
×
176
         end
177
      end
178
   end
179
   entry.attributes = consolidated
×
180
   return entry
×
181
end
182

183
--- Parse a BibTeX file and populate a bibliography table.
184
-- @tparam string fn Filename
185
-- @tparam table biblio Table of entries
186
local function parseBibtex (fn, biblio)
187
   fn = SILE.resolveFile(fn) or SU.error("Unable to resolve Bibtex file " .. fn)
×
188
   local fh, e = io.open(fn)
×
189
   if e then
×
190
      SU.error("Error reading bibliography file: " .. e)
×
191
   end
192
   local doc = fh:read("*all")
×
193
   local t = epnf.parsestring(bibtexparser, doc)
×
194
   if not t or not t[1] or t.id ~= "document" then
×
195
      SU.error("Error parsing bibtex")
×
196
   end
197
   for i = 1, #t do
×
198
      if t[i].id == "entry" then
×
199
         local ent = t[i][1]
×
200
         local entry = { type = ent.type, label = ent.label, attributes = ent[1] }
×
201
         if biblio[ent.label] then
×
202
            SU.warn("Duplicate entry key '" .. ent.label .. "', picking the last one")
×
203
         end
204
         biblio[ent.label] = consolidateEntry(entry, ent.label)
×
205
      end
206
   end
207
end
208

209
--- Copy fields from the parent entry to the child entry.
210
-- BibLaTeX/Biber have a complex inheritance system for fields.
211
-- This implementation is more naive, but should be sufficient for reasonable
212
-- use cases.
213
-- @tparam table parent Parent entry
214
-- @tparam table entry Child entry
215
local function fieldsInherit (parent, entry)
216
   local map = crossrefmap[parent.type] and crossrefmap[parent.type][entry.type]
×
217
   if not map then
×
218
      -- @xdata and any other unknown types: inherit all missing fields
219
      for field, value in pairs(parent.attributes) do
×
220
         if not entry.attributes[field] then
×
221
            entry.attributes[field] = value
×
222
         end
223
      end
224
      return -- done
×
225
   end
226
   for field, value in pairs(parent.attributes) do
×
227
      if map[field] == nil and not entry.attributes[field] then
×
228
         entry.attributes[field] = value
×
229
      end
230
      for childfield, parentfield in pairs(map) do
×
231
         if parentfield and not entry.attributes[parentfield] then
×
232
            entry.attributes[parentfield] = parent.attributes[childfield]
×
233
         end
234
      end
235
   end
236
end
237

238
--- Resolve the 'crossref' and 'xdata' fields on a bibliography entry.
239
-- (Supplementing the entry with the attributes of the parent entry.)
240
-- Once resolved recursively, the crossref and xdata fields are removed
241
-- from the entry.
242
-- So this is intended to be called at first use of the entry, and have no
243
-- effect on subsequent uses: BibTeX does seem to mandate cross references
244
-- to be defined before the entry that uses it, or even in the same bibliography
245
-- file.
246
-- Implementation note:
247
-- We are not here to check the consistency of the BibTeX file, so there is
248
-- no check that xdata refers only to @xdata entries
249
-- Removing the crossref field implies we won't track its use and implicitly
250
-- cite referenced entries in the bibliography over a certain threshold.
251
-- @tparam table bib Bibliography
252
-- @tparam table entry Bibliography entry
253
local function crossrefAndXDataResolve (bib, entry)
254
   local refs
255
   local xdata = entry.attributes.xdata
×
256
   if xdata then
×
257
      refs = xdata and pl.stringx.split(xdata, ",")
×
258
      entry.attributes.xdata = nil
×
259
   end
260
   local crossref = entry.attributes.crossref
×
261
   if crossref then
×
262
      refs = refs or {}
×
263
      table.insert(refs, crossref)
×
264
      entry.attributes.crossref = nil
×
265
   end
266

267
   if not refs then
×
268
      return
×
269
   end
270
   for _, ref in ipairs(refs) do
×
271
      local parent = bib[ref]
×
272
      if parent then
×
273
         crossrefAndXDataResolve(bib, parent)
×
274
         fieldsInherit(parent, entry)
×
275
      else
276
         SU.warn("Unknown crossref " .. ref .. " in bibliography entry " .. entry.label)
×
277
      end
278
   end
279
end
280

281
local function resolveEntry (bib, key)
282
   local entry = bib[key]
×
283
   if not entry then
×
284
      SU.warn("Unknown citation key " .. key)
×
285
      return
×
286
   end
287
   if entry.type == "xdata" then
×
288
      SU.warn("Skipped citation of @xdata entry " .. key)
×
289
      return
×
290
   end
291
   crossrefAndXDataResolve(bib, entry)
×
292
   return entry
×
293
end
294

295
function package:loadOptPackage (pack)
×
296
   local ok, _ = pcall(function ()
×
297
      self:loadPackage(pack)
×
298
      return true
×
299
   end)
300
   SU.debug("bibtex", "Optional package " .. pack .. (ok and " loaded" or " not loaded"))
×
301
   return ok
×
302
end
303

304
function package:_init ()
×
305
   base._init(self)
×
306
   SILE.scratch.bibtex = { bib = {}, cited = { keys = {}, citnums = {} } }
×
307
   Bibliography = require("packages.bibtex.bibliography")
×
308
   -- For DOI, PMID, PMCID and URL support.
309
   self:loadPackage("url")
×
310
   -- For underline styling support
311
   self:loadPackage("rules")
×
312
   -- For TeX-like math support (extension)
313
   self:loadPackage("math")
×
314
   -- For superscripting support in number formatting
315
   -- Play fair: try to load 3rd-party optional textsubsuper package.
316
   -- If not available, fallback to raiselower to implement textsuperscript
317
   if not self:loadOptPackage("textsubsuper") then
×
318
      self:loadPackage("raiselower")
×
319
      self:registerCommand("textsuperscript", function (_, content)
×
320
         SILE.call("raise", { height = "0.7ex" }, function ()
×
321
            SILE.call("font", { size = "1.5ex" }, content)
×
322
         end)
323
      end)
324
   end
325
end
326

327
function package.declareSettings (_)
×
328
   SILE.settings:declare({
×
329
      parameter = "bibtex.style",
330
      type = "string",
331
      default = "chicago",
332
      help = "BibTeX style",
333
   })
334
end
335

336
function package:registerCommands ()
×
337
   self:registerCommand("loadbibliography", function (options, _)
×
338
      local file = SU.required(options, "file", "loadbibliography")
×
339
      parseBibtex(file, SILE.scratch.bibtex.bib)
×
340
   end)
341

342
   -- LEGACY COMMANDS
343

344
   self:registerCommand("bibstyle", function (_, _)
×
345
      SU.deprecated("\\bibstyle", "\\set[parameter=bibtex.style]", "0.13.2", "0.14.0")
×
346
   end)
347

348
   self:registerCommand("cite", function (options, content)
×
349
      local style = SILE.settings:get("bibtex.style")
×
350
      if style == "csl" then
×
351
         SILE.call("csl:cite", options, content)
×
352
         return -- done via CSL
×
353
      end
354
      if not self._deprecated_legacy_warning then
×
355
         self._deprecated_legacy_warning = true
×
356
         SU.warn("Legacy bibtex.style is deprecated, consider enabling the CSL implementation.")
×
357
      end
358
      if not options.key then
×
359
         options.key = SU.ast.contentToString(content)
×
360
      end
361
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
362
      if not entry then
×
363
         return
×
364
      end
365
      -- Keep track of cited entries
366
      table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
367
      local citnum = #SILE.scratch.bibtex.cited.keys
×
368
      SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
369

370
      local bibstyle = require("packages.bibtex.styles." .. style)
×
371
      local cite = Bibliography.produceCitation(options, SILE.scratch.bibtex.bib, bibstyle)
×
372
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
373
   end)
374

375
   self:registerCommand("reference", function (options, content)
×
376
      local style = SILE.settings:get("bibtex.style")
×
377
      if style == "csl" then
×
378
         SILE.call("csl:reference", options, content)
×
379
         return -- done via CSL
×
380
      end
381
      if not self._deprecated_legacy_warning then
×
382
         self._deprecated_legacy_warning = true
×
383
         SU.warn("Legacy bibtex.style is deprecated, consider enabling the CSL implementation.")
×
384
      end
385
      if not options.key then
×
386
         options.key = SU.ast.contentToString(content)
×
387
      end
388
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
389
      if not entry then
×
390
         return
×
391
      end
392

393
      local citnum = SILE.scratch.bibtex.cited.citnums[options.key]
×
394
      if not citnum then
×
395
         SU.warn("Reference to a non-cited entry " .. options.key)
×
396
         -- Make it cited
397
         table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
398
         citnum = #SILE.scratch.bibtex.cited.keys
×
399
         SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
400
      end
401
      local bibstyle = require("packages.bibtex.styles." .. style)
×
402
      local cite, err = Bibliography.produceReference(options, SILE.scratch.bibtex.bib, bibstyle)
×
403
      if cite == Bibliography.Errors.UNKNOWN_TYPE then
×
404
         SU.warn("Unknown type @" .. err .. " in citation for reference " .. options.key)
×
405
         return
×
406
      end
407
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
408
   end)
409

410
   -- NEW CSL IMPLEMENTATION
411

412
   -- Hooks for CSL processing
413

414
   self:registerCommand("bibSmallCaps", function (_, content)
×
415
      -- To avoid attributes in the CSL-processed content
416
      SILE.call("font", { features = "+smcp" }, content)
×
417
   end)
418

419
   -- CSL 1.0.2 appendix VI
420
   -- "If the bibliography entry for an item renders any of the following
421
   -- identifiers, the identifier should be anchored as a link, with the
422
   -- target of the link as follows:
423
   --   url: output as is
424
   --   doi: prepend with “https://doi.org/”
425
   --   pmid: prepend with “https://www.ncbi.nlm.nih.gov/pubmed/”
426
   --   pmcid: prepend with “https://www.ncbi.nlm.nih.gov/pmc/articles/”
427
   -- NOT IMPLEMENTED:
428
   --   "Citation processors should include an option flag for calling
429
   --   applications to disable bibliography linking behavior."
430
   -- (But users can redefine these commands to their liking...)
431
   self:registerCommand("bibLink", function (options, content)
×
432
      SILE.call("href", { src = options.src }, {
×
433
         SU.ast.createCommand("url", {}, { content[1] }),
×
434
      })
435
   end)
436
   self:registerCommand("bibURL", function (_, content)
×
437
      local link = content[1]
×
438
      if not link:match("^https?://") then
×
439
         -- Play safe
440
         link = "https://" .. link
×
441
      end
442
      SILE.call("bibLink", { src = link }, content)
×
443
   end)
444
   self:registerCommand("bibDOI", function (_, content)
×
445
      local link = content[1]
×
446
      if not link:match("^https?://") then
×
447
         link = "https://doi.org/" .. link
×
448
      end
449
      SILE.call("bibLink", { src = link }, content)
×
450
   end)
451
   self:registerCommand("bibPMID", function (_, content)
×
452
      local link = content[1]
×
453
      if not link:match("^https?://") then
×
454
         link = "https://www.ncbi.nlm.nih.gov/pubmed/" .. link
×
455
      end
456
      SILE.call("bibLink", { src = link }, content)
×
457
   end)
458
   self:registerCommand("bibPMCID", function (_, content)
×
459
      local link = content[1]
×
460
      if not link:match("^https?://") then
×
461
         link = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. link
×
462
      end
463
      SILE.call("bibLink", { src = link }, content)
×
464
   end)
465

466
   self:registerCommand("bibRule", function (_, content)
×
467
      local n = content[1] and tonumber(content[1]) or 3
×
468
      local width = n .. "em"
×
469
      SILE.call("raise", { height = "0.4ex" }, function ()
×
470
         SILE.call("hrule", { height = "0.4pt", width = width })
×
471
      end)
472
   end)
473

474
   -- Style and locale loading
475

476
   self:registerCommand("bibliographystyle", function (options, _)
×
477
      local sty = SU.required(options, "style", "bibliographystyle")
×
478
      local style = loadCslStyle(sty)
×
479
      -- FIXME: lang is mandatory until we can map document.lang to a resolved
480
      -- BCP47 with region always present, as this is what CSL locales require.
481
      if not options.lang then
×
482
         -- Pick the default locale from the style, if any
483
         options.lang = style.globalOptions["default-locale"]
×
484
      end
485
      local lang = SU.required(options, "lang", "bibliographystyle")
×
486
      local locale = loadCslLocale(lang)
×
487
      SILE.scratch.bibtex.engine = CslEngine(style, locale, {
×
488
         localizedPunctuation = SU.boolean(options.localizedPunctuation, false),
489
         italicExtension = SU.boolean(options.italicExtension, true),
490
         mathExtension = SU.boolean(options.mathExtension, true),
491
      })
492
   end)
493

494
   self:registerCommand("csl:cite", function (options, content)
×
495
      -- TODO:
496
      -- - multiple citation keys (but how to handle locators then?)
497
      local locator
498
      for k, v in pairs(options) do
×
499
         if k ~= "key" then
×
500
            if not locators[k] then
×
501
               SU.warn("Unknown option '" .. k .. "' in \\csl:cite")
×
502
            else
503
               if not locator then
×
504
                  local label = locators[k]
×
505
                  locator = { label = label, value = v }
×
506
               else
507
                  SU.warn("Multiple locators in \\csl:cite, using the first one")
×
508
               end
509
            end
510
         end
511
      end
512
      if not SILE.scratch.bibtex.engine then
×
513
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
514
      end
515
      local engine = SILE.scratch.bibtex.engine
×
516
      if not options.key then
×
517
         options.key = SU.ast.contentToString(content)
×
518
      end
519
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
520
      if not entry then
×
521
         return
×
522
      end
523

524
      -- Keep track of cited entries
525
      table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
526
      local citnum = #SILE.scratch.bibtex.cited.keys
×
527
      SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
528

529
      local csljson = bib2csl(entry, citnum)
×
530
      csljson.locator = locator
×
531
      local cite = engine:cite(csljson)
×
532

533
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
534
   end)
535

536
   self:registerCommand("csl:reference", function (options, content)
×
537
      if not SILE.scratch.bibtex.engine then
×
538
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
539
      end
540
      local engine = SILE.scratch.bibtex.engine
×
541
      if not options.key then
×
542
         options.key = SU.ast.contentToString(content)
×
543
      end
544
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
545
      if not entry then
×
546
         return
×
547
      end
548

549
      local citnum = SILE.scratch.bibtex.cited.citnums[options.key]
×
550
      if not citnum then
×
551
         SU.warn("Reference to a non-cited entry " .. options.key)
×
552
         -- Make it cited
553
         table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
554
         citnum = #SILE.scratch.bibtex.cited.keys
×
555
         SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
556
      end
557
      local cslentry = bib2csl(entry, citnum)
×
558
      local cite = engine:reference(cslentry)
×
559

560
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
561
   end)
562

563
   self:registerCommand("printbibliography", function (options, _)
×
564
      if not SILE.scratch.bibtex.engine then
×
565
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
566
      end
567
      local engine = SILE.scratch.bibtex.engine
×
568

569
      local bib
570
      if SU.boolean(options.cited, true) then
×
571
         bib = {}
×
572
         for _, key in ipairs(SILE.scratch.bibtex.cited.keys) do
×
573
            bib[key] = SILE.scratch.bibtex.bib[key]
×
574
         end
575
      else
576
         bib = SILE.scratch.bibtex.bib
×
577
      end
578

579
      local entries = {}
×
580
      local ncites = #SILE.scratch.bibtex.cited.keys
×
581
      for key, entry in pairs(bib) do
×
582
         if entry.type ~= "xdata" then
×
583
            crossrefAndXDataResolve(bib, entry)
×
584
            if entry then
×
585
               local citnum = SILE.scratch.bibtex.cited.citnums[key]
×
586
               if not citnum then
×
587
                  -- This is just to make happy CSL styles that require a citation number
588
                  -- However, table order is not guaranteed in Lua so the output may be
589
                  -- inconsistent across runs with styles that use this number for sorting.
590
                  -- This may only happen for non-cited entries in the bibliography, and it
591
                  -- would be a bad practive to use such a style to print the full bibliography,
592
                  -- so I don't see a strong need to fix this at the expense of performance.
593
                  -- (and we can't really, some styles might have several sorting criteria
594
                  -- leading to impredictable order anyway).
595
                  ncites = ncites + 1
×
596
                  citnum = ncites
×
597
               end
598
               local cslentry = bib2csl(entry, citnum)
×
599
               table.insert(entries, cslentry)
×
600
            end
601
         end
602
      end
603
      print("<bibliography: " .. #entries .. " entries>")
×
604
      local cite = engine:reference(entries)
×
605
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
606

607
      SILE.scratch.bibtex.cited = { keys = {}, citnums = {} }
×
608
   end)
609
end
610

611
package.documentation = [[
612
\begin{document}
613
BibTeX is a citation management system.
614
It was originally designed for TeX but has since been integrated into a variety of situations.
615
This experimental package allows SILE to read and process Bib(La)TeX \code{.bib} files and output citations and full text references.
616

617
\smallskip
618
\noindent
619
\em{Loading a bibliography}
620
\novbreak
621

622
\indent
623
To load a BibTeX file, issue the command \autodoc:command{\loadbibliography[file=<whatever.bib>]}.
624
You can load multiple files, and the entries will be merged into a single bibliography database.
625

626
\smallskip
627
\noindent
628
\em{Producing citations and references (legacy commands)}
629
\novbreak
630

631
\indent
632
The “legacy” implementation is based on a custom rendering system.
633
The plan is to eventually deprecate it in favor of the CSL implementation.
634

635
To produce an inline citation, call \autodoc:command{\cite{<key>}}, which will typeset something like “Jones 1982”.
636
If you want to cite a particular page number, use \autodoc:command{\cite[page=22]{<key>}}.
637

638
To produce a bibliographic reference, use \autodoc:command{\reference{<key>}}.
639

640
The \autodoc:setting[check=false]{bibtex.style} setting controls the style of the bibliography.
641
It currently defaults to \code{chicago}, the only style supported out of the box.
642
It can however be set to \code{csl} to enforce the use of the CSL implementation on the above commands.
643

644
This implementation doesn’t currently produce full bibliography listings.
645
(Actually, you can use the \autodoc:command{\printbibliography} introduced below, but then it always uses the CSL implementation for rendering the bibliography, differing from the output of the \autodoc:command{\reference} command.)
646

647
\smallskip
648
\noindent
649
\em{Producing citations and references (CSL implementation)}
650
\novbreak
651

652
\indent
653
While an experimental work-in-progress, the CSL (Citation Style Language) implementation is more powerful and flexible than the legacy commands.
654

655
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}).
656

657
The command accepts a few additional options:
658

659
\begin{itemize}
660
\item{\autodoc:parameter{localizedPunctuation} (default \code{false}): whether to use localized punctuation – this is non-standard but may be useful when using a style that was not designed for the target language;}
661
\item{\autodoc:parameter{italicExtension} (default \code{true}): whether to convert \code{_text_} to italic text (“à la Markdown”);}
662
\item{\autodoc:parameter{mathExtension} (default \code{true}): whether to recognize \code{$formula$} as math formulae in (a subset of the) TeX-like syntax.}
663
\end{itemize}
664

665
The locale and styles files are searched in the \code{csl/locales} and \code{csl/styles} directories, respectively, in your project directory, or in the Lua package path.
666
For convenience and testing, SILE bundles the \code{chicago-author-date} and \code{chicago-author-date-fr} styles, and the \code{en-US} and \code{fr-FR} locales.
667
If you don’t specify a style or locale, the author-date style and the \code{en-US} locale will be used.
668

669
To produce an inline citation, call \autodoc:command{\csl:cite{<key>}}, which will typeset something like “(Jones 1982)”.
670
If you want to cite a particular page number, use \autodoc:command{\csl:cite[page=22]{<key>}}. Other “locator”  options are available (article, chapter, column, line, note, paragraph, section, volume, etc.) – see the CSL documentation for details.
671
Some frequent abbreviations are also supported (art, chap, col, fig…)
672

673
To produce a bibliography of cited references, use \autodoc:command{\printbibliography}.
674
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).
675
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.
676

677
To produce a bibliographic reference, use \autodoc:command{\csl:reference{<key>}}.
678
Note that this command is not intended for actual use, but for testing purposes.
679
It may be removed in the future.
680

681
\smallskip
682
\noindent
683
\em{Notes on the supported BibTeX syntax}
684
\novbreak
685

686
\indent
687
The BibTeX file format is a plain text format for bibliographies.
688

689
The \code{@type\{…\}} syntax is used to specify an entry, where \code{type} is the type of the entry, and is case-insensitive.
690
Any content outside entries is ignored.
691

692
The \code{@preamble} and \code{@comment} special entries are ignored.
693
The former is specific to TeX-based systems, and the latter is a comment (everything between the balanced braces is ignored).
694

695
The \code{@string\{key=value\}} special entry is used to define a string or “abbreviation,” for use in other subsequent entries.
696

697
The \code{@xdata} entry is used to define an entry that can be used as a reference in other entries.
698
Such entries are not printed in the bibliography.
699
Normally, they cannot be cited directly.
700
In this implementation, a warning is raised if they are; but as they have no known type, their formatting is not well-defined, and might not be meaningful.
701

702
Regular bibliography entries have the following syntax:
703

704
\begin[type=autodoc:codeblock]{raw}
705
@type{key,
706
  field1 = value1,
707
  field2 = value2,
708
  …
709
}
710
\end{raw}
711

712
The entry key is a unique identifier for the entry, and is case-sensitive.
713
Entries consist of fields, which are key-value pairs.
714
The field names are case-insensitive.
715
Spaces and line breaks are not important, except for readability.
716
On the contrary, commas are compulsory between any two fields of an entry.
717

718
String values shall be enclosed in either double quotes or curly braces.
719
The latter allows using quotes inside the string, while the former does not without escaping them with a backslash.
720

721
When string values are not enclosed in quotes or braces, they must not contain any whitespace characters.
722
The value is then considered to be a reference to an abbreviation previously defined in a \code{@string} entry.
723
If no such abbreviation is found, the value is considered to be a string literal.
724
(This allows a decent fallback for fields where curly braces or double quotes could historically be omitted, such as numerical values, and one-word strings.)
725

726
String values are assumed to be in the UTF-8 encoding, and shall not contain (La)TeX commands.
727
Special character sequences from TeX (such as \code{`} assumed to be an opening quote) are not supported.
728
There are exceptions to this rule.
729
Notably, the \code{~} character can be used to represent a non-breaking space (when not backslash-escaped), and the \code{\\&} sequence is accepted (though this implementation does not mandate escaping ampersands).
730
With the CSL renderer, see also the non-standard extensions above.
731

732
Values can also be composed by concatenating strings, using the \code{#} character.
733

734
Besides using string references, entries have two other \em{parent-child} inheritance mechanisms allowing to reuse fields from other entries, without repeating them: the \code{crossref} and \code{xdata} fields.
735

736
The \code{crossref} field is used to reference another entry by its key.
737
The \code{xdata} field accepts a comma-separated list of keys of entries that are to be inherited.
738

739
Some BibTeX implementations automatically include entries referenced with the \code{crossref} field in the bibliography, when a certain threshold is met.
740
This implementation does not do that.
741

742
Depending on the types of the parent and child entries, the child entry may inherit some or all fields from the parent entry, and some inherited fields may be reassigned in the child entry.
743
For instance, the \code{title} in a \code{@collection} entry is inherited as the \code{booktitle} field in a \code{@incollection} child entry.
744
Some BibTeX implementations allow configuring the data inheritance behavior, but this implementation does not.
745
It is also currently quite limited on the fields that are reassigned, and only provides a subset of the mappings defined in the BibLaTeX manual, appendix B.
746

747
Here is an example of a BibTeX file showing some of the abovementioned features:
748

749
\begin[type=autodoc:codeblock]{raw}
750
@string{JIT = "Journal of Interesting Things"}
751
...
752
This text is ignored
753
...
754
@xdata{jit-vol1-iss2,
755
  journal = JIT # { (JIT)},
756
  year    = {2020},
757
  month   = {jan},
758
  volume  = {1},
759
  number  = {2},
760
}
761
@article{my-article,
762
  author  = {Doe, John and Smith, Jane}
763
  title   = {Theories & Practices},
764
  xdata   = {jit-1-2},
765
  pages   = {100--200},
766
}
767
\end{raw}
768

769
Some fields have a special syntax.
770
The \code{author}, \code{editor} and \code{translator} fields accept a list of names, separated by the keyword \code{and}.
771
The legacy \code{month} field accepts a three-letter abbreviation for the month in English, or a number from 1 to 12.
772
The more powerful \code{date} field accepts a date-time following the ISO 8601-2 Extended Date/Time Format specification level 1 (such as \code{YYYY-MM-DD}, or a date range \code{YYYY-MM-DD/YYYY-MM-DD}, and more).
773
\end{document}
774
]]
×
775

776
return package
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc