| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 1 | #' Put a few rows of a table into one category | 
 | 2 | #' | 
| Hao Zhu | bd95bb2 | 2017-05-22 16:08:49 -0400 | [diff] [blame] | 3 | #' @description Group a few rows in a table together under a label. | 
 | 4 | #' | 
 | 5 | #' @param kable_input Output of `knitr::kable()` with `format` specified | 
 | 6 | #' @param group_label A character string for the name of the group | 
 | 7 | #' @param start_row A numeric value that tells the function in which row the | 
 | 8 | #' group starts. Note that the counting excludes header rows and other group | 
 | 9 | #' labeling rows | 
 | 10 | #' @param end_row A numeric value that tells the function in which row the group | 
 | 11 | #' ends. | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 12 | #' @param index A named vector providing the index for robust row-grouping tasks. | 
 | 13 | #' Basically, you can use it in the same way as `add_header_above()`. | 
| Hao Zhu | bd95bb2 | 2017-05-22 16:08:49 -0400 | [diff] [blame] | 14 | #' @param label_row_css A character string for any customized css used for the | 
 | 15 | #' labeling row. By default, the labeling row will have a solid black line | 
 | 16 | #' underneath. Only useful for HTML documents. | 
 | 17 | #' @param latex_gap_space A character value telling LaTeX how large the gap | 
 | 18 | #' between the previous row and the group labeling row. Only useful for LaTeX | 
 | 19 | #' documents. | 
| Hao Zhu | ac7e70f | 2017-08-02 00:18:36 -0400 | [diff] [blame] | 20 | #' @param escape A T/F value showing whether special characters should be | 
 | 21 | #' escaped. | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 22 | #' @param colnum A numeric that determines how many columns the text should span. | 
 | 23 | #' The default setting will have the text span the entire length. | 
| Hao Zhu | bd95bb2 | 2017-05-22 16:08:49 -0400 | [diff] [blame] | 24 | #' | 
| Hao Zhu | 78e6122 | 2017-05-24 20:53:35 -0400 | [diff] [blame] | 25 | #' @examples x <- knitr::kable(head(mtcars), "html") | 
 | 26 | #' # Put Row 2 to Row 5 into a Group and label it as "Group A" | 
 | 27 | #' group_rows(x, "Group A", 2, 5) | 
 | 28 | #' | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 29 | #' @export | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 30 | group_rows <- function(kable_input, group_label = NULL, | 
 | 31 |                        start_row = NULL, end_row = NULL, | 
 | 32 |                        index = NULL, | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 33 |                        label_row_css = "border-bottom: 1px solid;", | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 34 |                        latex_gap_space = "0.3em", | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 35 |                        escape = TRUE, colnum = NULL) { | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 36 |  | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 37 |   kable_format <- attr(kable_input, "format") | 
 | 38 |   if (!kable_format %in% c("html", "latex")) { | 
| Hao Zhu | 401ebd8 | 2018-01-14 17:10:20 -0500 | [diff] [blame] | 39 |     warning("Please specify format in kable. kableExtra can customize either ", | 
 | 40 |             "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ", | 
 | 41 |             "for details.") | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 42 |     return(kable_input) | 
 | 43 |   } | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 44 |   if (is.null(index)) { | 
 | 45 |     if (kable_format == "html") { | 
 | 46 |       return(group_rows_html(kable_input, group_label, start_row, end_row, | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 47 |                              label_row_css, escape, colnum)) | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 48 |     } | 
 | 49 |     if (kable_format == "latex") { | 
 | 50 |       return(group_rows_latex(kable_input, group_label, start_row, end_row, | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 51 |                               latex_gap_space, escape, colnum)) | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 52 |     } | 
 | 53 |   } else { | 
 | 54 |     index <- group_row_index_translator(index) | 
 | 55 |     out <- kable_input | 
 | 56 |     if (kable_format == "html") { | 
 | 57 |       for (i in 1:nrow(index)) { | 
 | 58 |         out <- group_rows_html(out, index$header[i], | 
 | 59 |                                index$start[i], index$end[i], | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 60 |                                label_row_css, escape, colnum) | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 61 |       } | 
 | 62 |     } | 
 | 63 |     if (kable_format == "latex") { | 
 | 64 |       for (i in 1:nrow(index)) { | 
 | 65 |         out <- group_rows_latex(out, index$header[i], | 
 | 66 |                                index$start[i], index$end[i], | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 67 |                                latex_gap_space, escape, colnum) | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 68 |       } | 
 | 69 |     } | 
 | 70 |     return(out) | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 71 |   } | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 72 | } | 
 | 73 |  | 
 | 74 | group_row_index_translator <- function(index) { | 
 | 75 |   index <- standardize_header_input(index) | 
 | 76 |   index$start <- cumsum(c(1, index$colspan))[1:length(index$colspan)] | 
 | 77 |   index$end <- cumsum(index$colspan) | 
 | 78 |   index$header <- trimws(index$header) | 
 | 79 |   index <- index[index$header != "", ] | 
 | 80 |   return(index) | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 81 | } | 
 | 82 |  | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 83 | group_rows_html <- function(kable_input, group_label, start_row, end_row, | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 84 |                             label_row_css, escape, colnum) { | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 85 |   kable_attrs <- attributes(kable_input) | 
| Hao Zhu | 558c72f | 2017-07-24 15:12:00 -0400 | [diff] [blame] | 86 |   kable_xml <- read_kable_as_xml(kable_input) | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 87 |   kable_tbody <- xml_tpart(kable_xml, "tbody") | 
 | 88 |  | 
| Hao Zhu | ac7e70f | 2017-08-02 00:18:36 -0400 | [diff] [blame] | 89 |   if (escape) { | 
 | 90 |     group_label <- escape_html(group_label) | 
 | 91 |   } | 
 | 92 |  | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 93 |   group_header_rows <- attr(kable_input, "group_header_rows") | 
 | 94 |   group_seq <- seq(start_row, end_row) | 
 | 95 |   if (!is.null(group_header_rows)) { | 
 | 96 |     group_seq <- positions_corrector(group_seq, group_header_rows, | 
 | 97 |                                      length(xml_children(kable_tbody))) | 
 | 98 |   } | 
 | 99 |  | 
 | 100 |   # Insert a group header row | 
 | 101 |   starting_node <- xml_child(kable_tbody, group_seq[1]) | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 102 |   kable_ncol <- ifelse(is.null(colnum), | 
 | 103 |                        length(xml_children(starting_node)), | 
 | 104 |                        colnum) | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 105 |   group_header_row_text <- paste0( | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 106 |     '<tr groupLength="', length(group_seq), '"><td colspan="', kable_ncol, | 
 | 107 |     '" style="', label_row_css, '"><strong>', group_label, | 
 | 108 |     "</strong></td></tr>" | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 109 |   ) | 
 | 110 |   group_header_row <- read_xml(group_header_row_text, options = "COMPACT") | 
 | 111 |   xml_add_sibling(starting_node, group_header_row, .where = "before") | 
 | 112 |  | 
 | 113 |   # add indentations to items | 
| Hao Zhu | f2dfd14 | 2017-07-24 14:43:28 -0400 | [diff] [blame] | 114 |   out <- as_kable_xml(kable_xml) | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 115 |   attributes(out) <- kable_attrs | 
 | 116 |   attr(out, "group_header_rows") <- c(attr(out, "group_header_rows"), group_seq[1]) | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 117 |   out <- add_indent_html(out, positions = seq(start_row, end_row)) | 
| Hao Zhu | 62cdde5 | 2017-05-20 22:16:03 -0400 | [diff] [blame] | 118 |   return(out) | 
 | 119 | } | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 120 |  | 
| Hao Zhu | fc14c9b | 2017-05-22 14:03:22 -0400 | [diff] [blame] | 121 | group_rows_latex <- function(kable_input, group_label, start_row, end_row, | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 122 |                              gap_space, escape, colnum) { | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 123 |   table_info <- magic_mirror(kable_input) | 
| Hao Zhu | d2c0f73 | 2017-08-26 10:40:14 -0400 | [diff] [blame] | 124 |   out <- enc2utf8(as.character(kable_input)) | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 125 |  | 
| Hao Zhu | 064990d | 2017-10-17 18:08:42 -0400 | [diff] [blame] | 126 |   if (table_info$duplicated_rows) { | 
 | 127 |     dup_fx_out <- fix_duplicated_rows_latex(out, table_info) | 
 | 128 |     out <- dup_fx_out[[1]] | 
 | 129 |     table_info <- dup_fx_out[[2]] | 
 | 130 |   } | 
 | 131 |  | 
| Hao Zhu | ac7e70f | 2017-08-02 00:18:36 -0400 | [diff] [blame] | 132 |   if (escape) { | 
 | 133 |     group_label <- escape_latex(group_label) | 
 | 134 |     group_label <- gsub("\\\\", "\\\\\\\\", group_label) | 
 | 135 |   } | 
 | 136 |  | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 137 |   # Add group label | 
 | 138 |   rowtext <- table_info$contents[start_row + 1] | 
 | 139 |   if (table_info$booktabs) { | 
 | 140 |     new_rowtext <- paste0( | 
 | 141 |       "\\\\addlinespace[", gap_space, "]\n", | 
| bsalzer | 8593327 | 2018-02-12 17:26:46 -0500 | [diff] [blame^] | 142 |       "\\\\multicolumn{", ifelse(is.null(colnum), | 
 | 143 |                                  table_info$ncol, | 
 | 144 |                                  colnum), | 
 | 145 |       "}{l}{\\\\textbf{", group_label, | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 146 |       "}}\\\\\\\\\n", | 
 | 147 |       rowtext | 
 | 148 |     ) | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 149 |   } else { | 
 | 150 |     rowtext <- paste0("\\\\hline\n", rowtext) | 
 | 151 |     new_rowtext <- paste0( | 
 | 152 |       "\\\\hline\n\\\\multicolumn{", table_info$ncol, "}{l}{\\\\textbf{", | 
 | 153 |       group_label, "}}\\\\\\\\\n", rowtext | 
 | 154 |     ) | 
 | 155 |   } | 
 | 156 |   out <- sub(rowtext, new_rowtext, out) | 
| Hao Zhu | 8f20299 | 2017-07-15 02:20:18 -0400 | [diff] [blame] | 157 |   out <- gsub("\\\\addlinespace\n", "", out) | 
| Hao Zhu | d2c0f73 | 2017-08-26 10:40:14 -0400 | [diff] [blame] | 158 |   out <- structure(out, format = "latex", class = "knitr_kable") | 
| Hao Zhu | 32f43f7 | 2017-06-20 18:24:54 -0400 | [diff] [blame] | 159 |   table_info$group_rows_used <- TRUE | 
 | 160 |   attr(out, "kable_meta") <- table_info | 
| Hao Zhu | 49483bf | 2017-09-12 11:21:00 -0400 | [diff] [blame] | 161 |   out <- add_indent_latex(out, seq(start_row, end_row)) | 
| Hao Zhu | d972e7f | 2017-05-22 13:27:15 -0400 | [diff] [blame] | 162 |   return(out) | 
 | 163 | } |