blob: 6fa69712daee1ac4e34b79403031dfa023c228a1 [file] [log] [blame]
Hao Zhu96a50b52017-06-14 18:09:35 -04001#' Add a header column
2#'
3#' @description Experimenting. Please don't use it in production
4#'
5#' @export
Hao Zhu2ce42b92017-06-15 17:15:33 -04006add_header_left <- function(kable_input, header = NULL, header_name = "",
7 align = "c") {
Hao Zhu96a50b52017-06-14 18:09:35 -04008 if (is.null(header)) return(kable_input)
9 kable_format <- attr(kable_input, "format")
10 if (!kable_format %in% c("html", "latex")) {
11 stop("Please specify output format in your kable function. Currently ",
12 "generic markdown table using pandoc is not supported.")
13 }
14 if (kable_format == "html") {
Hao Zhu2ce42b92017-06-15 17:15:33 -040015 return(add_header_left_html(kable_input, header, header_name, align))
Hao Zhu96a50b52017-06-14 18:09:35 -040016 }
17 if (kable_format == "latex") {
Hao Zhu2ce42b92017-06-15 17:15:33 -040018 return(add_header_left_latex(kable_input, header, header_name, align))
Hao Zhu96a50b52017-06-14 18:09:35 -040019 }
20}
21
22# HTML
Hao Zhu2ce42b92017-06-15 17:15:33 -040023add_header_left_html <- function(kable_input, header, header_name, align) {
Hao Zhu96a50b52017-06-14 18:09:35 -040024 kable_attrs <- attributes(kable_input)
25 kable_xml <- read_xml(as.character(kable_input), options = "COMPACT")
26 kable_thead <- xml_tpart(kable_xml, "thead")
27 kable_tbody <- xml_tpart(kable_xml, "tbody")
28
Hao Zhu2ce42b92017-06-15 17:15:33 -040029 align <- switch(align, "c" = "center", "l" = "left", "r" = "right")
30
Hao Zhu96a50b52017-06-14 18:09:35 -040031 new_header <- paste0(
Hao Zhu2ce42b92017-06-15 17:15:33 -040032 '<th style="text-align:', align, '; vertical-align: bottom;" rowspan="',
Hao Zhu96a50b52017-06-14 18:09:35 -040033 length(xml_children(kable_thead)), '">', header_name, '</th>'
34 )
35 new_header <- read_xml(new_header, options = c("COMPACT"))
36 xml_add_child(xml_child(kable_thead, 1), new_header, .where = 0)
37
Hao Zhu2ce42b92017-06-15 17:15:33 -040038 header <- standardize_header(header, length(xml_children(kable_tbody)))
Hao Zhu96a50b52017-06-14 18:09:35 -040039 for (i in 1:nrow(header)) {
40 new_row_item <- paste0(
Hao Zhu2ce42b92017-06-15 17:15:33 -040041 '<td style="text-align:', align, '; vertical-align: middle;" rowspan="',
Hao Zhu96a50b52017-06-14 18:09:35 -040042 header$rowspan[i], '">', header$header[i], '</td>')
43 new_row_item <- read_xml(new_row_item, options = "COMPACT")
44 target_row <- xml_child(kable_tbody, header$row[i])
45 xml_add_child(target_row, new_row_item, .where = 0)
46 }
47
48 out <- structure(as.character(kable_xml), format = "html",
49 class = "knitr_kable")
50 attributes(out) <- kable_attrs
51 return(out)
52}
53
Hao Zhu2ce42b92017-06-15 17:15:33 -040054standardize_header <- function(header, n_row) {
Hao Zhu96a50b52017-06-14 18:09:35 -040055 header_names <- names(header)
56
57 if (is.null(header_names)) {
58 return(data.frame(header = header, row = 1:length(header),
59 rowspan = 1, row.names = NULL))
60 }
61
62 names(header)[header_names == ""] <- header[header_names == ""]
63 header[header_names == ""] <- 1
64 header_names <- names(header)
65 header <- as.numeric(header)
66 names(header) <- header_names
67 if (sum(header) < n_row) {
68 header <- c(header, " " = n_row - sum(header))
69 }
70 row_pos <- c(1, cumsum(header)[-length(header)] + 1)
71 return(data.frame(
72 header = names(header),
73 row = row_pos, rowspan = header, row.names = NULL
74 ))
75}
76
Hao Zhu2ce42b92017-06-15 17:15:33 -040077add_header_left_latex <- function(kable_input, header, header_name, align) {
78 table_info <- magic_mirror(kable_input)
79 usepackage_latex("multirow")
80 if (!table_info$booktabs) {
81 warning("add_header_left only supports LaTeX table with booktabs. Please",
82 " use kable(..., booktabs = T) in your kable function.")
83 }
84 out <- as.character(kable_input)
85 contents <- table_info$contents
86 header_name <- escape_latex(header_name)
87 header <- standardize_header(header, length(contents) - 1)
88 header$header <- escape_latex(header$header)
89 header$header <- gsub("\\\\", "\\\\\\\\", header$header)
90 header$row <- header$row + 1
91 header$row_end <- header$row + header$rowspan - 1
92
93 # Align
94 out <- sub(paste0(table_info$begin_tabular, "\\{"),
95 paste0(table_info$begin_tabular, "{", align,
96 ifelse(table_info$booktabs, "", "|")),
97 out, perl = T)
98
99 # Header
100 if (!is.null(table_info$new_header_row)) {
101 new_header_row <- table_info$new_header_row
102 for (i in 1:length(new_header_row)) {
103 out <- sub(regex_escape(new_header_row[i]),
104 paste0(" & ", new_header_row[i]), out)
105 cline_old <- cline_gen(table_info$header_df[[i]], table_info$booktabs)
106 cline_old <- regex_escape(cline_old)
107 table_info$header_df[[i]] <- rbind(
108 data.frame(header = " ", colspan = 1),
109 table_info$header_df[[i]]
110 )
111 cline_new <- cline_gen(table_info$header_df[[i]], table_info$booktabs)
112 out <- sub(cline_old, cline_new, out)
113 }
114 }
115 out <- sub(contents[1], paste0(header_name, " & ", contents[1]), out)
116 table_info$contents[1] <- paste0(header_name, " & ", contents[1])
117
118 # move existing midrules if exists
119 out_lines <- read_lines(out)
120 tbody_start_row <- which(out_lines == "\\midrule")
121 tbody_end_row <- which(out_lines == "\\bottomrule")
122 before_tbody <- out_lines[seq(1, tbody_start_row)]
123 tbody <- out_lines[seq(tbody_start_row + 1, tbody_end_row - 1)]
124 after_tbody <- out_lines[seq(tbody_end_row, length(out_lines))]
125
126 # Remove addlinespace in this case
127 tbody <- tbody[tbody != "\\addlinespace"]
128
129 midrule_exist <- str_sub(tbody, 1, 9) == "\\cmidrule"
130 if (sum(midrule_exist) > 0) {
131 existing_midrules <- which(midrule_exist)
132 tbody[existing_midrules] <- unlist(lapply(
133 tbody[existing_midrules], cmidrule_plus_one
134 ))
135 out <- paste0(c(before_tbody, tbody, after_tbody), collapse = "\n")
136 }
137
138 for (j in 1:nrow(header)) {
139 new_row_pre <- paste0(
140 "\\\\multirow\\{", -header$rowspan[j], "\\}\\{\\*\\}\\{", header$header[j], "\\} & "
141 )
142 new_row_text <- paste0(new_row_pre, contents[header$row_end[j]])
143 out <- sub(contents[header$row_end[j]], new_row_text, out)
144 table_info$contents[header$row_end[j]] <- new_row_text
145 if (j != nrow(header)) {
146 out <- sub(
147 paste0(contents[header$row_end[j]], "\\\\\\\\\n"),
148 paste0(contents[header$row_end[j]],
149 "\\\\\\\\\n\\\\cmidrule[0.5pt](l{2pt}r{2pt}){1-1}\n"),
150 out
151 )
152 }
153 }
154
155 for (k in setdiff(seq(2, length(contents)), header$row_end)) {
156 out <- sub(contents[k],
157 paste0(" & ", contents[k]),
158 out)
159 table_info$contents[k] <- paste0(" & ", contents[k])
160 }
161
162 out <- structure(out, format = "latex", class = "knitr_kable")
163 attr(out, "original_kable_meta") <- table_info
164 return(out)
165}
166
167cmidrule_plus_one <- function(x) {
168 start_pos <- as.numeric(str_match(x, "\\)\\{(.*)-")[2]) + 1
169 stop_pos <- as.numeric(str_match(x, "-(.*)\\}")[2]) + 1
170 return(
171 paste0("\\cmidrule[0.5pt](l{2pt}r{2pt}){", start_pos, "-", stop_pos, "}")
172 )
Hao Zhu96a50b52017-06-14 18:09:35 -0400173}