I am creating a long PDF report using Quarto. I want to include two formats of cross-reference:
As discussed in Section 3.1
As discussed in My awesome section.
It is possible to achieve either like this:
---
format:
pdf:
number-sections: true
---
# Why Stack Overflow is so great {#sec-stack}
I can use style 1, the section number, with the explicit ID, e.g. as seen in @sec-stack.
I can use style 2, the section title, with a link [Why Stack Overflow is so great].
I can also custom text with [Custom text before the link][Why Stack Overflow is so great].
This produces the desired output:
The problem is that the document is being redrafted by several authors. If a section title is changed from Why Stack Overflow is so great to Why I love Stack Overflow, it breaks the cross-references using the second style (section title).
I am looking for a way to refer to a section using the explicit identifier @sec-stack
, and have it display the title instead of the section number. This would be something like [@sec-stack]
rather than @sec-stack
.
There are various options in the Quarto cross-referencing docs. However, I can't see a way to make it so that cross-reference text updates if the section title is updated, provided the explicit identifier remains the same.
Does this exist?
I have written a Lua filter name_crossref.lua
to get the cross-reference text instead of section/figure/table number. And the following approach works for both HTML and pdf output format.
For section or subsection title, its use is very simple. We just need to use \nameref{sec-id}
where sec-id
is the section identifier used in headers as #sec-id
For images or tables generated from code chunks, we need to use some chunk options. we need to use link
as code chunk class, need to define an id for cross-referring the image or table later with link-id
, and need to define a title that will be used as cross-reference text with link-title
. And then use the id that we have assigned to link-id
to refer the generated table/image using \nameref{id}
.
For images added with markdown syntax or markdown tables, the use of this filter is a bit hacky. We need to use nested divs (created with pandoc div syntax, ::::
for outer div and :::
for inner div). In the first div, we have to add the link
class, link-id
, link-title
, and in the second div we need to add another class cell-output-display
. And similarly, then use the id that we have assigned to link-id
to refer to the generated table/image using \nameref{id}
.
---
title: "Cross Referencing the Name"
author: Shafee
format:
html: default
pdf: default
number-sections: true
filters:
- name_crossref.lua
---
# Why Quarto is so great {#sec-stack}
`r stringi::stri_rand_lipsum(1)`
See \nameref{sec-stack}.
## How it is so {#how}
`r stringi::stri_rand_lipsum(1)`
See \nameref{how}.
## Images
```{r}
#| classes: link
#| link-id: fig1
#| link-title: My Awesome plot
plot(1:10)
```
`r stringi::stri_rand_lipsum(1)`
see \nameref{fig1}
## Tables
```{r}
#| classes: link
#| link-id: tab1
#| link-title: Mtcars Data
head(mtcars)
```
`r stringi::stri_rand_lipsum(1)`
see \nameref{tab1}
# Markdown Images
:::: {.link link-id="fig2" link-title="Scatter plot of mpg"}
::: {.cell-output-display}
![mpg](test-filename/mpg.png)
:::
::::
`r stringi::stri_rand_lipsum(4)`
see \nameref{fig2}
# Markdown Table
:::: {.link link-id="tab2" link-title="Markdown table"}
::: {.cell-output-display}
| Col1 | Col2 | Col3 |
|------|------|------|
| A | B | C |
| E | F | G |
| A | G | G |
: My Caption
:::
::::
`r stringi::stri_rand_lipsum(4)`
see \nameref{tab2}
name_crossref.lua
local str = pandoc.utils.stringify
function get_header_data(data)
local get_headers = {
Header = function(el)
local id = el.identifier
local text = str(el.content):gsub("^[%d.]+ ", "")
table.insert(data, {id = id, text = text})
end,
Div = function(el)
if el.attributes["link-id"] then
local id = el.attributes["link-id"]
local text = el.attributes["link-title"]
table.insert(data, {id = id, text = text})
end
end
}
return get_headers
end
function change_ref(data)
local change_rawinline = {
RawInline = function(el)
for key, value in pairs(data) do
if el.text:match("\\nameref{(.*)}") == value.id then
local target = "#" .. value.id
local link = pandoc.Link(value.text, target)
return link
end
end
end
}
return change_rawinline
end
local function add_div_id(div)
return {
Div = function(elem)
if elem.classes:includes("cell-output-display")
or elem.classes:includes("cell-output-stdout")then
elem.identifier = div.attributes["link-id"]
return elem
end
end
}
end
function Div(el)
if el.classes:includes('link') then
return el:walk(add_div_id(el))
end
end
function Pandoc(doc)
local header_data = {}
doc:walk(get_header_data(header_data))
return doc:walk(change_ref(header_data))
end