Skip to content

Instantly share code, notes, and snippets.

@mnazarov
Last active March 1, 2018 23:59
Show Gist options
  • Save mnazarov/75c2c21048e8aca9bc5acb27e0234d85 to your computer and use it in GitHub Desktop.
Save mnazarov/75c2c21048e8aca9bc5acb27e0234d85 to your computer and use it in GitHub Desktop.
Use flextable with rmarkdown for Word (docx) output

Update (nov 8, 2017)

Implementation idea

  • Code from flextable::body_add_flextable generates an XML code for a table to be inserted into Word document.
  • In pandoc 2.0 (not yet released) raw blocks can be inserted, in particular openxml blocks for 'raw docx output' (based on raw_attribute extension, see jgm/pandoc#3537)

So, to print flextable in docx document with rmarkdown we need a function that will print flextables xml into a raw openxml block. This can be achieved with either of:

  • a custom renderer function that can be supplied to the render chunk option
  • a knit_print method for flextable class.

Advantage: clean integration of flextable and rmarkdown

Drawback: pandoc 2.0 is not yet released, so need to be built from source to use this method. Moreover some syntax is changed and rmarkdown does not support it yet, so there can be issues (e.g. for html_document format smart punctuation option is not working, since it was renamed in pandoc 2.0)

Code

See docx_functions.R for full code, but basically the custom render function looks like:

render_flextable <- function(x, ...) {
  if (knitr::opts_knit$get("rmarkdown.pandoc.to") == "docx" && rmarkdown::pandoc_version() >= 2) 
    knitr::asis_output(
        paste(
            "```{=openxml}",
            docx_str(x),  # <- this is the function which generates table's XML code
            "```", 
            sep = "\n"
        )
    )
  else  # fallback to default behaviour (htmlwidget)
    knitr::knit_print(tabwid(x))
}

Note

After you install pandoc 2.0 (from source), an easy way to let rmarkdown use it is to set:

Sys.setenv("RSTUDIO_PANDOC" = "/path/to/pandoc-2.0/bin/")

before rendering your .Rmd document

---
title: "Insert `flextable`s into Word documents generated with `rmarkdown`"
author: Maxim Nazarov
output:
word_document:
keep_md: yes
html_document:
keep_md: yes
smart: false
---
# Examples
```{r}
# remotes::install_github("davidgohel/flextable")
library(flextable)
knit_print.flextable = render_flextable
```
## Using `knit_print.flextable`:
```{r}
flextable(head(mtcars))
```
## Custom `render` option:
```{r render = render_flextable}
flextable(head(iris))
```
## UPD: not used anymore, see davidgohel/flextable sources
#' @title Render flextable in rmarkdown (including Word output)
#' @description Function to use in the rmarkdown's \code{render} chunk option.
#' For Word (docx) output, if pandoc vesion >= 2.0 is used, a raw XML block
#' with the table code will be inserted, otherwise it falls back to inserting
#' a screenshot from htmlwidget generated with \code{\link{tabwid}}.
#' For HTML output, you won't need an extra call to \code{\link{tabwid}}, but
#' can just use \code{\link{flextable}} inside a chunk.
#' @note To insert all flextables automatically, define
#' \code{knit_print.flextable = render_flextable} in the beginning of
#' the rmarkdown document
#' @seealso vignette("knit_print", package = "knitr")
#' @param x a \code{flextable} object
#' @param ... further arguments
#' @author Maxim Nazarov
#' @export
render_flextable <- function(x, ...) {
if (knitr::opts_knit$get("rmarkdown.pandoc.to") == "docx" &&
rmarkdown::pandoc_version() >= 2)
knitr::asis_output(paste("```{=openxml}", docx_str(x), "```", sep = "\n"))
else # fallback to default behaviour
knitr::knit_print(tabwid(x))
}
docx_str <- function( x ){
UseMethod("docx_str")
}
## This is basically a copy of body_add_flextable, which just generates needed
## XML without inserting into a document (x is a flextable object here), as
## a consequence images are not supported since a document seems to be needed
## for that.
docx_str.regulartable <- function(x, align = "center", ...){
imgs <- character(0)
align <- match.arg(align, c("center", "left", "right"), several.ok = FALSE)
align <- c("center" = "center", "left" = "start", "right" = "end")[align]
align <- as.character(align)
dims <- dim(x)
widths <- dims$widths
colswidths <- paste0("<w:gridCol w:w=\"", round(widths*72*20, 0), "\"/>", collapse = "")
out <- paste0(
"<w:tbl xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" ",
"xmlns:w15=\"http://schemas.microsoft.com/office/word/2012/wordml\" ",
"xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" ",
"xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\" ",
"xmlns:w14=\"http://schemas.microsoft.com/office/word/2010/wordml\" ",
"xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" ",
"xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" ",
"xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" ",
"xmlns:ns9=\"http://schemas.openxmlformats.org/schemaLibrary/2006/main\" ",
"xmlns:wne=\"http://schemas.microsoft.com/office/word/2006/wordml\" ",
"xmlns:c=\"http://schemas.openxmlformats.org/drawingml/2006/chart\" ",
"xmlns:ns12=\"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing\" ",
"xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" ",
"xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\" ",
"xmlns:xdr=\"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\" ",
"xmlns:dsp=\"http://schemas.microsoft.com/office/drawing/2008/diagram\" ",
"xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" ",
"xmlns:ns19=\"urn:schemas-microsoft-com:office:excel\" xmlns:w10=\"urn:schemas-microsoft-com:office:word\" ",
"xmlns:ns21=\"urn:schemas-microsoft-com:office:powerpoint\" xmlns:ns23=\"http://schemas.microsoft.com/office/2006/coverPageProps\" ",
"xmlns:odx=\"http://opendope.org/xpaths\" xmlns:odc=\"http://opendope.org/conditions\" ",
"xmlns:odq=\"http://opendope.org/questions\" xmlns:oda=\"http://opendope.org/answers\" ",
"xmlns:odi=\"http://opendope.org/components\" xmlns:odgm=\"http://opendope.org/SmartArt/DataHierarchy\" ",
"xmlns:ns30=\"http://schemas.openxmlformats.org/officeDocument/2006/bibliography\" ",
"xmlns:ns31=\"http://schemas.openxmlformats.org/drawingml/2006/compatibility\" ",
"xmlns:ns32=\"http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas\" ",
"xmlns:wpc=\"http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas\" ",
"xmlns:wpg=\"http://schemas.microsoft.com/office/word/2010/wordprocessingGroup\" ",
"xmlns:wps=\"http://schemas.microsoft.com/office/word/2010/wordprocessingShape\">")
out <- paste0(out, "<w:tblPr><w:tblLayout w:type=\"fixed\"/>",
sprintf( "<w:jc w:val=\"%s\"/>", align ),
"</w:tblPr>" )
out = paste0(out, "<w:tblGrid>" )
out = paste0(out, colswidths )
out = paste0(out, "</w:tblGrid>" )
if( !is.null(x$header) ){
xml_content <- format(x$header, header = TRUE, type = "wml")
imgs <- append( imgs, attr(xml_content, "imgs")$image_src )
out = paste0(out, xml_content )
}
if( !is.null(x$body) ){
xml_content <- format(x$body, header = FALSE, type = "wml")
imgs <- append( imgs, attr(xml_content, "imgs")$image_src )
out = paste0(out, xml_content )
}
imgs <- unique(imgs)
out <- paste0(out, "</w:tbl>" )
if( length(imgs) > 0 ) {
warning("Images are not supported yet for docx-rmarkdown generation",
call. = FALSE)
}
out
}
docx_str.complextable <- docx_str.regulartable
@rickyars
Copy link

rickyars commented Mar 1, 2018

How does this work now that it's been integrated? I can't quite figure it out. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment