Search code examples
formattingmarkdownpandocquartocsl

Format specific authors with Bold font in bibliography created with Quarto


I've put together a webpage for my research group using quarto - we have a page listing the lab's publications

I've adapted a .csl file to fit most of my formatting needs (eg. listing chronologically instead of alphabetically), but I'm stuck on one last thing. We want to format authors from our lab in bold - while leaving external collaborators in normal font-weight. Its easy enough to set all authors to render in bold, but from what I can gather there isn't a way to define a list of specific strings that should be formatted in bold?

I've found some documentation that suggests using rich text in my .bib file. However, I'm finding that if I put something like this in the .bib file:

author = <b>Skeeter</b>

When I run quarto render the formatting is output in the .html file as:

&lt;b&gt;Skeeter&lt;/b&gt;

so it shows up literally as <b>Skeeter</b> in the document rather than Skeeter.

My question is whether anyone is aware of a work around that I could employ to achieve this goal? Is there a way to have quarto format specific text values in bold or a way to have a simple script run every time quarto render is called to format things after the fact? Ideally I'd like to render the publications.qmd file to both .html and .pdf formats - so running a completely separate script after rendering is complete to edit docs/Publications.html would only solve half the problem - the .pdf would still be rendered without the bold formatting.

edit: As mentioned in an answer below - \textbf{Skeeter} would be more appropriate for a .bib file. And this does work for formatting words in titles - but unfortunately does not work for authors because of the way they are parsed, sorted, etc. I also tried a csl json format instead, but same issue - adding <b>Skeeter</b> does not result in Skeeter

The Solution: @shafee provided a great solution below that works like a charm. Create a bold-author.lua filter, save it it the main directory, and specify the author names you want to bold in the YAML header, eg.

filters:
  - section-bibliographies
  - bold-author.lua
bold-auth-name:
  - family: Knox
    given: S. H.
  - family: Skeeter
    given: J.

One minor quirk however - it skipped over authors with two given names, ie. a first & middle initial for authors with two given names, so

Skeeter, J., & Knox, S. H. (2023, April). Ongoing and Proposed Research in the Burns Bog Ecological Conservancy Area.

Changing to:

bold-auth-name:
  - family: Knox
    given: S.

would give: Skeeter, J., & Knox, S. H. (2023, April). Ongoing and Proposed Research in the Burns Bog Ecological Conservancy Area.

So I updated the provided lua filter slightly to:

str = pandoc.utils.stringify

local function highlight_author_filter(auths)
  return {
    Para = function(el)
      if el.t == "Para" then
        for k,_ in ipairs(el.content) do
          for key, val in ipairs(auths) do
            local first_part = val.family .. ","
            local full = val.family .. ", " .. val.given

            given_initials = {}
            for w in val.given:gmatch("%S+") do
              table.insert(given_initials,w)
            end

            if #given_initials == 1  then
              if el.content[k].t == "Str" and el.content[k].text == first_part 
              and el.content[k+1].t == "Space" and el.content[k+2].t == "Str"
              and el.content[k+2].text:find(given_initials[1]) then
                local _,e = el.content[k+2].text:find(given_initials[1])
                local rest = el.content[k+2].text:sub(e+1) 
                el.content[k] = pandoc.Strong { pandoc.Str(full) }
                el.content[k+1] = pandoc.Str(rest)
                table.remove(el.content, k+2)
              end
            elseif #given_initials == 2 and #el.content >= k+4 and el.content[k+4].text ~= nil then
              if el.content[k].t == "Str" and el.content[k].text == first_part 
              and el.content[k+1].t == "Space" and el.content[k+2].t == "Str"
              and el.content[k+2].text:find(given_initials[1])
              and el.content[k+4].text:find(given_initials[2]) then
                local _,e = el.content[k+4].text:find(given_initials[2])
                local rest = el.content[k+4].text:sub(e+1) 
                el.content[k] = pandoc.Strong { pandoc.Str(full) }
                el.content[k+1] = pandoc.Str(rest)
                table.remove(el.content, k+4)
                table.remove(el.content, k+3)
                table.remove(el.content, k+2)
              end
            end
          end
        end
      end
    return el
    end
  }
end


local function get_auth_name(auths)
  return {
    Meta = function(m)
      for key, val in ipairs(m['bold-auth-name']) do
        local auth = {
          ["family"] = str(val.family),
          ["given"] = str(val.given)
        }
        table.insert(auths, auth)
      end
    end
  }
end


local function highlight_author_name(auths)
  return {
    Div = function(el)
      if el.classes:includes("references") then
        return el:walk(highlight_author_filter(auths))
      end
      return nil
    end
  }
end

function Pandoc(doc)
  local bold_auth_name = {}
  doc:walk(get_auth_name(bold_auth_name))
  return doc:walk(highlight_author_name(bold_auth_name))
end

Probably a more elegant way to do it, but I'm not super familiar w/ Lua. Just providing here for anyone who needs it. Again all credit to @shafee :D


Solution

  • You can use a lua filter to highlight specific Author surname and initials while rendering the Pubs.qmd document (No need to do multiple rendering and create other files)

    Use the bold-author.lua filter after the section-bibliographies filter. And then specify the Author's family and given under the bold-auth-name yaml key (got the idea of key-names family and given from lab_publications.json file).

    Pubs.qmd

    ---
    title: "Publications"
    format: html
    bibliography: lab_publications.json
    section-bibs-bibliography: lab_publications.json
    section-bibs-level: 2
    citeproc: False
    csl: american-geophysical-union.csl
    filters:
      - section-bibliographies
      - bold-author.lua
    bold-auth-name:
      - family: Skeeter
        given: J.
      - family: Satriawan
        given: T.
    ---
    
    ## Theses
    
    ::: {.hidden}
    
    @satriawan_interannual_2022
    
    @russell_increased_2021
    
    @nyberg_impacts_2021
    
    :::
    
    ## Research Talks & Poster Presentations
    
    ::: {.hidden}
    
    @skeeter_ongoing_2023
    @knox_ubc_2023
    @ng_characterizing_2022
    @lu_investigating_2022
    @satriawan_interannual_2022_a
    @satriawan_interannual_2022_b
    @satriawan_interannual_2021
    @satriawan_interannual_2020
    @nyberg_impacts_2020
    
    :::
    
    

    bold-author.lua

    The actual implementation of this filter is based on this answer, I have then further modified it so that it works for multiple authors and the family and given values from the quarto document are passed to the filter correctly.

    str = pandoc.utils.stringify
    
    local function highlight_author_filter(auths)
      return {
        Para = function(el)
          if el.t == "Para" then
            for k,_ in ipairs(el.content) do
              for key, val in ipairs(auths) do
                local first_part = val.family .. ","
                local second_part = "^" .. val.given
                local full = val.family .. ", " .. val.given
                if el.content[k].t == "Str" and el.content[k].text == first_part
                and el.content[k+1].t == "Space"
                and el.content[k+2].t == "Str" and el.content[k+2].text:find(second_part) then
                    local _,e = el.content[k+2].text:find(second_part)
                    local rest = el.content[k+2].text:sub(e+1) 
                    el.content[k] = pandoc.Strong { pandoc.Str(full) }
                    el.content[k+1] = pandoc.Str(rest)
                    table.remove(el.content, k+2)
                end
              end
            end
          end
        return el
        end
      }
    end
    
    
    local function get_auth_name(auths)
      return {
        Meta = function(m)
          for key, val in ipairs(m['bold-auth-name']) do
            local auth = {
              ["family"] = str(val.family),
              ["given"] = str(val.given)
            }
            table.insert(auths, auth)
          end
        end
      }
    end
    
    
    local function highlight_author_name(auths)
      return {
        Div = function(el)
          if el.classes:includes("references") then
            return el:walk(highlight_author_filter(auths))
          end
          return nil
        end
      }
    end
    
    function Pandoc(doc)
      local bold_auth_name = {}
      doc:walk(get_auth_name(bold_auth_name))
      return doc:walk(highlight_author_name(bold_auth_name))
    end
    
    

    The only limitation of this filter I am aware of is that its only works with the section-bibliographies filter

    rendered output

    rendered output