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

sile-typesetter / sile / 10866173456

14 Sep 2024 11:07PM UTC coverage: 65.603% (-3.3%) from 68.912%
10866173456

push

github

web-flow
Merge c40733634 into 8bed2e6bb

101 of 1236 new or added lines in 10 files covered. (8.17%)

72 existing lines in 7 files now uncovered.

12300 of 18749 relevant lines covered (65.6%)

5164.31 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)
×
348
      if not options.key then
×
349
         options.key = SU.ast.contentToString(content)
×
350
      end
NEW
351
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
352
      if not entry then
×
353
         return
×
354
      end
355
      local style = SILE.settings:get("bibtex.style")
×
356
      local bibstyle = require("packages.bibtex.styles." .. style)
×
357
      local cite = Bibliography.produceCitation(options, SILE.scratch.bibtex.bib, bibstyle)
×
358
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
359
   end)
360

361
   self:registerCommand("reference", function (options, content)
×
362
      if not options.key then
×
363
         options.key = SU.ast.contentToString(content)
×
364
      end
NEW
365
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
366
      if not entry then
×
367
         return
×
368
      end
369
      local style = SILE.settings:get("bibtex.style")
×
370
      local bibstyle = require("packages.bibtex.styles." .. style)
×
371
      local cite, err = Bibliography.produceReference(options, SILE.scratch.bibtex.bib, bibstyle)
×
372
      if cite == Bibliography.Errors.UNKNOWN_TYPE then
×
373
         SU.warn("Unknown type @" .. err .. " in citation for reference " .. options.key)
×
374
         return
×
375
      end
376
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
377
   end)
378

379
   -- NEW CSL IMPLEMENTATION
380

381
   -- Internal commands for CSL processing
382

NEW
383
   self:registerCommand("bibSmallCaps", function (_, content)
×
384
      -- To avoid attributes in the CSL-processed content
NEW
385
      SILE.call("font", { features = "+smcp" }, content)
×
386
   end)
387

388
   -- CSL 1.0.2 appendix VI
389
   -- "If the bibliography entry for an item renders any of the following
390
   -- identifiers, the identifier should be anchored as a link, with the
391
   -- target of the link as follows:
392
   --   url: output as is
393
   --   doi: prepend with “https://doi.org/”
394
   --   pmid: prepend with “https://www.ncbi.nlm.nih.gov/pubmed/”
395
   --   pmcid: prepend with “https://www.ncbi.nlm.nih.gov/pmc/articles/”
396
   -- NOT IMPLEMENTED:
397
   --   "Citation processors should include an option flag for calling
398
   --   applications to disable bibliography linking behavior."
399
   -- (But users can redefine these commands to their liking...)
NEW
400
   self:registerCommand("bibLink", function (options, content)
×
NEW
401
      SILE.call("href", { src = options.src }, {
×
NEW
402
         SU.ast.createCommand("url", {}, { content[1] }),
×
403
      })
404
   end)
NEW
405
   self:registerCommand("bibURL", function (_, content)
×
NEW
406
      local link = content[1]
×
NEW
407
      if not link:match("^https?://") then
×
408
         -- Play safe
NEW
409
         link = "https://" .. link
×
410
      end
NEW
411
      SILE.call("bibLink", { src = link }, content)
×
412
   end)
NEW
413
   self:registerCommand("bibDOI", function (_, content)
×
NEW
414
      local link = content[1]
×
NEW
415
      if not link:match("^https?://") then
×
NEW
416
         link = "https://doi.org/" .. link
×
417
      end
NEW
418
      SILE.call("bibLink", { src = link }, content)
×
419
   end)
NEW
420
   self:registerCommand("bibPMID", function (_, content)
×
NEW
421
      local link = content[1]
×
NEW
422
      if not link:match("^https?://") then
×
NEW
423
         link = "https://www.ncbi.nlm.nih.gov/pubmed/" .. link
×
424
      end
NEW
425
      SILE.call("bibLink", { src = link }, content)
×
426
   end)
NEW
427
   self:registerCommand("bibPMCID", function (_, content)
×
NEW
428
      local link = content[1]
×
NEW
429
      if not link:match("^https?://") then
×
NEW
430
         link = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. link
×
431
      end
NEW
432
      SILE.call("bibLink", { src = link }, content)
×
433
   end)
434

435
   -- Style and locale loading
436

NEW
437
   self:registerCommand("bibliographystyle", function (options, _)
×
NEW
438
      local sty = SU.required(options, "style", "bibliographystyle")
×
NEW
439
      local style = loadCslStyle(sty)
×
440
      -- FIXME: lang is mandatory until we can map document.lang to a resolved
441
      -- BCP47 with region always present, as this is what CSL locales require.
NEW
442
      if not options.lang then
×
443
         -- Pick the default locale from the style, if any
NEW
444
         options.lang = style.globalOptions["default-locale"]
×
445
      end
NEW
446
      local lang = SU.required(options, "lang", "bibliographystyle")
×
NEW
447
      local locale = loadCslLocale(lang)
×
NEW
448
      SILE.scratch.bibtex.engine = CslEngine(style, locale, {
×
449
         localizedPunctuation = SU.boolean(options.localizedPunctuation, false),
450
         italicExtension = SU.boolean(options.italicExtension, true),
451
         mathExtension = SU.boolean(options.mathExtension, true),
452
      })
453
   end)
454

NEW
455
   self:registerCommand("csl:cite", function (options, content)
×
456
      -- TODO:
457
      -- - multiple citation keys (but how to handle locators then?)
458
      local locator
NEW
459
      for k, v in pairs(options) do
×
NEW
460
         if k ~= "key" then
×
NEW
461
            if not locators[k] then
×
NEW
462
               SU.warn("Unknown option '" .. k .. "' in \\csl:cite")
×
463
            else
NEW
464
               if not locator then
×
NEW
465
                  local label = locators[k]
×
NEW
466
                  locator = { label = label, value = v }
×
467
               else
NEW
468
                  SU.warn("Multiple locators in \\csl:cite, using the first one")
×
469
               end
470
            end
471
         end
472
      end
NEW
473
      if not SILE.scratch.bibtex.engine then
×
NEW
474
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
475
      end
NEW
476
      local engine = SILE.scratch.bibtex.engine
×
NEW
477
      if not options.key then
×
NEW
478
         options.key = SU.ast.contentToString(content)
×
479
      end
NEW
480
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
NEW
481
      if not entry then
×
NEW
482
         return
×
483
      end
484

485
      -- Keep track of cited entries
NEW
486
      table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
NEW
487
      local citnum = #SILE.scratch.bibtex.cited.keys
×
NEW
488
      SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
489

NEW
490
      local csljson = bib2csl(entry, citnum)
×
NEW
491
      csljson.locator = locator
×
NEW
492
      local cite = engine:cite(csljson)
×
493

NEW
494
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
495
   end)
496

NEW
497
   self:registerCommand("csl:reference", function (options, content)
×
NEW
498
      if not SILE.scratch.bibtex.engine then
×
NEW
499
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
500
      end
NEW
501
      local engine = SILE.scratch.bibtex.engine
×
NEW
502
      if not options.key then
×
NEW
503
         options.key = SU.ast.contentToString(content)
×
504
      end
NEW
505
      local entry = resolveEntry(SILE.scratch.bibtex.bib, options.key)
×
NEW
506
      if not entry then
×
NEW
507
         return
×
508
      end
509

NEW
510
      local citnum = SILE.scratch.bibtex.cited.citnums[options.key]
×
NEW
511
      if not citnum then
×
NEW
512
         SU.warn("Reference to a non-cited entry " .. options.key)
×
513
         -- Make it cited
NEW
514
         table.insert(SILE.scratch.bibtex.cited.keys, options.key)
×
NEW
515
         citnum = #SILE.scratch.bibtex.cited.keys
×
NEW
516
         SILE.scratch.bibtex.cited.citnums[options.key] = citnum
×
517
      end
NEW
518
      local cslentry = bib2csl(entry, citnum)
×
NEW
519
      local cite = engine:reference(cslentry)
×
520

NEW
521
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
522
   end)
523

NEW
524
   self:registerCommand("printbibliography", function (options, _)
×
NEW
525
      if not SILE.scratch.bibtex.engine then
×
NEW
526
         SILE.call("bibliographystyle", { lang = "en-US", style = "chicago-author-date" })
×
527
      end
NEW
528
      local engine = SILE.scratch.bibtex.engine
×
529

530
      local bib
NEW
531
      if SU.boolean(options.cited, true) then
×
NEW
532
         bib = {}
×
NEW
533
         for _, key in ipairs(SILE.scratch.bibtex.cited.keys) do
×
NEW
534
            bib[key] = SILE.scratch.bibtex.bib[key]
×
535
         end
536
      else
NEW
537
         bib = SILE.scratch.bibtex.bib
×
538
      end
539

NEW
540
      local entries = {}
×
NEW
541
      local ncites = #SILE.scratch.bibtex.cited.keys
×
NEW
542
      for key, entry in pairs(bib) do
×
NEW
543
         if entry.type ~= "xdata" then
×
NEW
544
            crossrefAndXDataResolve(bib, entry)
×
NEW
545
            if entry then
×
NEW
546
               local citnum = SILE.scratch.bibtex.cited.citnums[key]
×
NEW
547
               if not citnum then
×
548
                  -- This is just to make happy CSL styles that require a citation number
549
                  -- However, table order is not guaranteed in Lua so the output may be
550
                  -- inconsistent across runs with styles that use this number for sorting.
551
                  -- This may only happen for non-cited entries in the bibliography, and it
552
                  -- would be a bad practive to use such a style to print the full bibliography,
553
                  -- so I don't see a strong need to fix this at the expense of performance.
554
                  -- (and we can't really, some styles might have several sorting criteria
555
                  -- leading to impredictable order anyway).
NEW
556
                  ncites = ncites + 1
×
NEW
557
                  citnum = ncites
×
558
               end
NEW
559
               local cslentry = bib2csl(entry, citnum)
×
NEW
560
               table.insert(entries, cslentry)
×
561
            end
562
         end
563
      end
NEW
564
      print("<bibliography: " .. #entries .. " entries>")
×
NEW
565
      local cite = engine:reference(entries)
×
NEW
566
      SILE.processString(("<sile>%s</sile>"):format(cite), "xml")
×
567

NEW
568
      SILE.scratch.bibtex.cited = { keys = {}, citnums = {} }
×
569
   end)
570
end
571

572
package.documentation = [[
573
\begin{document}
574
BibTeX is a citation management system.
575
It was originally designed for TeX but has since been integrated into a variety of situations.
576

577
This experimental package allows SILE to read and process BibTeX \code{.bib} files and output citations and full text references.
578

579
To load a BibTeX file, issue the command \autodoc:command{\loadbibliography[file=<whatever.bib>]}
580

581
\smallskip
582
\noindent
583
\em{Producing citations and references (legacy commands)}
584
\novbreak
585

586
\indent
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>}}.
589

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

592
This implementation doesn’t currently produce full bibliography listings, and the only supported bibliography style is Chicago referencing.
593

594
\smallskip
595
\noindent
596
\em{Producing citations and references (CSL implementation)}
597
\novbreak
598

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

602
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}).
603

604
The command accepts a few additional options:
605

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

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

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

620
To produce a bibliography of cited references, use \autodoc:command{\printbibliography}.
621
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).
622
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.
623

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

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

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

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

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

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

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

649
Regular bibliography entries have the following syntax:
650

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

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

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

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

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

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

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

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

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

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

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

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

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

723
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