Skip to content

Commit

Permalink
add footnote_options (#23)
Browse files Browse the repository at this point in the history
* add footnote_options

* update NEWS.md
  • Loading branch information
atusy committed Oct 20, 2020
1 parent 38f78d0 commit 944fb1d
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 96 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export("%>%")
export(as_flextable)
export(as_paragraph_md)
export(colformat_md)
export(footnote_options)
export(separate_header)
export(span_header)
export(with_blanks)
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ftExtra 0.0.3.2
# ftExtra 0.0.3.3

* Support markdown footnote with `colformat_md`. Currently, one footnote per a cell is allowed, and it must be located at the end of the cell content (#22).
* Add `footnote_options()` to configure options for footnotes (#23).
* Support single- and double-quotes by a lua filter (dfc82e0).

# ftExtra 0.0.3
Expand Down
43 changes: 15 additions & 28 deletions R/as-paragraph-md.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
header <- flextable::as_paragraph("")[[1L]][-1L, ]

vertical_align <- function(sup, sub) {
.f <- rep(FALSE, max(1, length(sup), length(sub)))
.f <- rep(FALSE, max(1L, length(sup), length(sub)))
sup <- sup %||% .f
sub <- sub %||% .f
dplyr::if_else(
Expand Down Expand Up @@ -33,22 +33,22 @@ image_size <- function(x, y = "width") {
parse_md <- function(x,
.from = "markdown",
auto_color_link = "blue",
.env_footnotes = NULL) {
if (!is.character(auto_color_link) || length(auto_color_link) != 1) {
.footnote_options = NULL) {
if (!is.character(auto_color_link) || length(auto_color_link) != 1L) {
stop("`auto_color_link` must be a string")
}

md_df <- md2df(x, .from = .from)

if (is.null(.env_footnotes) || (all(names(md_df) != "Note"))) {
if (is.null(.footnote_options) || (all(names(md_df) != "Note"))) {
y <- md_df
} else {
.env_footnotes$n <- .env_footnotes$n + 1L
ref <- data.frame(txt = .env_footnotes$ref[[.env_footnotes$n]],
.footnote_options$n <- .footnote_options$n + 1L
ref <- data.frame(txt = .footnote_options$ref[[.footnote_options$n]],
Superscript = TRUE,
stringsAsFactors = FALSE)
.env_footnotes$value <- c(
.env_footnotes$value,
.footnote_options$value <- c(
.footnote_options$value,
list(construct_chunk(as.list(dplyr::bind_rows(ref, md_df[md_df$Note, ])),
auto_color_link))
)
Expand All @@ -58,16 +58,6 @@ parse_md <- function(x,
construct_chunk(as.list(y), auto_color_link)
}

md2df <- function(x, .from) {
ast <- md2ast(x, .from = .from)

if ((ast$blocks[[1]]$t != "Para") || (length(ast$blocks) > 1)) {
stop("Markdown text must be a single paragraph")
}

ast2df(ast)
}

construct_chunk <- function(x, auto_color_link = "blue") {
dplyr::bind_rows(
header,
Expand Down Expand Up @@ -97,8 +87,7 @@ construct_chunk <- function(x, auto_color_link = "blue") {
#' @param auto_color_link A color of the link texts.
#' @param .from
#' Pandoc's `--from` argument (default: `'markdown+autolink_bare_uris'`).
#' @param .env_footnotes
#' Internally used by `colformat_md`.
#' @param .footnote_options Spec options for footnotes via `colformat_md`.
#'
#' @examples
#' if (rmarkdown::pandoc_available()) {
Expand All @@ -116,12 +105,10 @@ construct_chunk <- function(x, auto_color_link = "blue") {
as_paragraph_md <- function(x,
auto_color_link = "blue",
.from = "markdown+autolink_bare_uris",
.env_footnotes = NULL) {
structure(
lapply(x, parse_md,
.from = .from,
auto_color_link = auto_color_link,
.env_footnotes = .env_footnotes),
class = "paragraph"
)
.footnote_options = NULL) {
structure(lapply(x, parse_md,
.from = .from,
auto_color_link = auto_color_link,
.footnote_options = .footnote_options),
class = "paragraph")
}
12 changes: 12 additions & 0 deletions R/ast2df.R
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,15 @@ ast2df <- function(x) {
tibble::as_tibble() %>%
dplyr::mutate_if(is.logical, dplyr::coalesce, FALSE)
}

#' Convert Pandoc's Markdown to data frame
#' @noRd
md2df <- function(x, .from) {
ast <- md2ast(x, .from = .from)

if ((ast$blocks[[1]]$t != "Para") || (length(ast$blocks) > 1)) {
stop("Markdown text must be a single paragraph")
}

ast2df(ast)
}
83 changes: 30 additions & 53 deletions R/colformat.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
#' @param x A `flextable` object
#' @param j Columns to be treated as markdown texts.
#' Selection can be done by the semantics of `dplyr::select()`.
#' @param footnote_ref
#' One of "1", "a", "A", "i", "I", or "*" to as a choice for a symbol to
#' cross-reference footnotes.
#' @param footnote_max
#' A max number of footnotes.
#' @param footnote_inline
#' Whether to append footnotes on the same line (default: `FALSE`).
#' @param footnote_sep
#' A separator of footnotes when `footnote_inline = TRUE` (default: `"; "`).
#' @param part
#' One of "body", "header", and "all". If "all", formatting proceeds in the
#' order of "header" and "body".
#' @param .footnote_options
#' Options for footnotes generated by `footnote_options()`.
#' @inheritParams as_paragraph_md
#'
#' @examples
Expand All @@ -26,65 +22,46 @@
#' @export
colformat_md <- function(x,
j = where(is.character),
part = c("body", "header", "all"),
auto_color_link = "blue",
footnote_ref = c("1", "a", "A", "i", "I", "*"),
footnote_max = 26,
footnote_inline = FALSE,
footnote_sep = "; ",
.footnote_options = footnote_options(),
.from = "markdown+autolink_bare_uris"
) {
body <- x$body$dataset
.j <- rlang::enexpr(j)
col <- names(tidyselect::eval_select(rlang::expr(c(!!.j)), body))
part <- match.arg(part)
.footnote_options$caller <- "colformat_md"

if (length(col) == 0) {
if (part == "all") {
for (part in c("header", "body")) {
x <- colformat_md(x, j = !!.j, part = part,
auto_color_link = auto_color_link,
.footnote_options = .footnote_options,
.from = .from)
.footnote_options$value <- list()
}
return(x)
}

footnotes <- new.env()
footnotes$ref <- generate_ref(footnote_ref, footnote_max)
footnotes$value <- list()
footnotes$n <- 0L
dataset <- x[[part]]$dataset
col <- names(tidyselect::eval_select(rlang::expr(c(!!.j)), dataset))

if (length(col) == 0) {
return(x)
}

ft <- flextable::compose(x, i = seq(nrow(body)), j = col, part = "body",
# Must evaluate outside add_footnotes due to lazy evaluation of arguments
ft <- flextable::compose(x,
i = seq(nrow(dataset)), j = col, part = part,
value = as_paragraph_md(
unlist(body[col], use.names = FALSE),
unlist(dataset[col], use.names = FALSE),
auto_color_link = auto_color_link,
.from = .from, .env_footnotes = footnotes
.from = .from,
.footnote_options = .footnote_options
))

if (footnotes$n == 0L) {
return(ft)
}

pos <- rep(1L, footnotes$n)
flextable::footnote(ft, i = pos, j = pos, part = "body",
value = structure(footnotes$value, class = "paragraph"),
ref_symbols = rep("", footnotes$n),
inline = footnote_inline,
sep = footnote_sep)
add_footnotes(ft, part, .footnote_options)
}

where <- function(...) {
tidyselect::vars_select_helpers$where(...)
}

ref_generators <- list(
`1` = function(n) as.character(seq(n)),
a = function(n) letters[seq(n)],
A = function(n) LETTERS[seq(n)],
i = function(n) tolower(as.roman(seq(n))),
I = function(n) as.roman(seq(n)),
`*` = function(n) vapply(seq(n),
function(i) paste(rep("*", i), collapse = ""),
NA_character_)

)

generate_ref <- function(ref, n) {
ref <- match.arg(as.character(ref), names(ref_generators))
if ((ref %in% c("a", "A")) && (n > 26)) {
stop('If `footnote_symbol` is "a" or "A", `footnote_max` must be <= 26')
}
ref_generators[[ref]](n)
}
69 changes: 69 additions & 0 deletions R/footnote.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#' Options for footnotes
#'
#' Configure options for footnotes.
#'
#' @param ref
#' One of "1", "a", "A", "i", "I", or "*" to as a choice for a symbol to
#' cross-reference footnotes.
#' @param start
#' A starting number of footnotes.
#' @param max
#' A max number of footnotes.
#' @inheritParams flextable::footnote
#'
#' @return An environment
#'
#' @examples
#' footnote_options("1", start = 1L)
#'
#' @export
footnote_options <- function(ref = c("1", "a", "A", "i", "I", "*"),
prefix = "",
suffix = "",
start = 1L,
max = 26L,
inline = FALSE,
sep = "; ") {
env <- new.env()
env$ref <- generate_ref(ref, max, prefix, suffix)
env$value <- list()
env$n <- start - 1L
env$inline <- inline
env$sep <- sep
env
}

ref_generators <- list(
`1` = function(n) as.character(seq(n)),
a = function(n) letters[seq(n)],
A = function(n) LETTERS[seq(n)],
i = function(n) tolower(as.roman(seq(n))),
I = function(n) as.roman(seq(n)),
`*` = function(n) vapply(seq(n),
function(i) paste(rep("*", i), collapse = ""),
NA_character_)
)

generate_ref <- function(ref, n, prefix, suffix) {
ref <- match.arg(as.character(ref), names(ref_generators))
if ((ref %in% c("a", "A")) && (n > 26)) {
stop('If `footnote_symbol` is "a" or "A", `footnote_max` must be <= 26')
}
paste0(prefix, ref_generators[[ref]](n), suffix)
}

add_footnotes <- function(x, part, .footnote_options) {
n <- length(.footnote_options$value)

if (n == 0L) {
return(x)
}

pos <- rep(1L, n)
flextable::footnote(x, i = pos, j = pos, part = part,
value = structure(.footnote_options$value,
class = "paragraph"),
ref_symbols = rep("", n),
inline = .footnote_options$inline,
sep = .footnote_options$sep)
}
4 changes: 2 additions & 2 deletions man/as_paragraph_md.Rd

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

18 changes: 6 additions & 12 deletions man/colformat_md.Rd

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

38 changes: 38 additions & 0 deletions man/footnote_options.Rd

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

0 comments on commit 944fb1d

Please sign in to comment.