From ea72166c4bff9d3746f044d3ebd1974bde20375c Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 11:49:46 +0200 Subject: [PATCH 01/10] magrittr dependency eliminated --- DESCRIPTION | 9 ++++---- NAMESPACE | 1 - NEWS.md | 4 ++++ R/latex2exp.R | 1 - R/parser.R | 38 +++++++++++++++++--------------- vignettes/supported-commands.Rmd | 2 +- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 97418f2..ed2bec8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: latex2exp Type: Package Title: Use LaTeX Expressions in Plots -Version: 0.9.7 -Date: 2022-12-28 +Version: 0.9.8 +Date: 2024-07-05 Authors@R: person("Stefano", "Meschiari", email="stefano.meschiari@gmail.com", role=c("aut", "cre")) Description: Parses and converts LaTeX math formulas to R's plotmath expressions, used to enter mathematical formulas and symbols to be rendered as @@ -11,8 +11,7 @@ License: MIT + file LICENSE URL: https://www.stefanom.io/latex2exp/, https://github.com/stefano-meschiari/latex2exp BugReports: https://github.com/stefano-meschiari/latex2exp/issues Imports: - stringr, - magrittr + stringr Encoding: UTF-8 Suggests: testthat, @@ -28,5 +27,5 @@ Suggests: rlang, dplyr VignetteBuilder: knitr -RoxygenNote: 7.2.2 +RoxygenNote: 7.2.3 Language: en-US diff --git a/NAMESPACE b/NAMESPACE index 33eb66e..c902e21 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -12,7 +12,6 @@ importFrom(graphics,par) importFrom(graphics,plot.new) importFrom(graphics,plot.window) importFrom(graphics,text) -importFrom(magrittr,"%>%") importFrom(stringr,fixed) importFrom(stringr,str_c) importFrom(stringr,str_detect) diff --git a/NEWS.md b/NEWS.md index 44159d2..fcc2850 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# 0.9.8 (07/05/2024) + +* The code is reworked to eliminate dependencies to stringr and magrittr. + # 0.9.7 (12/28/2022) ## Bug fixes * In math mode, numbers that started with 0 would be displayed without it (e.g. diff --git a/R/latex2exp.R b/R/latex2exp.R index 90f2615..2564acd 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,4 +1,3 @@ -#' @importFrom magrittr %>% #' @importFrom stringr str_c #' @importFrom stringr str_detect #' @importFrom stringr str_replace_all diff --git a/R/parser.R b/R/parser.R index 5de3a63..214a06e 100644 --- a/R/parser.R +++ b/R/parser.R @@ -77,18 +77,20 @@ parse_latex <- function(latex_string, validate_input(latex_string) } if (depth == 0) { - latex_string <- latex_string %>% - str_replace_fixed('\\|', '\\@pipe ') %>% - - str_replace_all("\\\\['\\$\\{\\}\\[\\]\\!\\?\\_\\^]", function(char) { + latex_string <- str_replace_fixed(latex_string, '\\|', '\\@pipe ') + latex_string <- str_replace_all(latex_string, + "\\\\['\\$\\{\\}\\[\\]\\!\\?\\_\\^]", function(char) { str_c("\\ESCAPED@", as.integer(charToRaw(str_replace_fixed(char, "\\", ""))), "{}") - }) %>% + }) - str_replace_all("([^\\\\]?)\\\\,", "\\1\\\\@SPACE1{}") %>% - str_replace_all("([^\\\\]?)\\\\;", "\\1\\\\@SPACE2{}") %>% - str_replace_all("([^\\\\]?)\\\\\\s", "\\1\\\\@SPACE2{}") + latex_string <- str_replace_all(latex_string, + "([^\\\\]?)\\\\,", "\\1\\\\@SPACE1{}") + latex_string <- str_replace_all(latex_string, + "([^\\\\]?)\\\\;", "\\1\\\\@SPACE2{}") + latex_string <- str_replace_all(latex_string, + "([^\\\\]?)\\\\\\s", "\\1\\\\@SPACE2{}") cat_trace("String with special tokens substituted: ", latex_string) } @@ -481,13 +483,14 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { # any arguments that were not specified (e.g. if # there is no argument specified for the command, # substitute '' for '$arg1') - tok$rendered <- tok$rendered %>% - str_replace_fixed("$P", "phantom()") %>% - str_replace_fixed("$arg1", "") %>% - str_replace_fixed("$arg2", "") %>% - str_replace_fixed("$sup", "") %>% - str_replace_fixed("$sub", "") %>% - str_replace_fixed("$opt", "") + tkr <- tok$rendered + tkr <- str_replace_fixed(tkr, "$P", "phantom()") + tkr <- str_replace_fixed(tkr, "$arg1", "") + tkr <- str_replace_fixed(tkr, "$arg2", "") + tkr <- str_replace_fixed(tkr, "$sup", "") + tkr <- str_replace_fixed(tkr,"$sub", "") + tkr <- str_replace_fixed(tkr, "$opt", "") + tok$rendered <- tkr if (tok_idx != length(tokens) && tok$command == "\\frac") { tok$right_separator <- " * phantom(.)" @@ -562,9 +565,8 @@ validate_input <- function(latex_string) { "' includes a '\\\\' command. Line breaks are not currently supported.") } - test_string <- latex_string %>% - str_replace_fixed("\\{", "") %>% - str_replace_fixed("\\}", "") + test_string <- str_replace_fixed(latex_string, "\\{", "") + test_string <- str_replace_fixed(test_string, "\\}", "") # check that opened and closed braces match in number opened_braces <- nrow(str_match_all(test_string, "[^\\\\]*?(\\{)")[[1]]) - diff --git a/vignettes/supported-commands.Rmd b/vignettes/supported-commands.Rmd index e55e007..f046272 100644 --- a/vignettes/supported-commands.Rmd +++ b/vignettes/supported-commands.Rmd @@ -33,7 +33,7 @@ encodeGraphic <- function(g, ..., style="") { supported <- latex2exp_supported() supported$category <- str_to_title(supported$category) -supported <- supported %>% filter(!is.na(example)) +supported <- filter(supported, !is.na(example)) supported$command <- supported$example ``` From 1a058139234608cef2900b554bb7ca7dec094b18 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 11:57:56 +0200 Subject: [PATCH 02/10] str_c() replaced by paste0() --- NAMESPACE | 1 - R/demos.R | 2 +- R/latex2exp.R | 7 +++---- R/parser.R | 42 +++++++++++++++++++++--------------------- R/plots.R | 26 +++++++++++++------------- R/utils.R | 4 ++-- 6 files changed, 40 insertions(+), 42 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index c902e21..7b70de7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,7 +13,6 @@ importFrom(graphics,plot.new) importFrom(graphics,plot.window) importFrom(graphics,text) importFrom(stringr,fixed) -importFrom(stringr,str_c) importFrom(stringr,str_detect) importFrom(stringr,str_length) importFrom(stringr,str_match) diff --git a/R/demos.R b/R/demos.R index 98098c9..c543a53 100644 --- a/R/demos.R +++ b/R/demos.R @@ -32,7 +32,7 @@ latex2exp_examples <- function(cex=1) { y <- seq(0.95, 0.05, length.out = length(examples)) text( - 0.5, y, str_c("TeX(r\"(", examples, ")\")"), pos = 2, cex = 0.5 * cex, family = 'mono' + 0.5, y, paste0("TeX(r\"(", examples, ")\")"), pos = 2, cex = 0.5 * cex, family = 'mono' ) text(0.5, y, TeX(examples), pos = 4, cex=cex) return(invisible(TRUE)) diff --git a/R/latex2exp.R b/R/latex2exp.R index 2564acd..5ad5460 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,4 +1,3 @@ -#' @importFrom stringr str_c #' @importFrom stringr str_detect #' @importFrom stringr str_replace_all #' @importFrom stringr str_replace @@ -105,11 +104,11 @@ TeX <- hack_parentheses=grid$hack_parentheses[[row]]) if (bold && italic) { - rendered <- str_c("bolditalic(", rendered, ")") + rendered <- paste0("bolditalic(", rendered, ")") } else if (bold) { - rendered <- str_c("bold(", rendered, ")") + rendered <- paste0("bold(", rendered, ")") } else if (italic) { - rendered <- str_c("italic(", rendered, ")") + rendered <- paste0("italic(", rendered, ")") } cat_trace("Rendered as ", rendered, " with parameters ", toString(grid[row,])) diff --git a/R/parser.R b/R/parser.R index 214a06e..b73e9bc 100644 --- a/R/parser.R +++ b/R/parser.R @@ -29,8 +29,8 @@ clone_token <- function(tok) { } .find_substring <- function(string, boundary_characters) { - pattern <- str_c("^[^", - str_c("\\", boundary_characters, collapse=""), + pattern <- paste0("^[^", + paste0("\\", boundary_characters, collapse=""), "]+") ret <- str_match(string, pattern)[1,1] if ((is.na(ret) || nchar(ret) == 0) && nchar(string) > 0) { @@ -80,7 +80,7 @@ parse_latex <- function(latex_string, latex_string <- str_replace_fixed(latex_string, '\\|', '\\@pipe ') latex_string <- str_replace_all(latex_string, "\\\\['\\$\\{\\}\\[\\]\\!\\?\\_\\^]", function(char) { - str_c("\\ESCAPED@", + paste0("\\ESCAPED@", as.integer(charToRaw(str_replace_fixed(char, "\\", ""))), "{}") }) @@ -130,7 +130,7 @@ parse_latex <- function(latex_string, # Continue until we encounter a separator current_fragment <- str_sub(current_fragment, 2) - command <- str_c("\\", + command <- paste0("\\", .find_substring(current_fragment, .math_separators)) cat_trace("Found token ", command, " in text_mode: ", text_mode) token <- .token2(command, text_mode) @@ -143,7 +143,7 @@ parse_latex <- function(latex_string, ch %in% c(".", "{", "}", "[", "]", "(", ")", "|")) { # a \\left or \\right command has started. eat up the next character # and append it to the command. - token$command <- str_c(token$command, ch) + token$command <- paste0(token$command, ch) i <- i + 1 } else if (ch == "{") { argument <- .find_substring_matching(current_fragment, @@ -206,7 +206,7 @@ parse_latex <- function(latex_string, advance <- advance + nchar(argument) + 2 } else if (nextch == "\\") { # Advance until a separator is found - argument <- str_c("\\", + argument <- paste0("\\", .find_substring(str_sub(current_fragment, advance+2), separators)) advance <- advance + nchar(argument) } else { @@ -232,7 +232,7 @@ parse_latex <- function(latex_string, token <- .token2(" ", text_mode) tokens <- c(tokens, token) } else { - token$command <- str_c(token$command, " ") + token$command <- paste0(token$command, " ") } } i <- i + 1 @@ -247,12 +247,12 @@ parse_latex <- function(latex_string, if (ch == "'") { ch <- "\\'" } - token$command <- str_c(token$command, ch) + token$command <- paste0(token$command, ch) i <- i+1 } else if (ch %in% c("?", "!", "@", ":", ";")) { # ...or escape them to avoid introducing illegal characters in the # plotmath expression... - token <- .token2(str_c("\\ESCAPED@", utf8ToInt(ch)), TRUE) + token <- .token2(paste0("\\ESCAPED@", utf8ToInt(ch)), TRUE) tokens <- c(tokens, token) i <- i + 1 } else if (ch == "'") { @@ -343,7 +343,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { tok$left_separator <- '' } - str_c("'", arg, "'") + paste0("'", arg, "'") #next } else if (!tok$text_mode || tok$is_command) { # translate using the translation table in symbols.R @@ -364,7 +364,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { } if (tok$text_mode && !tok$is_command) { - tok$rendered <- str_c("'", tok$rendered, "'") + tok$rendered <- paste0("'", tok$rendered, "'") } @@ -375,12 +375,12 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { split <- str_match(tok$rendered, "(^[0-9\\.]*)(.*)") if (split[1, 3] != "") { - tok$rendered <- str_c(split[1,2], "*", split[1,3]) + tok$rendered <- paste0(split[1,2], "*", split[1,3]) } else { tok$rendered <- split[1,2] } if (str_starts(tok$rendered, "0") && str_length(tok$rendered) > 1) { - tok$rendered <- str_c("0*", str_sub(tok$rendered, 2)) + tok$rendered <- paste0("0*", str_sub(tok$rendered, 2)) } } @@ -425,17 +425,17 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { if (length(tok$args) > 0) { for (argidx in seq_along(tok$args)) { args <- render_latex(tok$args[[argidx]], user_defined, hack_parentheses=hack_parentheses) - argfmt <- str_c("$arg", argidx) + argfmt <- paste0("$arg", argidx) if (str_detect(tok$rendered, fixed(argfmt))) { tok$rendered <- str_replace_all(tok$rendered, fixed(argfmt), args) } else { if (tok$rendered != "{}") { - tok$rendered <- str_c(tok$rendered, " * {", + tok$rendered <- paste0(tok$rendered, " * {", args, "}") } else { - tok$rendered <- str_c("{", args, "}") + tok$rendered <- paste0("{", args, "}") } } } @@ -450,14 +450,14 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { } else { # the current token is not consuming an optional argument, so render # it as square brackets - tok$rendered <- str_c(tok$rendered, " * '[' *", + tok$rendered <- paste0(tok$rendered, " * '[' *", optarg, " * ']'") } } for (type in c("sub", "sup")) { - arg <- tok[[str_c(type, "_arg")]] - argfmt <- str_c("$", type) + arg <- tok[[paste0(type, "_arg")]] + argfmt <- paste0("$", type) if (length(arg) > 0) { rarg <- render_latex(arg, user_defined, hack_parentheses=hack_parentheses) @@ -532,12 +532,12 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { if (tok$skip) { "" } else { - str_c(tok$left_separator %??% "*", + paste0(tok$left_separator %??% "*", tok$rendered, tok$right_separator %??% "") } }) - str_c(rendered_tokens, collapse="") + paste0(rendered_tokens, collapse="") } # Validates the input LaTeX string diff --git a/R/plots.R b/R/plots.R index c550066..d26a1a6 100644 --- a/R/plots.R +++ b/R/plots.R @@ -57,31 +57,31 @@ latex2exp_supported <- function(show=FALSE, ...) { # create an example suitable to demo each category of latex commands examples <- sapply(commands, function(commands) { if (category %in% c("arithmetic operators", "binary operators", "arrows")) { - str_c("$\\alpha ", commands, " \\beta$") + paste0("$\\alpha ", commands, " \\beta$") } else if (category == "set operators") { - str_c("$A ", commands, " B$") + paste0("$A ", commands, " B$") } else if (commands == "\\frac" || commands == "\\overset") { "$\\frac{x+y}{x-y}$" } else if (commands == "\\lim") { "$\\lim_{x \\to \\infty} \\frac{1}{x}$" } else if (commands %in% c("\\sum", "\\prod", "\\int")) { - str_c("$", commands, "_{i=0}^{\\infty}$") + paste0("$", commands, "_{i=0}^{\\infty}$") } else if (commands == "\\sqrt") { "$\\sqrt[z]{x+y}$" } else if (commands %in% c("\\max", "\\min")) { - str_c("$", commands, "_{x \\in X} x^2$") + paste0("$", commands, "_{x \\in X} x^2$") } else if (commands %in% c("\\bigcup", "\\bigcap")) { - str_c("$", commands, "_{i} A_i$") + paste0("$", commands, "_{i} A_i$") } else if (category == "text size" || category == "formatting") { - str_c(commands, "{example text}") + paste0(commands, "{example text}") } else if (category %in% c("\\ ", "\\;", "\\,")) { - str_c("$x ", commands, " y$") + paste0("$x ", commands, " y$") } else if (commands == "\\braket") { - str_c("$\\braket{\\Psi | \\Psi}$") + paste0("$\\braket{\\Psi | \\Psi}$") } else if (category == "decorations" || category == "vector") { - str_c("$", commands, "{\\Psi}$") + paste0("$", commands, "{\\Psi}$") } else if (category == "layout and spacing") { - str_c("$A ", commands, " B$") + paste0("$A ", commands, " B$") } else if (category == "parentheses" || category == "parentheses (not scalable)") { op <- commands if (commands == "\\left(") { @@ -95,15 +95,15 @@ latex2exp_supported <- function(show=FALSE, ...) { } else if (commands == "\\left.") { clo <- "\\right." } else if (str_detect(commands, "^\\\\l")) { - clo <- str_c("\\r", str_replace(commands, fixed("\\l"), "")) + clo <- paste0("\\r", str_replace(commands, fixed("\\l"), "")) } else if (str_detect(commands, "^\\\\r")) { return(NA) } else { clo <- op } - str_c("$", op, " a+b ", clo, "$") + paste0("$", op, " a+b ", clo, "$") } else { - str_c("$", commands, "$") + paste0("$", commands, "$") } }) data.frame(category, command=commands, example=examples) diff --git a/R/utils.R b/R/utils.R index d748266..77ac5f3 100644 --- a/R/utils.R +++ b/R/utils.R @@ -17,8 +17,8 @@ print.latextoken2 <- function(x, depth=0, ...) { token <- x pad <- strrep(" ", depth) cat(pad, - if (depth > 0) str_c("| :", token$command, ":"), - if (!is.null(token$rendered)) str_c(" -> ", token$rendered), + if (depth > 0) paste0("| :", token$command, ":"), + if (!is.null(token$rendered)) paste0(" -> ", token$rendered), "\n", sep="") From ba38726e8b39047c22ac084c9a19cdd4ed5ca900 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 12:06:10 +0200 Subject: [PATCH 03/10] str_detect() replaced by grepl() --- NAMESPACE | 1 - R/latex2exp.R | 1 - R/parser.R | 18 +++++++++--------- R/plots.R | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 7b70de7..0211727 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,7 +13,6 @@ importFrom(graphics,plot.new) importFrom(graphics,plot.window) importFrom(graphics,text) importFrom(stringr,fixed) -importFrom(stringr,str_detect) importFrom(stringr,str_length) importFrom(stringr,str_match) importFrom(stringr,str_match_all) diff --git a/R/latex2exp.R b/R/latex2exp.R index 5ad5460..d9ba870 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,4 +1,3 @@ -#' @importFrom stringr str_detect #' @importFrom stringr str_replace_all #' @importFrom stringr str_replace #' @importFrom stringr str_split diff --git a/R/parser.R b/R/parser.R index b73e9bc..e31a40a 100644 --- a/R/parser.R +++ b/R/parser.R @@ -328,7 +328,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { tok <- tokens[[tok_idx]] tok$skip <- FALSE - tok$rendered <- if (str_detect(tok$command, "^\\\\ESCAPED@")) { + tok$rendered <- if (grepl("^\\\\ESCAPED@", tok$command)) { # a character, like '!' or '?' was escaped as \\ESCAPED@ASCII_SYMBOL. # return it as a string. arg <- str_match(tok$command, "@(\\d+)")[1,2] @@ -371,7 +371,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { # If the token starts with a number, break the number from # the rest of the string. This is because a plotmath symbol # cannot start with a number. - if (str_detect(tok$rendered, "^[0-9]") && !tok$text_mode) { + if (grepl("^[0-9]", tok$rendered) && !tok$text_mode) { split <- str_match(tok$rendered, "(^[0-9\\.]*)(.*)") if (split[1, 3] != "") { @@ -384,8 +384,8 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { } } - tok$left_operator <- str_detect(tok$rendered, fixed("$LEFT")) - tok$right_operator <- str_detect(tok$rendered, fixed("$RIGHT")) + tok$left_operator <- grepl("$LEFT", tok$rendered, fixed = TRUE) + tok$right_operator <- grepl("$RIGHT", tok$rendered, fixed = TRUE) if (tok_idx == 1) { tok$left_separator <- "" @@ -426,7 +426,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { for (argidx in seq_along(tok$args)) { args <- render_latex(tok$args[[argidx]], user_defined, hack_parentheses=hack_parentheses) argfmt <- paste0("$arg", argidx) - if (str_detect(tok$rendered, fixed(argfmt))) { + if (grepl(argfmt, tok$rendered, fixed = TRUE)) { tok$rendered <- str_replace_all(tok$rendered, fixed(argfmt), args) @@ -443,7 +443,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { if (length(tok$optional_arg) > 0) { optarg <- render_latex(tok$optional_arg, user_defined, hack_parentheses=hack_parentheses) - if (str_detect(tok$rendered, fixed("$opt"))) { + if (grepl("$opt", tok$rendered, fixed = TRUE)) { tok$rendered <- str_replace_all(tok$rendered, fixed("$opt"), optarg) @@ -462,7 +462,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { if (length(arg) > 0) { rarg <- render_latex(arg, user_defined, hack_parentheses=hack_parentheses) - if (str_detect(tok$rendered, fixed(argfmt))) { + if (grepl(argfmt, tok$rendered, fixed = TRUE)) { tok$rendered <- str_replace_all(tok$rendered, fixed(argfmt), rarg) } else { if (type == "sup") { @@ -551,7 +551,7 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { # validate_input <- function(latex_string) { for (possible_slash_pattern in c("\a", "\b", "\f", "\v")) { - if (str_detect(latex_string, fixed(possible_slash_pattern))) { + if (grepl(possible_slash_pattern, latex_string, fixed = TRUE)) { repr <- deparse(possible_slash_pattern) message("latex2exp: Detected possible missing backslash: you entered ", repr, ", did you mean to type ", @@ -559,7 +559,7 @@ validate_input <- function(latex_string) { } } - if (str_detect(latex_string, fixed("\\\\"))) { + if (grepl("\\\\", latex_string, fixed = TRUE)) { stop("The LaTeX string '", latex_string, "' includes a '\\\\' command. Line breaks are not currently supported.") diff --git a/R/plots.R b/R/plots.R index d26a1a6..ba66bb1 100644 --- a/R/plots.R +++ b/R/plots.R @@ -51,7 +51,7 @@ latex2exp_supported <- function(show=FALSE, ...) { supp <- lapply(latex_supported, function(it) { # remove all commands that include the '@' character, which are used internally to # escape certain commands. - names(it)[!str_detect(names(it), fixed("@"))] + names(it)[!grepl("@", names(it), fixed = TRUE)] }) supp <- mapply(function(category, commands) { # create an example suitable to demo each category of latex commands @@ -94,9 +94,9 @@ latex2exp_supported <- function(show=FALSE, ...) { clo <- "\\right|" } else if (commands == "\\left.") { clo <- "\\right." - } else if (str_detect(commands, "^\\\\l")) { + } else if (grepl("^\\\\l", commands)) { clo <- paste0("\\r", str_replace(commands, fixed("\\l"), "")) - } else if (str_detect(commands, "^\\\\r")) { + } else if (grepl("^\\\\r", commands)) { return(NA) } else { clo <- op From 749fc5265082811fa329a0e2bb5b44aceddbd35d Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 12:59:11 +0200 Subject: [PATCH 04/10] str_replace_all() replaced by gsub() --- NAMESPACE | 1 - R/demos.R | 11 +-- R/latex2exp.R | 195 +++++++++++++++++++----------------- R/parser.R | 208 ++++++++++++++++++--------------------- R/plots.R | 24 ++--- R/utils.R | 22 +++-- man/TeX.Rd | 25 ++--- man/latex2exp.Rd | 8 +- man/print.latextoken2.Rd | 4 +- man/render_latex.Rd | 12 +-- 10 files changed, 258 insertions(+), 252 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 0211727..338d92e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,7 +17,6 @@ importFrom(stringr,str_length) importFrom(stringr,str_match) importFrom(stringr,str_match_all) importFrom(stringr,str_replace) -importFrom(stringr,str_replace_all) importFrom(stringr,str_split) importFrom(stringr,str_starts) importFrom(stringr,str_sub) diff --git a/R/demos.R b/R/demos.R index c543a53..ff0bba2 100644 --- a/R/demos.R +++ b/R/demos.R @@ -5,7 +5,7 @@ #' #' @param cex Multiplier for font size #' @export -latex2exp_examples <- function(cex=1) { +latex2exp_examples <- function(cex = 1) { oldpar <- par(no.readonly = TRUE) on.exit(suppressWarnings(par(oldpar))) @@ -31,9 +31,8 @@ latex2exp_examples <- function(cex=1) { x <- 0 y <- seq(0.95, 0.05, length.out = length(examples)) - text( - 0.5, y, paste0("TeX(r\"(", examples, ")\")"), pos = 2, cex = 0.5 * cex, family = 'mono' - ) - text(0.5, y, TeX(examples), pos = 4, cex=cex) + text(0.5, y, paste0("TeX(r\"(", examples, ")\")"), pos = 2, cex = 0.5 * cex, + family = 'mono') + text(0.5, y, TeX(examples), pos = 4, cex = cex) return(invisible(TRUE)) -} \ No newline at end of file +} diff --git a/R/latex2exp.R b/R/latex2exp.R index d9ba870..ac69447 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,4 +1,3 @@ -#' @importFrom stringr str_replace_all #' @importFrom stringr str_replace #' @importFrom stringr str_split #' @importFrom stringr str_sub @@ -13,16 +12,19 @@ NULL #' Deprecated; use \code{\link{TeX}} instead. #' -#' @param string A character vector containing LaTeX expressions. Note that any backslashes must be escaped (e.g. "$\\alpha"). -#' @param output The returned object, one of "expression" (default, returns a plotmath expression ready for plotting), "character" (returns the expression as a string), and "ast" (returns the tree used to generate the expression). +#' @param string A character vector containing LaTeX expressions. Note that any +#' backslashes must be escaped (e.g. "$\\alpha"). +#' @param output The returned object, one of "expression" (default, returns a +#' plotmath expression ready for plotting), "character" (returns the +#' expression as a string), and "ast" (returns the tree used to generate the +#' expression). #' #' @return Returns an expression (see the \code{output} parameter). #' @export -latex2exp <- - function(string, output = c('expression', 'character', 'ast')) { - .Deprecated('TeX', 'latex2exp') - return(TeX(string, output=output)) - } +latex2exp <- function(string, output = c('expression', 'character', 'ast')) { + .Deprecated('TeX', 'latex2exp') + return(TeX(string, output = output)) +} #' Converts LaTeX to a \code{\link{plotmath}} expression. #' @@ -41,117 +43,124 @@ latex2exp <- #' "character" (returns the expression as a string), #' and "ast" (returns the tree used to generate the expression). #' -#' @return Returns a plotmath expression by default. The \code{output} parameter can -#' modify the type of the returned value. +#' @return Returns a plotmath expression by default. The \code{output} parameter +#' can modify the type of the returned value. #' -#' If more than one string is specified in the \code{input} parameter, returns a list -#' of expressions. +#' If more than one string is specified in the \code{input} parameter, returns a +#' list of expressions. #' #' @section Adding new commands: -#' New LaTeX commands can be defined by supplying the \code{user_defined} parameter. -#' The \code{user_defined} parameter is a list that contains LaTeX commands -#' as names, and template strings as values. A LaTeX command that matches -#' one of the names is translated into the corresponding string and included in -#' the final plotmath expression. The file \code{symbols.R} in the source code -#' of this package contains one such table that can be used as a reference. +#' New LaTeX commands can be defined by supplying the \code{user_defined} +#' parameter. The \code{user_defined} parameter is a list that contains LaTeX +#' commands as names, and template strings as values. A LaTeX command that +#' matches one of the names is translated into the corresponding string and +#' included in the final plotmath expression. The file \code{symbols.R} in the +#' source code of this package contains one such table that can be used as a +#' reference. #' #' The template string can include one of the following special template #' parameters: #' #' \itemize{ -#' \item \code{$arg1, $arg2, ...} represent the first, second, ... brace argument. -#' E.g. for \code{\\frac{x}{y}}, \code{$arg1} is \code{x} and \code{$arg2} is \code{y}. +#' \item \code{$arg1, $arg2, ...} represent the first, second, ... brace +#' argument. E.g. for \code{\\frac{x}{y}}, \code{$arg1} is \code{x} and +#' \code{$arg2} is \code{y}. #' \item \code{$opt} is an optional argument in square brackets. E.g. for #' \code{\\sqrt[2]{x}}, \code{$opt} is \code{2}. -#' \item \code{$sub} and \code{$sup} are arguments in the exponent (\code{^}) or subscript (\code{_}) -#' following the current expression. E.g. for \code{\\sum^{x}}, \code{$sup} is \code{x}. -#' \item \code{$LEFT} and \code{$RIGHT} are substituted the previous and following LaTeX expression -#' relative to the current token. +#' \item \code{$sub} and \code{$sup} are arguments in the exponent (\code{^}) or +#' subscript (\code{_}) following the current expression. E.g. for +#' \code{\\sum^{x}}, \code{$sup} is \code{x}. +#' \item \code{$LEFT} and \code{$RIGHT} are substituted the previous and +#' following LaTeX expression relative to the current token. #' } -#' See the Examples section for an example of using the \code{user_defined} option. +#' See the Examples section for an example of using the \code{user_defined} +#' option. #' #' @examples #' TeX("$\\alpha$") # plots the greek alpha character #' TeX("The ratio of 1 and 2 is $\\frac{1}{2}$") #' #' a <- 1:100 -#' plot(a, a^2, xlab=TeX("$\\alpha$"), ylab=TeX("$\\alpha^2$")) +#' plot(a, a^2, xlab = TeX("$\\alpha$"), ylab = TeX("$\\alpha^2$")) #' #' # create a \\variance command that takes a single argument -#' TeX("$\\variance{X} = 10$", user_defined=list("\\variance"="sigma[$arg1]^2")) +#' TeX("$\\variance{X} = 10$", +#' user_defined = list("\\variance" = "sigma[$arg1]^2")) #' @export -TeX <- - function(input, bold=FALSE, italic=FALSE, user_defined=list(), output = c('expression', 'character', 'ast')) { - if (length(input) > 1) { - return(sapply(input, TeX, bold=bold, italic=italic, user_defined=user_defined, output = output)) - } - stopifnot(is.character(input)) +TeX <- function(input, bold = FALSE, italic = FALSE, user_defined = list(), + output = c('expression', 'character', 'ast')) { + if (length(input) > 1) { + return(sapply(input, TeX, bold = bold, italic = italic, + user_defined = user_defined, output = output)) + } + stopifnot(is.character(input)) + + output <- match.arg(output) + parsed <- parse_latex(input) - output <- match.arg(output) - parsed <- parse_latex(input) + # Try all combinations of "hacks" in this grid, until one succeeds. + # As more hacks are introduced, the resulting expression will be less and + # less tidy, although it should still be visually equivalent to the + # desired output given the latex string. + grid <- expand.grid(hack_parentheses = c(FALSE, TRUE)) + successful <- FALSE + for (row in seq_len(nrow(grid))) { + # Make a deep clone of the LaTeX token tree + parsed_clone <- clone_token(parsed) + rendered <- render_latex(parsed_clone, user_defined, + hack_parentheses = grid$hack_parentheses[[row]]) - # Try all combinations of "hacks" in this grid, until one succeeds. - # As more hacks are introduced, the resulting expression will be less and - # less tidy, although it should still be visually equivalent to the - # desired output given the latex string. - grid <- expand.grid(hack_parentheses = c(FALSE, TRUE)) - successful <- FALSE - for (row in seq_len(nrow(grid))) { - # Make a deep clone of the LaTeX token tree - parsed_clone <- clone_token(parsed) - rendered <- render_latex(parsed_clone, user_defined, - hack_parentheses=grid$hack_parentheses[[row]]) - - if (bold && italic) { - rendered <- paste0("bolditalic(", rendered, ")") - } else if (bold) { - rendered <- paste0("bold(", rendered, ")") - } else if (italic) { - rendered <- paste0("italic(", rendered, ")") - } - - cat_trace("Rendered as ", rendered, " with parameters ", toString(grid[row,])) - - if (output == "ast") { - return(parsed) - } - - rendered_expression <- try({ - str2expression(rendered) - }, silent=TRUE) - - if (inherits(rendered_expression, "try-error")) { - error <- rendered_expression - cat_trace("Failed, trying next combination of hacks, error:", error, - " parsed as: ", rendered) - - if (row == 1) { - original_error <- error - } - } else { - successful <- TRUE - break - } + if (bold && italic) { + rendered <- paste0("bolditalic(", rendered, ")") + } else if (bold) { + rendered <- paste0("bold(", rendered, ")") + } else if (italic) { + rendered <- paste0("italic(", rendered, ")") } - if (!successful) { - stop("Error while converting LaTeX into valid plotmath.\n", - "Original string: ", input, "\n", - "Parsed expression: ", rendered, "\n", - original_error) - } - if (output == "character") { - return(rendered) - } + cat_trace("Rendered as ", rendered, " with parameters ", + toString(grid[row, ])) - # if the rendered expression is empty, return expression('') instead. - if (length(rendered_expression) == 0) { - rendered_expression <- expression('') + if (output == "ast") { + return(parsed) } - class(rendered_expression) <- c("latexexpression", "expression") - attr(rendered_expression, "latex") <- input - attr(rendered_expression, "plotmath") <- rendered + rendered_expression <- try({ + str2expression(rendered) + }, silent = TRUE) - rendered_expression + if (inherits(rendered_expression, "try-error")) { + error <- rendered_expression + cat_trace("Failed, trying next combination of hacks, error:", error, + " parsed as: ", rendered) + + if (row == 1) { + original_error <- error + } + } else { + successful <- TRUE + break + } + } + + if (!successful) { + stop("Error while converting LaTeX into valid plotmath.\n", + "Original string: ", input, "\n", + "Parsed expression: ", rendered, "\n", + original_error) + } + if (output == "character") { + return(rendered) + } + + # if the rendered expression is empty, return expression('') instead. + if (length(rendered_expression) == 0) { + rendered_expression <- expression('') } + + class(rendered_expression) <- c("latexexpression", "expression") + attr(rendered_expression, "latex") <- input + attr(rendered_expression, "plotmath") <- rendered + + rendered_expression +} diff --git a/R/parser.R b/R/parser.R index e31a40a..efdb57e 100644 --- a/R/parser.R +++ b/R/parser.R @@ -30,7 +30,7 @@ clone_token <- function(tok) { .find_substring <- function(string, boundary_characters) { pattern <- paste0("^[^", - paste0("\\", boundary_characters, collapse=""), + paste0("\\", boundary_characters, collapse = ""), "]+") ret <- str_match(string, pattern)[1,1] if ((is.na(ret) || nchar(ret) == 0) && nchar(string) > 0) { @@ -54,23 +54,21 @@ clone_token <- function(tok) { } else if (chars[i] == closing) { depth <- depth - 1 if (depth == 0) { - return(str_sub(string, start_expr+1, i-1)) + return(str_sub(string, start_expr + 1, i - 1)) } } } if (depth != 0) { - stop("Unmatched '", opening, "' (opened at position: ", start_expr, ") while parsing '", string, "'") + stop("Unmatched '", opening, "' (opened at position: ", start_expr, + ") while parsing '", string, "'") } else { return(string) } } -parse_latex <- function(latex_string, - text_mode=TRUE, - depth=0, - pos=0, - parent=NULL) { +parse_latex <- function(latex_string, text_mode = TRUE, depth = 0, pos = 0, + parent = NULL) { input <- latex_string if (depth == 0) { @@ -78,25 +76,31 @@ parse_latex <- function(latex_string, } if (depth == 0) { latex_string <- str_replace_fixed(latex_string, '\\|', '\\@pipe ') - latex_string <- str_replace_all(latex_string, - "\\\\['\\$\\{\\}\\[\\]\\!\\?\\_\\^]", function(char) { - paste0("\\ESCAPED@", - as.integer(charToRaw(str_replace_fixed(char, "\\", ""))), - "{}") - }) + # This one must be replaced by several calls to gsub() + #latex_string <- str_replace_all(latex_string, + # "\\\\['\\$\\{\\}\\[\\]\\!\\?\\_\\^]", function(char) { + # paste0("\\ESCAPED@", + # as.integer(charToRaw(str_replace_fixed(char, "\\", ""))), + # "{}") + # }) + latex_string <- gsub("\\\\'", "\\\\ESCAPED@39{}", latex_string) + latex_string <- gsub("\\\\\\$", "\\\\ESCAPED@36{}", latex_string) + latex_string <- gsub("\\\\\\{", "\\\\ESCAPED@123{}", latex_string) + latex_string <- gsub("\\\\\\}", "\\\\ESCAPED@125{}", latex_string) + latex_string <- gsub("\\\\\\[", "\\\\ESCAPED@91{}", latex_string) + latex_string <- gsub("\\\\\\]", "\\\\ESCAPED@93{}", latex_string) + latex_string <- gsub("\\\\\\!", "\\\\ESCAPED@33{}", latex_string) + latex_string <- gsub("\\\\\\?", "\\\\ESCAPED@63{}", latex_string) + latex_string <- gsub("\\\\\\_", "\\\\ESCAPED@95{}", latex_string) + latex_string <- gsub("\\\\\\^", "\\\\ESCAPED@94{}", latex_string) - latex_string <- str_replace_all(latex_string, - "([^\\\\]?)\\\\,", "\\1\\\\@SPACE1{}") - latex_string <- str_replace_all(latex_string, - "([^\\\\]?)\\\\;", "\\1\\\\@SPACE2{}") - latex_string <- str_replace_all(latex_string, - "([^\\\\]?)\\\\\\s", "\\1\\\\@SPACE2{}") + latex_string <- gsub("([^\\\\]?)\\\\,", "\\1\\\\@SPACE1{}", latex_string) + latex_string <- gsub("([^\\\\]?)\\\\;", "\\1\\\\@SPACE2{}", latex_string) + latex_string <- gsub("([^\\\\]?)\\\\\\s", "\\1\\\\@SPACE2{}", latex_string) cat_trace("String with special tokens substituted: ", latex_string) } - - i <- 1 tokens <- list() @@ -106,8 +110,9 @@ parse_latex <- function(latex_string, while (i <= nchar(latex_string)) { # Look at current character, previous character, and next character ch <- str_sub(latex_string, i, i) - prevch <- if (i == 1) "" else str_sub(latex_string, i-1, i-1) - nextch <- if (i == nchar(latex_string)) "" else str_sub(latex_string, i+1, i+1) + prevch <- if (i == 1) "" else str_sub(latex_string, i - 1, i - 1) + nextch <- if (i == nchar(latex_string)) "" else + str_sub(latex_string, i + 1, i + 1) # LaTeX string left to be processed current_fragment <- str_sub(latex_string, i) @@ -131,7 +136,7 @@ parse_latex <- function(latex_string, current_fragment <- str_sub(current_fragment, 2) command <- paste0("\\", - .find_substring(current_fragment, .math_separators)) + .find_substring(current_fragment, .math_separators)) cat_trace("Found token ", command, " in text_mode: ", text_mode) token <- .token2(command, text_mode) tokens <- c(tokens, token) @@ -146,25 +151,21 @@ parse_latex <- function(latex_string, token$command <- paste0(token$command, ch) i <- i + 1 } else if (ch == "{") { - argument <- .find_substring_matching(current_fragment, - "{", - "}") + argument <- .find_substring_matching(current_fragment, "{", "}") if (is.null(token)) { token <- .token2("", text_mode) tokens <- c(tokens, token) } - args <- parse_latex(argument, text_mode=text_mode, - depth=depth+1, parent=token, pos=i) + args <- parse_latex(argument, text_mode = text_mode, + depth = depth + 1, parent = token, pos = i) if (length(args) > 0) { token$args <- c(token$args, list(args)) } # advance by two units (the content of the braces + two braces) i <- i + nchar(argument) + 2 } else if (ch == "[") { - argument <- .find_substring_matching(current_fragment, - "[", - "]") + argument <- .find_substring_matching(current_fragment, "[", "]") if (is.null(token)) { token <- .token2("", text_mode) tokens <- c(tokens, token) @@ -172,8 +173,8 @@ parse_latex <- function(latex_string, token$optional_arg <- c( token$optional_arg, - parse_latex(argument, text_mode=text_mode, - depth=depth+1, parent=token, pos=i) + parse_latex(argument, text_mode = text_mode, + depth = depth + 1, parent = token, pos = i) ) # advance by two units (the content of the braces + two braces) @@ -191,31 +192,30 @@ parse_latex <- function(latex_string, # If there are spaces after the ^ or _ character, # consume them and advance past the spaces if (nextch == " ") { - n_spaces <- str_match(str_sub(current_fragment, 2), "\\s+")[1,1] + n_spaces <- str_match(str_sub(current_fragment, 2), "\\s+")[1, 1] advance <- advance + nchar(n_spaces) - nextch <- str_sub(current_fragment, advance + 1, advance+1) + nextch <- str_sub(current_fragment, advance + 1, advance + 1) } # Sub or sup arguments grouped with braces. This is easy! if (nextch == "{") { - argument <- .find_substring_matching(str_sub(current_fragment, advance+1), - "{", - "}") + argument <- .find_substring_matching(str_sub(current_fragment, + advance + 1), "{", "}") # advance by two units (the content of the braces + two braces) advance <- advance + nchar(argument) + 2 } else if (nextch == "\\") { # Advance until a separator is found argument <- paste0("\\", - .find_substring(str_sub(current_fragment, advance+2), separators)) + .find_substring(str_sub(current_fragment, advance + 2), separators)) advance <- advance + nchar(argument) } else { - argument <- str_sub(current_fragment, advance+1, advance+1) + argument <- str_sub(current_fragment, advance + 1, advance + 1) advance <- advance + nchar(argument) } - token[[arg_type]] <- parse_latex(argument, text_mode=text_mode, - depth=depth+1, parent=token, pos=i) + token[[arg_type]] <- parse_latex(argument, text_mode = text_mode, + depth = depth + 1, parent = token, pos = i) i <- i + advance } else if (ch == "$") { @@ -248,7 +248,7 @@ parse_latex <- function(latex_string, ch <- "\\'" } token$command <- paste0(token$command, ch) - i <- i+1 + i <- i + 1 } else if (ch %in% c("?", "!", "@", ":", ";")) { # ...or escape them to avoid introducing illegal characters in the # plotmath expression... @@ -271,16 +271,14 @@ parse_latex <- function(latex_string, str <- .find_substring(current_fragment, separators) # If in math mode, ignore spaces - token <- .token2(str_replace_all(str, - "\\s+", ""), - text_mode) + token <- .token2(gsub("\\s+", "", str), text_mode) tokens <- c(tokens, token) i <- i + nchar(str) } } } - }, error=function(e) { + }, error = function(e) { token_command <- if (is.null(token)) { "" } else { @@ -292,7 +290,8 @@ parse_latex <- function(latex_string, message("Last token parsed:", token$command) } if (!is.null(parent)) { - message("The error happened within the arguments of :", parent$command, "\n") + message("The error happened within the arguments of :", + parent$command, "\n") } }) @@ -311,16 +310,18 @@ parse_latex <- function(latex_string, #' returned by \code{parse_latex}. #' #' @param tokens tree of tokens -#' @param user_defined any custom definitions of commands passed to \code{\link{TeX}} -#' @param hack_parentheses render parentheses using \code{group('(', phantom(), '.')} and -#' \code{group(')', phantom(), '.')}. This is useful to return -#' valid expressions when the LaTeX source contains mismatched -#' parentheses, but makes the returned expression much -#' less tidy. +#' @param user_defined any custom definitions of commands passed to +#' \code{\link{TeX}} +#' @param hack_parentheses render parentheses using +#' \code{group('(', phantom(), '.')} and \code{group(')', phantom(), '.')}. +#' This is useful to return valid expressions when the LaTeX source contains +#' mismatched parentheses, but makes the returned expression much less tidy. #' @return String that should be parseable as a valid plotmath expression -render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { +render_latex <- function(tokens, user_defined = list(), + hack_parentheses = FALSE) { if (!is.null(tokens$children)) { - return(render_latex(tokens$children, user_defined, hack_parentheses=hack_parentheses)) + return(render_latex(tokens$children, user_defined, + hack_parentheses = hack_parentheses)) } translations <- c(user_defined, latex_supported_map) @@ -356,7 +357,8 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { # empty command; if followed by arguments such as sup or sub, render as # an empty token, otherwise skip if (tok$rendered == "") { - if (length(tok$args) > 0 || length(tok$sup_arg) > 0 || length(tok$sub_arg) > 0) { + if (length(tok$args) > 0 || length(tok$sup_arg) > 0 || + length(tok$sub_arg) > 0) { tok$rendered <- "{}" } else { tok$skip <- TRUE @@ -375,9 +377,9 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { split <- str_match(tok$rendered, "(^[0-9\\.]*)(.*)") if (split[1, 3] != "") { - tok$rendered <- paste0(split[1,2], "*", split[1,3]) + tok$rendered <- paste0(split[1, 2], "*", split[1, 3]) } else { - tok$rendered <- split[1,2] + tok$rendered <- split[1, 2] } if (str_starts(tok$rendered, "0") && str_length(tok$rendered) > 1) { tok$rendered <- paste0("0*", str_sub(tok$rendered, 2)) @@ -394,64 +396,50 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { if (tok$left_operator) { if (tok_idx == 1) { # Either this operator is the first token... - tok$rendered <- str_replace_all(tok$rendered, - fixed("$LEFT"), - "phantom()") - } else if (tokens[[tok_idx-1]]$right_operator) { + tok$rendered <- str_replace_fixed(tok$rendered, "$LEFT", "phantom()") + } else if (tokens[[tok_idx - 1]]$right_operator) { # or the previous token was also an operator or an open parentheses. # Bind the tokens using phantom() - tok$rendered <- str_replace_all(tok$rendered, - fixed("$LEFT"), - "phantom()") + tok$rendered <- str_replace_fixed(tok$rendered, "$LEFT", "phantom()") } else { - tok$rendered <- str_replace_all(tok$rendered, - fixed("$LEFT"), - "") + tok$rendered <- str_replace_fixed(tok$rendered, "$LEFT", "") tok$left_separator <- "" } } if (tok$right_operator) { if (tok_idx == length(tokens)) { - tok$rendered <- str_replace_all(tok$rendered, - fixed("$RIGHT"), - "phantom()") + tok$rendered <- str_replace_fixed(tok$rendered, "$RIGHT", "phantom()") } else { - tok$rendered <- str_replace_all(tok$rendered, - fixed("$RIGHT"), - "") - tokens[[tok_idx+1]]$left_separator <- "" + tok$rendered <- str_replace_fixed(tok$rendered, "$RIGHT", "") + tokens[[tok_idx + 1]]$left_separator <- "" } } if (length(tok$args) > 0) { for (argidx in seq_along(tok$args)) { - args <- render_latex(tok$args[[argidx]], user_defined, hack_parentheses=hack_parentheses) + args <- render_latex(tok$args[[argidx]], user_defined, + hack_parentheses = hack_parentheses) argfmt <- paste0("$arg", argidx) if (grepl(argfmt, tok$rendered, fixed = TRUE)) { - tok$rendered <- str_replace_all(tok$rendered, - fixed(argfmt), - args) + tok$rendered <- str_replace_fixed(tok$rendered, argfmt, args) } else { if (tok$rendered != "{}") { - tok$rendered <- paste0(tok$rendered, " * {", - args, "}") + tok$rendered <- paste0(tok$rendered, " * {", args, "}") } else { - tok$rendered <- paste0("{", args, "}") + tok$rendered <- paste0("{", args, "}") } } } } if (length(tok$optional_arg) > 0) { - optarg <- render_latex(tok$optional_arg, user_defined, hack_parentheses=hack_parentheses) + optarg <- render_latex(tok$optional_arg, user_defined, + hack_parentheses = hack_parentheses) if (grepl("$opt", tok$rendered, fixed = TRUE)) { - tok$rendered <- str_replace_all(tok$rendered, - fixed("$opt"), - optarg) + tok$rendered <- str_replace_fixed(tok$rendered, "$opt", optarg) } else { # the current token is not consuming an optional argument, so render # it as square brackets - tok$rendered <- paste0(tok$rendered, " * '[' *", - optarg, " * ']'") + tok$rendered <- paste0(tok$rendered, " * '[' *", optarg, " * ']'") } } @@ -460,19 +448,16 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { argfmt <- paste0("$", type) if (length(arg) > 0) { - rarg <- render_latex(arg, user_defined, hack_parentheses=hack_parentheses) + rarg <- render_latex(arg, user_defined, + hack_parentheses = hack_parentheses) if (grepl(argfmt, tok$rendered, fixed = TRUE)) { - tok$rendered <- str_replace_all(tok$rendered, fixed(argfmt), rarg) + tok$rendered <- str_replace_fixed(tok$rendered, argfmt, rarg) } else { if (type == "sup") { - tok$rendered <- sprintf("%s^{%s}", - tok$rendered, - rarg) + tok$rendered <- sprintf("%s^{%s}", tok$rendered, rarg) } else { - tok$rendered <- sprintf("%s[%s]", - tok$rendered, - rarg) + tok$rendered <- sprintf("%s[%s]", tok$rendered, rarg) } } @@ -501,10 +486,11 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { tok$left_separator <- "" tok$right_separator <- "" } - if (tok_idx > 1 && tokens[[tok_idx-1]]$command == "(") { + if (tok_idx > 1 && tokens[[tok_idx - 1]]$command == "(") { tok$left_separator <- "" } - if (tok_idx > 1 && tokens[[tok_idx]]$command == "(" && length(tokens[[tok_idx-1]]$sup_arg) > 0) { + if (tok_idx > 1 && tokens[[tok_idx]]$command == + "(" && length(tokens[[tok_idx - 1]]$sup_arg) > 0) { tok$left_separator <- "*" } } else { @@ -533,11 +519,11 @@ render_latex <- function(tokens, user_defined=list(), hack_parentheses=FALSE) { "" } else { paste0(tok$left_separator %??% "*", - tok$rendered, - tok$right_separator %??% "") + tok$rendered, + tok$right_separator %??% "") } }) - paste0(rendered_tokens, collapse="") + paste0(rendered_tokens, collapse = "") } # Validates the input LaTeX string @@ -560,9 +546,8 @@ validate_input <- function(latex_string) { } if (grepl("\\\\", latex_string, fixed = TRUE)) { - stop("The LaTeX string '", - latex_string, - "' includes a '\\\\' command. Line breaks are not currently supported.") + stop("The LaTeX string '", latex_string, + "' includes a '\\\\' command. Line breaks are not currently supported.") } test_string <- str_replace_fixed(latex_string, "\\{", "") @@ -581,11 +566,14 @@ validate_input <- function(latex_string) { } # check that the number of \left* and \right* commands match - lefts <- nrow(str_match_all(test_string, "[^\\\\]*\\\\left[\\(\\{\\|\\[\\.]")[[1]]) - rights <- nrow(str_match_all(test_string, "[^\\\\]*\\\\right[\\)\\}\\|\\]\\.]")[[1]]) + lefts <- nrow(str_match_all(test_string, + "[^\\\\]*\\\\left[\\(\\{\\|\\[\\.]")[[1]]) + rights <- nrow(str_match_all(test_string, + "[^\\\\]*\\\\right[\\)\\}\\|\\]\\.]")[[1]]) if (lefts != rights) { - stop("Mismatched number of \\left and \\right commands in '", latex_string, "' (", + stop("Mismatched number of \\left and \\right commands in '", + latex_string, "' (", lefts, " left commands, ", rights, " right commands.") } diff --git a/R/plots.R b/R/plots.R index ba66bb1..a8e416d 100644 --- a/R/plots.R +++ b/R/plots.R @@ -14,7 +14,7 @@ NULL #' @export #' @examples #' plot(TeX("Example equation: $a \\geq b$")) -plot.expression <- function(x, ..., main=NULL) { +plot.expression <- function(x, ..., main = NULL) { oldpar <- par(no.readonly = TRUE) dots <- list(...) on.exit(suppressWarnings(par(oldpar))) @@ -37,11 +37,11 @@ plot.expression <- function(x, ..., main=NULL) { #' @param ... Other parameters (not used) #' @return A data frame containing a table of supported LaTeX commands. #' @export -latex2exp_supported <- function(show=FALSE, ...) { +latex2exp_supported <- function(show = FALSE, ...) { dots <- list(...) - # the previous version of latex2exp accepted the parameter `plot` with the same - # meaning as show=TRUE. + # the previous version of latex2exp accepted the parameter `plot` with the + # same meaning as show = TRUE. if (!is.null(dots$plot) && dots$plot) { .Deprecated("Use the parameter show=TRUE instead of plot.") show <- TRUE @@ -49,14 +49,15 @@ latex2exp_supported <- function(show=FALSE, ...) { if (!show) { supp <- lapply(latex_supported, function(it) { - # remove all commands that include the '@' character, which are used internally to - # escape certain commands. + # remove all commands that include the '@' character, which are used + # internally to escape certain commands. names(it)[!grepl("@", names(it), fixed = TRUE)] }) supp <- mapply(function(category, commands) { # create an example suitable to demo each category of latex commands examples <- sapply(commands, function(commands) { - if (category %in% c("arithmetic operators", "binary operators", "arrows")) { + if (category %in% + c("arithmetic operators", "binary operators", "arrows")) { paste0("$\\alpha ", commands, " \\beta$") } else if (category == "set operators") { paste0("$A ", commands, " B$") @@ -82,7 +83,8 @@ latex2exp_supported <- function(show=FALSE, ...) { paste0("$", commands, "{\\Psi}$") } else if (category == "layout and spacing") { paste0("$A ", commands, " B$") - } else if (category == "parentheses" || category == "parentheses (not scalable)") { + } else if (category == "parentheses" || + category == "parentheses (not scalable)") { op <- commands if (commands == "\\left(") { clo <- "\\right)" @@ -106,11 +108,11 @@ latex2exp_supported <- function(show=FALSE, ...) { paste0("$", commands, "$") } }) - data.frame(category, command=commands, example=examples) + data.frame(category, command = commands, example = examples) }, names(supp), supp, SIMPLIFY = FALSE) - do.call(function(...) rbind.data.frame(..., make.row.names=FALSE, stringsAsFactors = FALSE), - supp) + do.call(function(...) rbind.data.frame(..., make.row.names = FALSE, + stringsAsFactors = FALSE), supp) } else { vignette("supported-commands", package = "latex2exp") } diff --git a/R/utils.R b/R/utils.R index 77ac5f3..e0ae6f2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -3,39 +3,43 @@ } str_replace_fixed <- function(string, pattern, replacement) { - str_replace_all(string, fixed(pattern), replacement) + #str_replace_all(string, fixed(pattern), replacement) + # This is a correct replacement of str_replace_all() only if one never + # uses a function for replacement... This is checked in v0.9.8 + gsub(pattern, replacement, string, fixed = TRUE) } -#' Prints out a parsed LaTeX object, as returned by TeX(..., output='ast'). +#' Prints out a parsed LaTeX object, as returned by TeX(..., output = 'ast'). #' This is primarily used for debugging. #' #' @param x The object #' @param depth Increases padding when recursing down the parsed structure #' @param ... (Ignored) #' @export -print.latextoken2 <- function(x, depth=0, ...) { +print.latextoken2 <- function(x, depth = 0, ...) { token <- x pad <- strrep(" ", depth) cat(pad, if (depth > 0) paste0("| :", token$command, ":"), if (!is.null(token$rendered)) paste0(" -> ", token$rendered), "\n", - sep="") + sep = "") - for (children_type in c("children", "args", "optional_arg", "sup_arg", "sub_arg")) { + for (children_type in + c("children", "args", "optional_arg", "sup_arg", "sub_arg")) { if (length(token[[children_type]]) > 0) { if (children_type != "children") { - cat(pad, "* <", children_type, ">", "\n", sep="") + cat(pad, "* <", children_type, ">", "\n", sep = "") } for (tok_idx in seq_along(token[[children_type]])) { c <- token[[children_type]][[tok_idx]] if (is.list(c)) { - cat(pad, " | [argument ", tok_idx, "]\n", sep="") + cat(pad, " | [argument ", tok_idx, "]\n", sep = "") for (cc in c) { - print(cc, depth+1) + print(cc, depth + 1) } } else { - print(c, depth+1) + print(c, depth + 1) } } } diff --git a/man/TeX.Rd b/man/TeX.Rd index 0b8c2e5..980c056 100644 --- a/man/TeX.Rd +++ b/man/TeX.Rd @@ -28,11 +28,11 @@ a plotmath expression ready for plotting), and "ast" (returns the tree used to generate the expression).} } \value{ -Returns a plotmath expression by default. The \code{output} parameter can -modify the type of the returned value. +Returns a plotmath expression by default. The \code{output} parameter +can modify the type of the returned value. -If more than one string is specified in the \code{input} parameter, returns a list -of expressions. +If more than one string is specified in the \code{input} parameter, returns a +list of expressions. } \description{ \code{TeX} converts a string comprising LaTeX commands (such as @@ -42,12 +42,13 @@ formatted text and equations. } \section{Adding new commands}{ -New LaTeX commands can be defined by supplying the \code{user_defined} parameter. -The \code{user_defined} parameter is a list that contains LaTeX commands -as names, and template strings as values. A LaTeX command that matches -one of the names is translated into the corresponding string and included in -the final plotmath expression. The file \code{symbols.R} in the source code -of this package contains one such table that can be used as a reference. +New LaTeX commands can be defined by supplying the \code{user_defined} +parameter. The \code{user_defined} parameter is a list that contains LaTeX +commands as names, and template strings as values. A LaTeX command that +matches one of the names is translated into the corresponding string and +included in the final plotmath expression. The file \code{symbols.R} in the +source code of this package contains one such table that can be used as a +reference. The template string can include one of the following special template parameters: @@ -70,8 +71,8 @@ TeX("$\\\\alpha$") # plots the greek alpha character TeX("The ratio of 1 and 2 is $\\\\frac{1}{2}$") a <- 1:100 -plot(a, a^2, xlab=TeX("$\\\\alpha$"), ylab=TeX("$\\\\alpha^2$")) +plot(a, a^2, xlab = TeX("$\\\\alpha$"), ylab = TeX("$\\\\alpha^2$")) # create a \\variance command that takes a single argument -TeX("$\\\\variance{X} = 10$", user_defined=list("\\\\variance"="sigma[$arg1]^2")) +TeX("$\\\\variance{X} = 10$", user_defined = list("\\\\variance" = "sigma[$arg1]^2")) } diff --git a/man/latex2exp.Rd b/man/latex2exp.Rd index f999b72..a9d14b0 100644 --- a/man/latex2exp.Rd +++ b/man/latex2exp.Rd @@ -7,9 +7,13 @@ latex2exp(string, output = c("expression", "character", "ast")) } \arguments{ -\item{string}{A character vector containing LaTeX expressions. Note that any backslashes must be escaped (e.g. "$\\alpha").} +\item{string}{A character vector containing LaTeX expressions. Note that any +backslashes must be escaped (e.g. "$\\alpha").} -\item{output}{The returned object, one of "expression" (default, returns a plotmath expression ready for plotting), "character" (returns the expression as a string), and "ast" (returns the tree used to generate the expression).} +\item{output}{The returned object, one of "expression" (default, returns a +plotmath expression ready for plotting), "character" (returns the +expression as a string), and "ast" (returns the tree used to generate the +expression).} } \value{ Returns an expression (see the \code{output} parameter). diff --git a/man/print.latextoken2.Rd b/man/print.latextoken2.Rd index f83f38d..d548e1a 100644 --- a/man/print.latextoken2.Rd +++ b/man/print.latextoken2.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/utils.R \name{print.latextoken2} \alias{print.latextoken2} -\title{Prints out a parsed LaTeX object, as returned by TeX(..., output='ast'). +\title{Prints out a parsed LaTeX object, as returned by TeX(..., output = 'ast'). This is primarily used for debugging.} \usage{ \method{print}{latextoken2}(x, depth = 0, ...) @@ -15,6 +15,6 @@ This is primarily used for debugging.} \item{...}{(Ignored)} } \description{ -Prints out a parsed LaTeX object, as returned by TeX(..., output='ast'). +Prints out a parsed LaTeX object, as returned by TeX(..., output = 'ast'). This is primarily used for debugging. } diff --git a/man/render_latex.Rd b/man/render_latex.Rd index bd94e0b..27586a1 100644 --- a/man/render_latex.Rd +++ b/man/render_latex.Rd @@ -9,13 +9,13 @@ render_latex(tokens, user_defined = list(), hack_parentheses = FALSE) \arguments{ \item{tokens}{tree of tokens} -\item{user_defined}{any custom definitions of commands passed to \code{\link{TeX}}} +\item{user_defined}{any custom definitions of commands passed to +\code{\link{TeX}}} -\item{hack_parentheses}{render parentheses using \code{group('(', phantom(), '.')} and -\code{group(')', phantom(), '.')}. This is useful to return -valid expressions when the LaTeX source contains mismatched -parentheses, but makes the returned expression much -less tidy.} +\item{hack_parentheses}{render parentheses using +\code{group('(', phantom(), '.')} and \code{group(')', phantom(), '.')}. +This is useful to return valid expressions when the LaTeX source contains +mismatched parentheses, but makes the returned expression much less tidy.} } \value{ String that should be parseable as a valid plotmath expression From 9e03e42e73b0df12c032fd2a11272e65b3243a84 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 13:04:35 +0200 Subject: [PATCH 05/10] str_replace() replaced by sub() --- NAMESPACE | 1 - R/latex2exp.R | 1 - R/parser.R | 4 ++-- R/plots.R | 2 +- man/TeX.Rd | 20 ++++++++++++-------- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 338d92e..20f1615 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,6 @@ importFrom(stringr,fixed) importFrom(stringr,str_length) importFrom(stringr,str_match) importFrom(stringr,str_match_all) -importFrom(stringr,str_replace) importFrom(stringr,str_split) importFrom(stringr,str_starts) importFrom(stringr,str_sub) diff --git a/R/latex2exp.R b/R/latex2exp.R index ac69447..c97cb6e 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,4 +1,3 @@ -#' @importFrom stringr str_replace #' @importFrom stringr str_split #' @importFrom stringr str_sub #' @importFrom stringr str_match diff --git a/R/parser.R b/R/parser.R index efdb57e..aeb0504 100644 --- a/R/parser.R +++ b/R/parser.R @@ -506,7 +506,7 @@ render_latex <- function(tokens, user_defined = list(), # If the token still starts with a "\", substitute it # with the corresponding expression - tok$rendered <- str_replace(tok$rendered, "^\\\\", "") + tok$rendered <- sub("^\\\\", "", tok$rendered) if (tok$rendered == "{}") { tok$skip <- TRUE @@ -541,7 +541,7 @@ validate_input <- function(latex_string) { repr <- deparse(possible_slash_pattern) message("latex2exp: Detected possible missing backslash: you entered ", repr, ", did you mean to type ", - str_replace(repr, fixed("\\"), "\\\\"), "?") + sub("\\\\?", "?", repr)) } } diff --git a/R/plots.R b/R/plots.R index a8e416d..4b37ce1 100644 --- a/R/plots.R +++ b/R/plots.R @@ -97,7 +97,7 @@ latex2exp_supported <- function(show = FALSE, ...) { } else if (commands == "\\left.") { clo <- "\\right." } else if (grepl("^\\\\l", commands)) { - clo <- paste0("\\r", str_replace(commands, fixed("\\l"), "")) + clo <- paste0("\\r", sub("\\l", "", commands, fixed = TRUE)) } else if (grepl("^\\\\r", commands)) { return(NA) } else { diff --git a/man/TeX.Rd b/man/TeX.Rd index 980c056..03946d8 100644 --- a/man/TeX.Rd +++ b/man/TeX.Rd @@ -54,16 +54,19 @@ The template string can include one of the following special template parameters: \itemize{ -\item \code{$arg1, $arg2, ...} represent the first, second, ... brace argument. - E.g. for \code{\\frac{x}{y}}, \code{$arg1} is \code{x} and \code{$arg2} is \code{y}. +\item \code{$arg1, $arg2, ...} represent the first, second, ... brace + argument. E.g. for \code{\\frac{x}{y}}, \code{$arg1} is \code{x} and + \code{$arg2} is \code{y}. \item \code{$opt} is an optional argument in square brackets. E.g. for \code{\\sqrt[2]{x}}, \code{$opt} is \code{2}. -\item \code{$sub} and \code{$sup} are arguments in the exponent (\code{^}) or subscript (\code{_}) - following the current expression. E.g. for \code{\\sum^{x}}, \code{$sup} is \code{x}. -\item \code{$LEFT} and \code{$RIGHT} are substituted the previous and following LaTeX expression - relative to the current token. +\item \code{$sub} and \code{$sup} are arguments in the exponent (\code{^}) or + subscript (\code{_}) following the current expression. E.g. for + \code{\\sum^{x}}, \code{$sup} is \code{x}. +\item \code{$LEFT} and \code{$RIGHT} are substituted the previous and + following LaTeX expression relative to the current token. } -See the Examples section for an example of using the \code{user_defined} option. +See the Examples section for an example of using the \code{user_defined} +option. } \examples{ @@ -74,5 +77,6 @@ a <- 1:100 plot(a, a^2, xlab = TeX("$\\\\alpha$"), ylab = TeX("$\\\\alpha^2$")) # create a \\variance command that takes a single argument -TeX("$\\\\variance{X} = 10$", user_defined = list("\\\\variance" = "sigma[$arg1]^2")) +TeX("$\\\\variance{X} = 10$", + user_defined = list("\\\\variance" = "sigma[$arg1]^2")) } From 4cb29838b848c8a9fdfe7153092fbe9c6b016088 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 13:08:20 +0200 Subject: [PATCH 06/10] str_split() replaced by strsplit() --- NAMESPACE | 1 - R/latex2exp.R | 1 - R/parser.R | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 20f1615..5d704c9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,6 @@ importFrom(stringr,fixed) importFrom(stringr,str_length) importFrom(stringr,str_match) importFrom(stringr,str_match_all) -importFrom(stringr,str_split) importFrom(stringr,str_starts) importFrom(stringr,str_sub) importFrom(stringr,str_trim) diff --git a/R/latex2exp.R b/R/latex2exp.R index c97cb6e..b261961 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,4 +1,3 @@ -#' @importFrom stringr str_split #' @importFrom stringr str_sub #' @importFrom stringr str_match #' @importFrom stringr str_trim diff --git a/R/parser.R b/R/parser.R index aeb0504..f47a6bf 100644 --- a/R/parser.R +++ b/R/parser.R @@ -41,7 +41,7 @@ clone_token <- function(tok) { } .find_substring_matching <- function(string, opening, closing) { - chars <- str_split(string, "")[[1]] + chars <- strsplit(string, "", fixed = TRUE)[[1]] depth <- 0 start_expr <- -1 From e0af9cd600119bea56bbb0bee7fd8979ce213774 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 13:31:18 +0200 Subject: [PATCH 07/10] str_starts() replaced by startsWith() --- NAMESPACE | 2 -- R/latex2exp.R | 3 --- R/parser.R | 30 +++++++++++++++--------------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 5d704c9..fb12439 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,5 @@ importFrom(stringr,fixed) importFrom(stringr,str_length) importFrom(stringr,str_match) importFrom(stringr,str_match_all) -importFrom(stringr,str_starts) -importFrom(stringr,str_sub) importFrom(stringr,str_trim) importFrom(utils,vignette) diff --git a/R/latex2exp.R b/R/latex2exp.R index b261961..1b1b3f6 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,7 +1,4 @@ -#' @importFrom stringr str_sub -#' @importFrom stringr str_match #' @importFrom stringr str_trim -#' @importFrom stringr str_starts #' @importFrom stringr str_match #' @importFrom stringr str_match_all #' @importFrom stringr fixed diff --git a/R/parser.R b/R/parser.R index f47a6bf..733f369 100644 --- a/R/parser.R +++ b/R/parser.R @@ -6,7 +6,7 @@ tok$sub_arg <- list() tok$children <- list() tok$command <- command - tok$is_command <- str_starts(command, fixed("\\")) + tok$is_command <- startsWith(command, "\\") tok$text_mode <- text_mode tok$left_operator <- tok$right_operator <- FALSE class(tok) <- "latextoken2" @@ -34,7 +34,7 @@ clone_token <- function(tok) { "]+") ret <- str_match(string, pattern)[1,1] if ((is.na(ret) || nchar(ret) == 0) && nchar(string) > 0) { - str_sub(string, 1, 1) + substring(string, 1, 1) } else { ret } @@ -54,7 +54,7 @@ clone_token <- function(tok) { } else if (chars[i] == closing) { depth <- depth - 1 if (depth == 0) { - return(str_sub(string, start_expr + 1, i - 1)) + return(substring(string, start_expr + 1, i - 1)) } } } @@ -109,13 +109,13 @@ parse_latex <- function(latex_string, text_mode = TRUE, depth = 0, pos = 0, withCallingHandlers({ while (i <= nchar(latex_string)) { # Look at current character, previous character, and next character - ch <- str_sub(latex_string, i, i) - prevch <- if (i == 1) "" else str_sub(latex_string, i - 1, i - 1) + ch <- substring(latex_string, i, i) + prevch <- if (i == 1) "" else substring(latex_string, i - 1, i - 1) nextch <- if (i == nchar(latex_string)) "" else - str_sub(latex_string, i + 1, i + 1) + substring(latex_string, i + 1, i + 1) # LaTeX string left to be processed - current_fragment <- str_sub(latex_string, i) + current_fragment <- substring(latex_string, i) cat_trace("Position: ", i, " ch: ", ch, " next: ", nextch, " current fragment: ", current_fragment, @@ -133,7 +133,7 @@ parse_latex <- function(latex_string, text_mode = TRUE, depth = 0, pos = 0, # another backslash, or a separator, or a dollar if (ch == "\\" && nextch != "\\") { # Continue until we encounter a separator - current_fragment <- str_sub(current_fragment, 2) + current_fragment <- substring(current_fragment, 2) command <- paste0("\\", .find_substring(current_fragment, .math_separators)) @@ -192,14 +192,14 @@ parse_latex <- function(latex_string, text_mode = TRUE, depth = 0, pos = 0, # If there are spaces after the ^ or _ character, # consume them and advance past the spaces if (nextch == " ") { - n_spaces <- str_match(str_sub(current_fragment, 2), "\\s+")[1, 1] + n_spaces <- str_match(substring(current_fragment, 2), "\\s+")[1, 1] advance <- advance + nchar(n_spaces) - nextch <- str_sub(current_fragment, advance + 1, advance + 1) + nextch <- substring(current_fragment, advance + 1, advance + 1) } # Sub or sup arguments grouped with braces. This is easy! if (nextch == "{") { - argument <- .find_substring_matching(str_sub(current_fragment, + argument <- .find_substring_matching(substring(current_fragment, advance + 1), "{", "}") # advance by two units (the content of the braces + two braces) @@ -207,10 +207,10 @@ parse_latex <- function(latex_string, text_mode = TRUE, depth = 0, pos = 0, } else if (nextch == "\\") { # Advance until a separator is found argument <- paste0("\\", - .find_substring(str_sub(current_fragment, advance + 2), separators)) + .find_substring(substring(current_fragment, advance + 2), separators)) advance <- advance + nchar(argument) } else { - argument <- str_sub(current_fragment, advance + 1, advance + 1) + argument <- substring(current_fragment, advance + 1, advance + 1) advance <- advance + nchar(argument) } @@ -381,8 +381,8 @@ render_latex <- function(tokens, user_defined = list(), } else { tok$rendered <- split[1, 2] } - if (str_starts(tok$rendered, "0") && str_length(tok$rendered) > 1) { - tok$rendered <- paste0("0*", str_sub(tok$rendered, 2)) + if (startsWith(tok$rendered, "0") && str_length(tok$rendered) > 1) { + tok$rendered <- paste0("0*", substring(tok$rendered, 2)) } } From 249f72f07109bb65a61cef4d862e18bc4b37c07f Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 13:35:24 +0200 Subject: [PATCH 08/10] str_length() replaced by nchar() --- NAMESPACE | 2 -- R/latex2exp.R | 2 -- R/parser.R | 4 ++-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index fb12439..327c235 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,8 +13,6 @@ importFrom(graphics,plot.new) importFrom(graphics,plot.window) importFrom(graphics,text) importFrom(stringr,fixed) -importFrom(stringr,str_length) importFrom(stringr,str_match) importFrom(stringr,str_match_all) -importFrom(stringr,str_trim) importFrom(utils,vignette) diff --git a/R/latex2exp.R b/R/latex2exp.R index 1b1b3f6..c8bac5c 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,8 +1,6 @@ -#' @importFrom stringr str_trim #' @importFrom stringr str_match #' @importFrom stringr str_match_all #' @importFrom stringr fixed -#' @importFrom stringr str_length NULL #' Deprecated; use \code{\link{TeX}} instead. diff --git a/R/parser.R b/R/parser.R index 733f369..d6b5284 100644 --- a/R/parser.R +++ b/R/parser.R @@ -348,7 +348,7 @@ render_latex <- function(tokens, user_defined = list(), #next } else if (!tok$text_mode || tok$is_command) { # translate using the translation table in symbols.R - translations[[str_trim(tok$command)]] %??% tok$command + translations[[trimws(tok$command)]] %??% tok$command } else { # leave as-is tok$command @@ -381,7 +381,7 @@ render_latex <- function(tokens, user_defined = list(), } else { tok$rendered <- split[1, 2] } - if (startsWith(tok$rendered, "0") && str_length(tok$rendered) > 1) { + if (startsWith(tok$rendered, "0") && nchar(tok$rendered) > 1) { tok$rendered <- paste0("0*", substring(tok$rendered, 2)) } } From d76f0d1768690411f5df13ca5e2d700aa6e59783 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 17:11:05 +0200 Subject: [PATCH 09/10] Dependency to stringr eliminated --- DESCRIPTION | 3 +- NAMESPACE | 3 -- R/latex2exp.R | 3 -- R/parser.R | 86 +++++++++++++++++++++----------- vignettes/supported-commands.Rmd | 31 ++++++------ vignettes/using-latex2exp.Rmd | 36 ++++++------- 6 files changed, 92 insertions(+), 70 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ed2bec8..7b125ac 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -10,10 +10,9 @@ Description: Parses and converts LaTeX math formulas to R's plotmath License: MIT + file LICENSE URL: https://www.stefanom.io/latex2exp/, https://github.com/stefano-meschiari/latex2exp BugReports: https://github.com/stefano-meschiari/latex2exp/issues -Imports: - stringr Encoding: UTF-8 Suggests: + tools, testthat, waldo, knitr, diff --git a/NAMESPACE b/NAMESPACE index 327c235..7680047 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -12,7 +12,4 @@ importFrom(graphics,par) importFrom(graphics,plot.new) importFrom(graphics,plot.window) importFrom(graphics,text) -importFrom(stringr,fixed) -importFrom(stringr,str_match) -importFrom(stringr,str_match_all) importFrom(utils,vignette) diff --git a/R/latex2exp.R b/R/latex2exp.R index c8bac5c..1db0b49 100644 --- a/R/latex2exp.R +++ b/R/latex2exp.R @@ -1,6 +1,3 @@ -#' @importFrom stringr str_match -#' @importFrom stringr str_match_all -#' @importFrom stringr fixed NULL #' Deprecated; use \code{\link{TeX}} instead. diff --git a/R/parser.R b/R/parser.R index d6b5284..8eb042a 100644 --- a/R/parser.R +++ b/R/parser.R @@ -29,15 +29,27 @@ clone_token <- function(tok) { } .find_substring <- function(string, boundary_characters) { - pattern <- paste0("^[^", - paste0("\\", boundary_characters, collapse = ""), - "]+") - ret <- str_match(string, pattern)[1,1] - if ((is.na(ret) || nchar(ret) == 0) && nchar(string) > 0) { - substring(string, 1, 1) - } else { - ret - } + # This appears overly complex, based on the value returned by str_match() + #pattern <- paste0("^[^", + # paste0("\\", boundary_characters, collapse = ""), + # "]+") + #ret <- str_match(string, pattern)[1,1] + #if ((is.na(ret) || nchar(ret) == 0) && nchar(string) > 0) { + # substring(string, 1, 1) + #} else { + # ret + #} + # Empty string is returned as such + if (nchar(string) == 0) + return("") + # Boundary characters at the beginning of the string is returned + first_char <- substring(string, 1, 1) + if (first_char %in% boundary_characters) + return(first_char) + # Otherwise, anything, starting from a boundary character is eliminated + boundary_pattern <- paste0("\\", boundary_characters, collapse = "") + pattern <- paste0("[", boundary_pattern, "].*$") + sub(pattern, "", string, perl = TRUE) } .find_substring_matching <- function(string, opening, closing) { @@ -192,7 +204,9 @@ parse_latex <- function(latex_string, text_mode = TRUE, depth = 0, pos = 0, # If there are spaces after the ^ or _ character, # consume them and advance past the spaces if (nextch == " ") { - n_spaces <- str_match(substring(current_fragment, 2), "\\s+")[1, 1] + #n_spaces <- str_match(substring(current_fragment, 2), "\\s+")[1, 1] + n_spaces <- regmatches(current_fragment, + regexpr("\\s+", substring(current_fragment, 2))) advance <- advance + nchar(n_spaces) nextch <- substring(current_fragment, advance + 1, advance + 1) } @@ -332,7 +346,9 @@ render_latex <- function(tokens, user_defined = list(), tok$rendered <- if (grepl("^\\\\ESCAPED@", tok$command)) { # a character, like '!' or '?' was escaped as \\ESCAPED@ASCII_SYMBOL. # return it as a string. - arg <- str_match(tok$command, "@(\\d+)")[1,2] + #arg <- str_match(tok$command, "@(\\d+)")[1,2] + arg <- substring(regmatches(tok$command, + regexpr("@(\\d+)", tok$command)), 2) arg <- intToUtf8(arg) if (arg == "'") { @@ -374,16 +390,19 @@ render_latex <- function(tokens, user_defined = list(), # the rest of the string. This is because a plotmath symbol # cannot start with a number. if (grepl("^[0-9]", tok$rendered) && !tok$text_mode) { - split <- str_match(tok$rendered, "(^[0-9\\.]*)(.*)") + # This is ultra-complex for something simple using sub() + #split <- str_match(tok$rendered, "(^[0-9\\.]*)(.*)") + #if (split[1, 3] != "") { + # tok$rendered <- paste0(split[1, 2], "*", split[1, 3]) + #} else { + # tok$rendered <- split[1, 2] + #} + tok$rendered <- sub("^([0-9\\.]+)(.+)", "\\1*\\2", tok$rendered) - if (split[1, 3] != "") { - tok$rendered <- paste0(split[1, 2], "*", split[1, 3]) - } else { - tok$rendered <- split[1, 2] - } - if (startsWith(tok$rendered, "0") && nchar(tok$rendered) > 1) { - tok$rendered <- paste0("0*", substring(tok$rendered, 2)) - } + # This is not needed any more with sub() + #if (startsWith(tok$rendered, "0") && nchar(tok$rendered) > 1) { + # tok$rendered <- paste0("0*", substring(tok$rendered, 2)) + #} } tok$left_operator <- grepl("$LEFT", tok$rendered, fixed = TRUE) @@ -553,11 +572,20 @@ validate_input <- function(latex_string) { test_string <- str_replace_fixed(latex_string, "\\{", "") test_string <- str_replace_fixed(test_string, "\\}", "") + n_match_all <- function(x, pattern) { + res <- gregexpr(pattern, x, perl = TRUE)[[1]] + if (length(res) == 1 && res == -1) 0 else length(res) + } + # check that opened and closed braces match in number - opened_braces <- nrow(str_match_all(test_string, "[^\\\\]*?(\\{)")[[1]]) - - nrow(str_match_all(test_string, "\\\\left\\{")[[1]]) - closed_braces <- nrow(str_match_all(test_string, "[^\\\\]*?(\\})")[[1]]) - - nrow(str_match_all(test_string, "\\\\right\\}")[[1]]) + #opened_braces <- nrow(str_match_all(test_string, "[^\\\\]*?(\\{)")[[1]]) - + # nrow(str_match_all(test_string, "\\\\left\\{")[[1]]) + opened_braces <- n_match_all(test_string, "[^\\\\]*?(\\{)") - + n_match_all(test_string, "\\\\left\\{") + #closed_braces <- nrow(str_match_all(test_string, "[^\\\\]*?(\\})")[[1]]) - + # nrow(str_match_all(test_string, "\\\\right\\}")[[1]]) + closed_braces <- n_match_all(test_string, "[^\\\\]*?(\\})") - + n_match_all(test_string, "\\\\right\\}") if (opened_braces != closed_braces) { stop("Mismatched number of braces in '", latex_string, "' (", @@ -566,10 +594,12 @@ validate_input <- function(latex_string) { } # check that the number of \left* and \right* commands match - lefts <- nrow(str_match_all(test_string, - "[^\\\\]*\\\\left[\\(\\{\\|\\[\\.]")[[1]]) - rights <- nrow(str_match_all(test_string, - "[^\\\\]*\\\\right[\\)\\}\\|\\]\\.]")[[1]]) + #lefts <- nrow(str_match_all(test_string, + # "[^\\\\]*\\\\left[\\(\\{\\|\\[\\.]")[[1]]) + lefts <- n_match_all(test_string, "[^\\\\]*\\\\left[\\(\\{\\|\\[\\.]") + #rights <- nrow(str_match_all(test_string, + # "[^\\\\]*\\\\right[\\)\\}\\|\\]\\.]")[[1]]) + rights <- n_match_all(test_string, "[^\\\\]*\\\\right[\\)\\}\\|\\]\\.]") if (lefts != rights) { stop("Mismatched number of \\left and \\right commands in '", diff --git a/vignettes/supported-commands.Rmd b/vignettes/supported-commands.Rmd index f046272..b588c00 100644 --- a/vignettes/supported-commands.Rmd +++ b/vignettes/supported-commands.Rmd @@ -12,7 +12,6 @@ vignette: > ```{r, setup, message=FALSE, echo=FALSE} library(latex2exp) library(reactable) -library(stringr) library(purrr) library(dplyr) library(htmltools) @@ -21,43 +20,43 @@ library(htmltools) ```{r, echo=FALSE} # Saves the plot to PNG and embeds it in an tag using base64 data. # from https://stackoverflow.com/questions/50244709/how-to-store-r-ggplot-graph-as-html-code-snippet -encodeGraphic <- function(g, ..., style="") { - png(tf1 <- tempfile(fileext = ".png"), ...) +encodeGraphic <- function(g, ..., style = "") { + png(tf1 <- tempfile(fileext = ".png"), ...) force(g) dev.off() # Close the file. - txt <- RCurl::base64Encode(readBin(tf1, "raw", file.info(tf1)[1, "size"]), "txt") - myImage <- htmltools::img(src=sprintf("data:image/png;base64,%s", txt), style=style) + txt <- RCurl::base64Encode(readBin(tf1, "raw", file.info(tf1)[1, "size"]), "txt") + myImage <- htmltools::img(src = sprintf("data:image/png;base64,%s", txt), style = style) return(myImage) } supported <- latex2exp_supported() -supported$category <- str_to_title(supported$category) +supported$category <- tools::toTitleCase(supported$category) supported <- filter(supported, !is.na(example)) supported$command <- supported$example ``` ```{r, echo=FALSE} reactable(supported, - searchable=TRUE, - compact=TRUE, - pagination=FALSE, - style="background-color:inherit", + searchable = TRUE, + compact = TRUE, + pagination = FALSE, + style = "background-color:inherit", columns = list( category = colDef("Category"), - command = colDef("LaTeX command", cell=function(content) { + command = colDef("LaTeX command", cell = function(content) { pre(code("TeX(r\"(", strong(content), ")\")")) }, minWidth = 200), - example = colDef("Example", cell=function(content, ...) { + example = colDef("Example", cell = function(content, ...) { tryCatch({ div( div( - encodeGraphic(plot(TeX(content), cex=4.25), - width=600, height=150, - style="max-height:70px; max-width:100%") + encodeGraphic(plot(TeX(content), cex = 4.25), + width = 600, height = 150, + style = "max-height:70px; max-width:100%") ) ) - }, error=function(e) { + }, error = function(e) { warning("Couldn't render ", content, " because of error ", e) }) }) diff --git a/vignettes/using-latex2exp.Rmd b/vignettes/using-latex2exp.Rmd index 6010edd..15c3122 100644 --- a/vignettes/using-latex2exp.Rmd +++ b/vignettes/using-latex2exp.Rmd @@ -10,10 +10,9 @@ vignette: > \usepackage[utf8]{inputenc} --- ```{r, include=FALSE} -knitr::opts_chunk$set(fig.width=7, fig.height=5, fig.retina=2) +knitr::opts_chunk$set(fig.width = 7, fig.height = 5, fig.retina = 2) library(latex2exp) library(reactable) -library(stringr) library(purrr) library(dplyr) library(ggplot2) @@ -47,42 +46,43 @@ TeX("\\textbf{Euler's identity} is $e^{i\\pi} + 1 = 0$.") You can quickly preview what a translated LaTeX string would look like by using `plot`: ```{r plot-formula, fig.height=2, fig.width=5} -plot(TeX(r'(A $\LaTeX$ formula: $\frac{2hc^2}{\lambda^5}\frac{1}{e^{\frac{hc}{\lambda k_B T}} - 1}$)'), cex=2, main="") +plot(TeX(r'(A $\LaTeX$ formula: $\frac{2hc^2}{\lambda^5}\frac{1}{e^{\frac{hc}{\lambda k_B T}} - 1}$)'), cex = 2, main = "") ``` The following example shows plotting in base graphics: ```{r base-plot, warning=FALSE} -x <- seq(0, 4, length.out=100) +x <- seq(0, 4, length.out = 100) alpha <- 1:5 -plot(x, xlim=c(0, 4), ylim=c(0, 10), - xlab='x', ylab=TeX(r'($\alpha x^\alpha$, where $\alpha \in \{1 \ldots 5\}$)'), - type='n', main=TeX(r'(Using $\LaTeX$ for plotting in base graphics!)', bold=TRUE)) +plot(x, xlim = c(0, 4), ylim = c(0, 10), + xlab = 'x', ylab = TeX(r'($\alpha x^\alpha$, where $\alpha \in \{1 \ldots 5\}$)'), + type = 'n', main = TeX(r'(Using $\LaTeX$ for plotting in base graphics!)', bold = TRUE)) for (a in alpha) { - lines(x, a*x^a, col=a) + lines(x, a*x^a, col = a) } legend('topleft', - legend=TeX(sprintf(r'($\alpha = %d$)', alpha)), - lwd=1, - col=alpha) + legend = TeX(sprintf(r'($\alpha = %d$)', alpha)), + lwd = 1, + col = alpha) ``` This example shows plotting in [ggplot2](https://ggplot2.tidyverse.org): + ```{r ggplot, warning=FALSE} -x <- seq(0, 4, length.out=100) +x <- seq(0, 4, length.out = 100) alpha <- 1:5 -data <- map_df(alpha, ~ tibble(v=.*x^., x=x, alpha=.)) +data <- map_df(alpha, ~ tibble(v = .*x^., x = x, alpha = .)) -p <- ggplot(data, aes(x=x, y=v, color=as.factor(alpha))) + +p <- ggplot(data, aes(x = x, y = v, color = as.factor(alpha))) + geom_line() + ylab(TeX(r'($\alpha x^\alpha$, where $\alpha \in 1\ldots 5$)')) + ggtitle(TeX(r'(Using $\LaTeX$ for plotting in ggplot2. I $\heartsuit$ ggplot!)')) + - coord_cartesian(ylim=c(-1, 10)) + - guides(color=guide_legend(title=NULL)) + - scale_color_discrete(labels=lapply(sprintf(r'($\alpha = %d$)', alpha), TeX)) + coord_cartesian(ylim = c(-1, 10)) + + guides(color = guide_legend(title = NULL)) + + scale_color_discrete(labels = lapply(sprintf(r'($\alpha = %d$)', alpha), TeX)) # Note that ggplot2 legend labels must be lists of expressions, not vectors of expressions print(p) @@ -92,5 +92,5 @@ print(p) Here are a few examples of what you can do with **latex2exp**: ```{r examples, fig.width=7, fig.height=6} -latex2exp_examples(cex=0.9) +latex2exp_examples(cex = 0.9) ``` From 393bccde72ee15535b09c605f87b25b8d8670153 Mon Sep 17 00:00:00 2001 From: Philippe Grosjean Date: Fri, 5 Jul 2024 18:31:04 +0200 Subject: [PATCH 10/10] Correction of numbers --- R/parser.R | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/R/parser.R b/R/parser.R index 8eb042a..8a6970a 100644 --- a/R/parser.R +++ b/R/parser.R @@ -397,12 +397,13 @@ render_latex <- function(tokens, user_defined = list(), #} else { # tok$rendered <- split[1, 2] #} - tok$rendered <- sub("^([0-9\\.]+)(.+)", "\\1*\\2", tok$rendered) + tok$rendered <- sub("^([0-9\\.]+)([^0-9\\.].*)", "\\1*\\2", tok$rendered) - # This is not needed any more with sub() - #if (startsWith(tok$rendered, "0") && nchar(tok$rendered) > 1) { - # tok$rendered <- paste0("0*", substring(tok$rendered, 2)) - #} + if (startsWith(tok$rendered, "0") && nchar(tok$rendered) > 1) { + tok$rendered <- paste0("0*", substring(tok$rendered, 2)) + } + # I need this to avoid double zeros before the decimal point + tok$rendered <- gsub("0*.", "0.", tok$rendered, fixed = TRUE) } tok$left_operator <- grepl("$LEFT", tok$rendered, fixed = TRUE)