Citation Backlinks Filter

GitHub build status

This filter modifies the bibliography, adding backlinks to the locations in the text where an item is cited.

Usage

This filter should be invoked after citeproc, e.g.

pandoc --citeproc --lua-filter citation-backlinks.lua ...

The filter doesn’t work yet as a Quarto extension.

License

This pandoc Lua filter is published under the MIT license, see file LICENSE for details.

Example

input.md

---
title: Lorem ipsum
author: Nullus
nocite: '@*'
bibliography: test/example.bib
---

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam
nisl @krewinkel2017, tincidunt et, mattis eget, convallis nec,
purus.

output.md

---
author: Nullus
bibliography: test/example.bib
nocite: "[@\\*]{#cite_1}"
title: Lorem ipsum
---

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam nisl
[Krewinkel and Winkler (2017)]{#cite_2}, tincidunt et, mattis eget,
convallis nec, purus.

::: {#refs .references .csl-bib-body .hanging-indent}
::: {#ref-krewinkel2017 .csl-entry}
Krewinkel, Albert, and Robert Winkler. 2017. "Formatting Open Science:
Agilely Creating Multiple Document Formats for Academic Manuscripts with
Pandoc Scholar." *PeerJ Computer Science* 3 (May): e112.
<https://doi.org/10.7717/peerj-cs.112>. Cited: [1](#cite_2)
:::

::: {#ref-smith2018 .csl-entry}
Smith, Arfon M., Kyle E. Niemeyer, Daniel S. Katz, Lorena A. Barba,
George Githinji, Melissa Gymrek, Kathryn D. Huff, et al. 2018. "Journal
of Open Source Software (JOSS): Design and First-Year Review." *PeerJ
Computer Science* 4 (February): e147.
<https://doi.org/10.7717/peerj-cs.147>.
:::
:::

Code

citation-backlinks.lua

--- citation-backlinks.lua – adds citation backlinks to the bibliography
---
--- Copyright: © 2022 John MacFarlane and Albert Krewinkel
--- License: MIT – see LICENSE for details

-- Makes sure users know if their pandoc version is too old for this
-- filter.
PANDOC_VERSION:must_be_at_least '2.17'

-- cites is a table mapping citation item identifiers
-- to an array of cite identifiers
local cites = {}

-- counter for cite identifiers
local cite_number = 1

local function with_latex_label(s, el)
  if FORMAT == "latex" then
    return {pandoc.RawInline("latex", "\\label{" .. s .. "}"), el}
  else
    return {el}
  end
end

function Cite(el)
  local cite_id = "cite_" .. cite_number
  cite_number = cite_number + 1
  for _,citation in ipairs(el.citations) do
    if cites[citation.id] then
      table.insert(cites[citation.id], cite_id)
    else
      cites[citation.id] = {cite_id}
    end
  end
  return pandoc.Span(with_latex_label(cite_id, el), pandoc.Attr(cite_id))
end

function append_inline(blocks, inlines)
  local last = blocks[#blocks]
  if last.t == 'Para' or last.t == 'Plain' then
    -- append to last block
    last.content:extend(inlines)
  else
    -- append as additional block
    blocks[#blocks + 1] = pandoc.Plain(inlines)
  end
  return blocks
end

function Div(el)
  local citation_id = el.identifier:match("ref%-(.+)")
  if citation_id then
    local backlinks = pandoc.Inlines{pandoc.Str("Cited:"),pandoc.Space()}
    for i,cite_id in ipairs(cites[citation_id] or {}) do
      local marker = pandoc.Str(i)
      if FORMAT == "latex" then
        marker = pandoc.RawInline("latex", "\\pageref{" .. cite_id .. "}")
      end
      if #backlinks > 2 then
        table.insert(backlinks, pandoc.Str(","))
        table.insert(backlinks, pandoc.Space())
      end
      table.insert(backlinks, pandoc.Link(marker, "#"..cite_id))
    end
    if #backlinks > 2 then
      append_inline(el.content, {pandoc.Space()} .. backlinks)
    end
    return el
  end
end