Search code examples
rlatexr-markdownkablekableextra

How to force linebreaks in kableExtra functions with escape = FALSE?


In kableExtra >= 0.8.0, the canonical way to insert a linebreak into text piped into a table from a kableExtra function such as add_header_above or pack_rows is to add an \n directly.

However, this appears not to work with the escape = FALSE argument, which is required if the text also contains LaTeX code.

How can one force linebreaks in kableExtra functions with escape = FALSE?

library(dplyr)
library(knitr)
library(kableExtra)

starwars %>%
  filter(species == 'Gungan' | species == 'Droid') %>%
  arrange(species) %>%
  select(name, eye_color) %>%
  kbl(booktabs = TRUE) %>%
  pack_rows(
    index = c(
      'The droids: everybody\'s favourite' = 6, 
      'The Gungans: only beloved of \nthose aged under $3^2$' = 3), 
    escape = FALSE)

Solution

  • ISSUE

    The issue at hand is that you wish to escape part of your header (i.e., the break) and not escape another part (i.e., the math code).

    Further Complications

    This core issue is further complicated by a number of factors:

    1. when and how kableExtra is programmed to deal with escaping
    2. a desire to have a solution that works for both html and LaTeX output
    3. when and how R evaluates code

    A SOLUTION

    Here is a solution that will work for both html and LaTeX output, but it is not as clean and straight forward as your original code:

    # a new version of `kableExtra::linebreak()` that takes into account what type 
    # of output is desired as well as how much escaping is necessary
    linebreak2 <- function(x, double_escape = TRUE, ...) {
      # if LaTeX insert text into a `\makecell[]{}` command and double escape
      if(knitr::is_latex_output())
        return(linebreak(x, double_escape = double_escape, ...))
      
      # if html output just replace `\n`s with `<br/>`s
      if(knitr::is_html_output())
        return(gsub("\n", "<br/>", x))
      
      # let x pass through for other types of output
      return(x)
    }
    
    # build the index named vector outside the pipe flow 
    # in order to set the names using `linebreak2()`
    index <- c(6, 3)
    names(index) <- c(
      'The droids: everybody\'s favourite',
      linebreak2('The Gungans: only beloved of \nthose aged under $3^2$')
    )
    
    # proceed as before
    starwars %>%
      filter(species == 'Gungan' | species == 'Droid') %>%
      arrange(species) %>%
      select(name, eye_color) %>%
      kbl(booktabs = TRUE) %>%
      pack_rows(index = index, escape = FALSE)
    

    PDF Output

    enter image description here

    HTML Output

    enter image description here