Skip to content

Commit

Permalink
Merge pull request #111 from ropensci/feature-add-get-protected
Browse files Browse the repository at this point in the history
add get_protected()
  • Loading branch information
zkamvar authored May 27, 2024
2 parents e13bbf3 + 1eb117e commit 2615835
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 7 deletions.
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
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\""
)
})

0 comments on commit 2615835

Please sign in to comment.