@@ -15,6 +15,8 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
15
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
16
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
17
]]
18
+
19
+ -- --citeproc was added in 2.11, so we never use the old pandoc-citeproc
18
20
PANDOC_VERSION :must_be_at_least ' 2.11'
19
21
20
22
local pandoc = require ' pandoc'
@@ -32,16 +34,25 @@ local metatype = pandoc.utils.type or
32
34
33
35
--- Collection of all cites in the document
34
36
local all_cites = {}
37
+
35
38
--- Document meta value
36
39
local doc_meta = pandoc .Meta {}
37
40
38
41
--- Div used by citeproc to insert the bibliography.
39
42
local refs_div = pandoc .Div ({}, pandoc .Attr (' refs' ))
40
43
44
+ --- 'references' metadata for each topic
45
+ local topic_refs = {}
46
+
41
47
-- Div filled by citeproc with properties set according to
42
48
-- the output format and the attributes of cs:bibliography
43
49
local refs_div_with_properties
44
50
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
+
45
56
--- Run citeproc on a pandoc document
46
57
--
47
58
-- Falls back to the external `pandoc-citeproc` filter if the built-in
65
76
--- Resolve citations in the document by combining all bibliographies
66
77
-- before running citeproc on the full document.
67
78
local function resolve_doc_citations (doc )
68
- -- combine all bibliographies
79
+ -- combine all bibliographies and references
69
80
local meta = doc .meta
70
81
local bibconf = meta .bibliography
71
82
meta .bibliography = pandoc .MetaList {}
72
83
if metatype (bibconf ) == ' table' then
73
84
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
75
101
end
76
102
end
77
103
-- add refs div to catch the created bibliography
@@ -80,30 +106,53 @@ local function resolve_doc_citations (doc)
80
106
doc = citeproc (doc )
81
107
-- remove catch-all bibliography and keep it for future use
82
108
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
85
112
return doc
86
113
end
87
114
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 )
91
117
-- We could just indiscriminately copy all meta fields, but let's be
92
118
-- explicit about what's important.
93
119
local fields = {
94
120
' bibliography' , ' references' , ' csl' , ' citation-style' ,
95
121
' link-citations' , ' citation-abbreviations' , ' lang' ,
96
122
' suppress-bibliography' , ' reference-section-title' ,
97
- ' notes-after-punctuation' , ' nocite'
123
+ ' notes-after-punctuation' , ' nocite' , ' link-bibliography '
98
124
}
99
125
local new_meta = pandoc .Meta {}
100
126
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
102
133
end
103
134
new_meta .bibliography = bibliography
104
135
return new_meta
105
136
end
106
137
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
+
107
156
local function remove_duplicates (classes )
108
157
local seen = {}
109
158
return classes :filter (function (x )
@@ -126,12 +175,12 @@ local function create_topic_bibliography (div)
126
175
return nil
127
176
end
128
177
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 )
130
179
local tmp_doc = pandoc .Pandoc (tmp_blocks , tmp_meta )
131
- local res = citeproc (tmp_doc )
180
+ local res = citeproc (tmp_doc , true )
132
181
-- First block of the result contains the dummy paragraph, second is
133
182
-- the refs Div filled by citeproc.
134
- div .content = res .blocks [2 ].content
183
+ div .content = ignore_duplicates ( res .blocks [2 ].content )
135
184
-- Set the classes and attributes as citeproc did it on refs_div
136
185
div .classes = remove_duplicates (refs_div_with_properties .classes )
137
186
div .attributes = refs_div_with_properties .attributes
@@ -145,6 +194,55 @@ local function create_topic_bibliography (div)
145
194
return div
146
195
end
147
196
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
+
148
246
return {
149
247
{
150
248
-- Collect all citations and the doc's Meta value for other filters.
@@ -153,4 +251,8 @@ return {
153
251
},
154
252
{ Pandoc = resolve_doc_citations },
155
253
{ Div = create_topic_bibliography },
254
+
255
+ -- These should probably be handled via a separate filter.
256
+ { Div = collect_numbered_refs },
257
+ { Cite = renumber_cites }
156
258
}
0 commit comments