Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# gtsummary (development version)

* Added `save_flex_docx()` to save a gtsummary table or a flextable to a Word (`.docx`) file via flextable. The `header` and `footer` arguments place the table caption in the Word document's page header and the footnotes, source notes, and abbreviations in the page footer (in-cell footnote reference symbols are retained). The `page` and `page_location` arguments add a page-number line (e.g. `"Page {PAGE} of {NUMPAGES}"`) to a chosen header/footer region and alignment. A collection of tables is also accepted—a `tbl_split` object (from `tbl_split_by_rows()`/`tbl_split_by_columns()`) or a plain list of flextables—writing each table to its own Word section and page. For a flextable, its caption (`flextable::set_caption()`) is relocated to the Word header and its footer part (`flextable::add_footer_lines()`) to the Word footer. The caption, footnotes, source notes, abbreviations, and page-number line in the Word header/footer match the table body font (family and size) instead of the Word template default. By default each Word region also inherits the styling applied to the corresponding flextable part, so e.g. `flextable::fontsize(size = 6, part = "footer")` yields a size-6 Word footer. The `header_style` and `footer_style` arguments (and the `save_flex_docx-lst:header_style`/`save_flex_docx-lst:footer_style` theme elements) accept named `officer::fp_text()` property lists to style the Word header and footer regions separately, overriding the inherited styling; the theme elements apply when the part carries no styling to inherit.
* Added `save_flex_docx()` to save a gtsummary table or a flextable to a Word (`.docx`) file via flextable. The `header` and `footer` arguments place the table caption in the Word document's page header and the footnotes, source notes, and abbreviations in the page footer (in-cell footnote reference symbols are retained). The `page` and `page_location` arguments add a page-number line (e.g. `"Page {PAGE} of {NUMPAGES}"`) to a chosen header/footer region and alignment. A collection of tables is also accepted—a `tbl_split` object (from `tbl_split_by_rows()`/`tbl_split_by_columns()`) or a plain list of flextables—writing each table to its own Word section and page. For a flextable, its caption (`flextable::set_caption()`) is relocated to the Word header and its footer part (`flextable::add_footer_lines()`) to the Word footer. The caption, footnotes, source notes, abbreviations, and page-number line in the Word header/footer match the table body font (family and size) instead of the Word template default. By default each Word region also inherits the styling applied to the corresponding flextable part, so e.g. `flextable::fontsize(size = 6, part = "footer")` yields a size-6 Word footer. The `header_style` and `footer_style` arguments (and the `save_flex_docx-lst:header_style`/`save_flex_docx-lst:footer_style` theme elements) accept named `officer::fp_text()` property lists to style the Word header and footer regions separately, overriding the inherited styling; the theme elements apply when the part carries no styling to inherit. The `pr_section` argument (and the `save_flex_docx-lst:pr_section` theme element) accepts an `officer::prop_section()` object for fine-grained control of the Word section—page margins, page size, orientation, and columns—while the header/footer regions remain managed by `save_flex_docx()`; for a collection the same section is applied to every table with the paging `type` fixed to `"nextPage"`.

* `modify_abbreviation()` and `remove_abbreviation()` now accept a character vector of abbreviations, allowing multiple abbreviations to be added or removed in a single call. `modify_abbreviation()` also gains `prefix`, `sep1`, and `sep2` arguments to customize the abbreviation source note's leading text (e.g. `c("Abbr.", "Abbrs.")`), the separator between the prefix and the abbreviations (e.g. `": "`), and the separator between abbreviations (e.g. `"; "`). Defaults are also configurable via the `modify_abbreviation-arg:prefix`, `modify_abbreviation-arg:sep1`, and `modify_abbreviation-arg:sep2` theme elements. (#2172)

Expand Down
79 changes: 71 additions & 8 deletions R/save_flex_docx.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@
#' styling to inherit (e.g. an empty footer). The page-number line adopts the
#' style of whichever region it is placed in (via `page_location`). Default is
#' `NULL`.
#' @param pr_section (`officer::prop_section`)\cr
#' an optional [`officer::prop_section()`] object used as the base Word section,
#' giving fine-grained control over page margins, page size, orientation, and
#' section columns (e.g.
#' `officer::prop_section(page_margins = officer::page_mar(top = 0.5))`). The
#' section's header and footer regions are always managed by `save_flex_docx()`
#' (the relocated caption and notes), so any `header_default`/`footer_default`
#' set on `pr_section` are ignored. For a collection (`tbl_split` or a list of
#' flextables) the same `pr_section` is applied to every table's section, and
#' the paging `type` is fixed to `"nextPage"` (any `type` on `pr_section` is
#' ignored) so tables page correctly. Overrides the
#' `save_flex_docx-lst:pr_section` theme element. Default is `NULL`.
#' @param ... These dots are for future extensions and must be empty.
#'
#' @export
Expand Down Expand Up @@ -104,6 +116,15 @@
#' as_flex_table(tbl) |>
#' flextable::set_caption("Table 1")
#' save_flex_docx(ft, path = tempfile(fileext = ".docx"))
#'
#' # customize the Word page margins via a prop_section()
#' save_flex_docx(
#' tbl,
#' path = tempfile(fileext = ".docx"),
#' pr_section = officer::prop_section(
#' page_margins = officer::page_mar(top = 0.5, bottom = 0.5)
#' )
#' )
save_flex_docx <- function(x,
path,
header = TRUE,
Expand All @@ -115,6 +136,7 @@ save_flex_docx <- function(x,
),
header_style = NULL,
footer_style = NULL,
pr_section = NULL,
...) {
set_cli_abort_call()

Expand Down Expand Up @@ -146,6 +168,18 @@ save_flex_docx <- function(x,
.check_flex_docx_style(footer_style, "footer_style")
check_pkg_installed(c("flextable", "officer"))

# resolve `pr_section` with argument-over-theme precedence, then validate. the
# resolved base section controls page margins/size/orientation/columns; its
# header/footer defaults are later overwritten by the relocated caption/notes.
pr_section <- pr_section %||% get_theme_element("save_flex_docx-lst:pr_section", eval = TRUE)
if (!is.null(pr_section) && !inherits(pr_section, "prop_section")) {
cli::cli_abort(
"The {.arg pr_section} argument must be an {.cls officer::prop_section}
object (e.g. from {.fn officer::prop_section}) or {.code NULL}.",
call = get_cli_abort_call()
)
}

# collections: one section (with its own header/footer) per table ------------
if (inherits(x, "tbl_split") || is_flextable_list) {
if (length(x) == 0L) {
Expand All @@ -163,7 +197,8 @@ save_flex_docx <- function(x,
page = page,
page_location = page_location,
header_style = header_style,
footer_style = footer_style
footer_style = footer_style,
pr_section = pr_section
)
)
}
Expand All @@ -181,13 +216,19 @@ save_flex_docx <- function(x,
)

# write the Word file --------------------------------------------------------
# `prop_section()` is only supplied when a region has content; otherwise
# `save_as_docx()` uses its default section.
if (length(built$header_fpars) > 0L || length(built$footer_fpars) > 0L) {
# a section is supplied when a region has content, or when the caller passed a
# `pr_section` (so custom page margins/size apply even with no caption/notes).
# otherwise `save_as_docx()` uses its default section.
has_content <- length(built$header_fpars) > 0L || length(built$footer_fpars) > 0L
if (has_content || !is.null(pr_section)) {
flextable::save_as_docx(
built$ft,
path = path,
pr_section = .flex_docx_prop_section(built$header_fpars, built$footer_fpars)
pr_section = .flex_docx_prop_section(
built$header_fpars,
built$footer_fpars,
base = pr_section
)
)
} else {
flextable::save_as_docx(built$ft, path = path)
Expand All @@ -207,7 +248,8 @@ save_flex_docx <- function(x,
#' @keywords internal
#' @noRd
.save_flex_docx_collection <- function(x, path, header, footer, page, page_location,
header_style = NULL, footer_style = NULL) {
header_style = NULL, footer_style = NULL,
pr_section = NULL) {
doc <- officer::read_docx()

for (i in seq_along(x)) {
Expand All @@ -224,10 +266,14 @@ save_flex_docx <- function(x,

doc <- flextable::body_add_flextable(doc, built$ft)

# every section uses the same base `pr_section` (page margins/size/etc.), but
# `type = "nextPage"` is forced so tables page correctly without blank pages,
# overriding any `type` set in the user's `pr_section`.
section <-
.flex_docx_prop_section(
built$header_fpars,
built$footer_fpars,
base = pr_section,
type = "nextPage"
)

Expand Down Expand Up @@ -369,11 +415,28 @@ save_flex_docx <- function(x,
#' @param header_fpars,footer_fpars (`list`)\cr lists of `officer::fpar` objects
#' @param ... additional arguments passed to `officer::prop_section()` (e.g.
#' `type`)
#' @param base (`officer::prop_section` or `NULL`)\cr an optional user-supplied
#' section whose properties (page margins, size, orientation, columns, and
#' `type`) are used as the base. Its `header_default`/`footer_default` are
#' always discarded: `save_flex_docx()` owns those regions.
#' @return an `officer::prop_section` object
#' @keywords internal
#' @noRd
.flex_docx_prop_section <- function(header_fpars, footer_fpars, ...) {
section_args <- list(...)
.flex_docx_prop_section <- function(header_fpars, footer_fpars, base = NULL, ...) {
# start from the user's base section fields (dropping its header/footer
# defaults, which we always own), then let `...` overrides win (e.g. the forced
# `type = "nextPage"` for collections), and finally attach our relocated
# caption/notes as the header/footer defaults.
section_args <- list()
if (!is.null(base)) {
base_fields <- unclass(base)
base_fields[c(
"header_default", "header_even", "header_first",
"footer_default", "footer_even", "footer_first"
)] <- NULL
section_args <- base_fields
}
section_args <- utils::modifyList(section_args, list(...))
if (length(header_fpars) > 0L) {
section_args$header_default <- do.call(officer::block_list, header_fpars)
}
Expand Down
Binary file modified R/sysdata.rda
Binary file not shown.
1 change: 1 addition & 0 deletions data-raw/gtsummary_theme_elements.csv
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ FALSE,add_stat_label,add_stat_label-arg:location,TRUE,FALSE,,
FALSE,as_flex_table,as_flex_table-lst:addl_cmds,FALSE,TRUE,"named list of expressions of {flextable} package commands inserted in the `as_flex_table()` call; do not include the `data=` argument; the list of expressions is inserted after the named call, e.g. if the named list element is `""tab_spanner""` the expressions will be inserted after expressions named `""tab_spanner""`; to see the names of the expressions run `as_flex_table(x, return_calls = TRUE)` and check the names.","list(autofit = list(rlang::expr(flextable::font(fontname = ""Bodoni 72"", part = ""all"")), rlang::expr(flextable::fontsize(size = 8, part = ""all""))))"
FALSE,save_flex_docx,save_flex_docx-lst:header_style,FALSE,TRUE,"named list of {officer} `fp_text()` properties (e.g. `list(font.size = 8, font.family = ""Arial"")`) used to style the text in the Word document header region created by `save_flex_docx()`; properties are merged onto the table body font, so unspecified properties are inherited; values set in the `header_style` argument override this element.","list(font.size = 8)"
FALSE,save_flex_docx,save_flex_docx-lst:footer_style,FALSE,TRUE,"named list of {officer} `fp_text()` properties (e.g. `list(font.size = 8, font.family = ""Arial"")`) used to style the text in the Word document footer region created by `save_flex_docx()`; properties are merged onto the table body font, so unspecified properties are inherited; values set in the `footer_style` argument override this element.","list(font.size = 8)"
FALSE,save_flex_docx,save_flex_docx-lst:pr_section,FALSE,TRUE,"an {officer} `prop_section()` object used as the base Word section for `save_flex_docx()`, controlling page margins, page size, orientation, and section columns; the section's header/footer defaults are always managed by `save_flex_docx()` (the relocated caption/notes) and cannot be set here, and in a collection the paging `type` is fixed to `""nextPage""`; the `pr_section` argument overrides this element.","officer::prop_section(page_margins = officer::page_mar(top = 0.5, bottom = 0.5))"
FALSE,as_gt,as_gt-lst:addl_cmds,FALSE,TRUE,"named list of expressions of {gt} package commands inserted in the `as_gt()` call; do not include the `data=` argument; the list of expressions is inserted after the named call, e.g. if the named list element is `""tab_spanner""` the expressions will be inserted after expressions named `""tab_spanner""`; to see the names of the expressions run `as_gt(x, return_calls = TRUE)` and check the names.",list(tab_spanner = rlang::expr(gt::tab_options(table.font.size = 'small')))
FALSE,as_hux_table,as_hux_table.gtsummary-lst:addl_cmds,FALSE,TRUE,"named list of expressions of {huxtable} package commands inserted in the `as_hux_table()` call; do not include the `data=` argument; the list of expressions is inserted after the named call, e.g. if the named list element is `""tab_spanner""` the expressions will be inserted after expressions named `""tab_spanner""`; to see the names of the expressions run `as_hux_table(x, return_calls = TRUE)` and check the names.",
FALSE,as_kable,as_kable-arg:dots,TRUE,FALSE,"named list of arguments passed to `knitr::kable()`, which also applies to calls from `as_kable_extra()`","list(booktabs = TRUE, longtable = TRUE, linesep = """")"
Expand Down
23 changes: 23 additions & 0 deletions man/save_flex_docx.Rd

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

Loading
Loading