0%

Create and format Word documents using R

读取/处理word documents (docx)的R包有:

我有个需求:

  1. 提取docx文件(源数据)中的文字/表格/图片
  2. 将指定的文件/表格/图片插入到模板docx中的指定位置
  3. 基于模板docx生成新的docx文件

因此我选择上述R包中的docxtractr包来提取源数据docx文件中的表格,选择officer包来提取源数据docx文件中的文字

剩下还有图片部分,上述所有R包并没有提供直接抓取图片的函数,但是按照docx文件的原理可以取巧一下

以OOXML保存的.docx可以直接解压缩,文档的文字/表格内容基本上存在/word/document.xml;而图片则是存在/word/media 目录中,按照图片顺序命名为imageX.png/imageX.jpeg等等

从上述原理可看出,如果docx文档比较简单(假如是纯文字),那么其实我们读取document.xml文件即可解析出文字部分;如果文档结构比较复杂,那么还是用现有的R包比较省事

这样我们提取图片的方法也有了,以下重点记录下docxtractr包和officer包如何实现上述的需求。。。

数据准备

首先加载两个R包,并用其对应函数读取源数据docx文件

officer_doc <- officer::read_docx(path = "./data.docx")
docxtractr_docx <- docxtractr::read_docx(path = "./data.docx")

使用officer::read_docx读取文件后,docx文件会默认解压缩到系统的临时目录下,因此我们用officer_doc$package_dir来列出所有图片路径

list.files(paste0(officer_doc$package_dir, "\\word\\media"), full.names = T)

可以用officer::docx_summary()查看docx文件中每行文字数据,生成一个数据框,大致上一行数据对应数据框的一行

summarydf <- docx_summary(officer_doc)
names(summarydf)
dim(summarydf)

使用docxtractr::docx_extract_tbl()提取docx文件中的指定表格(按照顺序从1开始到N);其会将表格提取并转化为数据框,这是其相比其他R包的优势所在

docx_extract_tbl(docxtractr_docx, 1)

offier包基本操作

经过上述步骤,我们已经能将文字/表格/图片均提取出来了,接着则需要将这些内容分别加入到模板docx中,officer包提供了多个操作:

  • Adding elements,添加段落/文字/图片/表格/目录等操作
  • Cursor manipulation,控制光标位置以便能在指定位置插入对应的elements
  • Remove content,清除内容,一般会搭配cursor来更新内容
  • Replace content,替换内容,一般会搭配cursor来替换段落中的指定文字等
  • Sections,章节,一般在结束时调用

以officer文档中一个demo文件作为源数据,我新建的一个docx文件作为模板,来演示以下功能

首先读入源数据docx文档,并设置图片路径

library(officer)
library(docxtractr)
library(flextable)

officer_doc <- officer::read_docx(path = "../Desktop/body_add_demo.docx")
docxtractr_docx <- docxtractr::read_docx(path = "../Desktop/body_add_demo.docx")
tempdir <- paste0(officer_doc$package_dir, "\\word\\media")

通过cursor_begin()来初始化下模板docx,即将光标移至文档最开始的位置

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin()

此时可以通过docx_show_chunk()函数来查看光标所在的段落文字

docx_show_chunk(doc_output)

光标是以段落为单位进行移动的,因此如果想移至下一段落,则可以用cursor_forward(),往上移则是cursor_backward(),如果想移至指定段落,则可以用cursor_reach()函数

doc_output <- doc_output %>% cursor_forward()

接着是增加/替换段落,一般来说我们移至指定段落后使用adding elements操作默认是在光标下方,比如body_add_par()

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>%
  body_add_par(value = "加上这个", style = "Normal") %>%
  docx_show_chunk()

如果更改body_add_par()函数中的pos参数,则可以将上述的添加变成替换(也可以将默认的下方变成上方)

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>%
  body_add_par(value = "替换成这个", style = "Normal", pos = "on") %>%
  docx_show_chunk()

如果不想替换整个段落,而是段落中的某个文字的话,则可以body_replace_all_text函数,比如替换"文字XX"段落中的XX为YY(首先需要先reach定位到改段落,然后再替换。。)

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>% cursor_reach(keyword = "文字XX") %>%
  body_replace_all_text(old_value = "XX", new_value = "YY", only_at_cursor = TRUE, fixed = TRUE)

如果你也跟我一样是使用中文的话,不出意外会报错,因为其在有中文的环境下对于词性的分割不是太理想,比如用docx_show_chunk看下:

> docx_show_chunk(doc_output)
2 text nodes found at this cursor. 
  <w:t>: '文字X'
  <w:t>: 'X

从上述可看出"文字XX"中的"XX"被分割成两部分,所以报错也难免了,解决方法可看:Replace text anywhere in the document, or at a cursor,简单的说就是如果我们想替换"XX",则在模板中将"XX"重新复制黏贴下,而不是用手动输入那种方式;更改后如下所示应该就正常了

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>% cursor_reach(keyword = "文字XX") %>%
  body_replace_all_text(old_value = "XX", new_value = "YY", only_at_cursor = TRUE, fixed = TRUE) %>%
  docx_show_chunk()
>2 text nodes found at this cursor. 
  <w:t>: '文字'
  <w:t>: 'YY'

接着我们需要插入表格,officer包能将flextable包所生成的表格进行呈现输出,而且后者也是一个非常好用的R包表格生成工具

  • 首先使用docxtractr包的docx_extract_tbl()函数抓取表格

  • 然后用flextable包的qflextable()转化生成表格

  • 最后用officer包的body_add_flextable()函数进行输出

    tb <- docx_extract_tbl(docxtractr_docx, 1) %>% qflextable()

    doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>% # Begin docx cursor_begin() %>% cursor_reach(keyword = "请在下方插入表格") %>% cursor_forward() %>% body_add_flextable(value = tb) %>% docx_show_chunk()

相比表格而言,插入图片就比较简单了,查到图片的临时路径,然后body_add_img()函数调用即可

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>% cursor_reach(keyword = "请在下方插入图片") %>%
  body_add_img(src = paste0(tempdir, "\\image1.png"), pos = "after", style = "centered") %>%
  docx_show_chunk()

如果出现报错:错误: could not match any style named 'centered' in c('Normal'),在说明在模板文件中没有叫做""centered"的style;解决办法很直接:在模板docx中新建一个叫做"centered"的style,并设置居中即可

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>% cursor_reach(keyword = "请在下方插入图片") %>%
  body_add_img(src = paste0(tempdir, "\\image1.png"), pos = "after", style = "centered", width = 4, height = 3) %>%
  docx_show_chunk()

最后我们将上述所有操作整合一下,使用print输出到本地

doc_output <- officer::read_docx(path = "../Desktop/template.docx") %>%
  # Begin docx
  cursor_begin() %>% 
  cursor_reach(keyword = "文字XX") %>%
  body_replace_all_text(old_value = "XX", new_value = "YY", only_at_cursor = TRUE, fixed = TRUE) %>%
  cursor_reach(keyword = "请在下方插入表格") %>%
  body_add_flextable(value = tb) %>%
  cursor_reach(keyword = "请在下方插入图片") %>%
  body_add_img(src = paste0(tempdir, "\\image1.png"), pos = "after", style = "centered", width = 4, height = 3)
print(doc_output, target = "../Desktop/demo.docx")

Summary

对于officer包其他功能,可以查阅其官方文档:https://davidgohel.github.io/officer/index.html,功能极其好用哈~

对于表格样式的修改,具体参考flextable包的官方文档:https://davidgohel.github.io/flextable/articles/overview.html,可选择的样式很齐全,基本满足常规的需求

对于其他处理docx文档的R包了解的并不深,大家可以去尝试下

参考资料

  1. https://www.r-bloggers.com/using-r-to-get-data-out-of-word-docs/
  2. http://www.sthda.com/english/wiki/create-and-format-word-documents-using-r-software-and-reporters-package
  3. Create and format Word documents using R software and Reporters package

本文出自于http://www.bioinfo-scrounger.com转载请注明出处