Unicode 字符输出与 print() 的差异

Differences in Unicode character output with print()

我在 Windows 上遇到了另一个 R 的数据帧和 Unicode 字符之间的奇怪交互,这次涉及 knitr 和 rmarkdown。

隐式打印正常工作

我正在尝试根据包含 Unicode 字符的数据框打印 HTML table,如以下简单示例所示:

---
title: "Unicode Print Test"
---

```{r, results='asis'}
library(knitr)
knitr::kable(data.frame(eta="\U03B7"), format="html")
```

当文档编织成 HTML 时,这会产生我想要的输出,如下所示:

显式打印不会

但在实际应用中,我需要从 for 循环中打印几个 table,这意味着我必须明确地 print() table:

```{r, results='asis'}
library(knitr)
x <- knitr::kable(data.frame(eta="\U03B7"), format="html")
print(x)
```

现在,当文档编织成 HTML:

时,Unicode 字符打印不正确

怎么办?

为什么会出现隐式打印和显式打印之间的这种差异?至少在 R 控制台中执行时,显式和隐式打印都会调用 knitr:::print.knitr_kable() 函数。我猜它与 evalaute 函数(来自同名包)有关,它实际上在 knitr 代码块中执行代码,但我不知道是什么。

有什么方法可以进行显式 print() 调用并获得格式正确的输出?我知道这个 locale workaround 似乎适用于其他一些 Unicode + Data Frame 问题,但不适用于这个问题。

编辑:根据一位知识渊博的评论者的说法,这是一个深层次的问题,涉及 R 在显示之前如何以及何时使用本机 Windows 编码转换字符。因此,在 Windows 上使用 print 函数时,这总是会成为一个问题,除非基数 R 发生显着变化。

更新的问题:是否有任何其他方法(除了 print()-ing 之外)让 kable 对象从内部表达式(例如 for 循环)显示?

会话信息()

## R version 3.5.1 (2018-07-02)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 7 x64 (build 7601) Service Pack 1
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=English_United States.1252 
## [2] LC_CTYPE=English_United States.1252   
## [3] LC_MONETARY=English_United States.1252
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.1252    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] knitr_1.20
## 
## loaded via a namespace (and not attached):
##  [1] compiler_3.5.1  backports_1.1.2 magrittr_1.5    rprojroot_1.3-2
##  [5] tools_3.5.1     htmltools_0.3.6 yaml_2.2.0      Rcpp_0.12.18   
##  [9] stringi_1.1.7   rmarkdown_1.10  highr_0.7       stringr_1.3.1  
## [13] digest_0.6.16   evaluate_0.11

因此,显然这个字符转换问题不太可能在不久的将来自行解决,可能只会在 OS 级别解决。但是根据@YihuiXie 在评论中提出的优秀建议,有两种方法可以解决这个问题。最佳解决方案将取决于您在其中创建 table 的上下文。

场景 1:仅表

如果您需要从 for 循环内部输出的唯一类型的对象是 tables,那么您可以在循环内的列表中累积 kable 对象,然后折叠在循环结束时将 kable 的列表转换为单个字符向量,并使用 knitr::asis_output 显示它。

```{r, results="asis"}
library(knitr)
character_list <- list(eta="\U03B7", sigma="\U03C3")
kable_list <- vector(mode="list", length = length(character_list))

for (i in 1:length(character_list)) {
  kable_list[[i]] <- knitr::kable(as.data.frame(character_list[i]),
                                  format="html"
                                  )
}

knitr::asis_output(paste(kable_list, collapse = '\n'))
```

这会在 HTML 文档中生成以下 table:

场景 2:表格和其他对象(例如绘图)

如果您在 for 循环的每次迭代中同时输出 tables 和其他对象(例如绘图),则上述解决方案将不起作用 - 您无法将绘图强制转换为特征向量!在这一点上,我们必须通过编写自定义的 knitr 输出挂钩来对 kable 输出进行一些 post 处理。

基本方法是用等效的 HTML 实体替换 table 单元格中的破坏序列。请注意,因为 table 是在 results="asis" 块中创建的,所以我们必须覆盖 chunk 级别的输出挂钩,而不是 output 级别的输出挂钩(令人困惑,我知道) .

```{r hook_override}
library(knitr)
default_hook <- knit_hooks$get("chunk")

knit_hooks$set(chunk = function(x, options) {
  # only attempt substitution if output is a character vector, which I *think* it always should be 
  if (is.character(x)) {
    # Match the <U+XXXX> pattern in the output
    match_data <- gregexpr("<U\+[0-9A-F]{4,8}>", x)
    # If a match is found, proceed with HTML entity substitution
    if (length(match_data[[1]]) >= 1 && match_data[[1]][1] != -1) {
      # Extract the matched strings from the output
      match_strings <- unlist(regmatches(x, match_data))
      # Extract the hexadecimal Unicode sequences from inside the <U > bracketing
      code_sequences <- unlist(regmatches(match_strings,
                                          gregexpr("[0-9A-F]{4,8}", match_strings)
                                          )
                               )
      # Replace any leading zero's with x, which is required for the HTML entities
      code_sequences <- gsub("^0{1,4}", "x", code_sequences)
      # Slap the &# on the front, and the ; on the end of each code sequence
      regmatches(x, match_data) <- list(paste0("&#", code_sequences, ";"))
    }
  }
  # "Print" the output
  default_hook(x, options)
})
``` 

```{r tables, results="asis"}
character_list <- list(eta="\U03B7", sigma="\U03C3")  
for (i in 1:length(character_list)) {
  x <- knitr::kable(as.data.frame(character_list[i]),
                    format="html"
                    )
  print(x)
}
```

```{r hook_reset}
knit_hooks$set(chunk = default_hook)
```

这会在 HTML 文档中生成以下 tables:

请注意,这次 sigma 不像第一个示例中那样显示为 σ,而是显示为 s!这是因为 sigma 在到达块输出挂钩之前被转换为 s 我不知道如何阻止 that 发生。如果您愿意,请随时发表评论 =)

我还意识到使用正则表达式在 HTML table 中进行替换可能很脆弱。如果这种方法碰巧对你的用例失败,也许使用 rvest 包来单独解析每个 table 单元格会更健壮。