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

sile-typesetter / sile / 14510870853

17 Apr 2025 07:55AM UTC coverage: 31.472% (-25.8%) from 57.267%
14510870853

push

github

web-flow
Merge pull request #2267 from Omikhleia/feat-csl-position

0 of 109 new or added lines in 2 files covered. (0.0%)

4871 existing lines in 34 files now uncovered.

6341 of 20148 relevant lines covered (31.47%)

2774.57 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 bibparser = require("packages.bibtex.support.bibparser")
×
12
local parseBibtex, crossrefAndXDataResolve = bibparser.parseBibtex, bibparser.crossrefAndXDataResolve
×
13

14
local bib2csl = require("packages.bibtex.support.bib2csl")
×
15
local locators = require("packages.bibtex.support.locators")
×
16

17
local Bibliography = require("packages.bibtex.bibliography") -- Legacy
×
18

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

46
local package = pl.class(base)
×
47
package._name = "bibtex"
×
48

49
local function resolveEntry (bib, key)
50
   local entry = bib[key]
×
51
   if not entry then
×
52
      SU.warn("Unknown citation key " .. key)
×
53
      return
×
54
   end
55
   if entry.type == "xdata" then
×
56
      SU.warn("Skipped citation of @xdata entry " .. key)
×
57
      return
×
58
   end
59
   crossrefAndXDataResolve(bib, entry)
×
60
   return entry
×
61
end
62

63
function package:loadOptPackage (pack)
×
64
   local ok, _ = pcall(function ()
×
65
      self:loadPackage(pack)
×
66
      return true
×
67
   end)
68
   SU.debug("bibtex", "Optional package " .. pack .. (ok and " loaded" or " not loaded"))
×
69
   return ok
×
70
end
71

72
function package:_init ()
×
73
   base._init(self)
×
74
   -- Formerly we used a SILE.scratch variable, but these expose too much of the internals to the outer world.
75
   -- So we now use a private member instead.
NEW
76
   self._data = {
×
77
      bib = {},
NEW
78
      cited = {
×
79
         keys = {}, -- Cited keys in the order they are cited (ordered set)
80
         refs = {}, -- Table of cited keys with their first citation number, last locator and last position (table)
81
         lastkey = nil, -- Last entry key used in a citation, to track ibid/ibid-with-locator (string)
82
      },
83
   }
84

85
   -- For DOI, PMID, PMCID and URL support.
86
   self:loadPackage("url")
×
87
   -- For underline styling support
88
   self:loadPackage("rules")
×
89
   -- For TeX-like math support (extension)
90
   self:loadPackage("math")
×
91
   -- For superscripting support in number formatting
92
   -- Play fair: try to load 3rd-party optional textsubsuper package.
93
   -- If not available, fallback to raiselower to implement textsuperscript
94
   if not self:loadOptPackage("textsubsuper") then
×
95
      self:loadPackage("raiselower")
×
96
      self:registerCommand("textsuperscript", function (_, content)
×
97
         -- Fake more or less ad hoc superscripting
98
         SILE.call("raise", { height = "0.7ex" }, function ()
×
99
            SILE.call("font", { size = "1.5ex" }, content)
×
100
         end)
101
      end)
102
   end
103
end
104

105
function package:declareSettings ()
×
106
   SILE.settings:declare({
×
107
      parameter = "bibtex.style",
108
      type = "string",
109
      default = "csl",
110
      help = "BibTeX style",
111
   })
112

113
   -- For CSL hanging-indent or second-field-align
114
   SILE.settings:declare({
×
115
      parameter = "bibliography.indent",
116
      type = "measurement",
117
      default = SILE.types.measurement("3em"),
118
      help = "Left indentation for bibliography entries when the citation style requires it.",
119
   })
120
end
121

122
--- Retrieve the CSL engine, creating it if necessary.
123
-- @treturn CslEngine CSL engine instance
124
function package:getCslEngine ()
×
NEW
125
   if not self._engine then
×
126
      SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
127
   end
NEW
128
   return self._engine
×
129
end
130

131
--- Retrieve an entry and mark it as cited if it is not already.
132
-- @tparam string key Citation key
133
-- @tparam boolean warn_uncited Warn if the entry is not cited yet
134
-- @treturn table Bibliography entry
135
-- @treturn number Citation number
136
-- @treturn string|nil Locator value
NEW
137
function package:_getEntryForCite (key, warn_uncited)
×
NEW
138
   local entry = resolveEntry(self._data.bib, key)
×
UNCOV
139
   if not entry then
×
UNCOV
140
      return
×
141
   end
142
   -- Keep track of cited entries
NEW
143
   local cited = self._data.cited.refs[key]
×
NEW
144
   if not cited then
×
145
      if warn_uncited then
×
NEW
146
         SU.warn("Reference to a non-cited entry " .. key)
×
147
      end
148
      -- Make it cited
NEW
149
      table.insert(self._data.cited.keys, key)
×
NEW
150
      local citnum = #self._data.cited.keys
×
NEW
151
      cited = { citnum = citnum }
×
NEW
152
      self._data.cited.refs[key] = cited
×
153
   end
NEW
154
   return entry, cited.citnum
×
155
end
156

157
--- Track the position of a citation acconrding to the CSL rules.
158
-- @tparam string key Citation key
159
-- @tparam table locator Locator (label and value)
160
-- @tparam boolean is_single Single or multiple citation
161
-- @treturn string Position of the citation (first, subsequent, ibid, ibid-with-locator)
NEW
162
function package:_getCitePosition (key, locator, is_single)
×
NEW
163
   local cited = self._data.cited.refs[key]
×
NEW
164
   if not cited then
×
165
      -- This method is assumed to be invoked only for cited entries
166
      -- (i.e. after a call to getEntryForCite).
NEW
167
      SU.error("Entry " .. key .. " not cited yet, cannot track position")
×
168
   end
169
   local pos
NEW
170
   if not cited.position then
×
NEW
171
      pos = "first"
×
172
   else
173
      -- CSL 1.0.2 for "ibid" and "ibid-with-locator":
174
      --    a. the current cite immediately follows on another cite, within the same citation,
175
      --       that references the same item
176
      --  or
177
      --    b. the current cite is the first cite in the citation, and the previous citation consists
178
      --       of a single cite referencing the same item.
NEW
179
      if self._data.cited.lastkey ~= key or not cited.single then
×
NEW
180
         pos = "subsequent"
×
NEW
181
      elseif cited.locator then
×
182
         -- CSL 1.0.2 rule when preceding cite does have a locator:
183
         --    If the current cite has the same locator, the position of the current cite is “ibid”.
184
         --    If the locator differs the position is “ibid-with-locator”.
185
         --    If the current cite lacks a locator its only position is “subsequent”."
NEW
186
         if locator then
×
NEW
187
            local same = cited.locator.label == locator.label and cited.locator.value == locator.value
×
NEW
188
            pos = same and "ibid" or "ibid-with-locator"
×
189
         else
NEW
190
            pos = "subsequent"
×
191
         end
192
      else
193
         -- CSL 1.0.2 rule when preceding cite does not have a locator:
194
         --    If the current cite has a locator, the position of the current cite is “ibid-with-locator”.
195
         --    Otherwise the position is “ibid”."
NEW
196
         pos = locator and "ibid-with-locator" or "ibid"
×
197
      end
198
   end
NEW
199
   cited.position = pos
×
NEW
200
   cited.locator = locator
×
NEW
201
   cited.single = is_single
×
NEW
202
   self._data.cited.lastkey = key
×
NEW
203
   return pos
×
204
end
205

206
--- Get the citation key from the options or content (of a command).
207
-- @tparam table options Options
208
-- @tparam table content Content
209
-- @treturn string Citation key
NEW
210
function package._getCitationKey (_, options, content)
×
NEW
211
   if options.key then
×
NEW
212
      return options.key
×
213
   end
NEW
214
   return SU.ast.contentToString(content)
×
215
end
216

217
--- Retrieve a locator from the options.
218
-- @tparam table options Options
219
-- @treturn table Locator
NEW
220
function package:_getLocator (options)
×
221
   local locator
UNCOV
222
   for k, v in pairs(options) do
×
223
      if k ~= "key" then
×
UNCOV
224
         if not locators[k] then
×
225
            SU.warn("Unknown option '" .. k .. "' in \\cite")
×
226
         else
227
            if not locator then
×
228
               local label = locators[k]
×
UNCOV
229
               locator = { label = label, value = v }
×
230
            else
231
               SU.warn("Multiple locators in \\cite, using the first one")
×
232
            end
233
         end
234
      end
235
   end
UNCOV
236
   return locator
×
237
end
238

239
function package:registerCommands ()
×
UNCOV
240
   self:registerCommand("loadbibliography", function (options, _)
×
UNCOV
241
      local file = SU.required(options, "file", "loadbibliography")
×
NEW
242
      parseBibtex(file, self._data.bib)
×
243
   end)
244

245
   self:registerCommand("nocite", function (options, content)
×
NEW
246
      local key = self:_getCitationKey(options, content)
×
247
      -- Just mark the entry as cited.
NEW
248
      self:_getEntryForCite(key, false) -- no warning if not yet cited
×
249
   end, "Mark an entry as cited without actually producing a citation.")
×
250

251
   -- LEGACY COMMANDS
252

UNCOV
253
   self:registerCommand("bibstyle", function (_, _)
×
UNCOV
254
      SU.deprecated("\\bibstyle", "\\set[parameter=bibtex.style]", "0.13.2", "0.14.0")
×
255
   end)
256

257
   self:registerCommand("cite", function (options, content)
×
UNCOV
258
      local style = SILE.settings:get("bibtex.style")
×
UNCOV
259
      if style == "csl" then
×
260
         SILE.call("csl:cite", options, content)
×
261
         return -- done via CSL
×
262
      end
263
      if not self._deprecated_legacy_warning then
×
264
         self._deprecated_legacy_warning = true
×
UNCOV
265
         SU.warn("Legacy bibtex.style is deprecated, consider enabling the CSL implementation.")
×
266
      end
267
      -- Ensure the key is set in the options as this was the legacy behavior
NEW
268
      options.key = self:_getCitationKey(options, content)
×
NEW
269
      local entry = self:_getEntryForCite(options.key, false) -- no warning if not yet cited
×
UNCOV
270
      if entry then
×
UNCOV
271
         local bibstyle = require("packages.bibtex.styles." .. style)
×
NEW
272
         local cite = Bibliography.produceCitation(options, self._data.bib, bibstyle)
×
273
         SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
274
      end
275
   end, "Produce a single citation.")
×
276

UNCOV
277
   self:registerCommand("reference", function (options, content)
×
278
      local style = SILE.settings:get("bibtex.style")
×
UNCOV
279
      if style == "csl" then
×
280
         SILE.call("csl:reference", options, content)
×
281
         return -- done via CSL
×
282
      end
283
      if not self._deprecated_legacy_warning then
×
284
         self._deprecated_legacy_warning = true
×
UNCOV
285
         SU.warn("Legacy bibtex.style is deprecated, consider enabling the CSL implementation.")
×
286
      end
287
      -- Ensure the key is set in the options as this was the legacy behavior
NEW
288
      options.key = self:_getCitationKey(options, content)
×
NEW
289
      local entry = self:_getEntryForCite(options.key, true) -- warn if uncited
×
UNCOV
290
      if entry then
×
UNCOV
291
         local bibstyle = require("packages.bibtex.styles." .. style)
×
NEW
292
         local cite, err = Bibliography.produceReference(options, self._data.bib, bibstyle)
×
293
         if cite == Bibliography.Errors.UNKNOWN_TYPE then
×
294
            SU.warn("Unknown type @" .. err .. " in citation for reference " .. options.key)
×
295
            return
×
296
         end
297
         SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
298
      end
UNCOV
299
   end, "Produce a single bibliographic reference.")
×
300

301
   -- CSL IMPLEMENTATION COMMANDS
302

303
   -- Hooks for CSL processing
304

UNCOV
305
   self:registerCommand("bibSmallCaps", function (_, content)
×
306
      -- To avoid attributes in the CSL-processed content
UNCOV
307
      SILE.call("font", { features = "+smcp" }, content)
×
308
   end)
309

310
   self:registerCommand("bibSuperScript", function (_, content)
×
311
      -- Superscripted content from CSL may contain characters that are not
312
      -- available in the font even with +sups.
313
      -- E.g. ACS style uses superscripted numbers for references, but also
314
      -- comma-separated lists of numbers, or ranges with an en-dash.
315
      -- We want to be consistent between all these cases, so we always
316
      -- use fake superscripts.
UNCOV
317
      SILE.call("textsuperscript", { fake = true }, content)
×
318
   end)
319

320
   -- CSL 1.0.2 appendix VI
321
   -- "If the bibliography entry for an item renders any of the following
322
   -- identifiers, the identifier should be anchored as a link, with the
323
   -- target of the link as follows:
324
   --   url: output as is
325
   --   doi: prepend with “https://doi.org/”
326
   --   pmid: prepend with “https://www.ncbi.nlm.nih.gov/pubmed/”
327
   --   pmcid: prepend with “https://www.ncbi.nlm.nih.gov/pmc/articles/”
328
   -- NOT IMPLEMENTED:
329
   --   "Citation processors should include an option flag for calling
330
   --   applications to disable bibliography linking behavior."
331
   -- (But users can redefine these commands to their liking...)
UNCOV
332
   self:registerCommand("bibLink", function (options, content)
×
UNCOV
333
      SILE.call("href", { src = options.src }, {
×
UNCOV
334
         SU.ast.createCommand("url", {}, { content[1] }),
×
335
      })
336
   end)
337
   self:registerCommand("bibURL", function (_, content)
×
UNCOV
338
      local link = content[1]
×
UNCOV
339
      if not link:match("^https?://") then
×
340
         -- Play safe
341
         link = "https://" .. link
×
342
      end
UNCOV
343
      SILE.call("bibLink", { src = link }, content)
×
344
   end)
UNCOV
345
   self:registerCommand("bibDOI", function (_, content)
×
346
      local link = content[1]
×
UNCOV
347
      if not link:match("^https?://") then
×
348
         link = "https://doi.org/" .. link
×
349
      end
350
      SILE.call("bibLink", { src = link }, content)
×
351
   end)
UNCOV
352
   self:registerCommand("bibPMID", function (_, content)
×
353
      local link = content[1]
×
UNCOV
354
      if not link:match("^https?://") then
×
355
         link = "https://www.ncbi.nlm.nih.gov/pubmed/" .. link
×
356
      end
357
      SILE.call("bibLink", { src = link }, content)
×
358
   end)
UNCOV
359
   self:registerCommand("bibPMCID", function (_, content)
×
360
      local link = content[1]
×
UNCOV
361
      if not link:match("^https?://") then
×
362
         link = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. link
×
363
      end
364
      SILE.call("bibLink", { src = link }, content)
×
365
   end)
366

367
   self:registerCommand("bibRule", function (_, content)
×
UNCOV
368
      local n = content[1] and tonumber(content[1]) or 3
×
UNCOV
369
      local width = n .. "em"
×
370
      SILE.call("raise", { height = "0.4ex" }, function ()
×
371
         SILE.call("hrule", { height = "0.4pt", width = width })
×
372
      end)
373
   end)
374

UNCOV
375
   self:registerCommand("bibBoxForIndent", function (_, content)
×
UNCOV
376
      local hbox = SILE.typesetter:makeHbox(content)
×
UNCOV
377
      local margin = SILE.types.length(SILE.settings:get("bibliography.indent"):absolute())
×
378
      if hbox.width > margin then
×
379
         SILE.typesetter:pushHbox(hbox)
×
380
         SILE.typesetter:typeset(" ")
×
381
      else
382
         hbox.width = margin
×
383
         SILE.typesetter:pushHbox(hbox)
×
384
      end
385
   end)
386

387
   -- Style and locale loading
388

UNCOV
389
   self:registerCommand("bibliographystyle", function (options, _)
×
UNCOV
390
      local sty = SU.required(options, "style", "bibliographystyle")
×
UNCOV
391
      local style = loadCslStyle(sty)
×
392
      -- FIXME: lang is mandatory until we can map document.lang to a resolved
393
      -- BCP47 with region always present, as this is what CSL locales require.
394
      if not options.lang then
×
395
         -- Pick the default locale from the style, if any
UNCOV
396
         options.lang = style.globalOptions["default-locale"]
×
397
      end
UNCOV
398
      local lang = SU.required(options, "lang", "bibliographystyle")
×
399
      local locale = loadCslLocale(lang)
×
NEW
400
      self._engine = CslEngine(style, locale, {
×
401
         localizedPunctuation = SU.boolean(options.localizedPunctuation, false),
402
         italicExtension = SU.boolean(options.italicExtension, true),
403
         mathExtension = SU.boolean(options.mathExtension, true),
404
      })
405
   end)
406

UNCOV
407
   self:registerCommand("csl:cite", function (options, content)
×
NEW
408
      local key = self:_getCitationKey(options, content)
×
NEW
409
      local entry, citnum = self:_getEntryForCite(key, false) -- no warning if not yet cited
×
410
      if entry then
×
411
         local engine = self:getCslEngine()
×
NEW
412
         local locator = self:_getLocator(options)
×
NEW
413
         local pos = self:_getCitePosition(key, locator, true) -- locator, single cite
×
414

415
         local cslentry = bib2csl(entry, citnum)
×
416
         cslentry.locator = locator
×
NEW
417
         cslentry.position = pos
×
418
         local cite = engine:cite(cslentry)
×
419

420
         SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
421
      end
UNCOV
422
   end, "Produce a single citation.")
×
423

UNCOV
424
   self:registerCommand("cites", function (_, content)
×
425
      if type(content) ~= "table" then
×
UNCOV
426
         SU.error("Table content expected in \\cites")
×
427
      end
428
      -- We need no count cites to properly handle ibid/ibid-with-locator, as these depend
429
      -- on the previous citation being a single cite.
NEW
430
      local children = {}
×
NEW
431
      local nb = 0
×
NEW
432
      for _, child in ipairs(content) do
×
NEW
433
         if type(child) == "table" then
×
NEW
434
            if child.command ~= "cite" then
×
435
               SU.error("Only \\cite commands are allowed in \\cites")
×
436
            end
NEW
437
            nb = nb + 1
×
NEW
438
            table.insert(children, child)
×
439
         end
440
         -- Silently ignore other content (normally only blank lines)
441
      end
NEW
442
      local is_single = nb == 1
×
443
      -- Now we can collect the citations
NEW
444
      local cites = {}
×
NEW
445
      for _, c in ipairs(children) do
×
NEW
446
         local o = c.options
×
NEW
447
         local key = self:_getCitationKey(o, c)
×
NEW
448
         local entry, citnum = self:_getEntryForCite(key, false) -- no warning if not yet cited
×
NEW
449
         if entry then
×
NEW
450
            local locator = self:_getLocator(o)
×
NEW
451
            local pos = self:_getCitePosition(key, locator, is_single) -- no locator, single or multiple citation
×
452

NEW
453
            local cslentry = bib2csl(entry, citnum)
×
NEW
454
            cslentry.locator = locator
×
NEW
455
            cslentry.position = pos
×
NEW
456
            cites[#cites + 1] = cslentry
×
457
         end
458
      end
UNCOV
459
      if #cites > 0 then
×
UNCOV
460
         local engine = self:getCslEngine()
×
UNCOV
461
         local cite = engine:cite(cites)
×
462
         SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
463
      end
464
   end, "Produce a group of citations.")
×
465

UNCOV
466
   self:registerCommand("csl:reference", function (options, content)
×
NEW
467
      local key = self:_getCitationKey(options, content)
×
NEW
468
      local entry, citnum = self:_getEntryForCite(key, nil, true) -- no locator, warn if not yet cited
×
469
      if entry then
×
470
         local engine = self:getCslEngine()
×
471

472
         local cslentry = bib2csl(entry, citnum)
×
473
         local cite = engine:reference(cslentry)
×
474

475
         SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
476
      end
UNCOV
477
   end, "Produce a single bibliographic reference.")
×
478

UNCOV
479
   self:registerCommand("printbibliography", function (options, _)
×
480
      local bib
UNCOV
481
      if SU.boolean(options.cited, true) then
×
482
         bib = {}
×
NEW
483
         for _, key in ipairs(self._data.cited.keys) do
×
NEW
484
            bib[key] = self._data.bib[key]
×
485
         end
486
      else
NEW
487
         bib = self._data.bib
×
488
      end
489

490
      local entries = {}
×
NEW
491
      local ncites = #self._data.cited.keys
×
UNCOV
492
      for key, entry in pairs(bib) do
×
493
         if entry.type ~= "xdata" then
×
494
            crossrefAndXDataResolve(bib, entry)
×
495
            if entry then
×
496
               local citnum
NEW
497
               local prevcite = self._data.cited.refs[key]
×
NEW
498
               if not prevcite then
×
499
                  -- This is just to make happy CSL styles that require a citation number
500
                  -- However, table order is not guaranteed in Lua so the output may be
501
                  -- inconsistent across runs with styles that use this number for sorting.
502
                  -- This may only happen for non-cited entries in the bibliography, and it
503
                  -- would be a bad practice to use such a style to print the full bibliography,
504
                  -- so I don't see a strong need to fix this at the expense of performance.
505
                  -- (and we can't really, some styles might have several sorting criteria
506
                  -- leading to unpredictable order anyway).
UNCOV
507
                  ncites = ncites + 1
×
UNCOV
508
                  citnum = ncites
×
509
               else
NEW
510
                  citnum = prevcite.citnum
×
511
               end
512
               local cslentry = bib2csl(entry, citnum)
×
513
               table.insert(entries, cslentry)
×
514
            end
515
         end
516
      end
517
      -- Reset the list of cited entries after having build the entries
NEW
518
      self._data.cited = { keys = {}, refs = {}, lastkey = nil }
×
519

NEW
520
      local engine = self:getCslEngine()
×
NEW
521
      local cite = engine:reference(entries)
×
522

UNCOV
523
      print("<bibliography: " .. #entries .. " entries>")
×
UNCOV
524
      if not SILE.typesetter:vmode() then
×
UNCOV
525
         SILE.call("par")
×
526
      end
527
      SILE.settings:temporarily(function ()
×
528
         local hanging_indent = SU.boolean(engine.bibliography.options["hanging-indent"], false)
×
529
         local must_align = engine.bibliography.options["second-field-align"]
×
530
         local lskip = (SILE.settings:get("document.lskip") or SILE.types.node.glue()):absolute()
×
531
         if hanging_indent or must_align then
×
532
            -- Respective to the fixed part of the current lskip, all lines are indented
533
            -- but the first one.
534
            local indent = SILE.settings:get("bibliography.indent"):absolute()
×
UNCOV
535
            SILE.settings:set("document.lskip", lskip.width + indent)
×
UNCOV
536
            SILE.settings:set("document.parindent", -indent)
×
537
            SILE.settings:set("current.parindent", -indent)
×
538
         else
539
            -- Fixed part of the current lskip, and no paragraph indentation
540
            SILE.settings:set("document.lskip", lskip.width)
×
UNCOV
541
            SILE.settings:set("document.parindent", SILE.types.length())
×
UNCOV
542
            SILE.settings:set("current.parindent", SILE.types.length())
×
543
         end
544
         SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
545
         SILE.call("par")
×
546
      end)
547
   end, "Produce a bibliography of references.")
×
548
end
549

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

556
\smallskip
557
\noindent
558
\em{Loading a bibliography}
559
\novbreak
560

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

565
\smallskip
566
\noindent
567
\em{Producing citations and references (CSL implementation)}
568
\novbreak
569

570
\indent
571
The CSL (Citation Style Language) implementation is more powerful and flexible than the former legacy solution available in earlier versions of this package (see below).
572

573
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}).
574

575
The command accepts a few additional options:
576

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

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

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

591
To mark an entry as cited without actually producing a citation, use \autodoc:command{\nocite{<key>}}.
592
This is useful when you want to include an entry in the bibliography without citing it in the text.
593

594
To generate multiple citations grouped correctly, use \autodoc:command{\cites{\cite{<key1>} \cite{<key2>}, …}}.
595
This wrapper command only accepts \autodoc:command{\cite} elements following their standard syntax.
596
Any other element triggers an error, and any text content is silently ignored.
597

598
To produce a bibliography of cited references, use \autodoc:command{\printbibliography}.
599
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).
600
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.
601

602
To produce a bibliographic reference, use \autodoc:command{\reference{<key>}}.
603
Note that this command is not intended for actual use, but for testing purposes.
604
It may be removed in the future.
605

606
\smallskip
607
\noindent
608
\em{Producing citations and references (legacy commands)}
609
\novbreak
610

611
\indent
612
The “legacy” implementation is based on a custom rendering system.
613
The plan is to eventually deprecate and remove it, as the CSL implementation covers more use cases and is more powerful.
614

615
The \autodoc:setting[check=false]{bibtex.style} setting controls the style of the bibliography.
616
It may be set, for instance, to \code{chicago}, the only style supported out of the box.
617
(By default, it is set to \code{csl} to enforce the use of the CSL implementation.)
618

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

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

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

627
\smallskip
628
\noindent
629
\em{Notes on the supported BibTeX syntax}
630
\novbreak
631

632
\indent
633
The BibTeX file format is a plain text format for bibliographies.
634

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

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

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

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

648
Regular bibliography entries have the following syntax:
649

650
\begin[type=autodoc:codeblock]{raw}
651
@type{key,
652
  field1 = value1,
653
  field2 = value2,
654
  …
655
}
656
\end{raw}
657

658
The entry key is a unique identifier for the entry, and is case-sensitive.
659
Entries consist of fields, which are key-value pairs.
660
The field names are case-insensitive.
661
Spaces and line breaks are not important, except for readability.
662
On the contrary, commas are compulsory between any two fields of an entry.
663

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

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

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

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

680
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.
681

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

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

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

693
Here is an example of a BibTeX file showing some of the abovementioned features:
694

695
\begin[type=autodoc:codeblock]{raw}
696
@string{JIT = "Journal of Interesting Things"}
697
...
698
This text is ignored
699
...
700
@xdata{jit-vol1-iss2,
701
  journal = JIT # { (JIT)},
702
  year    = {2020},
703
  month   = {jan},
704
  volume  = {1},
705
  number  = {2},
706
}
707
@article{my-article,
708
  author  = {Doe, John and Smith, Jane}
709
  title   = {Theories & Practices},
710
  xdata   = {jit-1-2},
711
  pages   = {100--200},
712
}
713
\end{raw}
714

715
Some fields have a special syntax.
716
The \code{author}, \code{editor} and \code{translator} fields accept a list of names, separated by the keyword \code{and}.
717
The legacy \code{month} field accepts a three-letter abbreviation for the month in English, or a number from 1 to 12.
718
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).
719
\end{document}
UNCOV
720
]]
×
721

UNCOV
722
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