Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/rmd/patch.R
12921 views
1
# patch.R
2
# Copyright (C) 2020-2022 Posit Software, PBC
3
4
# check whether knitr has native yaml chunk option parsing
5
knitr_has_yaml_chunk_options <- function() {
6
utils::packageVersion("knitr") >= "1.37.2"
7
}
8
9
# only works w/ htmltools >= 0.5.0.9003 so overwrite in the meantime
10
options(htmltools.preserve.raw = TRUE)
11
12
htmlPreserve <- function(x) {
13
x <- paste(x, collapse = "\n")
14
if (nzchar(x)) {
15
# use fenced code block if there are embedded newlines
16
if (grepl("\n", x, fixed = TRUE)) {
17
sprintf("\n```{=html}\n%s\n```\n", x)
18
} else {
19
# otherwise use inline span
20
sprintf("`%s`{=html}", x)
21
}
22
} else {
23
x
24
}
25
}
26
assignInNamespace("htmlPreserve", htmlPreserve, ns = "htmltools")
27
28
if (!knitr_has_yaml_chunk_options()) {
29
# override parse_block to assign chunk labels from yaml options
30
knitr_parse_block <- knitr:::parse_block
31
parse_block = function(
32
code,
33
header,
34
params.src,
35
markdown_mode = out_format('markdown')
36
) {
37
originalParamsSrc <- params.src
38
engine = sub('^([a-zA-Z0-9_]+).*$', '\\1', params.src)
39
partitioned <- partition_yaml_options(engine, code)
40
params = sub('^([a-zA-Z0-9_]+)', '', params.src)
41
params <- knitr:::parse_params(params)
42
unnamed_label <- knitr::opts_knit$get('unnamed.chunk.label')
43
if (startsWith(params$label, unnamed_label)) {
44
label <- partitioned$yaml[["label"]] %||% partitioned$yaml[["id"]]
45
if (!is.null(label)) {
46
params.src <- sub(
47
"^[a-zA-Z0-9_]+ *[ ,]?",
48
paste0(engine, " ", label, ", "),
49
params.src
50
)
51
}
52
}
53
54
# strip trailing comma and whitespace
55
params.src <- sub("\\s*,?\\s*$", "", params.src)
56
57
# look for other options to forward. note that ideally we could extract *all*
58
# parameters and then pass partitioned$code below, however we can construct
59
# cases where deparsed versions of the options include a newline, which causes
60
# an error. we'll wait and see if this capability is incorporated natively
61
# into knitr parse_block -- if it's not then we can pursue more robust versions
62
# of textual option forwarding that don't run into newlines
63
extra_opts <- list()
64
for (opt in c("ref.label", "ref-label")) {
65
if (!is.null(partitioned$yaml[[opt]])) {
66
value <- partitioned$yaml[[opt]]
67
opt <- sub("-", ".", opt)
68
extra_opts[[opt]] <- paste(
69
gsub(
70
"\n",
71
" ",
72
deparse(value, width.cutoff = 500, nlines = 1),
73
fixed = TRUE
74
),
75
collapse = " "
76
)
77
}
78
}
79
if (length(extra_opts) > 0) {
80
extra_opts <- paste(
81
paste0(names(extra_opts), "=", as.character(extra_opts), ", "),
82
collapse = ""
83
)
84
params.src <- paste0(params.src, ", ", sub(",\\s*$", "", extra_opts))
85
}
86
87
# proceed
88
block <- knitr_parse_block(code, header, params.src, markdown_mode)
89
block[["params"]][["original.params.src"]] <- originalParamsSrc
90
block[["params"]][["chunk.echo"]] <- isTRUE(params[["echo"]]) ||
91
isTRUE(partitioned$yaml[["echo"]])
92
block
93
}
94
assignInNamespace("parse_block", parse_block, ns = "knitr")
95
}
96
97
# override wrapping behavior for knitr_asis output (including htmlwidgets)
98
# to provide for enclosing output div and support for figure captions
99
wrap_asis_output <- function(options, x) {
100
# if the options are empty then this is inline output, return unmodified
101
if (length(options) == 0) {
102
return(x)
103
}
104
# x needs to be collapsed first as it could be a character vector (#5506)
105
x <- paste(x, collapse = "")
106
107
# generate output div
108
caption <- figure_cap(options)[[1]]
109
if (nzchar(caption)) {
110
x <- paste0(x, "\n\n", caption)
111
}
112
classes <- paste0("cell-output-display")
113
attrs <- NULL
114
if (isTRUE(options[["output.hidden"]])) {
115
classes <- paste0(classes, " .hidden")
116
}
117
118
if (identical(options[["html-table-processing"]], "none")) {
119
attrs <- paste(attrs, "html-table-processing=none")
120
}
121
122
# if this is an html table then wrap it further in ```{=html}
123
# (necessary b/c we no longer do this by overriding kable_html,
124
# which is in turn necessary to allow kableExtra to parse
125
# the return value of kable_html as valid xml)
126
if (
127
grepl("^<\\w+[ >]", x) &&
128
grepl("<\\/\\w+>\\s*$", x) &&
129
!grepl('^<div class="kable-table">', x)
130
) {
131
x <- paste0("`````{=html}\n", x, "\n`````")
132
}
133
134
# If asis output, don't include the output div
135
if (identical(options[["results"]], "asis")) {
136
return(x)
137
}
138
139
output_div(x, output_label_placeholder(options), classes, attrs)
140
}
141
142
add_html_caption <- function(options, x, ...) {
143
if (inherits(x, 'knit_asis_htmlwidget')) {
144
wrap_asis_output(options, x)
145
} else {
146
x
147
}
148
}
149
assignInNamespace("add_html_caption", add_html_caption, ns = "knitr")
150
151
# wrap was renamed to sew in 1.32.8.
152
if (utils::packageVersion("knitr") >= "1.32.8") {
153
knitr_sew <- knitr:::sew
154
sew <- function(x, options = list(), ...) {
155
# some sew s3 methods take the default chunk options
156
if (
157
missing(options) &&
158
inherits(x, c("knit_image_paths", "html_screenshot", "knit_embed_url"))
159
) {
160
options <- knitr::opts_chunk$get()
161
}
162
163
if (inherits(x, "knit_image_paths")) {
164
knitr:::sew.knit_image_paths(x, options, ...)
165
} else if (inherits(x, "knit_asis")) {
166
# delegate
167
is_html_widget <- inherits(x, "knit_asis_htmlwidget")
168
# knit_asis method checks on missing options which
169
# it gets in knitr because UseMethod() is called in generic
170
# but here we pass our default empty list options
171
x <- if (missing(options)) {
172
knitr:::sew.knit_asis(x, ...)
173
} else {
174
knitr:::sew.knit_asis(x, options, ...)
175
}
176
177
# if it's an html widget then it was already wrapped
178
# by add_html_caption
179
if (is_html_widget) {
180
x
181
} else {
182
wrap_asis_output(options, x)
183
}
184
185
# this used to be completely generic, however R 3.4 wasn't able to
186
# dispatch correctly via UseMethod so we do manual binding
187
} else if (inherits(x, "character")) {
188
knitr:::sew.character(x, options, ...)
189
} else if (inherits(x, "html_screenshot")) {
190
knitr:::sew.html_screenshot(x, options, ...)
191
} else if (inherits(x, "knit_embed_url")) {
192
knitr:::sew.knit_embed_url(x, options, ...)
193
} else if (inherits(x, "source")) {
194
knitr:::sew.source(x, options, ...)
195
} else if (inherits(x, "warning")) {
196
knitr:::sew.warning(x, options, ...)
197
} else if (inherits(x, "message")) {
198
knitr:::sew.message(x, options, ...)
199
} else if (inherits(x, "error")) {
200
knitr:::sew.error(x, options, ...)
201
} else if (inherits(x, "list")) {
202
knitr:::sew.list(x, options, ...)
203
} else if (inherits(x, "recordedplot")) {
204
knitr:::sew.recordedplot(x, options, ...)
205
} else if (inherits(x, "rglRecordedplot") && requireNamespace("rgl")) {
206
rgl:::sew.rglRecordedplot(x, options, ...)
207
} else {
208
# this works generically for recent versions of R however
209
# not for R < 3.5
210
knitr_sew(x, options, ...)
211
}
212
}
213
assignInNamespace("sew", sew, ns = "knitr")
214
} else {
215
knitr_wrap <- knitr:::wrap
216
wrap <- function(x, options = list(), ...) {
217
if (inherits(x, "knit_image_paths")) {
218
knitr:::wrap.knit_image_paths(x, options, ...)
219
} else if (inherits(x, "knit_asis")) {
220
# delegate
221
is_html_widget <- inherits(x, "knit_asis_htmlwidget")
222
x <- knitr:::wrap.knit_asis(x, options, ...)
223
224
# if it's an html widget then it was already wrapped
225
# by add_html_caption
226
if (is_html_widget) {
227
x
228
} else {
229
wrap_asis_output(options, x)
230
}
231
232
# this used to be completely generic, however R 3.4 wasn't able to
233
# dispatch correctly via UseMethod so we do manual binding
234
} else if (inherits(x, "character")) {
235
knitr:::wrap.character(x, options, ...)
236
} else if (inherits(x, "html_screenshot")) {
237
knitr:::wrap.html_screenshot(x, options, ...)
238
} else if (inherits(x, "knit_embed_url")) {
239
knitr:::wrap.knit_embed_url(x, options, ...)
240
} else if (inherits(x, "source")) {
241
knitr:::wrap.source(x, options, ...)
242
} else if (inherits(x, "warning")) {
243
knitr:::wrap.warning(x, options, ...)
244
} else if (inherits(x, "message")) {
245
knitr:::wrap.message(x, options, ...)
246
} else if (inherits(x, "error")) {
247
knitr:::wrap.error(x, options, ...)
248
} else if (inherits(x, "list")) {
249
knitr:::wrap.list(x, options, ...)
250
} else if (inherits(x, "recordedplot")) {
251
knitr:::wrap.recordedplot(x, options, ...)
252
} else {
253
# this works generically for recent versions of R however
254
# not for R < 3.5
255
knitr_wrap(x, options, ...)
256
}
257
}
258
assignInNamespace("wrap", wrap, ns = "knitr")
259
}
260
261
262
# patch knitr_print.knitr_kable to enclose html output in pandoc RawBlock
263
knitr_raw_block <- function(x, format) {
264
knitr::asis_output(paste0("\n\n```{=", format, "}\n", x, "\n```\n\n"))
265
}
266
knitr_kable_html <- knitr:::kable_html
267
kable_html <- function(...) {
268
x <- knitr_kable_html(...)
269
knitr_raw_block(x, "html")
270
}
271
272
# kableExtra::kable_styling parses/post-processes the output of kable_html
273
# as xml. e.g. see https://github.com/haozhu233/kableExtra/blob/a6af5c067c2b4ca8317736f4a3e6c0f7db508fef/R/kable_styling.R#L216
274
# this means that we can't simply inject pandoc RawBlock delimiters into
275
# the return value of kable_html, as it will cause the xml parser to fail,
276
# e.g. see https://github.com/quarto-dev/quarto-cli/issues/75. As a result
277
# we no longer do this processing (see commented out assignInNamespace below)
278
# note that we did this mostly for consistency of markdown output (raw HTML
279
# always marked up correctly). as a practical matter pandoc I believe that
280
# pandoc will successfully parse the RawBlock into it's AST so we won't lose
281
# any functionality (e.g. crossref table caption handling)
282
283
# assignInNamespace("kable_html", kable_html, ns = "knitr")
284
285
# patch knitr:::valid_path to remove # prefix and colons from file names
286
knitr_valid_path <- knitr:::valid_path
287
valid_path = function(prefix, label) {
288
label <- sub("^#", "", label)
289
path <- knitr_valid_path(prefix, label)
290
gsub(":", "-", path, fixed = TRUE)
291
}
292
assignInNamespace("valid_path", valid_path, ns = "knitr")
293
294
295
# add special language comment options support in knitr
296
# it was added in 1.46 but we need to support older version too
297
# https://github.com/quarto-dev/quarto-cli/pull/7799
298
# FIXME: can be cleaned when knitr 1.45 is considered too old
299
if (
300
knitr_has_yaml_chunk_options() && utils::packageVersion("knitr") <= "1.45"
301
) {
302
knitr_comment_chars <- knitr:::comment_chars
303
knitr_comment_chars$ojs <- "//"
304
knitr_comment_chars$mermaid <- "%%"
305
knitr_comment_chars$dot <- "//"
306
assignInNamespace("comment_chars", knitr_comment_chars, ns = "knitr")
307
}
308
309