Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add get_protected() #111

Merged
merged 11 commits into from
May 27, 2024
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Imports:
magrittr,
purrr,
R6,
rlang (>= 0.4.5),
xml2,
xslt,
yaml
Expand All @@ -55,5 +56,5 @@ Config/testthat/edition: 3
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
RoxygenNote: 7.3.1
VignetteBuilder: knitr
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

export(find_between)
export(get_protected)
export(md_ns)
export(protect_curly)
export(protect_math)
Expand Down
16 changes: 16 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# tinkr 0.2.0.9000

## NEW FEATURES

* `get_protected()` function (and yarn method) will return nodes which have
been protected in some way by {tinkr} via one of the `protect_` family of
functions. Adopting this pattern is preferred over using
`md:text[@asis='true']` as the attribute names may change in the future
(@zkamvar, #111; reviewed: @maelle)
* Block math will now include the delimiters and the softbreaks for protection
(issue/review: #113, @maelle; implemented: #111, @zkamvar)

## NEW IMPORTS

* We now import {rlang} for error handling. Because we already import {purrr},
this does not impact the dependency footprint (suggested: @maelle, #111;
implemented: @zkamvar, #111).

## BUG FIX

* Bare links in Markdown (e.g. `<https://example.com/one>`) are no longer
Expand Down
16 changes: 11 additions & 5 deletions R/asis-nodes.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protect_math <- function(body, ns = md_ns()) {
}

set_asis <- function(nodes) {
xml2::xml_set_attr(nodes[xml2::xml_name(nodes) != "softbreak"], "asis", "true")
xml2::xml_set_attr(nodes, "asis", "true")
}

# INLINE MATH ------------------------------------------------------------------
Expand Down Expand Up @@ -178,8 +178,8 @@ fix_partial_inline <- function(tag, body, ns) {
# paste the lines together and create new nodes
n <- length(math_lines)
char <- as.character(math_lines)
char[[1]] <- sub("[$]", "$</text><text asis='true'>", char[[1]])
char[[n]] <- sub("[<]text ", "<text asis='true' ", char[[n]])
char[[1]] <- sub("[$]", "$</text><text asis='true' math='true'>", char[[1]])
char[[n]] <- sub("[<]text ", "<text asis='true' math='true' ", char[[n]])
nodes <- paste(char, collapse = "")
nodes <- make_text_nodes(nodes)
# add the new nodes to the bottom of the existing math lines
Expand All @@ -198,7 +198,7 @@ fix_fully_inline <- function(math) {
# <text>this is </text><text asis='true'>$\LaTeX$</text><text> text</text>
char <- gsub(
pattern = inline_dollars_regex("full"),
replacement = "</text><text asis='true'>\\1</text><text>",
replacement = "</text><text asis='true' math='true'>\\1</text><text>",
x = char,
perl = TRUE
)
Expand Down Expand Up @@ -246,7 +246,12 @@ make_text_nodes <- function(txt) {
# BLOCK MATH ------------------------------------------------------------------

find_block_math <- function(body, ns) {
find_between(body, ns, pattern = "md:text[contains(text(), '$$')]", include = FALSE)
# https://github.com/ropensci/tinkr/issues/113#issue-2302065427
find_between(body,
ns,
pattern = "md:text[contains(text(), '$$')]",
include = TRUE
)
}

find_between_inlines <- function(body, ns, tag) {
Expand All @@ -259,6 +264,7 @@ protect_block_math <- function(body, ns) {
# get all of the internal nodes
bm <- xml2::xml_find_all(bm, ".//descendant-or-self::md:*", ns = ns)
set_asis(bm)
xml2::xml_set_attr(bm, "math", "true")
}

# TICK BOXES -------------------------------------------------------------------
Expand Down
26 changes: 26 additions & 0 deletions R/class-yarn.R
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,32 @@ yarn <- R6::R6Class("yarn",
message("to use the `protect_unescaped()` method, you will need to re-read your document with `yarn$new(sourcepos = TRUE)`")
}
invisible(self)
},
#' @description Return nodes whose contents are protected from being escaped
#' @param type a character vector listing the protections to be included.
#' Defaults to `NULL`, which includes all protected nodes:
#' - math: via the [protect_math()] function
#' - curly: via the `protect_curly()` function
#' - unescaped: via the `protect_unescaped()` function
#'
#' @examples
#' path <- system.file("extdata", "basic-curly.md", package = "tinkr")
#' ex <- tinkr::yarn$new(path, sourcepos = TRUE)
#' # protect curly braces
#' ex$protect_curly()
#' # add math and protect it
#' ex$add_md(c("## math\n",
#' "$c^2 = a^2 + b^2$\n",
#' "$$",
#' "\\sum_{i}^k = x_i + 1",
#' "$$\n")
#' )
#' ex$protect_math()
#' # get protected now shows all the protected nodes
#' ex$get_protected()
#' ex$get_protected(c("math", "curly")) # only show the math and curly
get_protected = function(type = NULL) {
get_protected(self$body, type = type, self$ns)
}
),
private = list(
Expand Down
41 changes: 41 additions & 0 deletions R/get_protected.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#' Get protected nodes
#'
#' @param body an `xml_document` object
#' @param type a character vector listing the protections to be included.
#' Defaults to `NULL`, which includes all protected nodes:
#' - math: via the `protect_math()` function
#' - curly: via the `protect_curly()` function
#' - unescaped: via the `protect_unescaped()` function
#' @param ns the namespace of the document (defaults to [md_ns()])
#' @return an `xml_nodelist` object.
#' @export
#' @examples
#' path <- system.file("extdata", "basic-curly.md", package = "tinkr")
#' ex <- tinkr::yarn$new(path, sourcepos = TRUE)
#' # protect curly braces
#' ex$protect_curly()
#' # add math and protect it
#' ex$add_md(c("## math\n",
#' "$c^2 = a^2 + b^2$\n",
#' "$$",
#' "\\sum_{i}^k = x_i + 1",
#' "$$\n")
#' )
#' ex$protect_math()
#' # get protected now shows all the protected nodes
#' get_protected(ex$body)
#' get_protected(ex$body, c("math", "curly")) # only show the math and curly
get_protected <- function(body, type = NULL, ns = md_ns()) {
protections <- c(
math = "@math",
curly = "@curly",
unescaped = "(@asis and text()='[' or text()=']')"
)
if (!is.null(type)) {
keep <- rlang::arg_match(type, names(protections), multiple = TRUE)
} else {
keep <- TRUE
}
xpath <- sprintf(".//node()[%s]", paste(protections[keep], collapse = " or "))
xml2::xml_find_all(body, xpath, ns = ns)
}
44 changes: 44 additions & 0 deletions man/get_protected.Rd

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

2 changes: 1 addition & 1 deletion man/protect_unescaped.Rd

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

66 changes: 66 additions & 0 deletions man/yarn.Rd

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

6 changes: 6 additions & 0 deletions tests/testthat/test-asis-nodes.R
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ test_that("block math can be protected", {
expect_snapshot(show_user(m$protect_math()$tail(48), force = TRUE))
expect_length(xml2::xml_ns(m$body), 1L)
expect_equal(md_ns()[[1]], xml2::xml_ns(m$body)[[1]])
# 3 math blocks with code examples
expect_length(grep("$$", m$show(), fixed = TRUE), 12)
# 3 math delimiters included in the get_protected
expect_equal(sum(xml2::xml_text(m$get_protected("math")) == "$$"), 6)


})


Expand Down
45 changes: 45 additions & 0 deletions tests/testthat/test-get_protected.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
test_that("protected nodes can be accessed", {
path <- withr::local_tempfile()
# five protected curly elements
curlies <- c("## curlies",
"\nThis line has {xml2} one and {tinkr} two curlies!",
"\n![a pretty kitten](https://placekitten.com/200/300){#kitteh alt='a picture of a kitten'}",
"\n![a pretty puppy](https://placedog.net/200/300){#dog alt=\"a picture",
"of a dog\"}",
# two protected unescaped elements
"\n[span with attributes]{.span-with-attributes ",
"style='color: red'}",
""
)
# six protected math elements
math <- c("## math",
"\n$c^2 = a^2 + b^2$", # 1
"\n$$", # 2
# 3 <softbreak>
"\\sum_{i}^k = x_i + 1", # 4
# 5 <softbreak>
"$$", # 6
""
)
writeLines(c(curlies, "\n", math), path)
ex <- tinkr::yarn$new(path, sourcepos = TRUE)
# we should have two protected elements right off due to the braces
zkamvar marked this conversation as resolved.
Show resolved Hide resolved
expect_length(ex$get_protected(), 2)

# one inline math, two softbreaks, one line of block
ex$protect_math()
expect_length(ex$get_protected(), 2 + 6)

# we should have six protected curly nodes
ex$protect_curly()
expect_length(ex$get_protected(), 2 + 6 + 5)

expect_length(ex$get_protected("curly"), 5)
expect_length(ex$get_protected("math"), 6)
expect_length(ex$get_protected("unescaped"), 2)

expect_error(ex$get_protected(c("curly", "shemp")),
"not \"shemp\""
)
})

Loading