add collapse_rows html
diff --git a/NAMESPACE b/NAMESPACE
index a572229..905a515 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -4,6 +4,7 @@
 export(add_footnote)
 export(add_header_above)
 export(add_indent)
+export(collapse_rows)
 export(column_spec)
 export(group_rows)
 export(kable_styling)
@@ -40,4 +41,5 @@
 importFrom(xml2,xml_children)
 importFrom(xml2,xml_has_attr)
 importFrom(xml2,xml_name)
+importFrom(xml2,xml_remove)
 importFrom(xml2,xml_text)
diff --git a/R/collapse_rows.R b/R/collapse_rows.R
new file mode 100644
index 0000000..57d8987
--- /dev/null
+++ b/R/collapse_rows.R
@@ -0,0 +1,81 @@
+#' Collapse repeat rows to multirow cell
+#'
+#' @export
+collapse_rows <- function(kable_input, columns) {
+  if (is.null(columns)) {
+    stop("Please specify numeric positions of columns you want to collapse.")
+  }
+  kable_format <- attr(kable_input, "format")
+  if (!kable_format %in% c("html", "latex")) {
+    message("Currently generic markdown table using pandoc is not supported.")
+    return(kable_input)
+  }
+  if (kable_format == "html") {
+    return(collapse_rows_html(kable_input, columns))
+  }
+  if (kable_format == "latex") {
+    return(collapse_rows_latex(kable_input, columns))
+  }
+}
+
+collapse_rows_html <- function(kable_input, columns) {
+  kable_attrs <- attributes(kable_input)
+  kable_xml <- read_xml(as.character(kable_input), options = "COMPACT")
+  kable_tbody <- xml_tpart(kable_xml, "tbody")
+
+  kable_dt <- rvest::html_table(xml2::read_html(as.character(kable_input)))[[1]]
+  kable_dt$row_id <- rownames(kable_dt)
+  collapse_matrix <- collapse_row_matrix(kable_dt, columns)
+
+  for (i in 1:nrow(collapse_matrix)) {
+    matrix_row <- collapse_matrix[i, ]
+    if (sum(matrix_row) != length(matrix_row)) {
+      target_row <- xml_child(kable_tbody, i)
+      row_node_rm_count <- 0
+      for (j in 1:length(matrix_row)) {
+        if (matrix_row[j] != 1) {
+          collapsing_col <- as.numeric(sub("x", "", names(matrix_row)[j])) -
+            row_node_rm_count
+          target_cell <- xml_child(target_row, collapsing_col)
+          if (matrix_row[j] == 0) {
+            xml_remove(target_cell)
+            row_node_rm_count <- row_node_rm_count + 1
+          } else {
+            xml_attr(target_cell, "rowspan") <- matrix_row[j]
+            xml_attr(target_cell, "style") <- paste0(
+              xml_attr(target_cell, "style"),
+              "vertical-align: middle !important;")
+          }
+        }
+      }
+    }
+  }
+
+  out <- structure(as.character(kable_xml), format = "html",
+                   class = "knitr_kable")
+  attributes(out) <- kable_attrs
+  return(out)
+}
+
+collapse_rows_latex <- function(kable_input, columns) {
+  # table_info <- magic_mirror(kable_input)
+  # target_row <- table_info$contents[row + 1]
+  # new_row <- latex_row_cells(target_row)
+  # if (bold) {
+  #   new_row <- lapply(new_row, function(x) {
+  #     paste0("\\\\bfseries{", x, "}")
+  #   })
+  # }
+  # if (italic) {
+  #   new_row <- lapply(new_row, function(x) {
+  #     paste0("\\\\em{", x, "}")
+  #   })
+  # }
+  # new_row <- paste(unlist(new_row), collapse = " & ")
+  #
+  # out <- sub(target_row, new_row, as.character(kable_input), perl = T)
+  # out <- structure(out, format = "latex", class = "knitr_kable")
+  # attr(out, "original_kable_meta") <- table_info
+  # return(out)
+  kable_input
+}
diff --git a/R/kableExtra-package.R b/R/kableExtra-package.R
index a753a32..b0674be 100644
--- a/R/kableExtra-package.R
+++ b/R/kableExtra-package.R
@@ -4,7 +4,7 @@
 #' str_extract str_replace_all str_trim str_extract_all str_sub
 #' @importFrom xml2 read_xml xml_attr xml_has_attr xml_attr<- read_html
 #' xml_child xml_children xml_name xml_add_sibling xml_add_child xml_text
-#' xml_text<-
+#' xml_remove xml_text<-
 #' @importFrom rvest html_table
 #' @importFrom knitr knit_meta_add
 #' @importFrom rmarkdown latex_dependency
diff --git a/R/util.R b/R/util.R
index 90c84bc..3f5c470 100644
--- a/R/util.R
+++ b/R/util.R
@@ -55,3 +55,15 @@
 latex_row_cells <- function(x) {
   strsplit(x, " \\& ")
 }
+
+collapse_row_matrix <- function(kable_dt, columns)  {
+  mapping_matrix <- list()
+  for (i in columns) {
+    mapping_matrix[[paste0("x", i)]] <- unlist(lapply(
+      rle(kable_dt[, i])$length, function(x) {
+      c(x, rep(0, x - 1))
+      }))
+  }
+  mapping_matrix <- data.frame(mapping_matrix)
+  return(mapping_matrix)
+}
diff --git a/man/collapse_rows.Rd b/man/collapse_rows.Rd
new file mode 100644
index 0000000..aafb7e5
--- /dev/null
+++ b/man/collapse_rows.Rd
@@ -0,0 +1,11 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/collapse_rows.R
+\name{collapse_rows}
+\alias{collapse_rows}
+\title{Collapse repeat rows to multirow cell}
+\usage{
+collapse_rows(kable_input, columns)
+}
+\description{
+Collapse repeat rows to multirow cell
+}
diff --git a/man/row_spec.Rd b/man/row_spec.Rd
index 169c05a..deb7912 100644
--- a/man/row_spec.Rd
+++ b/man/row_spec.Rd
@@ -9,7 +9,8 @@
 \arguments{
 \item{kable_input}{Output of `knitr::kable()` with `format` specified}
 
-\item{row}{A numeric value indicating which row to be selected}
+\item{row}{A numeric value indicating which row to be selected. You don't
+need to count in header rows or group labeling rows.}
 
 \item{bold}{A T/F value to control whether the text of the selected row
 need to be bolded.}
diff --git a/tests/visual_tests/collapse_rows_html.Rmd b/tests/visual_tests/collapse_rows_html.Rmd
new file mode 100644
index 0000000..f9546c3
--- /dev/null
+++ b/tests/visual_tests/collapse_rows_html.Rmd
@@ -0,0 +1,16 @@
+---
+title: "Untitled"
+output: html_document
+---
+
+```{r}
+library(knitr)
+library(kableExtra)
+collapse_rows_dt <- data.frame(C1 = c(rep("a", 10), rep("b", 5)),
+                 C2 = c(rep("c", 7), rep("d", 3), rep("c", 2), rep("d", 3)),
+                 C3 = 1:15,
+                 C4 = sample(c(0,1), 15, replace = TRUE))
+kable(collapse_rows_dt, "html", align = "c") %>%
+  kable_styling() %>%
+  collapse_rows(1:2)
+```