Skip to content

Add save_flex_docx() to save gtsummary tables and flextables to Word#2437

Merged
ddsjoberg merged 20 commits into
mainfrom
feat/as_flex_word
Jul 6, 2026
Merged

Add save_flex_docx() to save gtsummary tables and flextables to Word#2437
ddsjoberg merged 20 commits into
mainfrom
feat/as_flex_word

Conversation

@ddsjoberg

@ddsjoberg ddsjoberg commented Jul 5, 2026

Copy link
Copy Markdown
Owner

What changes are proposed in this pull request?

Added save_flex_docx() to save a gtsummary table or a flextable to a Word (.docx) file via flextable. The header and footer arguments (both default FALSE) optionally place the table caption in the Word document's page header and the footnotes, source notes, and abbreviations in the page footer; when FALSE, that content is rendered as part of the table. 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. The pr_section argument accepts an officer::prop_section() for fine-grained control of the Word section (page margins, size, orientation, columns).

If there is an GitHub issue associated with this pull request, please provide link.

None.


Summary of changes

  • New export save_flex_docx() — writes a gtsummary table or a flextable to a Word .docx file via flextable.
  • Accepted input — a gtsummary table, a tbl_split object, a flextable, or a plain list of flextables.
  • header / footer arguments (default FALSE) — optionally place the table caption in the Word page header and the footnotes, source notes, and abbreviations in the page footer. When FALSE (the default), that content is rendered as part of the table. When relocated, in-cell footnote reference symbols are retained and only the footnote text is moved. For a flextable, the caption (flextable::set_caption()) is relocated to the Word header and the footer part (flextable::add_footer_lines()) to the Word footer.
  • page / page_location arguments — add a page-number line (e.g. "Page {PAGE} of {NUMPAGES}") to a chosen header/footer region and alignment.
  • Collection support — a tbl_split object or a plain list of flextables writes each table to its own Word section and page.
  • pr_section argument (and 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, 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".
  • Font matching / inheritance — when relocated, 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; each Word region also inherits the styling applied to the corresponding flextable part (e.g. flextable::fontsize(size = 6, part = "footer") yields a size-6 Word footer).
  • Documented limitation — the function help notes that relocated header/footer content is rendered as plain text (markdown/HTML not interpreted, emphasis stripped; only font family/size carried over).
  • Tests added in tests/testthat/test-save_flex_docx.R, including the tbl_split/list, pr_section, and page-line paths (skips when flextable/officer are unavailable).
  • NEWS.md bullet added; NAMESPACE, man/save_flex_docx.Rd, and the pkgdown reference index regenerated/updated.

Reviewer Checklist (if item does not apply, mark is as complete)

  • PR branch has pulled the most recent updates from main branch.
  • If a bug was fixed, a unit test was added.
  • Run pkgdown::build_site(). Check the R console for errors, and review the rendered website.
  • Code coverage is suitable for any new functions/features: devtools::test_coverage()
  • usethis::use_spell_check() runs with no spelling errors in documentation
  • All GitHub Action workflows pass with a ✅

When the branch is ready to be merged into master:

  • Update NEWS.md with the changes from this pull request under the heading "# gtsummary (development version)". If there is an issue associated with the pull request, reference it in parentheses at the end update (see NEWS.md for examples).
  • Increment the version number using usethis::use_version(which = "dev")
  • Run usethis::use_spell_check() again
  • Approve Pull Request
  • Merge the PR. Please use "Squash and merge".

ddsjoberg and others added 11 commits July 5, 2026 03:45
Add as_flex_word() to write a gtsummary table to a Word (.docx) file via
flextable, with the table caption optionally placed in the page header and
footnotes/source notes/abbreviations in the page footer. The page and
page_location arguments add a page-number line to a chosen header/footer
region. tbl_split objects are accepted, writing each split table to its own
Word section and page.

Co-authored-by: Ona <no-reply@ona.com>
Co-authored-by: Ona <no-reply@ona.com>
The Word header/footer regions (caption, footnotes, source notes,
abbreviations, and the page-number line) rendered in the Word template
default font (Cambria) while the flextable body used its own default,
producing a font mismatch. Resolve the flextable body font via
get_flextable_defaults() and apply it (family and size) to every
header/footer run, including the {PAGE}/{NUMPAGES} field runs.

Co-authored-by: Ona <no-reply@ona.com>
Add three ways to customize the Word output:

- addl_cmds: a list of flextable command expressions inserted into the
  call list from as_flex_table(return_calls = TRUE) before evaluation.
  Named entries insert after the matching call; unnamed entries are
  appended. Applied after the as_flex_table-lst:addl_cmds theme commands.
- header_style / footer_style: named officer::fp_text() property lists
  that style the Word header and footer regions separately. Each region
  resolves as base body font -> theme element -> argument (argument wins),
  merged via update.fp_text so unspecified properties are inherited. The
  page-number line follows its region's resolved style.
- New theme elements as_flex_word-lst:header_style and
  as_flex_word-lst:footer_style backing the styling arguments.

Applies to both single gtsummary tables and tbl_split objects.

Co-authored-by: Ona <no-reply@ona.com>
…rd()

Each Word region now inherits the styling applied to the corresponding
flextable part, so e.g. flextable::fontsize(size = 6, part = "footer")
yields a size-6 Word footer. Precedence is body font < theme style <
extracted part font < argument style; header_style/footer_style still win
and the theme elements apply only when the part carries no styling.

The Word header font size follows the body font unless the header part
size is explicitly changed, since as_flex_table() always bakes a fixed
header size. Sub-family properties (hansi/cs/eastasia) are collapsed onto
the extracted font.family to avoid mismatched ascii/hAnsi Word runs.

Also filter removed/replaced footnotes in .flex_word_footer_lines() so
remove_footnote_*() no longer leaks spurious NA lines into the footer.

Co-authored-by: Ona <no-reply@ona.com>
Rename the exported function, its theme elements
(as_flex_word-lst:* -> save_flex_docx-lst:*), internal helpers, tests,
docs, and the pkgdown reference entry. Regenerate R/sysdata.rda from the
theme-elements CSV and man/ via roxygen.

Co-authored-by: Ona <no-reply@ona.com>
Each table's section already ends with a "nextPage" section break, which
starts the following section on a new page. The additional explicit
body_add_break() therefore inserted an extra blank page between tables.
Remove the explicit page break and rely on the section break for
pagination.

Co-authored-by: Ona <no-reply@ona.com>
body_end_block_section() appends a trailing empty paragraph and a closing
section, so ending the last table that way rendered an extra blank page.
Close sections for all tables except the last with a block section, and
set the last table's header/footer as the document's default section via
body_set_default_section(), which adds no trailing paragraph/section.

Co-authored-by: Ona <no-reply@ona.com>
…_flex_docx()

.flex_docx_footer_lines() filtered footnote removals/replacements but did
not evaluate a body footnote's `rows` predicate. For tbl_split_by_rows(),
a footnote scoped to rows absent from a section still appeared in that
section's Word footer (e.g. "2 my label" on a page without the referenced
row). Evaluate the `rows` predicate against the table body and drop
footnotes matching no rows, mirroring as_flex_table().

Co-authored-by: Ona <no-reply@ona.com>
Generalize save_flex_docx() to also accept a flextable object or a plain
list of flextables, in addition to gtsummary tables and tbl_split objects.
For a flextable, the caption is relocated to the Word header and the footer
part to the Word footer, matching the gtsummary behavior. Remove the
include/addl_cmds arguments (handle upstream) in favor of passing a
pre-built flextable.

Co-authored-by: Ona <no-reply@ona.com>
Add a pr_section argument (and matching save_flex_docx-lst:pr_section theme
element) accepting an officer::prop_section() to control page margins, page
size, orientation, and section columns. The header/footer regions remain
managed by save_flex_docx() (relocated caption/notes), so any
header_default/footer_default on pr_section are ignored. For collections the
same section is applied to every table with the paging type fixed to
'nextPage' to avoid blank pages. The argument overrides the theme element.

Co-authored-by: Ona <no-reply@ona.com>
ddsjoberg and others added 3 commits July 5, 2026 13:49
Add pr_section for fine-grained Word section control in save_flex_docx()
Word header/footer regions already inherit the styling of the corresponding
flextable part (e.g. flextable::fontsize(size = 6, part = 'footer') yields a
size-6 Word footer), so the explicit header_style/footer_style arguments and
their paired save_flex_docx-lst:header_style / save_flex_docx-lst:footer_style
theme elements are redundant. Users style the header/footer by styling the
flextable parts before saving. The body-font base and part-font inheritance
are unchanged.

Co-authored-by: Ona <no-reply@ona.com>
Add a pr_section argument (and matching save_flex_docx-lst:pr_section theme
element) accepting an officer::prop_section() to control page margins, page
size, orientation, and section columns. The header/footer regions remain
managed by save_flex_docx() (relocated caption/notes), so any
header_default/footer_default on pr_section are ignored. For collections the
same section is applied to every table with the paging type fixed to
'nextPage' to avoid blank pages. The argument overrides the theme element.

Co-authored-by: Ona <no-reply@ona.com>
@ddsjoberg ddsjoberg force-pushed the feat/as_flex_word branch from a98401e to 821c33f Compare July 5, 2026 21:15
@ddsjoberg ddsjoberg changed the title Add as_flex_word() to save gtsummary tables to Word Add save_flex_docx() to save gtsummary tables and flextables to Word Jul 6, 2026
ddsjoberg and others added 4 commits July 5, 2026 19:29
The header/footer defaults changed to FALSE; these tests relied on the
old TRUE defaults to relocate content into the Word header/footer regions.

Co-authored-by: Ona <no-reply@ona.com>
@ddsjoberg ddsjoberg marked this pull request as ready for review July 6, 2026 04:16
@ddsjoberg ddsjoberg merged commit adc9664 into main Jul 6, 2026
10 checks passed
@ddsjoberg ddsjoberg deleted the feat/as_flex_word branch July 6, 2026 04:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant