Skip to content

Add 'multibib-renumber.lua' filter #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions _extensions/multibib/multibib-renumber.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
--[[
multibib-renumber – renumber numbered references and their citations

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
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'

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'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logging library should probably be a "soft" dependency:

local logging = require 'logging' or
  { __index = function () return (function () end) end }

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)

-- map from reference id to its new label
local ref_map = List()

-- ref counter
local ref_counter = 1

-- 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
-- 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 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
-- 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[id] = tostring(ref_counter)
logging.info('collect refs', 'id', id, 'label', text, '->', label)
ref_counter = ref_counter + 1
return div
end
end
end
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)
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
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_refs },
{ Cite = renumber_cites }
}
1 change: 1 addition & 0 deletions multibib-renumber.lua
14 changes: 8 additions & 6 deletions test/expected.txt
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion test/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ bibliography:
recommended-reading: test/secondary.bib
nocite: '@Knu86, @Bae'
---
@Nie72, @Bel
[see @Nie72, Section 1], [@Bel]

[@Knu86; @Bae]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep at least one citation as "nocite-only".


# References

Expand Down
34 changes: 34 additions & 0 deletions test/test.csl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl"
class="in-text" version="1.0"
demote-non-dropping-particle="never"
page-range-format="chicago"
default-locale="en-GB">
<citation>
<layout prefix="(" suffix=")" delimiter="; ">
<group prefix="[" suffix="]" delimiter=", ">
<text variable="citation-number"/>
<group delimiter="/">
<text variable="id"/>
<text variable="author"/>
<label variable="locator"/>
<text variable="locator"/>
</group>
</group>
</layout>
</citation>
<bibliography>
<sort>
<key variable="id"/>
</sort>
<layout>
<text variable="citation-number" prefix="[" suffix="]"
display="left-margin"/>
<group delimiter=", " display="right-inline">
<names variable="author"/>
<text variable="title" font-style="italic"/>
<date date-parts="year" form="text" variable="issued"/>
</group>
</layout>
</bibliography>
</style>
4 changes: 4 additions & 0 deletions test/test.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +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