1- --- A processor for bibliographies.
1+ --- A CSL processor for bibliographies.
22--
33-- @copyright License: MIT (c) 2025 Omikhleia
44--
@@ -23,13 +23,6 @@ local resolveFile = SILE and SILE.resolveFile or function (filename)
2323end
2424
2525-- Loaders for CSL locale and style files.
26- -- They will look for a file in the following order:
27- -- - First in csl/locales/ (resp. csl/styles/) wherever SILE looks at these from the working directory.
28- -- This allows users to put their own CSL files in a simple place
29- -- - Then in `packages/bibtex/csl/locales/` (ibid.)
30- -- This allows users to put their own CSL files e.g. in a local copy of the package, following its structure
31- -- - Then in `packages.bibtex.csl.locales.locales` (ibid.)
32- -- This allows users to use CSL files from the (extended) Lua path, e.g. from a module.
3326local function loadCslLocale (name )
3427 local filename = resolveFile (" csl/locales/" .. name .. " .xml" )
3528 or resolveFile (" packages/bibtex/csl/locales/locales-" .. name .. " .xml" )
@@ -59,6 +52,71 @@ local function loadCslStyle (name)
5952 return style
6053end
6154
55+ local OP = {
56+ EQ = 0 ,
57+ IN = 1 ,
58+ }
59+
60+ local CSLVAR = {
61+ [" type" ] = OP .EQ ,
62+ keyword = OP .IN ,
63+ }
64+ local function isMatching (entry , var , val )
65+ if not entry [var ] then
66+ return false
67+ end
68+ if CSLVAR [var ] == OP .EQ then
69+ return entry [var ] == val
70+ elseif CSLVAR [var ] == OP .IN then
71+ return entry [var ]:contains (val )
72+ end
73+ SU .error (" Unsupported CSL variable " .. var .. " in filter" )
74+ end
75+ local getIssuedYear = function (entry )
76+ local d = entry .issued
77+ if not d then
78+ return nil
79+ end
80+ if type (d ) == " table" then -- range of dates
81+ if d .startdate then
82+ return d .startdate .year
83+ end
84+ if d .enddate then
85+ return d .enddate .year
86+ end
87+ end
88+ return d .year
89+ end
90+ local function builtinFilter (name )
91+ local year = name :match (" ^issued%-(%d+)$" )
92+ if year then
93+ return function (entry )
94+ local issuedYear = getIssuedYear (entry )
95+ return issuedYear and issuedYear == year
96+ end
97+ end
98+ local year1 , year2 = name :match (" ^issued%-(%d+)%-(%d+)$" )
99+ if year1 and year2 then
100+ return function (entry )
101+ local issuedYear = getIssuedYear (entry )
102+ return issuedYear and tonumber (issuedYear ) >= tonumber (year1 ) and tonumber (issuedYear ) <= tonumber (year2 )
103+ end
104+ end
105+ local varkey , val = name :match (" ^not%-([^-]+)%-(.+)$" )
106+ if varkey and val then
107+ return function (entry )
108+ return not isMatching (entry , varkey , val )
109+ end
110+ end
111+ varkey , val = name :match (" ^([^-]+)%-(.+)$" )
112+ if varkey and val then
113+ return function (entry )
114+ return isMatching (entry , varkey , val )
115+ end
116+ end
117+ SU .error (" Unknown filter " .. f )
118+ end
119+
62120-- CSL ENTRY PROXY (PSEUDO-CLASS)
63121
64122local NIL_SENTINEL = {} -- Sentinel value to indicate that a field is overridden to nil
@@ -68,7 +126,7 @@ local NIL_SENTINEL = {} -- Sentinel value to indicate that a field is overridden
68126-- so that we can for instance cache the CSL item and override some fields at some later
69127-- processing time.
70128-- @tparam table entry Orignal table to proxy
71- -- @treturn table Proxy object wrapping the original entry
129+ -- @treturn table Proxy object wrapping the original entry
72130local function CslEntry (entry )
73131 local proxy = {
74132 _entry = entry ,
101159local CslProcessor = pl .class ()
102160
103161--- (Constructor) Create a new CSL Bibliography manager.
162+ -- Usage example:
163+ --
164+ -- local biblio = CslProcessor()
165+ --
104166-- @treturn CslProcessor New CSL Bibliography manager instance
105167function CslProcessor :_init ()
168+ self ._filter = {}
106169 self ._data = {
107170 bib = {},
108171 cited = {
@@ -128,9 +191,26 @@ function CslProcessor:getCslEngine ()
128191end
129192
130193---- Set the bibliography style and locale for the CSL engine.
194+ -- Example usage:
195+ --
196+ -- biblio:setBibliographyStyle('chicago-author-date', 'en-US', {
197+ -- localizedPunctuation = false,
198+ -- italicExtension = true,
199+ -- mathExtension = true,
200+ -- })
201+ --
202+ -- Style and locale files are searched in the following order:
203+ --
204+ -- - First in `csl/locales/` (resp. `csl/styles/`) wherever SILE looks at these from the working directory.
205+ -- This allows users to put their own CSL files in a simple place
206+ -- - Then in `packages/bibtex/csl/locales/` (ibid.)
207+ -- This allows users to put their own CSL files e.g. in a local copy of the package, following its structure
208+ -- - Then in `packages.bibtex.csl.locales.locales` (ibid.)
209+ -- This allows users to use CSL files from the (extended) Lua path, e.g. from a module.
210+ --
131211-- @tparam string stylename Name of the CSL style to use
132- -- @tparam string lang Language code for the locale (e.g., "en-US")
133- -- @tparam [opt] table options Additional options for the CSL engine
212+ -- @tparam string lang Language code for the locale (e.g., "en-US")
213+ -- @tparam [opt] table options Additional options for the CSL engine
134214function CslProcessor :setBibliographyStyle (stylename , lang , options )
135215 options = options or {
136216 localizedPunctuation = false ,
@@ -196,6 +276,8 @@ function CslProcessor:_getEntryForCite (key, warn_uncited)
196276end
197277
198278--- Retrieve a locator from an options table.
279+ -- Keys are expected to be locators like "page", "chapter", etc. as per CSL rules,
280+ -- Some some extra convenience abbreviations are also supported.
199281-- @tparam table options Options (key-value pairs) that may contain a locator
200282-- @treturn table Locator
201283function CslProcessor :_getLocator (options )
219301
220302--- Track the position of a citation acconrding to the CSL rules.
221303-- @tparam string key Citation key
222- -- @tparam {label=string, value=string}|nil locator Locator
304+ -- @tparam {label=string,value=string}|nil locator Locator
223305-- @tparam boolean is_single Single or multiple citation
224306-- @treturn string Position of the citation ("first", "subsequent", "ibid", "ibid-with-locator")
225307function CslProcessor :_getCitePosition (key , locator , is_single )
@@ -276,15 +358,22 @@ function CslProcessor:_adapter (entry, citnum)
276358 return cslentry
277359end
278360
361+ --- Load a bibliography file and parse it.
362+ -- @tparam string bibfile Path to the BibTeX file to load
363+ function CslProcessor :loadBibliography (bibfile )
364+ local bib = self ._data .bib
365+ parseBibtex (bibfile , bib )
366+ end
367+
279368--- Cite a sigle entry with optional locator.
280369-- Usage example:
281- -- ```lua
282- -- local cite = biblio:cite({
283- -- key = 'mykey1',
284- -- page = "191-193",
285- -- })
286- -- ```
287- -- @tparam {key=string, [string]=string} item Citation item with a key and optional locator
370+ --
371+ -- local cite = biblio:cite({
372+ -- key = 'mykey1',
373+ -- page = "191-193",
374+ -- })
375+ --
376+ -- @tparam {key=string,[string]=string} item Citation item with a key and optional locator
288377-- @treturn string|nil Formatted citation string or nil if the entry is not found
289378function CslProcessor :cite (item )
290379 local key = item .key
@@ -311,13 +400,14 @@ end
311400
312401--- Cite multiple entries with optional locators.
313402-- Usage example:
314- -- ```lua
315- -- local cites = biblio:cites({
316- -- { key = 'mykey1', page = "191" },
317- -- { key = 'mykey2', chapter = "2" },
318- -- { key = 'mykey3' },
319- -- })
320- -- @tparam table items List of citation items, each with a key and optional locator
403+ --
404+ -- local cites = biblio:cites({
405+ -- { key = 'mykey1', page = "191" },
406+ -- { key = 'mykey2', chapter = "2" },
407+ -- { key = 'mykey3' },
408+ -- })
409+ --
410+ -- @tparam {key:string,[string]:string}[] items List of citation items, each with a key and optional locator
321411-- @treturn string|nil Formatted citation string or nil if no entries are found
322412function CslProcessor :cites (items )
323413 local is_single = # items == 1
@@ -343,10 +433,10 @@ function CslProcessor:cites (items)
343433end
344434
345435--- Retrieve a reference for a given key.
346- -- This is used to get the full reference for an entry, e.g. for a bibliography.
347- -- It will return the entry as a CSL item, with the citation number set .
436+ -- This is used to get the full reference for an entry, though this is more for debugging
437+ -- purposes than for actual bibliography processing .
348438-- @tparam string key Citation key
349- -- @treturn string[nul Formatted reference string or nil if the entry is not found
439+ -- @treturn string|nil Formatted reference string or nil if the entry is not found
350440function CslProcessor :reference (key )
351441 local entry , citnum = self :_getEntryForCite (key , true ) -- warn if not yet cited
352442 if entry then
@@ -357,11 +447,30 @@ function CslProcessor:reference (key)
357447 end
358448end
359449
360- --- Retrieve a bibliography of entries
361- -- @tparam {cited=boolean} options Options for the bibliography
450+ --- Retrieve a bibliography of entries.
451+ -- Supported options are:
452+ --
453+ -- - `cited`: boolean, whether to include only cited entries (default: true)
454+ -- - `filter`: string, filters to apply to the entries, if cited is false.
455+ --
456+ -- The filter is a space-separated list of filter names.
457+ -- These can consist of named filters, or built-in filters.
458+ -- The list is understood as a logical AND, i.e. all filters must match.
459+ --
460+ -- Built-in filters are:
461+ --
462+ -- - `issued-Y`: entries issued in year Y
463+ -- - `issued-Y1-Y2`: entries issued between two years Y1 and Y2
464+ -- - `type-x`: entries of the given type (e.g. book, article-journal, etc.)
465+ -- - `not-type-x`: entries that are not of the given type
466+ -- - `keyword-x`: entries that have the given keyword
467+ -- - `not-keyword-x`: entries that do not have the given keyword
468+ --
469+ -- @tparam {cited=boolean,filter=string} options Options for the bibliography
362470-- @treturn string Formatted bibliography string
363471function CslProcessor :bibliography (options )
364472 local bib
473+ local filter = options .filter
365474 if SU .boolean (options .cited , true ) then
366475 bib = {}
367476 for _ , key in ipairs (self ._data .cited .keys ) do
@@ -397,7 +506,10 @@ function CslProcessor:bibliography (options)
397506 citnum = prevcite .citnum
398507 end
399508 local cslentry = self :_adapter (entry , citnum )
400- table.insert (entries , cslentry )
509+ local isFiltered = not filter or self :applyFilter (cslentry , filter )
510+ if isFiltered then
511+ table.insert (entries , cslentry )
512+ end
401513 end
402514 end
403515 end
@@ -410,12 +522,26 @@ function CslProcessor:bibliography (options)
410522 return cite
411523end
412524
413- --- Load a bibliography file and parse it.
414- -- @tparam string bibfile Path to the BibTeX file to load
415- -- @treturn nil
416- function CslProcessor :loadBibliography (bibfile )
417- local bib = self ._data .bib
418- parseBibtex (bibfile , bib )
525+ --- Define a named filter for the CSL processor.
526+ -- @tparam string name Name of the filter
527+ -- @tparam function filterFn Function taking a CSL entry and returning true if the filter matches
528+ function CslProcessor :defineFilter (name , filterFn )
529+ self ._filter [name ] = filterFn
530+ end
531+
532+ --- Apply a filter to a CSL entry.
533+ -- It will check if the entry matches the filter by calling the filter function.
534+ -- @tparam CslEntry entry CSL entry to filter
535+ -- @tparam string names Names of the filters to apply (separated by spaces)
536+ function CslProcessor :applyFilter (entry , names )
537+ local filters = pl .stringx .split (names , " " ):filter (function (s ) return s ~= " " end )
538+ for _ , f in ipairs (filters ) do
539+ local filterFn = self ._filter [f ] or builtinFilter (f )
540+ if not filterFn (entry ) then
541+ return false
542+ end
543+ end
544+ return true
419545end
420546
421547local bibTagsToHtml = {
@@ -431,10 +557,12 @@ local function biblink (url, _, class)
431557end
432558
433559--- Convert a formatted bibliography to HTML format.
434- -- NOTE: Very naive implementation for now :)
560+ --
561+ -- **NOTE**: Very naive implementation for now. Private, may change in the future.
562+ --
435563-- @tparam string out The bibliography output as a string
436564-- @treturn string HTML formatted bibliography
437- function CslProcessor :toHtml (out )
565+ function CslProcessor :_toHtml (out )
438566 -- Replace custom tags with HTML equivalents
439567 for tag , html in pairs (bibTagsToHtml ) do
440568 local openTag = " <" .. tag .. " >"
0 commit comments