gt表格中的KaTeX公式未能正确转换为DOCX公式

前端开发 2026-07-09

我有一个gt表格,我使用 gt::md() 来添加LaTeX公式。这个在html导出时工作良好,但这并不能在docx中渲染为有效的公式。它将公式原样保留,没有任何解决方案。

---
title: "Testing"
format: 
    docx: default
    html: default
---
library(gt)
exibble |>
    dplyr::select(currency, char) |>
    gt() |>
    tab_header(title = md("$$\\frac{1}{2}$$")) |> 
    cols_label(
        currency = md("$$Currency_{1}$$")
    )

以下是docx结果的截图:

docx 结果截图

以下是在HTML中的正确期望格式:

HTML 中的预期格式截图

我在寻找一个能同时适用于这两种格式的解决方案。

解决方案

用LaTeX编写的数学公式现在可以通过md() 和fmt_markdown() 渲染为HTML。LaTeX公式可以在 $ 或 $$ 分隔符之间设置,用于行内或块级渲染。虽然这需要katex包来工作(这是一个可选依赖),但输出中的公式渲染是无依赖的 (#375, #616, #1163)。 (#1578) [1]

这似乎表明 {gt} 无法将MathJax转换为Word的 Office Open XML。

现在我们可以将gt表格导出为Word文档。这要归功于 @thebioengineer(Ellis Hughes!),他不仅通过gtsave() 实现了这种输出(写出.docx文件),还通过as_word()(给你一个OOXML字符串)实现了同样的效果 (#121, #929)。 (#962, #986, #1016) [2]

听起来似乎能保留格式,但gt仓库中关于docx的开放问题却给出不同的情况。

1. {flextable}

我建议使用 flextable,它让你可以使用 as_equation 来创建在HTML和 Docx输出中都能正确显示的“有效”公式。

---
title: "Testing"
output:
  word_document: default
  html_document: default
---

```{r, echo=FALSE, message=FALSE}
library(gt)        # for exibble
library(flextable) # for flextable
library(ftExtra)   # for colformat_md formatting markdown
library(equatags)  # To use "as_equation", package 'equatags' is required; also equatags::mathjax_install()  must be executed only once to install necessary dependencies.

set_flextable_defaults(na_str = "NA")
flextable(exibble[, c("currency", "char")]) |>
  set_header_labels(currency = "$$Currency_{1}$$") |>
  # format markdown within header and body!
  colformat_md(part = "all") |>
  autofit() |>
  set_caption(
    caption = as_paragraph(
      as_equation("\\dfrac{1}{2}")
    )
  )

得到

[![表格在 Word 输出(左)和 HTML 输出(右)](/uploadfile/blog2026/9640658002ad4412a91e2550e48e1860.jpg)](/uploadfile/blog2026/9640658002ad4412a91e2550e48e1860.jpg)

---

### 2. 使用图片(折衷做法)

一种折衷的解决方案是使用 `gtsave()` 将你的 `gt` 表格截成图片,再由它包装 `webshot2::webshot`。不过要让它工作,我们需要用 `gt::html(katex::katex_mathml("\\frac{1}{2}"))` 将你的公式转换为 HTML,这样转换后的 MathJax 才能被正确捕获。

```none
---
title: "Testing"
format: 
    docx: default
    html: default
---

```{r echo=FALSE, message=FALSE, warning=FALSE, out.width = "200px"}
library(gt)
library(katex)

X <- exibble[, c("currency", "char")] |>
    gt() |>
    tab_header(title = html(katex_mathml("\\frac{1}{2}"))) |>
    cols_label(
        currency = html(katex_mathml("Currency_{1}"))
    )
gtsave(X, "table.png", zoom = 7)
knitr::include_graphics("table.png")

得到

[![res2](/uploadfile/blog2026/c47cc1da496c46d0a2c66120f48ca6a5.jpg)](/uploadfile/blog2026/c47cc1da496c46d0a2c66120f48ca6a5.jpg)

---

### 3. 将 $$content$$ 替换为在 [word run](https://learn.microsoft.com/en-sg/answers/questions/5644120/microsoft-word-fails-to-correctly-render-nested-ma) 内的有效 MathML

[`equatags::transform_mathjax()`](https://www.rdocumentation.org/packages/equatags/versions/0.2.2/topics/transform_mathjax) 提供了一种将 MathJax 转换为“MathML”XML 的方法,如下所示:

```r
transform_mathjax(x = "Currency_{1}", to = "mml") |> cat()

<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mml="http://www.w3.org/1998/Math/MathML">
  <m:r>
    <m:t>Currenc</m:t>
  </m:r>
  <m:sSub>
    <m:e>
      <m:r>
        <m:t>y</m:t>
      </m:r>
    </m:e>
    <m:sub>
      <m:r>
        <m:t>1</m:t>
      </m:r>
    </m:sub>
  </m:sSub>
</m:oMath>

现在如果你把gt表导出为word-xml,你可以清楚地看到MathJax Currency_{1} 并没有被转换成Word能正确读取为公式的有效OOXML:

exibble[, c("currency", "char")] |>
    gt() |>
    tab_header(title = md("$$\\frac{1}{2}$$")) |>
    cols_label(currency = md("$$Currency_{1}$$")) |>
    as_word() |> cat()

## ...
<w:t xml:space="preserve">$$Currency_{1}$$</w:t>
## ...

因此思路是用上方由 transform_mathjax 提供的MathML替换 $$ 标签内的内容,然后把这些片段插入到Word运行标签中。注意:只有输出为Word时才会执行此步骤。如果输出为HTML,则会输出你正常的 gt 小部件。

代码

---
title: "Testing"
format: 
    docx: default
    html: default
---

```{r echo=FALSE, message=FALSE, warning=FALSE, results='asis'}
library(gt)
library(equatags)
library(gsubfn)

X <- exibble[, c("currency", "char")] |>
  gt() |>
  tab_header(title = md("$$\\frac{1}{2}$$")) |>
  cols_label(currency = md("$$Currency_{1}$$"))

if (!knitr::is_html_output() & !knitr::is_latex_output()){
  replace_math <- \(x) gsubfn("\\$\\$(.*?)\\$\\$", \(m) sprintf("<w:r>%s</w:r>", transform_mathjax(m, to="mml")), x)
  cat("```{=openxml}", replace_math(as_word(X)), "```", sep = "\n")
}else X

```

得到 res

注意事项:

  • "1/2" 会被写成表格捕获文本,而不是居中显示在表格之上
  • 如果你打开这个Word文件,会出现一个与安全相关的弹出消息
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章