@@ -15,6 +15,8 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1515ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1616OR 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
1820PANDOC_VERSION :must_be_at_least ' 2.11'
1921
2022local List = require ' pandoc.List'
@@ -31,39 +33,66 @@ local metatype = pandoc.utils.type or
3133
3234--- Collection of all cites in the document
3335local all_cites = {}
36+
3437--- Document meta value
3538local doc_meta = pandoc .Meta {}
3639
3740--- Div used by citeproc to insert the bibliography.
3841local refs_div = pandoc .Div ({}, pandoc .Attr (' refs' ))
3942
43+ --- 'references' metadata for each topic
44+ local topic_refs = {}
45+
4046-- Div filled by citeproc with properties set according to
4147-- the output format and the attributes of cs:bibliography
4248local refs_div_with_properties
4349
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+
4455--- 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 }
5367 return run_json_filter (doc , ' pandoc' , opts )
5468 end
5569end
5670
5771--- 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.
5973local function resolve_doc_citations (doc )
60- -- combine all bibliographies
74+ -- combine all bibliographies and references
6175 local meta = doc .meta
6276 local bibconf = meta .bibliography
6377 meta .bibliography = pandoc .MetaList {}
6478 if metatype (bibconf ) == ' table' then
6579 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
6796 end
6897 end
6998 -- add refs div to catch the created bibliography
@@ -72,30 +101,53 @@ local function resolve_doc_citations (doc)
72101 doc = citeproc (doc )
73102 -- remove catch-all bibliography and keep it for future use
74103 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
77107 return doc
78108end
79109
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 )
83112 -- We could just indiscriminately copy all meta fields, but let's be
84113 -- explicit about what's important.
85114 local fields = {
86115 ' bibliography' , ' references' , ' csl' , ' citation-style' ,
87116 ' link-citations' , ' citation-abbreviations' , ' lang' ,
88117 ' suppress-bibliography' , ' reference-section-title' ,
89- ' notes-after-punctuation' , ' nocite'
118+ ' notes-after-punctuation' , ' nocite' , ' link-bibliography '
90119 }
91120 local new_meta = pandoc .Meta {}
92121 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
94128 end
95129 new_meta .bibliography = bibliography
96130 return new_meta
97131end
98132
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+
99151local function remove_duplicates (classes )
100152 local seen = {}
101153 return classes :filter (function (x )
@@ -118,18 +170,67 @@ local function create_topic_bibliography (div)
118170 return nil
119171 end
120172 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 )
122174 local tmp_doc = pandoc .Pandoc (tmp_blocks , tmp_meta )
123- local res = citeproc (tmp_doc )
175+ local res = citeproc (tmp_doc , true )
124176 -- First block of the result contains the dummy paragraph, second is
125177 -- the refs Div filled by citeproc.
126- div .content = res .blocks [2 ].content
178+ div .content = ignore_duplicates ( res .blocks [2 ].content )
127179 -- Set the classes and attributes as citeproc did it on refs_div
128180 div .classes = remove_duplicates (refs_div_with_properties .classes )
129181 div .attributes = refs_div_with_properties .attributes
130182 return div
131183end
132184
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+
133234return {
134235 {
135236 -- Collect all citations and the doc's Meta value for other filters.
@@ -138,4 +239,8 @@ return {
138239 },
139240 { Pandoc = resolve_doc_citations },
140241 { Div = create_topic_bibliography },
242+
243+ -- These should probably be handled via a separate filter.
244+ { Div = collect_numbered_refs },
245+ { Cite = renumber_cites }
141246}
0 commit comments