blob: 97336e5f01fc1ca5aad5c64ce5ee972d3903f4d2 [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 = "",
Hao Zhu520d91b2017-06-16 01:41:26 -04007 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 Zhu520d91b2017-06-16 01:41:26 -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 Zhu520d91b2017-06-16 01:41:26 -040077add_header_left_latex <- function(kable_input, header, header_name, align,
78 full_midline = F) {
Hao Zhu2ce42b92017-06-15 17:15:33 -040079 table_info <- magic_mirror(kable_input)
80 usepackage_latex("multirow")
81 if (!table_info$booktabs) {
82 warning("add_header_left only supports LaTeX table with booktabs. Please",
83 " use kable(..., booktabs = T) in your kable function.")
84 }
85 out <- as.character(kable_input)
86 contents <- table_info$contents
87 header_name <- escape_latex(header_name)
88 header <- standardize_header(header, length(contents) - 1)
89 header$header <- escape_latex(header$header)
90 header$header <- gsub("\\\\", "\\\\\\\\", header$header)
91 header$row <- header$row + 1
92 header$row_end <- header$row + header$rowspan - 1
93
94 # Align
95 out <- sub(paste0(table_info$begin_tabular, "\\{"),
96 paste0(table_info$begin_tabular, "{", align,
97 ifelse(table_info$booktabs, "", "|")),
98 out, perl = T)
Hao Zhu520d91b2017-06-16 01:41:26 -040099 table_info$align_vector <- c(align, table_info$align_vector)
Hao Zhu2ce42b92017-06-15 17:15:33 -0400100
101 # Header
102 if (!is.null(table_info$new_header_row)) {
103 new_header_row <- table_info$new_header_row
104 for (i in 1:length(new_header_row)) {
105 out <- sub(regex_escape(new_header_row[i]),
106 paste0(" & ", new_header_row[i]), out)
107 cline_old <- cline_gen(table_info$header_df[[i]], table_info$booktabs)
108 cline_old <- regex_escape(cline_old)
109 table_info$header_df[[i]] <- rbind(
110 data.frame(header = " ", colspan = 1),
111 table_info$header_df[[i]]
112 )
113 cline_new <- cline_gen(table_info$header_df[[i]], table_info$booktabs)
114 out <- sub(cline_old, cline_new, out)
115 }
116 }
117 out <- sub(contents[1], paste0(header_name, " & ", contents[1]), out)
118 table_info$contents[1] <- paste0(header_name, " & ", contents[1])
119
120 # move existing midrules if exists
121 out_lines <- read_lines(out)
122 tbody_start_row <- which(out_lines == "\\midrule")
123 tbody_end_row <- which(out_lines == "\\bottomrule")
124 before_tbody <- out_lines[seq(1, tbody_start_row)]
125 tbody <- out_lines[seq(tbody_start_row + 1, tbody_end_row - 1)]
126 after_tbody <- out_lines[seq(tbody_end_row, length(out_lines))]
127
128 # Remove addlinespace in this case
129 tbody <- tbody[tbody != "\\addlinespace"]
130
131 midrule_exist <- str_sub(tbody, 1, 9) == "\\cmidrule"
132 if (sum(midrule_exist) > 0) {
133 existing_midrules <- which(midrule_exist)
134 tbody[existing_midrules] <- unlist(lapply(
135 tbody[existing_midrules], cmidrule_plus_one
136 ))
137 out <- paste0(c(before_tbody, tbody, after_tbody), collapse = "\n")
138 }
139
140 for (j in 1:nrow(header)) {
141 new_row_pre <- paste0(
142 "\\\\multirow\\{", -header$rowspan[j], "\\}\\{\\*\\}\\{", header$header[j], "\\} & "
143 )
144 new_row_text <- paste0(new_row_pre, contents[header$row_end[j]])
145 out <- sub(contents[header$row_end[j]], new_row_text, out)
146 table_info$contents[header$row_end[j]] <- new_row_text
147 if (j != nrow(header)) {
148 out <- sub(
149 paste0(contents[header$row_end[j]], "\\\\\\\\\n"),
150 paste0(contents[header$row_end[j]],
Hao Zhu520d91b2017-06-16 01:41:26 -0400151 "\\\\\\\\\n\\\\cmidrule[0.5pt](l{2pt}r{2pt}){1-",
152 ifelse(full_midline, str_count(tbody[1], " & ") + 2, 1),
153 "}\n"),
Hao Zhu2ce42b92017-06-15 17:15:33 -0400154 out
155 )
156 }
157 }
158
159 for (k in setdiff(seq(2, length(contents)), header$row_end)) {
160 out <- sub(contents[k],
161 paste0(" & ", contents[k]),
162 out)
163 table_info$contents[k] <- paste0(" & ", contents[k])
164 }
165
166 out <- structure(out, format = "latex", class = "knitr_kable")
167 attr(out, "original_kable_meta") <- table_info
168 return(out)
169}
170
171cmidrule_plus_one <- function(x) {
172 start_pos <- as.numeric(str_match(x, "\\)\\{(.*)-")[2]) + 1
173 stop_pos <- as.numeric(str_match(x, "-(.*)\\}")[2]) + 1
174 return(
175 paste0("\\cmidrule[0.5pt](l{2pt}r{2pt}){", start_pos, "-", stop_pos, "}")
176 )
Hao Zhu96a50b52017-06-14 18:09:35 -0400177}