以这个 Rmd 文件为例 - https://github.com/rstudio/learnr/blob/master/inst/tutorials/ex-setup-r/ex-setup-r.Rmd
YAML 标头具有此内容 -
output:
learnr::tutorial:
progressive: true
allow_skip: true
我想把它改成——
output:
ioslides_presentation:
widescreen: true
有没有办法以编程方式进行此编辑,即我可以编写一些将 Rmd 文件作为输入、编辑 YAML 标头并生成新 Rmd 文件的函数吗?
谢谢!
我认为一个快速函数可以做到这一点。
change_yaml_matter <- function(input_file, ..., output_file) {
input_lines <- readLines(input_file)
delimiters <- grep("^---\s*$", input_lines)
if (!length(delimiters)) {
stop("unable to find yaml delimiters")
} else if (length(delimiters) == 1L) {
if (delimiters[1] == 1L) {
stop("cannot find second delimiter, first is on line 1")
} else {
# found just one set, assume it is *closing* the yaml matter;
# fake a preceding line of delimiter
delimiters <- c(0L, delimiters[1])
}
}
delimiters <- delimiters[1:2]
yaml_list <- yaml::yaml.load(input_lines[ (delimiters[1]+1):(delimiters[2]-1) ])
dots <- list(...)
yaml_list <- c(yaml_list[ setdiff(names(yaml_list), names(dots)) ], dots)
output_lines <- c(
if (delimiters[1] > 0) input_lines[1:(delimiters[1])],
strsplit(yaml::as.yaml(yaml_list), "n")[[1]],
input_lines[ -(1:(delimiters[2]-1)) ]
)
if (missing(output_file)) {
return(output_lines)
} else {
writeLines(output_lines, con = output_file)
return(invisible(output_lines))
}
}
...
在哪里是你想要的。意思是:如果你想替换yaml物质的output:
组件,那么你给出一个命名列表作为output=list(...)
。
如果我使用我在上一个答案中使用的 rmarkdown 文档,那么保持不变,它看起来像这样:
readLines("~/StackOverflow/1883604/62095186.Rmd")
# [1] "---"
# [2] "title: Hello"
# [3] "output: html_document"
# [4] "params:"
# [5] " intab: TRUE"
# [6] "---"
# [7] ""
# [8] "# Headline 1"
# [9] ""
# [10] "## Headline 2 `r if (params$intab) "{.tabset}"`"
# [11] ""
# [12] "### Headline 3 in a tab"
# [13] ""
# [14] "### Headline 4 in a tab"
# [15] ""
# [16] "### Headline 5 in a tab"
# [17] ""
# [18] ""
为了更改output
部分,我添加一个嵌套的命名列表,如下所示:
change_yaml_matter("~/StackOverflow/1883604/62095186.Rmd",
output=list(ioslides_presentation=list(widescreen=TRUE)))
# [1] "---"
# [2] "title: Hello"
# [3] "params:"
# [4] " intab: yes"
# [5] "output:"
# [6] " ioslides_presentation:"
# [7] " widescreen: yes"
# [8] "---"
# [9] ""
# [10] "# Headline 1"
# [11] ""
# [12] "## Headline 2 `r if (params$intab) "{.tabset}"`"
# [13] ""
# [14] "### Headline 3 in a tab"
# [15] ""
# [16] "### Headline 4 in a tab"
# [17] ""
# [18] "### Headline 5 in a tab"
# [19] ""
# [20] ""
你可以改变yaml问题的任何部分。(我怀疑,您唯一无法更改的是,如果您碰巧有名为input_file
或output_file
的 yaml 参数。如果您实际上拥有带有这些 yaml 顶级参数的 Rmd 文件,那么您可以轻松地将此处的命名参数重命名为其他内容,例如Mxyzptlk
和其他内容......您不太可能在生产中看到这些内容。
笔记:
- 这没有将任何内容保存到文件中,您必须自己执行此操作。将
output_file="path/to/new.RMd"
添加到您的呼叫中,它将写入一个新文件。 - 当您在参数中包含
output_file=
时,如果您选择不捕获返回值,它将显示为不返回任何内容。这是由于我返回时invisible
;如果您确实想要查看和保存,要么捕获到变量并查看该变量,要么将函数调用包装在 paren 中,如(change_yaml_matter(...))
.
YAML 的诀窍是知道yaml::
会将每个顶级视为列表的命名元素,并且其内容以相同的方式递归列出。例如
str(yaml::yaml.load("
---
top1:
level2a:
level3a: 123
level3b: 456
level2b: 789
top2: quux
---"))
# List of 2
# $ top1:List of 2
# ..$ level2a:List of 2
# .. ..$ level3a: int 123
# .. ..$ level3b: int 456
# ..$ level2b: int 789
# $ top2: chr "quux"
要分配新值,只需提供嵌套的命名列表。
我稍微修改了一下。
使用给定的版本,如果更改了现有的 yaml 元素,则会将其移动到 yaml 标头的末尾。
通过我的修改,具有更改值的现有元素将保留其在标题中的位置。
change_yaml_matter <- function(input_file, ..., output_file) {
input_lines <- readLines(input_file)
delimiters <- grep("^---\s*$", input_lines)
if (!length(delimiters)) {
stop("unable to find yaml delimiters")
} else if (length(delimiters) == 1L) {
if (delimiters[1] == 1L) {
stop("cannot find second delimiter, first is on line 1")
} else {
# found just one set, assume it is *closing* the yaml matter;
# fake a preceding line of delimiter
delimiters <- c(0L, delimiters[1])
}
}
delimiters <- delimiters[1:2]
yaml_list <- yaml::yaml.load(
input_lines[ (delimiters[1]+1):(delimiters[2]-1) ])
dots <- list(...)
for (element_name in names(dots)){
if(element_name %in% names(yaml_list)) {
yaml_list[element_name] <- dots[element_name]
} else {
yaml_list <- c(yaml_list,dots[element_name])
}
}
output_lines <- c(
if (delimiters[1] > 0) input_lines[1:(delimiters[1])],
strsplit(yaml::as.yaml(yaml_list), "n")[[1]],
input_lines[ -(1:(delimiters[2]-1)) ]
)
if (missing(output_file)) {
return(output_lines)
} else {
writeLines(output_lines, con = output_file)
return(invisible(output_lines))
}
}