Skip to content

Commit

Permalink
format_tt escape argument
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentarelbundock committed Feb 1, 2024
1 parent c68cdb7 commit 1f7dbb7
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 1 deletion.
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ Package: tinytable
Type: Package
Title: Simple and Configurable Tables in 'HTML', 'LaTeX', 'Markdown', 'Word', 'PNG', 'PDF', and 'Typst' Formats
Description: Create highly customized tables with this simple and dependency-free package. Data frames can be converted to 'HTML', 'LaTeX', 'Markdown', 'Word', 'PNG', 'PDF', or 'Typst' tables. The user interface is minimalist and easy to learn. The syntax concise. 'HTML' tables can be customized using the flexible 'Bootstrap' framework, and 'LaTeX' code with the 'tabularray' package.
Version: 0.0.2.9005
Version: 0.0.2.9006
Depends:
R (>= 4.1.0)
Enhances:
knitr
Suggests:
altdoc,
ggplot2,
htmltools,
markdown,
palmerpenguins,
pandoc,
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ New:
- `Typst` tables are now supported using the `tablex` extension:
- https://typst.app/
- https://github.com/PgBiel/typst-tablex
- `escape` argument in `format_tt()` escapes or substitutes special characters in LaTeX or HTML output to prevent compilation and rendering errors.
- `notes` argument in `tt()` can insert superscript markers inside cells to refer to notes at the bottom of the page.
- `tt(x, notes = list("*" = list(i = 0:1, j = 2, text = "Hello world)))`
- `notes` agument in `tt()` now works wth Markdown and Word, but must be a single string.
Expand Down
39 changes: 39 additions & 0 deletions R/escape.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
escape_text <- function(x, output = "latex") {
if (length(x) < 1 || all(is.na(x))) {
return(x)
}

out <- x

if (isTRUE(output == "latex")) {
# LaTeX escaping code adapted from the `gt` package, published under MIT
# https://github.com/rstudio/gt/
# YEAR: 2018-2024
# COPYRIGHT HOLDER: gt authors
# If all text elements are `NA_character_` then return `text` unchanged
latex_special_chars <- c(
"\\" = "\\textbackslash{}",
"~" = "\\textasciitilde{}",
"^" = "\\textasciicircum{}",
"&" = "\\&",
"%" = "\\%",
"$" = "\\$",
"#" = "\\#",
"_" = "\\_",
"{" = "\\{",
"}" = "\\}"
)
na_out <- is.na(out)
m <- gregexpr("[\\\\&%$#_{}~^]", out[!na_out], perl = TRUE)
special_chars <- regmatches(out[!na_out], m)
escaped_chars <- lapply(special_chars, function(x) {
latex_special_chars[x]
})
regmatches(out[!na_out], m) <- escaped_chars
} else if (isTRUE(output == "html")) {
assert_dependency("htmltools")
out <- htmltools::htmlEscape(out)
}

return(out)
}
19 changes: 19 additions & 0 deletions R/format_tt.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#' @param date A string passed to the `format()` function, such as "%Y-%m-%d". See the "Details" section in `?strptime`
#' @param bool A function to format logical columns. Defaults to title case.
#' @param other A function to format columns of other types. Defaults to `as.character()`.
#' @param escape Logical or String; if TRUE, escape special characters to display them as text in the format of the output of a `tt()` table. If `format_tt()` is called as a standalone function instead of on a `tt()` table, the `escape` argument accepts strings to specify the escaping method: "latex" or "html".
#' @param markdown Logical; if TRUE, render markdown syntax in cells. Ex: `_italicized text_` is properly italicized in HTML and LaTeX.
#' @param sprintf String passed to the `?sprintf` function to format numbers or interpolate strings with a user-defined pattern (similar to the `glue` package, but using Base R).
#' @inheritParams tt
Expand Down Expand Up @@ -42,6 +43,7 @@ format_tt <- function(x,
date = "%Y-%m-%d",
bool = function(column) tools::toTitleCase(tolower(column)),
other = as.character,
escape = FALSE,
markdown = FALSE,
sprintf = NULL
) {
Expand All @@ -61,6 +63,7 @@ format_tt <- function(x,
url = url,
date = date,
bool = bool,
escape = escape,
markdown = markdown,
other = other)
out <- meta(out, "lazy_format", c(meta(out)$lazy_format, list(cal)))
Expand All @@ -79,6 +82,7 @@ format_tt <- function(x,
date = date,
bool = bool,
other = other,
escape = escape,
markdown = markdown)
}
return(out)
Expand All @@ -96,6 +100,7 @@ format_tt_lazy <- function(x,
url = FALSE,
date = "%Y-%m-%d",
bool = identity,
escape = FALSE,
markdown = FALSE,
other = as.character
) {
Expand Down Expand Up @@ -195,6 +200,20 @@ format_tt_lazy <- function(x,

} # loop over columns

# escape latex characters
if (!isFALSE(escape)) {
if (isTRUE(escape == "latex")) {
o <- "latex"
} else if (isTRUE(escape == "html")) {
o <- "html"
} else {
o <- meta(x)$output
}
for (col in j) {
x[[col]] <- escape_text(x[[col]], output = o)
}
}

# markdown at the very end
if (isTRUE(markdown)) {
assert_dependency("markdown")
Expand Down
1 change: 1 addition & 0 deletions inst/tinytest/_tinysnapshot/escape-html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1] "<!DOCTYPE html> \n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>tinytable_xnclv9tvdcq09jxgyitf</title>\n <link href=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n <style>\n.table td.tinytable_css_4or5kg1sue01fe9hmaas, .table th.tinytable_css_4or5kg1sue01fe9hmaas { border-bottom: solid 0.1em #d3d8dc; }\n </style>\n <script src=\"https://polyfill.io/v3/polyfill.min.js?features=es6\"></script>\n <script id=\"MathJax-script\" async src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"></script>\n <script>\n MathJax = {\n tex: {\n inlineMath: [['$', '$'], ['\\\\(', '\\\\)']]\n },\n svg: {\n fontCache: 'global'\n }\n };\n </script>\n </head>\n\n <body>\n <div class=\"container\">\n <table class=\"table table-borderless\" id=\"tinytable_xnclv9tvdcq09jxgyitf\" style=\"width: auto; margin-left: auto; margin-right: auto;\" data-quarto-disable-processing='true'>\n <thead>\n \n <tr>\n <th scope=\"col\">LaTeX</th>\n <th scope=\"col\">HTML</th>\n </tr>\n </thead>\n \n <tbody>\n <tr>\n <td>Dollars $</td>\n <td>&lt;br&gt;</td>\n </tr>\n <tr>\n <td>Percent %</td>\n <td>&lt;sup&gt;4&lt;/sup&gt;</td>\n </tr>\n <tr>\n <td>Underscore _</td>\n <td>&lt;emph&gt;blah&lt;/emph&gt;</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <script src=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js\"></script>\n <script>\n function styleCell_tinytable_mj8hiuwkqnddps3hrarw(i, j, css_id) {\n var table = document.getElementById(\"tinytable_xnclv9tvdcq09jxgyitf\");\n table.rows[i].cells[j].classList.add(css_id);\n }\n function insertSpanRow(i, colspan, content) {\n var table = document.getElementById('tinytable_xnclv9tvdcq09jxgyitf');\n var newRow = table.insertRow(i);\n var newCell = newRow.insertCell(0);\n newCell.setAttribute(\"colspan\", colspan);\n // newCell.innerText = content;\n // this may be unsafe, but innerText does not interpret <br>\n newCell.innerHTML = content;\n }\nwindow.addEventListener('load', function () { styleCell_tinytable_mj8hiuwkqnddps3hrarw(0, 0, 'tinytable_css_4or5kg1sue01fe9hmaas') })\nwindow.addEventListener('load', function () { styleCell_tinytable_mj8hiuwkqnddps3hrarw(0, 1, 'tinytable_css_4or5kg1sue01fe9hmaas') })\n </script>\n\n </body>\n\n</html>"
1 change: 1 addition & 0 deletions inst/tinytest/_tinysnapshot/escape-latex.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1] "\\begin{table}\n\\centering\n\\begin{tblr}[ %% tabularray outer open\n] %% tabularray outer close\n{ %% tabularray inner open\ncolspec={Q[]Q[]},\n} %% tabularray inner close\n\\toprule\nLaTeX & HTML \\\\ \\midrule %% TinyTableHeader\nDollars \\$ & <br> \\\\\nPercent \\% & <sup>4</sup> \\\\\nUnderscore \\_ & <emph>blah</emph> \\\\\n\\bottomrule\n\\end{tblr}\n\\end{table}"
14 changes: 14 additions & 0 deletions inst/tinytest/test-escape.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
source("helpers.R")
using("tinysnapshot")

dat <- data.frame(
"LaTeX" = c("Dollars $", "Percent %", "Underscore _"),
"HTML" = c("<br>", "<sup>4</sup>", "<emph>blah</emph>")
)

set.seed(1024) # reproducibility of html unique IDs
tab <- tt(dat) |> format_tt(escape = TRUE) |> save_tt("latex")
expect_snapshot_print(tab, "escape-latex")

tab <- tt(dat) |> format_tt(escape = TRUE) |> save_tt("html")
expect_snapshot_print(tab, "escape-html")
3 changes: 3 additions & 0 deletions man/format_tt.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions vignettes/tutorial.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,25 @@ format_tt(dat, digits = 1, num_suffix = TRUE)
```


## Escape special characters

LaTeX and HTML use special characters to indicate strings which should be interpreted rather than displayed as text. For example, including underscores or dollar signs in LaTeX can cause compilation errors in some documents. To display those special characters, we need to substitute or escape them with backslashes, depending on the output format. The `escape` argument of `format_tt()` can be used to do this automatically:

```{r}
dat <- data.frame(
"LaTeX" = c("Dollars $", "Percent %", "Underscore _"),
"HTML" = c("<br>", "<sup>4</sup>", "<emph>blah</emph>")
)
tt(dat) |> format_tt(escape = TRUE)
```

When applied to a `tt()` table, `format_tt()` will determine the type of escaping to do automatically. When applied to a string or vector, we must specify the type of escaping to apply:

```{r}
format_tt("_ Dollars $", escape = "latex")
```

## Markdown

Markdown can be rendered in cells by using the `markdown` argument of the `format_tt()` function (note: this requires installing the `markdown` as an optional dependency).
Expand Down

0 comments on commit 1f7dbb7

Please sign in to comment.