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

sile-typesetter / sile / 10889547617

16 Sep 2024 06:10PM UTC coverage: 65.948% (+4.3%) from 61.663%
10889547617

push

github

web-flow
Merge 3f89664d9 into f6a392073

89 of 1259 new or added lines in 10 files covered. (7.07%)

1 existing line in 1 file now uncovered.

12383 of 18777 relevant lines covered (65.95%)

5778.83 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

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

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

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

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

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

47
local Bibliography
48

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

341
   -- LEGACY COMMANDS
342

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

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

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

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

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

409
   -- NEW CSL IMPLEMENTATION
410

411
   -- Internal commands for CSL processing
412

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

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

465
   -- Style and locale loading
466

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

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

515
      -- Keep track of cited entries
NEW
516
      table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
NEW
517
      local citnum = #SILE.scratch.bibtex.cited.keys
×
NEW
518
      SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
519

NEW
520
      local csljson = bib2csl(entry, citnum)
×
NEW
521
      csljson.locator = locator
×
NEW
522
      local cite = engine:cite(csljson)
×
523

NEW
524
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
525
   end)
526

NEW
527
   self:registerCommand("csl:reference", function (options, content)
×
NEW
528
      if not SILE.scratch.bibtex.engine then
×
NEW
529
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
530
      end
NEW
531
      local engine = SILE.scratch.bibtex.engine
×
NEW
532
      if not options.key then
×
NEW
533
         options.key = SU.ast.contentToString(content)
×
534
      end
NEW
535
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
NEW
536
      if not entry then
×
NEW
537
         return
×
538
      end
539

NEW
540
      local citnum = SILE.scratch.bibtex.cited.citnums[options.key]
×
NEW
541
      if not citnum then
×
NEW
542
         SU.warn("Reference to a non-cited entry " .. options.key)
×
543
         -- Make it cited
NEW
544
         table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
NEW
545
         citnum = #SILE.scratch.bibtex.cited.keys
×
NEW
546
         SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
547
      end
NEW
548
      local cslentry = bib2csl(entry, citnum)
×
NEW
549
      local cite = engine:reference(cslentry)
×
550

NEW
551
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
552
   end)
553

NEW
554
   self:registerCommand("printbibliography", function (options, _)
×
NEW
555
      if not SILE.scratch.bibtex.engine then
×
NEW
556
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
557
      end
NEW
558
      local engine = SILE.scratch.bibtex.engine
×
559

560
      local bib
NEW
561
      if SU.boolean(options.cited, true) then
×
NEW
562
         bib = {}
×
NEW
563
         for _, key in ipairs(SILE.scratch.bibtex.cited.keys) do
×
NEW
564
            bib[key] = SILE.scratch.bibtex.bib[key]
×
565
         end
566
      else
NEW
567
         bib = SILE.scratch.bibtex.bib
×
568
      end
569

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

NEW
598
      SILE.scratch.bibtex.cited = { keys = {}, citnums = {} }
×
599
   end)
600
end
601

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

608
\smallskip
609
\noindent
610
\em{Loading a bibliography}
611
\novbreak
612

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

617
\smallskip
618
\noindent
619
\em{Producing citations and references (legacy commands)}
620
\novbreak
621

622
\indent
623
The “legacy” implementation is based on a custom rendering system.
624
The plan is to eventually deprecate it in favor of the CSL implementation.
625

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

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

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

635
This implementation doesn’t currently produce full bibliography listings.
636
(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.)
637

638
\smallskip
639
\noindent
640
\em{Producing citations and references (CSL implementation)}
641
\novbreak
642

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

646
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}).
647

648
The command accepts a few additional options:
649

650
\begin{itemize}
651
\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;}
652
\item{\autodoc:parameter{italicExtension} (default \code{true}): whether to convert \code{_text_} to italic text (“à la Markdown”);}
653
\item{\autodoc:parameter{mathExtension} (default \code{true}): whether to recognize \code{$formula$} as math formulae in (a subset of the) TeX-like syntax.}
654
\end{itemize}
655

656
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.
657
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.
658
If you don’t specify a style or locale, the author-date style and the \code{en-US} locale will be used.
659

660
To produce an inline citation, call \autodoc:command{\csl:cite{<key>}}, which will typeset something like “(Jones 1982)”.
661
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.
662
Some frequent abbreviations are also supported (art, chap, col, fig…)
663

664
To produce a bibliography of cited references, use \autodoc:command{\printbibliography}.
665
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).
666
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.
667

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

672
\smallskip
673
\noindent
674
\em{Notes on the supported BibTeX syntax}
675
\novbreak
676

677
\indent
678
The BibTeX file format is a plain text format for bibliographies.
679

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

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

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

688
The \code{@xdata} entry is used to define an entry that can be used as a reference in other entries.
689
Such entries are not printed in the bibliography.
690
Normally, they cannot be cited directly.
691
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.
692

693
Regular bibliography entries have the following syntax:
694

695
\begin[type=autodoc:codeblock]{raw}
696
@type{key,
697
  field1 = value1,
698
  field2 = value2,
699
  …
700
}
701
\end{raw}
702

703
The entry key is a unique identifier for the entry, and is case-sensitive.
704
Entries consist of fields, which are key-value pairs.
705
The field names are case-insensitive.
706
Spaces and line breaks are not important, except for readability.
707
On the contrary, commas are compulsory between any two fields of an entry.
708

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

712
When string values are not enclosed in quotes or braces, they must not contain any whitespace characters.
713
The value is then considered to be a reference to an abbreviation previously defined in a \code{@string} entry.
714
If no such abbreviation is found, the value is considered to be a string literal.
715
(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.)
716

717
String values are assumed to be in the UTF-8 encoding, and shall not contain (La)TeX commands.
718
Special character sequences from TeX (such as \code{`} assumed to be an opening quote) are not supported.
719
There are exceptions to this rule.
720
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).
721
With the CSL renderer, see also the non-standard extensions above.
722

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

725
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.
726

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

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

733
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.
734
For instance, the \code{title} in a \code{@collection} entry is inherited as the \code{booktitle} field in a \code{@incollection} child entry.
735
Some BibTeX implementations allow configuring the data inheritance behavior, but this implementation does not.
736
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.
737

738
Here is an example of a BibTeX file showing some of the abovementioned features:
739

740
\begin[type=autodoc:codeblock]{raw}
741
@string{JIT = "Journal of Interesting Things"}
742
...
743
This text is ignored
744
...
745
@xdata{jit-vol1-iss2,
746
  journal = JIT # { (JIT)},
747
  year    = {2020},
748
  month   = {jan},
749
  volume  = {1},
750
  number  = {2},
751
}
752
@article{my-article,
753
  author  = {Doe, John and Smith, Jane}
754
  title   = {Theories & Practices},
755
  xdata   = {jit-1-2},
756
  pages   = {100--200},
757
}
758
\end{raw}
759

760
Some fields have a special syntax.
761
The \code{author}, \code{editor} and \code{translator} fields accept a list of names, separated by the keyword \code{and}.
762
The legacy \code{month} field accepts a three-letter abbreviation for the month in English, or a number from 1 to 12.
763
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).
764
\end{document}
765
]]
×
766

767
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