From 5bf0fa1be7ead76b4e40ade6d358a9fb72593173 Mon Sep 17 00:00:00 2001 From: William Lupton Date: Sun, 15 Dec 2024 17:48:14 +0000 Subject: [PATCH 1/2] Add 'multibib-renumber.lua' filter The initial version is just code that has been removed from https://github.com/pandoc-ext/multibib/pull/8. --- _extensions/multibib/multibib-renumber.lua | 73 ++++++++++++++++++++++ multibib-renumber.lua | 1 + test/test.yaml | 1 + 3 files changed, 75 insertions(+) create mode 100644 _extensions/multibib/multibib-renumber.lua create mode 120000 multibib-renumber.lua diff --git a/_extensions/multibib/multibib-renumber.lua b/_extensions/multibib/multibib-renumber.lua new file mode 100644 index 0000000..aa5a16a --- /dev/null +++ b/_extensions/multibib/multibib-renumber.lua @@ -0,0 +1,73 @@ +--[[ +multibib-renumber – renumber numbered references and their citations + +Copyright © 2018-2024 Albert Krewinkel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +]] + +local pandoc = require 'pandoc' +local List = require 'pandoc.List' + +-- this filter should run after the multibib filter +-- (the logic is too dependent on the CSL, although it should do no harm) + +-- map from reference id to its new label +local ref_map = List() + +-- ref counter +local ref_counter = 1 + +local function collect_numbered_refs(div) + if div.attr.classes:includes('csl-entry') then + local identifier = div.attr.identifier + local content = div.content + -- expect single Para with a Span (depending on style) possibly containing + -- the citation number (only do anything if it does) + if (#div.content > 0 and #div.content[1].content > 0 and + div.content[1].content[1].tag == 'Span') then + local span = div.content[1].content[1] + local content = span.content + if #content > 0 then + local text = content[1].text + local pre, num, post = content[1].text:match("^(%p*)(%d+)(%p*)$") + if pre and num and post then + local ident = identifier:gsub('^ref%-', '') + local label = string.format('%s%d%s', pre, ref_counter, post) + content[1] = pandoc.Str(label) + ref_map[ident] = label + ref_counter = ref_counter + 1 + return div + end + end + end + end +end + +local function renumber_cites(cite) + -- only consider cites with single citations + if #cite.citations == 1 then + local id = cite.citations[1].id + local label = ref_map[id] + -- only change the content if the label is defined + if label then + cite.content = label + return cite + end + end +end + +return { + { Div = collect_numbered_refs }, + { Cite = renumber_cites } +} diff --git a/multibib-renumber.lua b/multibib-renumber.lua new file mode 120000 index 0000000..19a4ac5 --- /dev/null +++ b/multibib-renumber.lua @@ -0,0 +1 @@ +_extensions/multibib/multibib-renumber.lua \ No newline at end of file diff --git a/test/test.yaml b/test/test.yaml index 896da93..07f2bd4 100644 --- a/test/test.yaml +++ b/test/test.yaml @@ -3,3 +3,4 @@ to: plain standalone: true filters: - multibib.lua + - multibib-renumber.lua From 3a1df2ed4fcc36bb68ec28420ca7d60f50c5e21c Mon Sep 17 00:00:00 2001 From: William Lupton Date: Mon, 16 Dec 2024 15:31:50 +0000 Subject: [PATCH 2/2] Fix problems with multiple citations and missing links --- _extensions/multibib/multibib-renumber.lua | 88 +++++++++++++++++++--- test/expected.txt | 14 ++-- test/input.md | 4 +- test/test.csl | 34 +++++++++ test/test.yaml | 3 + 5 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 test/test.csl diff --git a/_extensions/multibib/multibib-renumber.lua b/_extensions/multibib/multibib-renumber.lua index aa5a16a..b9e0127 100644 --- a/_extensions/multibib/multibib-renumber.lua +++ b/_extensions/multibib/multibib-renumber.lua @@ -1,7 +1,7 @@ --[[ multibib-renumber – renumber numbered references and their citations -Copyright © 2018-2024 Albert Krewinkel +Copyright © 2024 William Lupton and contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -19,6 +19,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. local pandoc = require 'pandoc' local List = require 'pandoc.List' +local stringify = pandoc.utils.stringify + +-- logging +local script_dir = require('pandoc.path').directory(PANDOC_SCRIPT_FILE) +package.path = string.format('%s/?.lua;%s/../?.lua;%s/../scripts/?.lua;%s', + script_dir, script_dir, script_dir, package.path) +local logging = require 'logging' +logging.setlogprefix() +if false then logging.setloglevel(1) end + -- this filter should run after the multibib filter -- (the logic is too dependent on the CSL, although it should do no harm) @@ -28,8 +38,19 @@ local ref_map = List() -- ref counter local ref_counter = 1 -local function collect_numbered_refs(div) +-- pass 1: process references: +-- * populate ref_map, mapping reference ids to their new labels (numbers) +-- * modify reference labels to be the new numbers +-- +-- this will operate on divs (created by citeproc) like this one (markdown): +-- +-- ::: {#ref-Bel .csl-entry} +-- [\[1\] ]{.csl-left-margin}[Bellori, *Le vite de' pittori, scultori e +-- architetti moderni*, 1672]{.csl-right-inline} +-- ::: +local function collect_refs(div) if div.attr.classes:includes('csl-entry') then + logging.debug('csl entry', div) local identifier = div.attr.identifier local content = div.content -- expect single Para with a Span (depending on style) possibly containing @@ -39,13 +60,15 @@ local function collect_numbered_refs(div) local span = div.content[1].content[1] local content = span.content if #content > 0 then + local id = identifier:gsub('^ref%-', '') local text = content[1].text local pre, num, post = content[1].text:match("^(%p*)(%d+)(%p*)$") if pre and num and post then - local ident = identifier:gsub('^ref%-', '') + -- replace num with the current ref counter (1, 2, ...) local label = string.format('%s%d%s', pre, ref_counter, post) content[1] = pandoc.Str(label) - ref_map[ident] = label + ref_map[id] = tostring(ref_counter) + logging.info('collect refs', 'id', id, 'label', text, '->', label) ref_counter = ref_counter + 1 return div end @@ -54,20 +77,65 @@ local function collect_numbered_refs(div) end end +-- pass 2: process citations: +-- * for each citation, use ref_map to find its new label (number) +-- * update the citation content to use the new label (this is the messy bit, +-- because we have to do string processing, which we do at the Str level so +-- as to retain formatting, links etc.) +-- +-- for example, given '([3, Knu86]; [1, Bae])', map the '3' in '[3,' to '1' +-- and the '[1,' to '3', resulting in '([4, Knu86]; [3, Bae])' local function renumber_cites(cite) - -- only consider cites with single citations - if #cite.citations == 1 then - local id = cite.citations[1].id + logging.debug('cite', cite) + local content = cite.content + local changed = false + for _, citation in ipairs(cite.citations) do + local id = citation.id local label = ref_map[id] -- only change the content if the label is defined if label then - cite.content = label - return cite + local found = false + + -- only substitute the first, because we assume that the citations are + -- referenced in the content in citation order (see below for the other + -- trick) + -- XXX the opening '[' should be configurable + local function substitute_first(str) + if not found then + local pre, num, post = + str.text:match('^(.-%[)(%d+)(%D)') + if pre and num and post then + -- the other trick is that we use '!label!' to avoid the + -- substituted value from being substituted again + local text = pre .. '!' .. label .. '!' .. post + logging.debug('citation id', id, 'label', label, str, '->', text) + str.text = text + found = true + return str + end + end + end + + content = content:walk({Str = substitute_first}) + if found then + changed = true + end end end + + if changed then + -- map '!label!' back to 'label' + content = content:walk({Str = function(str) + str.text = str.text:gsub('!(%d+)!', '%1') + return str end}) + logging.info('renumber cites', stringify(cite.content), '->', + stringify(content)) + cite.content = content + return cite + end end return { - { Div = collect_numbered_refs }, + { Div = collect_refs }, { Cite = renumber_cites } } diff --git a/test/expected.txt b/test/expected.txt index 3800faa..e7789cb 100644 --- a/test/expected.txt +++ b/test/expected.txt @@ -1,17 +1,19 @@ Multiple Bibliographies Demo -Nietzsche (1872), Bellori (1672) +(see [2, Nie72/section/1]), ([1, Bel]) + +([4, Knu86]; [3, Bae]) References -Bellori. 1672. Le Vite de’ Pittori, Scultori e Architetti Moderni. +[1] Bellori, Le vite de’ pittori, scultori e architetti moderni, 1672 -Nietzsche, Friedrich. 1872. Die Geburt Der Tragödie Aus Dem Geiste Der -Musik. +[2] Friedrich Nietzsche, Die geburt der tragödie aus dem geiste der +musik, 1872 Recommended Reading -Bätschmann, Oskar. 1985. Pygmalion Als Betrachter. +[3] Oskar Bätschmann, Pygmalion als betrachter, 1985 -Knuth, Donald E. 1986. The TeXbook. +[4] Donald E. Knuth, The TeXbook, 1986 diff --git a/test/input.md b/test/input.md index e9c0abb..d0de249 100644 --- a/test/input.md +++ b/test/input.md @@ -5,7 +5,9 @@ bibliography: recommended-reading: test/secondary.bib nocite: '@Knu86, @Bae' --- -@Nie72, @Bel +[see @Nie72, Section 1], [@Bel] + +[@Knu86; @Bae] # References diff --git a/test/test.csl b/test/test.csl new file mode 100644 index 0000000..78f9c83 --- /dev/null +++ b/test/test.csl @@ -0,0 +1,34 @@ + + diff --git a/test/test.yaml b/test/test.yaml index 07f2bd4..feec8a9 100644 --- a/test/test.yaml +++ b/test/test.yaml @@ -1,6 +1,9 @@ input-files: ['test/input.md'] to: plain standalone: true +metadata: + link-citations: true filters: - multibib.lua - multibib-renumber.lua +csl: test/test.csl