blob: daecbd9ce9576902506a9d5dd301f3ba536a90c1 [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)
Hao Zhu1aff7342018-04-02 18:33:15 -040087 footnote_contents <- lapply(footnote_contents, linebreak)
Hao Zhud4630872018-03-26 11:26:36 -040088 footnote_titles <- lapply(footnote_titles, escape_latex2)
Hao Zhu1aff7342018-04-02 18:33:15 -040089 footnote_titles <- lapply(footnote_titles, linebreak)
Hao Zhue0782ab2018-01-09 13:24:13 -050090 }
91 }
Hao Zhu8dd65a92018-01-05 20:40:27 -050092 footnote_table <- footnote_table_maker(
93 kable_format, footnote_titles, footnote_contents
94 )
95 if (kable_format == "html") {
Hao Zhucdd7f922018-01-08 11:39:40 -050096 return(footnote_html(kable_input, footnote_table, footnote_as_chunk))
Hao Zhu8dd65a92018-01-05 20:40:27 -050097 }
Hao Zhu19c4fa52018-01-09 12:01:14 -050098 if (kable_format == "latex") {
Hao Zhu17814c72018-01-10 11:32:14 -050099 return(footnote_latex(kable_input, footnote_table, footnote_as_chunk,
100 threeparttable))
Hao Zhu19c4fa52018-01-09 12:01:14 -0500101 }
Hao Zhu8dd65a92018-01-05 20:40:27 -0500102}
103
104footnote_table_maker <- function(format, footnote_titles, footnote_contents) {
105 number_index <- read.csv(system.file("symbol_index.csv",
106 package = "kableExtra"))
107 if (format == "latex") {
108 symbol_index <- number_index$symbol.latex
109 } else {
110 symbol_index <- number_index$symbol.html
111 }
Hao Zhu8dd65a92018-01-05 20:40:27 -0500112
113 if (!is.null(footnote_contents$general)) {
114 footnote_contents$general <- data.frame(
115 index = "",
Hao Zhubab692d2018-01-09 17:49:55 -0500116 footnote = footnote_contents$general
Hao Zhu8dd65a92018-01-05 20:40:27 -0500117 )
118 }
119 if (!is.null(footnote_contents$number)) {
120 footnote_contents$number <- data.frame(
121 index = as.character(1:length(footnote_contents$number)),
122 footnote = footnote_contents$number
123 )
124 }
125 if (!is.null(footnote_contents$alphabet)) {
126 footnote_contents$alphabet <- data.frame(
127 index = letters[1:length(footnote_contents$alphabet)],
128 footnote = footnote_contents$alphabet
129 )
130 }
131 if (!is.null(footnote_contents$symbol)) {
132 footnote_contents$symbol <- data.frame(
133 index = symbol_index[1:length(footnote_contents$symbol)],
134 footnote = footnote_contents$symbol
135 )
136 }
137
138 out <- list()
139 out$contents <- footnote_contents
140 out$titles <- footnote_titles
141 return(out)
142}
143
144# HTML
Hao Zhu1ac13ad2018-01-08 16:12:24 -0500145footnote_html <- function(kable_input, footnote_table, footnote_as_chunk) {
Hao Zhu8dd65a92018-01-05 20:40:27 -0500146 kable_attrs <- attributes(kable_input)
147 kable_xml <- read_kable_as_xml(kable_input)
148
Hao Zhucdd7f922018-01-08 11:39:40 -0500149 new_html_footnote <- html_tfoot_maker(footnote_table, footnote_as_chunk)
Hao Zhu8dd65a92018-01-05 20:40:27 -0500150 xml_add_child(kable_xml, new_html_footnote)
151
152 out <- as_kable_xml(kable_xml)
153 attributes(out) <- kable_attrs
Hao Zhuf2100832018-01-11 16:20:29 -0500154 if (!"kableExtra" %in% class(out)) class(out) <- c("kableExtra", class(out))
Hao Zhu8dd65a92018-01-05 20:40:27 -0500155 return(out)
156}
157
Hao Zhucdd7f922018-01-08 11:39:40 -0500158html_tfoot_maker <- function(footnote_table, footnote_as_chunk) {
Hao Zhu8dd65a92018-01-05 20:40:27 -0500159 footnote_types <- names(footnote_table$contents)
160 footnote_text <- c()
161 for (i in footnote_types) {
Hao Zhucdd7f922018-01-08 11:39:40 -0500162 footnote_text <- c(footnote_text, html_tfoot_maker_(
Hao Zhu8dd65a92018-01-05 20:40:27 -0500163 footnote_table$contents[[i]], footnote_table$titles[[i]], i,
164 footnote_as_chunk))
165 }
166 footnote_text <- paste0(
167 "<tfoot>", paste0(footnote_text, collapse = ""), "</tfoot>"
168 )
169 footnote_node <- read_html(footnote_text, options = c("RECOVER", "NOERROR"))
170 return(xml_child(xml_child(footnote_node, 1), 1))
171}
172
Hao Zhucdd7f922018-01-08 11:39:40 -0500173html_tfoot_maker_ <- function(ft_contents, ft_title, ft_type, ft_chunk) {
Hao Zhu8dd65a92018-01-05 20:40:27 -0500174 footnote_text <- apply(ft_contents, 1, function(x) {
175 paste0('<sup>', x[1], '</sup> ', x[2])
176 })
177 if (ft_title != "") {
178 title_text <- paste0('<strong>', ft_title, '</strong>')
179 footnote_text <- c(title_text, footnote_text)
180 }
181 if (!ft_chunk) {
182 footnote_text <- paste0(
183 '<tr><td style="padding: 0; border: 0;" colspan="100%">',
184 footnote_text, '</td></tr>'
185 )
186 } else {
187 footnote_text <- paste0(
188 '<tr><td style="padding: 0; border: 0;" colspan="100%">',
Hao Zhucdd7f922018-01-08 11:39:40 -0500189 paste0(footnote_text, collapse = " "),
Hao Zhu8dd65a92018-01-05 20:40:27 -0500190 '</td></tr>'
191 )
192 }
Hao Zhu8dd65a92018-01-05 20:40:27 -0500193 return(footnote_text)
194}
Hao Zhucdd7f922018-01-08 11:39:40 -0500195
196# LaTeX
Hao Zhu17814c72018-01-10 11:32:14 -0500197footnote_latex <- function(kable_input, footnote_table, footnote_as_chunk,
198 threeparttable) {
Hao Zhu1ac13ad2018-01-08 16:12:24 -0500199 table_info <- magic_mirror(kable_input)
Hao Zhu3fc0e882018-04-03 16:06:41 -0400200 out <- solve_enc(kable_input)
Hao Zhu17814c72018-01-10 11:32:14 -0500201
Hao Zhu19c4fa52018-01-09 12:01:14 -0500202 footnote_text <- latex_tfoot_maker(footnote_table, footnote_as_chunk,
Hao Zhu17814c72018-01-10 11:32:14 -0500203 table_info$ncol, threeparttable)
204 if (threeparttable) {
Hao Zhu23bde3a2018-03-28 16:00:55 -0400205 if (table_info$tabular %in% c("longtable", "longtabu") ) {
Hao Zhu17814c72018-01-10 11:32:14 -0500206 out <- sub(paste0("\\\\begin\\{", table_info$tabular, "\\}"),
Hao Zhu27c7c852018-03-26 16:18:23 -0400207 paste0("\\\\begin{ThreePartTable}\n\\\\begin{TableNotes}",
208 ifelse(footnote_as_chunk, "[para]", ""),
Hao Zhu5b8eb2b2018-05-20 18:16:06 -0400209 "\n", footnote_text,
Hao Zhu27c7c852018-03-26 16:18:23 -0400210 "\n\\\\end{TableNotes}\n\\\\begin{",
Hao Zhu17814c72018-01-10 11:32:14 -0500211 table_info$tabular, "}"),
212 out)
Hao Zhu23bde3a2018-03-28 16:00:55 -0400213 out <- sub(paste0("\\\\end\\{",table_info$tabular, "\\}"),
214 paste0("\\\\end{", table_info$tabular,
215 "}\n\\\\end{ThreePartTable}"),
216 out)
217 if (table_info$booktabs) {
218 out <- sub("\\\\bottomrule", "\\\\bottomrule\n\\\\insertTableNotes", out)
219 } else {
220 out <- sub("\\\\hline\n\\\\end\\{longtable\\}",
221 "\\\\hline\n\\\\insertTableNotes\n\\\\end\\{longtable\\}",
222 out)
223 }
Hao Zhu27c7c852018-03-26 16:18:23 -0400224 } else {
Hao Zhu23bde3a2018-03-28 16:00:55 -0400225 if (table_info$tabular == "tabu") {
226 stop("Please use `longtable = T` in your kable function. ",
227 "Full width threeparttable only works with longtable.")
228 }
229 out <- sub(paste0("\\\\begin\\{", table_info$tabular, "\\}"),
230 paste0("\\\\begin{threeparttable}\n\\\\begin{",
231 table_info$tabular, "}"),
232 out)
233 out <- sub(table_info$end_tabular,
234 paste0("\\\\end{", table_info$tabular,
235 "}\n\\\\begin{tablenotes}",
236 ifelse(footnote_as_chunk, "[para]", ""),
Hao Zhu5b8eb2b2018-05-20 18:16:06 -0400237 "\n", footnote_text,
Hao Zhu23bde3a2018-03-28 16:00:55 -0400238 "\n\\\\end{tablenotes}\n\\\\end{threeparttable}"),
Hao Zhu27c7c852018-03-26 16:18:23 -0400239 out)
240 }
Hao Zhu17814c72018-01-10 11:32:14 -0500241 } else {
Hao Zhu23bde3a2018-03-28 16:00:55 -0400242 if (table_info$booktabs) {
243 out <- sub("\\\\bottomrule",
244 paste0("\\\\bottomrule\n", footnote_text), out)
245 } else {
246 out <- sub(table_info$end_tabular,
247 paste0(footnote_text, "\n\\\\end{", table_info$tabular, "}"),
248 out)
249 }
Hao Zhu17814c72018-01-10 11:32:14 -0500250 }
251
Hao Zhu19c4fa52018-01-09 12:01:14 -0500252 out <- structure(out, format = "latex", class = "knitr_kable")
253 attr(out, "kable_meta") <- table_info
254 return(out)
Hao Zhu19c4fa52018-01-09 12:01:14 -0500255}
Hao Zhucdd7f922018-01-08 11:39:40 -0500256
Hao Zhu17814c72018-01-10 11:32:14 -0500257latex_tfoot_maker <- function(footnote_table, footnote_as_chunk, ncol,
258 threeparttable) {
Hao Zhu19c4fa52018-01-09 12:01:14 -0500259 footnote_types <- names(footnote_table$contents)
260 footnote_text <- c()
Hao Zhu17814c72018-01-10 11:32:14 -0500261 if (threeparttable) {
262 for (i in footnote_types) {
263 footnote_text <- c(footnote_text, latex_tfoot_maker_tpt_(
264 footnote_table$contents[[i]], footnote_table$titles[[i]],
265 footnote_as_chunk, ncol))
266 }
267 } else {
268 for (i in footnote_types) {
269 footnote_text <- c(footnote_text, latex_tfoot_maker_(
270 footnote_table$contents[[i]], footnote_table$titles[[i]],
271 footnote_as_chunk, ncol))
272 }
Hao Zhu19c4fa52018-01-09 12:01:14 -0500273 }
274 footnote_text <- paste0(footnote_text, collapse = "\n")
275 return(footnote_text)
276}
Hao Zhu9f917482018-01-08 18:09:33 -0500277
Hao Zhu17814c72018-01-10 11:32:14 -0500278latex_tfoot_maker_ <- function(ft_contents, ft_title, ft_chunk, ncol) {
Hao Zhu19c4fa52018-01-09 12:01:14 -0500279 footnote_text <- apply(ft_contents, 1, function(x) {
280 if (x[1] == "") {
281 x[2]
282 } else {
283 paste0('\\\\textsuperscript{', x[1], '} ', x[2])
284 }
285 })
286 if (ft_title != "") {
287 title_text <- paste0('\\\\textbf{', ft_title, '} ')
288 footnote_text <- c(title_text, footnote_text)
289 }
290 if (!ft_chunk) {
291 footnote_text <- paste0(
292 '\\\\multicolumn{', ncol, '}{l}{', footnote_text, '}\\\\\\\\'
293 )
294 } else {
295 footnote_text <- paste0(
296 '\\\\multicolumn{', ncol, '}{l}{',
297 paste0(footnote_text, collapse = " "),
298 '}\\\\\\\\'
299 )
300 }
301 return(footnote_text)
Hao Zhucdd7f922018-01-08 11:39:40 -0500302}
Hao Zhu17814c72018-01-10 11:32:14 -0500303
304latex_tfoot_maker_tpt_ <- function(ft_contents, ft_title, ft_chunk, ncol) {
305 footnote_text <- apply(ft_contents, 1, function(x) {
306 if (x[1] == "") {
307 paste0('\\\\item ', x[2])
308 } else {
309 paste0('\\\\item[', x[1], '] ', x[2])
310 }
311 })
312 if (ft_title != "") {
313 title_text <- paste0('\\\\item \\\\textbf{', ft_title, '} ')
314 footnote_text <- c(title_text, footnote_text)
315 }
316 footnote_text <- paste0(footnote_text, collapse = "\n")
317 # if (!ft_chunk) {
318 # footnote_text <- paste0(footnote_text, collapse = "\n")
319 # } else {
320 # footnote_text <- paste0(footnote_text, collapse = " ")
321 # }
322 return(footnote_text)
323}