blob: 0d1b3be9b31b6856ae4c869648db11563590cb29 [file] [log] [blame]
Hao Zhucdd7f922018-01-08 11:39:40 -05001#' Add footnote (new)
Hao Zhu8dd65a92018-01-05 20:40:27 -05002#'
Hao Zhue0782ab2018-01-09 13:24:13 -05003#' @description `footnote` provides a more flexible way to add footnote. You
4#' can add mutiple sets of footnote using differeny notation system. It is
5#' also possible to specify footnote section header one by one and print
6#' footnotes as a chunk of texts.
7#'
8#' @param kable_input HTML or LaTeX table generated by `knitr::kable`
9#' @param general Text for general footnote comments. Footnotes in this section
10#' won't be labeled with any notations
11#' @param number A vector of footnote texts. Footnotes here will be numbered.
12#' There is no upper cap for the number of footnotes here
13#' @param alphabet A vector of footnote texts, Footnotes here will be labeled
14#' with abc. The vector here should not have more than 26 elements.
15#' @param symbol A vector of footnote texts, Footnotes here will be labeled
16#' with special symbols. The vector here should not have more than 20 elements.
17#' @param footnote_order The order of how to arrange `general`, `number`,
18#' `alphabet` and `symbol`.
19#' @param footnote_as_chunk T/F value. Default is FALSE. It controls whether
20#' the footnotes should be printed in a chunk (without line break).
21#' @param escape T/F value. It controls whether the contents and titles should
22#' be escaped against HTML or LaTeX. Default is TRUE.
Hao Zhu17814c72018-01-10 11:32:14 -050023#' @param threeparttable T/F value for whether to use LaTeX package
24#' threeparttable. Threeparttable will force the width of caption and
25#' footnotes be the width of the original table. It's useful when you have
26#' long paragraph of footnotes.
Hao Zhue0782ab2018-01-09 13:24:13 -050027#' @param general_title Section header for general footnotes. Default is
28#' "Note: ".
29#' @param number_title Section header for number footnotes. Default is "".
30#' @param alphabet_title Section header for alphabet footnotes. Default is "".
31#' @param symbol_title Section header for symbol footnotes. Default is "".
32#'
Hao Zhub1e2e3d2018-01-09 13:31:42 -050033#' @examples dt <- mtcars[1:5, 1:5]
Hao Zhu593f57e2018-01-09 13:30:01 -050034#' footnote(knitr::kable(dt, "html"), alphabet = c("Note a", "Note b"))
35#'
Hao Zhu8dd65a92018-01-05 20:40:27 -050036#' @export
Hao Zhucdd7f922018-01-08 11:39:40 -050037footnote <- function(kable_input,
Hao Zhu1ac13ad2018-01-08 16:12:24 -050038 general = NULL,
39 number = NULL,
40 alphabet = NULL,
41 symbol = NULL,
42 footnote_order = c("general", "number",
43 "alphabet", "symbol"),
44 footnote_as_chunk = FALSE,
Hao Zhue0782ab2018-01-09 13:24:13 -050045 escape = TRUE,
Hao Zhu17814c72018-01-10 11:32:14 -050046 threeparttable = FALSE,
Hao Zhu1ac13ad2018-01-08 16:12:24 -050047 general_title = "Note: ",
48 number_title = "",
49 alphabet_title = "",
50 symbol_title = ""
Hao Zhu8dd65a92018-01-05 20:40:27 -050051) {
52 kable_format <- attr(kable_input, "format")
53 if (!kable_format %in% c("html", "latex")) {
Hao Zhu401ebd82018-01-14 17:10:20 -050054 warning("Please specify format in kable. kableExtra can customize either ",
55 "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ",
56 "for details.")
Hao Zhu8dd65a92018-01-05 20:40:27 -050057 return(kable_input)
58 }
Hao Zhu8dd65a92018-01-05 20:40:27 -050059 if (length(alphabet) > 26) {
60 alphabet <- alphabet[1:26]
61 warning("Please don't use more than 26 footnotes in table_footnote ",
62 "alphabet. Use number instead.")
63 }
64 if (length(symbol) > 20) {
65 symbol <- symbol[1:20]
66 warning("Please don't use more than 20 footnotes in table_footnote ",
67 "symbol. Use number instead.")
68 }
Hao Zhue0782ab2018-01-09 13:24:13 -050069 footnote_titles <- list(
70 general = general_title, number = number_title,
71 alphabet = alphabet_title, symbol = symbol_title
72 )
73 footnote_contents <- list(
74 general = general, number = number, alphabet = alphabet, symbol = symbol
75 )
76 notnull <- names(footnote_contents)[!sapply(footnote_contents, is.null)]
77 if (length(notnull) == 0) {return(kable_input)}
Hao Zhu8dd65a92018-01-05 20:40:27 -050078 footnote_order <- footnote_order[footnote_order %in% notnull]
79 footnote_titles <- footnote_titles[footnote_order]
80 footnote_contents <- footnote_contents[footnote_order]
Hao Zhue0782ab2018-01-09 13:24:13 -050081 if (escape) {
82 if (kable_format == "html") {
83 footnote_contents <- lapply(footnote_contents, escape_html)
84 footnote_titles <- lapply(footnote_titles, escape_html)
85 } else {
Hao Zhud4630872018-03-26 11:26:36 -040086 footnote_contents <- lapply(footnote_contents, escape_latex2)
87 footnote_titles <- lapply(footnote_titles, escape_latex2)
Hao Zhue0782ab2018-01-09 13:24:13 -050088 }
89 }
Hao Zhu8dd65a92018-01-05 20:40:27 -050090 footnote_table <- footnote_table_maker(
91 kable_format, footnote_titles, footnote_contents
92 )
93 if (kable_format == "html") {
Hao Zhucdd7f922018-01-08 11:39:40 -050094 return(footnote_html(kable_input, footnote_table, footnote_as_chunk))
Hao Zhu8dd65a92018-01-05 20:40:27 -050095 }
Hao Zhu19c4fa52018-01-09 12:01:14 -050096 if (kable_format == "latex") {
Hao Zhu17814c72018-01-10 11:32:14 -050097 return(footnote_latex(kable_input, footnote_table, footnote_as_chunk,
98 threeparttable))
Hao Zhu19c4fa52018-01-09 12:01:14 -050099 }
Hao Zhu8dd65a92018-01-05 20:40:27 -0500100}
101
102footnote_table_maker <- function(format, footnote_titles, footnote_contents) {
103 number_index <- read.csv(system.file("symbol_index.csv",
104 package = "kableExtra"))
105 if (format == "latex") {
106 symbol_index <- number_index$symbol.latex
107 } else {
108 symbol_index <- number_index$symbol.html
109 }
Hao Zhu8dd65a92018-01-05 20:40:27 -0500110
111 if (!is.null(footnote_contents$general)) {
112 footnote_contents$general <- data.frame(
113 index = "",
Hao Zhubab692d2018-01-09 17:49:55 -0500114 footnote = footnote_contents$general
Hao Zhu8dd65a92018-01-05 20:40:27 -0500115 )
116 }
117 if (!is.null(footnote_contents$number)) {
118 footnote_contents$number <- data.frame(
119 index = as.character(1:length(footnote_contents$number)),
120 footnote = footnote_contents$number
121 )
122 }
123 if (!is.null(footnote_contents$alphabet)) {
124 footnote_contents$alphabet <- data.frame(
125 index = letters[1:length(footnote_contents$alphabet)],
126 footnote = footnote_contents$alphabet
127 )
128 }
129 if (!is.null(footnote_contents$symbol)) {
130 footnote_contents$symbol <- data.frame(
131 index = symbol_index[1:length(footnote_contents$symbol)],
132 footnote = footnote_contents$symbol
133 )
134 }
135
136 out <- list()
137 out$contents <- footnote_contents
138 out$titles <- footnote_titles
139 return(out)
140}
141
142# HTML
Hao Zhu1ac13ad2018-01-08 16:12:24 -0500143footnote_html <- function(kable_input, footnote_table, footnote_as_chunk) {
Hao Zhu8dd65a92018-01-05 20:40:27 -0500144 kable_attrs <- attributes(kable_input)
145 kable_xml <- read_kable_as_xml(kable_input)
146
Hao Zhucdd7f922018-01-08 11:39:40 -0500147 new_html_footnote <- html_tfoot_maker(footnote_table, footnote_as_chunk)
Hao Zhu8dd65a92018-01-05 20:40:27 -0500148 xml_add_child(kable_xml, new_html_footnote)
149
150 out <- as_kable_xml(kable_xml)
151 attributes(out) <- kable_attrs
Hao Zhuf2100832018-01-11 16:20:29 -0500152 if (!"kableExtra" %in% class(out)) class(out) <- c("kableExtra", class(out))
Hao Zhu8dd65a92018-01-05 20:40:27 -0500153 return(out)
154}
155
Hao Zhucdd7f922018-01-08 11:39:40 -0500156html_tfoot_maker <- function(footnote_table, footnote_as_chunk) {
Hao Zhu8dd65a92018-01-05 20:40:27 -0500157 footnote_types <- names(footnote_table$contents)
158 footnote_text <- c()
159 for (i in footnote_types) {
Hao Zhucdd7f922018-01-08 11:39:40 -0500160 footnote_text <- c(footnote_text, html_tfoot_maker_(
Hao Zhu8dd65a92018-01-05 20:40:27 -0500161 footnote_table$contents[[i]], footnote_table$titles[[i]], i,
162 footnote_as_chunk))
163 }
164 footnote_text <- paste0(
165 "<tfoot>", paste0(footnote_text, collapse = ""), "</tfoot>"
166 )
167 footnote_node <- read_html(footnote_text, options = c("RECOVER", "NOERROR"))
168 return(xml_child(xml_child(footnote_node, 1), 1))
169}
170
Hao Zhucdd7f922018-01-08 11:39:40 -0500171html_tfoot_maker_ <- function(ft_contents, ft_title, ft_type, ft_chunk) {
Hao Zhu8dd65a92018-01-05 20:40:27 -0500172 footnote_text <- apply(ft_contents, 1, function(x) {
173 paste0('<sup>', x[1], '</sup> ', x[2])
174 })
175 if (ft_title != "") {
176 title_text <- paste0('<strong>', ft_title, '</strong>')
177 footnote_text <- c(title_text, footnote_text)
178 }
179 if (!ft_chunk) {
180 footnote_text <- paste0(
181 '<tr><td style="padding: 0; border: 0;" colspan="100%">',
182 footnote_text, '</td></tr>'
183 )
184 } else {
185 footnote_text <- paste0(
186 '<tr><td style="padding: 0; border: 0;" colspan="100%">',
Hao Zhucdd7f922018-01-08 11:39:40 -0500187 paste0(footnote_text, collapse = " "),
Hao Zhu8dd65a92018-01-05 20:40:27 -0500188 '</td></tr>'
189 )
190 }
Hao Zhu8dd65a92018-01-05 20:40:27 -0500191 return(footnote_text)
192}
Hao Zhucdd7f922018-01-08 11:39:40 -0500193
194# LaTeX
Hao Zhu17814c72018-01-10 11:32:14 -0500195footnote_latex <- function(kable_input, footnote_table, footnote_as_chunk,
196 threeparttable) {
Hao Zhu1ac13ad2018-01-08 16:12:24 -0500197 table_info <- magic_mirror(kable_input)
Hao Zhu19c4fa52018-01-09 12:01:14 -0500198 out <- enc2utf8(as.character(kable_input))
Hao Zhu17814c72018-01-10 11:32:14 -0500199
Hao Zhu25028072018-01-10 12:08:51 -0500200 if (table_info$tabular == "longtable" & threeparttable == TRUE) {
Hao Zhu17814c72018-01-10 11:32:14 -0500201 threeparttable <- FALSE
202 warning("threeparttable does not support longtable.")
203 }
Hao Zhu19c4fa52018-01-09 12:01:14 -0500204 footnote_text <- latex_tfoot_maker(footnote_table, footnote_as_chunk,
Hao Zhu17814c72018-01-10 11:32:14 -0500205 table_info$ncol, threeparttable)
206 if (threeparttable) {
Hao Zhu17814c72018-01-10 11:32:14 -0500207 out <- sub(paste0("\\\\begin\\{", table_info$tabular, "\\}"),
Hao Zhu23456762018-03-26 12:30:10 -0400208 paste0("\\\\begin{ThreePartTable}\n\\\\begin{",
Hao Zhu17814c72018-01-10 11:32:14 -0500209 table_info$tabular, "}"),
210 out)
Hao Zhu17814c72018-01-10 11:32:14 -0500211 out <- sub(table_info$end_tabular,
212 paste0("\\\\end{", table_info$tabular,
213 "}\n\\\\begin{tablenotes}",
214 ifelse(footnote_as_chunk, "[para]", ""),
215 "\n\\\\small\n", footnote_text,
Hao Zhu23456762018-03-26 12:30:10 -0400216 "\n\\\\end{tablenotes}\n\\\\end{ThreePartTable}"),
Hao Zhu17814c72018-01-10 11:32:14 -0500217 out)
Hao Zhu2223ece2018-03-26 11:45:31 -0400218 } else if (table_info$booktabs) {
219 out <- sub("\\\\bottomrule",
220 paste0("\\\\bottomrule\n", footnote_text), out)
Hao Zhu17814c72018-01-10 11:32:14 -0500221 } else {
222 out <- sub(table_info$end_tabular,
223 paste0(footnote_text, "\n\\\\end{", table_info$tabular, "}"),
224 out)
225 }
226
Hao Zhu19c4fa52018-01-09 12:01:14 -0500227 out <- structure(out, format = "latex", class = "knitr_kable")
228 attr(out, "kable_meta") <- table_info
229 return(out)
Hao Zhu19c4fa52018-01-09 12:01:14 -0500230}
Hao Zhucdd7f922018-01-08 11:39:40 -0500231
Hao Zhu17814c72018-01-10 11:32:14 -0500232latex_tfoot_maker <- function(footnote_table, footnote_as_chunk, ncol,
233 threeparttable) {
Hao Zhu19c4fa52018-01-09 12:01:14 -0500234 footnote_types <- names(footnote_table$contents)
235 footnote_text <- c()
Hao Zhu17814c72018-01-10 11:32:14 -0500236 if (threeparttable) {
237 for (i in footnote_types) {
238 footnote_text <- c(footnote_text, latex_tfoot_maker_tpt_(
239 footnote_table$contents[[i]], footnote_table$titles[[i]],
240 footnote_as_chunk, ncol))
241 }
242 } else {
243 for (i in footnote_types) {
244 footnote_text <- c(footnote_text, latex_tfoot_maker_(
245 footnote_table$contents[[i]], footnote_table$titles[[i]],
246 footnote_as_chunk, ncol))
247 }
Hao Zhu19c4fa52018-01-09 12:01:14 -0500248 }
249 footnote_text <- paste0(footnote_text, collapse = "\n")
250 return(footnote_text)
251}
Hao Zhu9f917482018-01-08 18:09:33 -0500252
Hao Zhu17814c72018-01-10 11:32:14 -0500253latex_tfoot_maker_ <- function(ft_contents, ft_title, ft_chunk, ncol) {
Hao Zhu19c4fa52018-01-09 12:01:14 -0500254 footnote_text <- apply(ft_contents, 1, function(x) {
255 if (x[1] == "") {
256 x[2]
257 } else {
258 paste0('\\\\textsuperscript{', x[1], '} ', x[2])
259 }
260 })
261 if (ft_title != "") {
262 title_text <- paste0('\\\\textbf{', ft_title, '} ')
263 footnote_text <- c(title_text, footnote_text)
264 }
265 if (!ft_chunk) {
266 footnote_text <- paste0(
267 '\\\\multicolumn{', ncol, '}{l}{', footnote_text, '}\\\\\\\\'
268 )
269 } else {
270 footnote_text <- paste0(
271 '\\\\multicolumn{', ncol, '}{l}{',
272 paste0(footnote_text, collapse = " "),
273 '}\\\\\\\\'
274 )
275 }
276 return(footnote_text)
Hao Zhucdd7f922018-01-08 11:39:40 -0500277}
Hao Zhu17814c72018-01-10 11:32:14 -0500278
279latex_tfoot_maker_tpt_ <- function(ft_contents, ft_title, ft_chunk, ncol) {
280 footnote_text <- apply(ft_contents, 1, function(x) {
281 if (x[1] == "") {
282 paste0('\\\\item ', x[2])
283 } else {
284 paste0('\\\\item[', x[1], '] ', x[2])
285 }
286 })
287 if (ft_title != "") {
288 title_text <- paste0('\\\\item \\\\textbf{', ft_title, '} ')
289 footnote_text <- c(title_text, footnote_text)
290 }
291 footnote_text <- paste0(footnote_text, collapse = "\n")
292 # if (!ft_chunk) {
293 # footnote_text <- paste0(footnote_text, collapse = "\n")
294 # } else {
295 # footnote_text <- paste0(footnote_text, collapse = " ")
296 # }
297 return(footnote_text)
298}