@@ -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 List = require ' pandoc.List'
@@ -31,39 +33,66 @@ local metatype = pandoc.utils.type or
31
33
32
34
--- Collection of all cites in the document
33
35
local all_cites = {}
36
+
34
37
--- Document meta value
35
38
local doc_meta = pandoc .Meta {}
36
39
37
40
--- Div used by citeproc to insert the bibliography.
38
41
local refs_div = pandoc .Div ({}, pandoc .Attr (' refs' ))
39
42
43
+ --- 'references' metadata for each topic
44
+ local topic_refs = {}
45
+
40
46
-- Div filled by citeproc with properties set according to
41
47
-- the output format and the attributes of cs:bibliography
42
48
local refs_div_with_properties
43
49
50
+ -- Whether utils.citeproc() supports a 'quiet' argument
51
+ -- (it doesn't yet, but perhaps it will, in which case this
52
+ -- will use the appropriate pandoc version check)
53
+ local supports_quiet_arg = false
54
+
44
55
--- Run citeproc on a pandoc document
45
- local citeproc
46
- if utils .citeproc then
47
- -- Built-in Lua function
48
- citeproc = utils .citeproc
49
- else
50
- -- Use pandoc as a citeproc processor
51
- citeproc = function (doc )
52
- local opts = {' --from=json' , ' --to=json' , ' --citeproc' , ' --quiet' }
56
+ local function citeproc (doc , quiet )
57
+ -- utils.citeproc() was added in 2.19.1
58
+ if utils .citeproc and supports_quiet_arg then
59
+ -- Built-in Lua function
60
+ return utils .citeproc (doc , quiet )
61
+ else
62
+ -- Use pandoc as a citeproc processor
63
+ local path = PANDOC_STATE .resource_path
64
+ local opts = {' --from=json' , ' --to=json' , ' --citeproc' ,
65
+ ' --resource-path=' .. table.concat (path , ' :' ),
66
+ quiet and ' --quiet' or nil }
53
67
return run_json_filter (doc , ' pandoc' , opts )
54
68
end
55
69
end
56
70
57
71
--- Resolve citations in the document by combining all bibliographies
58
- -- before running pandoc- citeproc on the full document.
72
+ -- before running citeproc on the full document.
59
73
local function resolve_doc_citations (doc )
60
- -- combine all bibliographies
74
+ -- combine all bibliographies and references
61
75
local meta = doc .meta
62
76
local bibconf = meta .bibliography
63
77
meta .bibliography = pandoc .MetaList {}
64
78
if metatype (bibconf ) == ' table' then
65
79
for _ , value in pairs (bibconf ) do
66
- table.insert (meta .bibliography , stringify (value ))
80
+ -- support list-valued items
81
+ if metatype (value ) ~= ' List' then value = List {value } end
82
+ for _ , val in ipairs (value ) do
83
+ table.insert (meta .bibliography , stringify (val ))
84
+ end
85
+ end
86
+ end
87
+ local refconf = meta .references
88
+ meta .references = pandoc .MetaList {}
89
+ if metatype (refconf ) == ' table' then
90
+ for topic , refs in pairs (refconf ) do
91
+ -- save topic references for meta_for_citeproc()
92
+ topic_refs [topic ] = refs
93
+ for _ , ref in ipairs (refs ) do
94
+ table.insert (meta .references , ref )
95
+ end
67
96
end
68
97
end
69
98
-- add refs div to catch the created bibliography
@@ -72,30 +101,53 @@ local function resolve_doc_citations (doc)
72
101
doc = citeproc (doc )
73
102
-- remove catch-all bibliography and keep it for future use
74
103
refs_div_with_properties = table.remove (doc .blocks )
75
- -- restore bibliography to original value
76
- doc .meta .bibliography = orig_bib
104
+ -- restore bibliography and references to original values
105
+ doc .meta .bibliography = bibconf
106
+ doc .meta .references = refconf
77
107
return doc
78
108
end
79
109
80
- --- Explicitly create a new meta object with all fields relevant for
81
- --- pandoc-citeproc.
82
- local function meta_for_pandoc_citeproc (bibliography )
110
+ --- Explicitly create a new meta object with all fields relevant for citeproc.
111
+ local function meta_for_citeproc (bibliography , topic )
83
112
-- We could just indiscriminately copy all meta fields, but let's be
84
113
-- explicit about what's important.
85
114
local fields = {
86
115
' bibliography' , ' references' , ' csl' , ' citation-style' ,
87
116
' link-citations' , ' citation-abbreviations' , ' lang' ,
88
117
' suppress-bibliography' , ' reference-section-title' ,
89
- ' notes-after-punctuation' , ' nocite'
118
+ ' notes-after-punctuation' , ' nocite' , ' link-bibliography '
90
119
}
91
120
local new_meta = pandoc .Meta {}
92
121
for _ , field in ipairs (fields ) do
93
- new_meta [field ] = doc_meta [field ]
122
+ local value = doc_meta [field ]
123
+ -- replace 'references' with the topic references
124
+ if field == ' references' and metatype (value ) == ' table' and topic then
125
+ value = topic_refs [topic ]
126
+ end
127
+ new_meta [field ] = value
94
128
end
95
129
new_meta .bibliography = bibliography
96
130
return new_meta
97
131
end
98
132
133
+ -- list of ref-xxx identifiers that have already been output
134
+ local identifiers = List ()
135
+
136
+ -- ignore duplicate references (the first definition will win)
137
+ local function ignore_duplicates (blocks )
138
+ local new_blocks = pandoc .Blocks {}
139
+ for _ , block in ipairs (blocks ) do
140
+ local identifier = block .attr .identifier
141
+ if not identifiers :includes (identifier ) then
142
+ local new_block = pandoc .walk_block (block , {Span = _span })
143
+ new_blocks :insert (new_block )
144
+ identifiers :insert (identifier )
145
+ end
146
+ end
147
+
148
+ return new_blocks
149
+ end
150
+
99
151
local function remove_duplicates (classes )
100
152
local seen = {}
101
153
return classes :filter (function (x )
@@ -118,18 +170,67 @@ local function create_topic_bibliography (div)
118
170
return nil
119
171
end
120
172
local tmp_blocks = {pandoc .Para (all_cites ), refs_div }
121
- local tmp_meta = meta_for_pandoc_citeproc (bibfile )
173
+ local tmp_meta = meta_for_citeproc (bibfile , name )
122
174
local tmp_doc = pandoc .Pandoc (tmp_blocks , tmp_meta )
123
- local res = citeproc (tmp_doc )
175
+ local res = citeproc (tmp_doc , true )
124
176
-- First block of the result contains the dummy paragraph, second is
125
177
-- the refs Div filled by citeproc.
126
- div .content = res .blocks [2 ].content
178
+ div .content = ignore_duplicates ( res .blocks [2 ].content )
127
179
-- Set the classes and attributes as citeproc did it on refs_div
128
180
div .classes = remove_duplicates (refs_div_with_properties .classes )
129
181
div .attributes = refs_div_with_properties .attributes
130
182
return div
131
183
end
132
184
185
+ -- renumber numbered references and their citations
186
+ -- (this logic should probably be in a separate filter; it's too
187
+ -- dependent on the CSL, although it should do no harm)
188
+
189
+ -- map from reference id to its new label
190
+ local ref_map = List ()
191
+
192
+ -- ref counter
193
+ local ref_counter = 1
194
+
195
+ local function collect_numbered_refs (div )
196
+ if div .attr .classes :includes (' csl-entry' ) then
197
+ local identifier = div .attr .identifier
198
+ local content = div .content
199
+ -- expect single Para with a Span (depending on style) possibly containing
200
+ -- the citation number (only do anything if it does)
201
+ if (# div .content > 0 and # div .content [1 ].content > 0 and
202
+ div .content [1 ].content [1 ].tag == ' Span' ) then
203
+ local span = div .content [1 ].content [1 ]
204
+ local content = span .content
205
+ if # content > 0 then
206
+ local text = content [1 ].text
207
+ local pre , num , post = content [1 ].text :match (" ^(%p*)(%d+)(%p*)$" )
208
+ if pre and num and post then
209
+ local ident = identifier :gsub (' ^ref%-' , ' ' )
210
+ local label = string.format (' %s%d%s' , pre , ref_counter , post )
211
+ content [1 ] = pandoc .Str (label )
212
+ ref_map [ident ] = label
213
+ ref_counter = ref_counter + 1
214
+ return div
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ local function renumber_cites (cite )
222
+ -- only consider cites with single citations
223
+ if # cite .citations == 1 then
224
+ local id = cite .citations [1 ].id
225
+ local label = ref_map [id ]
226
+ -- only change the content if the label is defined
227
+ if label then
228
+ cite .content = label
229
+ return cite
230
+ end
231
+ end
232
+ end
233
+
133
234
return {
134
235
{
135
236
-- Collect all citations and the doc's Meta value for other filters.
@@ -138,4 +239,8 @@ return {
138
239
},
139
240
{ Pandoc = resolve_doc_citations },
140
241
{ Div = create_topic_bibliography },
242
+
243
+ -- These should probably be handled via a separate filter.
244
+ { Div = collect_numbered_refs },
245
+ { Cite = renumber_cites }
141
246
}
0 commit comments