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

sile-typesetter / sile / 13971572131

20 Mar 2025 02:18PM UTC coverage: 58.91% (-5.4%) from 64.296%
13971572131

push

github

web-flow
Merge pull request #2087 from jodros/twoside-headers

Declare SILE.scratch.headers in twoside only

2 of 2 new or added lines in 1 file covered. (100.0%)

1120 existing lines in 54 files now uncovered.

12668 of 21504 relevant lines covered (58.91%)

1910.23 hits per line

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

0.0
/packages/bibtex/support/bibparser.lua
UNCOV
1
local epnf = require("epnf")
×
UNCOV
2
local nbibtex = require("packages.bibtex.support.nbibtex")
×
UNCOV
3
local namesplit, parse_name = nbibtex.namesplit, nbibtex.parse_name
×
UNCOV
4
local isodatetime = require("packages.bibtex.support.isodatetime")
×
5

UNCOV
6
local nbsp = luautf8.char(0x00A0)
×
7
local function sanitize (str)
8
   local s = str
×
9
      -- TeX special characters:
10
      -- Backslash-escaped tilde is a tilde,
11
      -- but standalone tilde is a non-breaking space
UNCOV
12
      :gsub(
×
13
         "(.?)~",
14
         function (prev)
15
            if prev == "\\" then
×
16
               return "~"
×
17
            end
18
            return prev .. nbsp
×
19
         end
20
      )
21
      -- Other backslash-escaped characters are skipped
22
      -- TODO FIXME:
23
      -- This ok for \", \& etc. which we want to unescape,
24
      -- BUT what should we do with other TeX-like commands?
UNCOV
25
      :gsub(
×
26
         "\\",
27
         ""
28
      )
29
      -- We will wrap the content in <sile> tags so we need to XML-escape
30
      -- the input.
UNCOV
31
      :gsub("&", "&amp;")
×
UNCOV
32
      :gsub("<", "&lt;")
×
UNCOV
33
      :gsub(">", "&gt;")
×
UNCOV
34
   return s
×
35
end
36

37
-- luacheck: push ignore
38
-- stylua: ignore start
39
---@diagnostic disable: undefined-global, unused-local, lowercase-global
UNCOV
40
local bibtexparser = epnf.define(function (_ENV)
×
UNCOV
41
   local strings = {} -- Local store for @string entries
×
42

UNCOV
43
   local identifier = (SILE.parserBits.identifier + S":-")^1
×
UNCOV
44
   local balanced = C{ "{" * P" "^0 * C(((1 - S"{}") + V(1))^0) * "}" } / function (...) local t={...}; return t[2] end
×
UNCOV
45
   local quoted = C( P'"' * C(((1 - S'"\r\n\f\\') + (P'\\' * 1)) ^ 0) * '"' ) / function (...) local t={...}; return t[2] end
×
UNCOV
46
   local _ = WS^0
×
UNCOV
47
   local sep = S",;" * _
×
UNCOV
48
   local myID = C(identifier)
×
UNCOV
49
   local myStrID = myID / function (t) return strings[t] or t end
×
UNCOV
50
   local myTag = C(identifier) / function (t) return t:lower() end
×
UNCOV
51
   local pieces = balanced + quoted + myStrID
×
UNCOV
52
   local value = Ct(pieces * (WS * P("#") * WS * pieces)^0)
×
UNCOV
53
      / function (t) return table.concat(t) end / sanitize
×
UNCOV
54
   local pair = myTag * _ * "=" * _ * value * _ * sep^-1
×
UNCOV
55
      / function (...) local t= {...}; return t[1], t[#t] end
×
UNCOV
56
   local list = Cf(Ct("") * pair^0, rawset)
×
UNCOV
57
   local skippedType = Cmt(R("az", "AZ")^1, function(_, _, tag)
×
58
      -- ignore both @comment and @preamble
UNCOV
59
      local t = tag:lower()
×
UNCOV
60
      return t == "comment" or t == "preamble"
×
61
   end)
62

63
   START "document"
UNCOV
64
   document = (V"skipped" -- order important: skipped (@comment, @preamble) must be first
×
UNCOV
65
      + V"stringblock" -- order important: @string must be before @entry
×
UNCOV
66
      + V"entry")^1
×
UNCOV
67
      * (-1 + E("Unexpected character at end of input"))
×
UNCOV
68
   skipped  = WS + (V"blockskipped" + (1 - P"@")^1 ) / ""
×
UNCOV
69
   blockskipped = (P("@") * skippedType) + balanced / ""
×
UNCOV
70
   stringblock = Ct( P("@string") * _ * P("{") * pair * _ * P("}") * _ )
×
71
       / function (t)
×
UNCOV
72
          strings[t[1]] = t[2]
×
UNCOV
73
          return t end
×
UNCOV
74
   entry = Ct( P("@") * Cg(myTag, "type") * _ * P("{") * _ * Cg(myID, "label") * _ * sep * list * P("}") * _ )
×
75
end)
76
-- luacheck: pop
77
-- stylua: ignore end
78
---@diagnostic enable: undefined-global, unused-local, lowercase-global
79

80

UNCOV
81
local bibcompat = require("packages.bibtex.support.bibmaps")
×
UNCOV
82
local crossrefmap, fieldmap = bibcompat.crossrefmap, bibcompat.fieldmap
×
83
local months =
84
   { jan = 1, feb = 2, mar = 3, apr = 4, may = 5, jun = 6, jul = 7, aug = 8, sep = 9, oct = 10, nov = 11, dec = 12 }
×
85

86
local function consolidateEntry (entry, label)
UNCOV
87
   local consolidated = {}
×
88
   -- BibLaTeX aliases for legacy BibTeX fields
UNCOV
89
   for field, value in pairs(entry.attributes) do
×
UNCOV
90
      consolidated[field] = value
×
UNCOV
91
      local alias = fieldmap[field]
×
UNCOV
92
      if alias then
×
UNCOV
93
         if entry.attributes[alias] then
×
94
            SU.warn("Duplicate field '" .. field .. "' and alias '" .. alias .. "' in entry '" .. label .. "'")
×
95
         else
UNCOV
96
            consolidated[alias] = value
×
97
         end
98
      end
99
   end
100
   -- Names field split and parsed
UNCOV
101
   for _, field in ipairs({ "author", "editor", "translator", "shortauthor", "shorteditor", "holder" }) do
×
UNCOV
102
      if consolidated[field] then
×
103
         -- FIXME Check our corporate names behave, we are probably bad currently
104
         -- with nested braces !!!
105
         -- See biblatex manual v3.20 ยง2.3.3 Name Lists
106
         -- e.g. editor = {{National Aeronautics and Space Administration} and Doe, John}
UNCOV
107
         local names = namesplit(consolidated[field])
×
UNCOV
108
         for i = 1, #names do
×
UNCOV
109
            names[i] = parse_name(names[i])
×
110
         end
UNCOV
111
         consolidated[field] = names
×
112
      end
113
   end
114
   -- Month field in either number or string (3-letter code)
UNCOV
115
   if consolidated.month then
×
UNCOV
116
      local month = tonumber(consolidated.month) or months[consolidated.month:lower()]
×
UNCOV
117
      if month and (month >= 1 and month <= 12) then
×
UNCOV
118
         consolidated.month = month
×
119
      else
120
         SU.warn("Unrecognized month skipped in entry '" .. label .. "'")
×
121
         consolidated.month = nil
×
122
      end
123
   end
124
   -- Extended date fields
UNCOV
125
   for _, field in ipairs({ "date", "origdate", "eventdate", "urldate" }) do
×
UNCOV
126
      if consolidated[field] then
×
127
         local dt = isodatetime(consolidated[field])
×
128
         if dt then
×
129
            consolidated[field] = dt
×
130
         else
131
            SU.warn("Invalid '" .. field .. "' skipped in entry '" .. label .. "'")
×
132
            consolidated[field] = nil
×
133
         end
134
      end
135
   end
UNCOV
136
   entry.attributes = consolidated
×
UNCOV
137
   return entry
×
138
end
139

140
--- Parse a BibTeX file and populate a bibliography table.
141
-- @tparam string fn Filename
142
-- @tparam table biblio Table of entries
143
local function parseBibtex (fn, biblio)
UNCOV
144
   fn = SILE.resolveFile(fn) or SU.error("Unable to resolve Bibtex file " .. fn)
×
UNCOV
145
   local fh, e = io.open(fn)
×
UNCOV
146
   if e then
×
147
      SU.error("Error reading bibliography file: " .. e)
×
148
   end
UNCOV
149
   local doc = fh:read("*all")
×
UNCOV
150
   local t = epnf.parsestring(bibtexparser, doc)
×
UNCOV
151
   if not t or not t[1] or t.id ~= "document" then
×
152
      SU.error("Error parsing bibtex")
×
153
   end
UNCOV
154
   for i = 1, #t do
×
UNCOV
155
      if t[i].id == "entry" then
×
UNCOV
156
         local ent = t[i][1]
×
UNCOV
157
         local entry = { type = ent.type, label = ent.label, attributes = ent[1] }
×
UNCOV
158
         if biblio[ent.label] then
×
159
            SU.warn("Duplicate entry key '" .. ent.label .. "', picking the last one")
×
160
         end
UNCOV
161
         biblio[ent.label] = consolidateEntry(entry, ent.label)
×
162
      end
163
   end
164
end
165

166
--- Copy fields from the parent entry to the child entry.
167
-- BibLaTeX/Biber have a complex inheritance system for fields.
168
-- This implementation is more naive, but should be sufficient for reasonable
169
-- use cases.
170
-- @tparam table parent Parent entry
171
-- @tparam table entry Child entry
172
local function fieldsInherit (parent, entry)
UNCOV
173
   local map = crossrefmap[parent.type] and crossrefmap[parent.type][entry.type]
×
UNCOV
174
   if not map then
×
175
      -- @xdata and any other unknown types: inherit all missing fields
UNCOV
176
      for field, value in pairs(parent.attributes) do
×
UNCOV
177
         if not entry.attributes[field] then
×
UNCOV
178
            entry.attributes[field] = value
×
179
         end
180
      end
UNCOV
181
      return -- done
×
182
   end
183
   for field, value in pairs(parent.attributes) do
×
184
      if map[field] == nil and not entry.attributes[field] then
×
185
         entry.attributes[field] = value
×
186
      end
187
      for childfield, parentfield in pairs(map) do
×
188
         if parentfield and not entry.attributes[parentfield] then
×
189
            entry.attributes[parentfield] = parent.attributes[childfield]
×
190
         end
191
      end
192
   end
193
end
194

195
--- Resolve the 'crossref' and 'xdata' fields on a bibliography entry.
196
-- (Supplementing the entry with the attributes of the parent entry.)
197
-- Once resolved recursively, the crossref and xdata fields are removed
198
-- from the entry.
199
-- So this is intended to be called at first use of the entry, and have no
200
-- effect on subsequent uses: BibTeX does seem to mandate cross references
201
-- to be defined before the entry that uses it, or even in the same bibliography
202
-- file.
203
-- Implementation note:
204
-- We are not here to check the consistency of the BibTeX file, so there is
205
-- no check that xdata refers only to @xdata entries
206
-- Removing the crossref field implies we won't track its use and implicitly
207
-- cite referenced entries in the bibliography over a certain threshold.
208
-- @tparam table bib Bibliography
209
-- @tparam table entry Bibliography entry
210
local function crossrefAndXDataResolve (bib, entry)
211
   local refs
UNCOV
212
   local xdata = entry.attributes.xdata
×
UNCOV
213
   if xdata then
×
UNCOV
214
      refs = xdata and pl.stringx.split(xdata, ",")
×
UNCOV
215
      entry.attributes.xdata = nil
×
216
   end
UNCOV
217
   local crossref = entry.attributes.crossref
×
UNCOV
218
   if crossref then
×
UNCOV
219
      refs = refs or {}
×
UNCOV
220
      table.insert(refs, crossref)
×
UNCOV
221
      entry.attributes.crossref = nil
×
222
   end
223

UNCOV
224
   if not refs then
×
UNCOV
225
      return
×
226
   end
UNCOV
227
   for _, ref in ipairs(refs) do
×
UNCOV
228
      local parent = bib[ref]
×
UNCOV
229
      if parent then
×
UNCOV
230
         crossrefAndXDataResolve(bib, parent)
×
UNCOV
231
         fieldsInherit(parent, entry)
×
232
      else
233
         SU.warn("Unknown crossref " .. ref .. " in bibliography entry " .. entry.label)
×
234
      end
235
   end
236
end
237

UNCOV
238
return {
×
239
   parseBibtex = parseBibtex,
UNCOV
240
   crossrefAndXDataResolve = crossrefAndXDataResolve
×
241
}
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