Skip to content

Commit 3195dc4

Browse files
committed
Support multiple bibliographies per topic (etc.)
The full list of changes is: - Support multiple bibliographies per topic, i.e., in the 'bibliography' metadata, allow topic entries to be list-valued - Support the same topic structure for the 'references' metadata item, so can override references per topic - Ignore duplicate references, i.e. if the same reference exists in multiple topics (the first one encountered is used, but this is not satisfactory because the topic processing order is indeterminate). Note that something had to be done here, because these duplicates would have the same ids and therefore would be ambiguous - Take some changes from the diverged multiple-bibliographies repo, notably don't use utils.citeproc() because it doesn't have a 'quiet' option (which is needed for the second pass) [this is no longer fully the case; it's affected by some subsequent upstream changes] - Fix an undefined 'orig_bib' bug (that probably caused no problems) - Add filters to renumber citations in the case where they're referenced by citation number. These filters won't do anything in other cases, but nevertheless probably shouldn't be here
1 parent 08af1e3 commit 3195dc4

File tree

1 file changed

+114
-12
lines changed

1 file changed

+114
-12
lines changed

_extensions/multibib/multibib.lua

+114-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1515
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1616
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
]]
18+
19+
-- --citeproc was added in 2.11, so we never use the old pandoc-citeproc
1820
PANDOC_VERSION:must_be_at_least '2.11'
1921

2022
local pandoc = require 'pandoc'
@@ -32,16 +34,25 @@ local metatype = pandoc.utils.type or
3234

3335
--- Collection of all cites in the document
3436
local all_cites = {}
37+
3538
--- Document meta value
3639
local doc_meta = pandoc.Meta{}
3740

3841
--- Div used by citeproc to insert the bibliography.
3942
local refs_div = pandoc.Div({}, pandoc.Attr('refs'))
4043

44+
--- 'references' metadata for each topic
45+
local topic_refs = {}
46+
4147
-- Div filled by citeproc with properties set according to
4248
-- the output format and the attributes of cs:bibliography
4349
local refs_div_with_properties
4450

51+
-- Whether utils.citeproc() supports a 'quiet' argument
52+
-- (it doesn't yet, but perhaps it will, in which case this
53+
-- will use the appropriate pandoc version check)
54+
local supports_quiet_arg = false
55+
4556
--- Run citeproc on a pandoc document
4657
--
4758
-- Falls back to the external `pandoc-citeproc` filter if the built-in
@@ -65,13 +76,28 @@ end
6576
--- Resolve citations in the document by combining all bibliographies
6677
-- before running citeproc on the full document.
6778
local function resolve_doc_citations (doc)
68-
-- combine all bibliographies
79+
-- combine all bibliographies and references
6980
local meta = doc.meta
7081
local bibconf = meta.bibliography
7182
meta.bibliography = pandoc.MetaList{}
7283
if metatype(bibconf) == 'table' then
7384
for _, value in pairs(bibconf) do
74-
table.insert(meta.bibliography, stringify(value))
85+
-- support list-valued items
86+
if metatype(value) ~= 'List' then value = List{value} end
87+
for _, val in ipairs(value) do
88+
table.insert(meta.bibliography, stringify(val))
89+
end
90+
end
91+
end
92+
local refconf = meta.references
93+
meta.references = pandoc.MetaList{}
94+
if metatype(refconf) == 'table' then
95+
for topic, refs in pairs(refconf) do
96+
-- save topic references for meta_for_citeproc()
97+
topic_refs[topic] = refs
98+
for _, ref in ipairs(refs) do
99+
table.insert(meta.references, ref)
100+
end
75101
end
76102
end
77103
-- add refs div to catch the created bibliography
@@ -80,30 +106,53 @@ local function resolve_doc_citations (doc)
80106
doc = citeproc(doc)
81107
-- remove catch-all bibliography and keep it for future use
82108
refs_div_with_properties = table.remove(doc.blocks)
83-
-- restore bibliography to original value
84-
doc.meta.bibliography = orig_bib
109+
-- restore bibliography and references to original values
110+
doc.meta.bibliography = bibconf
111+
doc.meta.references = refconf
85112
return doc
86113
end
87114

88-
--- Explicitly create a new meta object with all fields relevant for
89-
--- pandoc-citeproc.
90-
local function meta_for_pandoc_citeproc (bibliography)
115+
--- Explicitly create a new meta object with all fields relevant for citeproc.
116+
local function meta_for_citeproc (bibliography, topic)
91117
-- We could just indiscriminately copy all meta fields, but let's be
92118
-- explicit about what's important.
93119
local fields = {
94120
'bibliography', 'references', 'csl', 'citation-style',
95121
'link-citations', 'citation-abbreviations', 'lang',
96122
'suppress-bibliography', 'reference-section-title',
97-
'notes-after-punctuation', 'nocite'
123+
'notes-after-punctuation', 'nocite', 'link-bibliography'
98124
}
99125
local new_meta = pandoc.Meta{}
100126
for _, field in ipairs(fields) do
101-
new_meta[field] = doc_meta[field]
127+
local value = doc_meta[field]
128+
-- replace 'references' with the topic references
129+
if field == 'references' and metatype(value) == 'table' and topic then
130+
value = topic_refs[topic]
131+
end
132+
new_meta[field] = value
102133
end
103134
new_meta.bibliography = bibliography
104135
return new_meta
105136
end
106137

138+
-- list of ref-xxx identifiers that have already been output
139+
local identifiers = List()
140+
141+
-- ignore duplicate references (the first definition will win)
142+
local function ignore_duplicates(blocks)
143+
local new_blocks = pandoc.Blocks{}
144+
for _, block in ipairs(blocks) do
145+
local identifier = block.attr.identifier
146+
if not identifiers:includes(identifier) then
147+
local new_block = pandoc.walk_block(block, {Span=_span})
148+
new_blocks:insert(new_block)
149+
identifiers:insert(identifier)
150+
end
151+
end
152+
153+
return new_blocks
154+
end
155+
107156
local function remove_duplicates(classes)
108157
local seen = {}
109158
return classes:filter(function(x)
@@ -126,12 +175,12 @@ local function create_topic_bibliography (div)
126175
return nil
127176
end
128177
local tmp_blocks = {pandoc.Para(all_cites), refs_div}
129-
local tmp_meta = meta_for_pandoc_citeproc(bibfile)
178+
local tmp_meta = meta_for_citeproc(bibfile, name)
130179
local tmp_doc = pandoc.Pandoc(tmp_blocks, tmp_meta)
131-
local res = citeproc(tmp_doc)
180+
local res = citeproc(tmp_doc, true)
132181
-- First block of the result contains the dummy paragraph, second is
133182
-- the refs Div filled by citeproc.
134-
div.content = res.blocks[2].content
183+
div.content = ignore_duplicates(res.blocks[2].content)
135184
-- Set the classes and attributes as citeproc did it on refs_div
136185
div.classes = remove_duplicates(refs_div_with_properties.classes)
137186
div.attributes = refs_div_with_properties.attributes
@@ -145,6 +194,55 @@ local function create_topic_bibliography (div)
145194
return div
146195
end
147196

197+
-- renumber numbered references and their citations
198+
-- (this logic should probably be in a separate filter; it's too
199+
-- dependent on the CSL, although it should do no harm)
200+
201+
-- map from reference id to its new label
202+
local ref_map = List()
203+
204+
-- ref counter
205+
local ref_counter = 1
206+
207+
local function collect_numbered_refs(div)
208+
if div.attr.classes:includes('csl-entry') then
209+
local identifier = div.attr.identifier
210+
local content = div.content
211+
-- expect single Para with a Span (depending on style) possibly containing
212+
-- the citation number (only do anything if it does)
213+
if (#div.content > 0 and #div.content[1].content > 0 and
214+
div.content[1].content[1].tag == 'Span') then
215+
local span = div.content[1].content[1]
216+
local content = span.content
217+
if #content > 0 then
218+
local text = content[1].text
219+
local pre, num, post = content[1].text:match("^(%p*)(%d+)(%p*)$")
220+
if pre and num and post then
221+
local ident = identifier:gsub('^ref%-', '')
222+
local label = string.format('%s%d%s', pre, ref_counter, post)
223+
content[1] = pandoc.Str(label)
224+
ref_map[ident] = label
225+
ref_counter = ref_counter + 1
226+
return div
227+
end
228+
end
229+
end
230+
end
231+
end
232+
233+
local function renumber_cites(cite)
234+
-- only consider cites with single citations
235+
if #cite.citations == 1 then
236+
local id = cite.citations[1].id
237+
local label = ref_map[id]
238+
-- only change the content if the label is defined
239+
if label then
240+
cite.content = label
241+
return cite
242+
end
243+
end
244+
end
245+
148246
return {
149247
{
150248
-- Collect all citations and the doc's Meta value for other filters.
@@ -153,4 +251,8 @@ return {
153251
},
154252
{ Pandoc = resolve_doc_citations },
155253
{ Div = create_topic_bibliography },
254+
255+
-- These should probably be handled via a separate filter.
256+
{ Div = collect_numbered_refs },
257+
{ Cite = renumber_cites }
156258
}

0 commit comments

Comments
 (0)