rr-markdownpandocquartocross-reference

Quarto cross-references: persistent cross-references when text changes


I am creating a long PDF report using Quarto. I want to include two formats of cross-reference:

  1. To the section/figure number, e.g.

As discussed in Section 3.1

  1. To the section/figure title, e.g.

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:

enter image description here

Problem

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).

enter image description here

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?


Solution

  • 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.

    Usage:

    1. 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

    2. 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}.

    3. 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
    
    

    First part of the generated output

    First part of the generated output