diff --git a/DESCRIPTION b/DESCRIPTION
index 3d04dd4811..ec4f292d05 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -98,6 +98,7 @@ Collate:
'print.R'
'reexports.R'
'render_as_html.R'
+ 'render_ir.R'
'resolver.R'
'shiny.R'
'summary_rows.R'
@@ -109,6 +110,7 @@ Collate:
'utils_render_common.R'
'utils_render_footnotes.R'
'utils_render_html.R'
+ 'utils_render_ir.R'
'utils_render_latex.R'
'utils_render_rtf.R'
'zzz.R'
diff --git a/R/render_ir.R b/R/render_ir.R
new file mode 100644
index 0000000000..763f8140e5
--- /dev/null
+++ b/R/render_ir.R
@@ -0,0 +1,62 @@
+#' Transform a **gt** table object to its intermediate representation (IR)
+#'
+#' Take a `gt_tbl` table object and transform it to valid gt IR text.
+#'
+#' @param data A table object that is created using the `gt()` function.
+#'
+#' @return A character object.
+#'
+#' @noRd
+render_to_ir <- function(data,
+ target = c("html", "latex", "rtf")) {
+
+ target <- match.arg(target)
+
+ data <- build_data(data = data, context = target)
+
+ # Composition of IR -------------------------------------------------------
+
+ # Upgrade `_styles` to gain a `html_style` column with CSS style rules
+ data <- add_css_styles(data = data)
+
+ # Create the column group component
+ colgroup_component <- create_col_group_ir(data = data)
+
+ # Create the heading component
+ heading_component <- create_heading_ir(data = data)
+
+ # Create the columns component
+ columns_component <- create_columns_ir(data = data)
+
+ # Create the body component
+ body_component <- create_body_ir(data = data)
+
+ # Create the source notes component
+ source_notes_component <- create_source_notes_ir(data = data)
+
+ # Create the footnotes component
+ footnotes_component <- create_footnotes_ir(data = data)
+
+ # Compose the IR
+ combine_as_ir(
+ data = data,
+ target = target,
+ colgroup_component = colgroup_component,
+ heading_component = heading_component,
+ columns_component = columns_component,
+ body_component = body_component,
+ source_notes_component = source_notes_component,
+ footnotes_component = footnotes_component
+ )
+}
+
+get_tr_count_from_component <- function(html_string) {
+
+ length(
+ xml2::xml_find_all(
+ xml2::read_html(
+ as.character(html_string)),
+ xpath = ".//tr"
+ )
+ )
+}
diff --git a/R/utils_render_html.R b/R/utils_render_html.R
index a14a1d7388..71d2d5bed5 100644
--- a/R/utils_render_html.R
+++ b/R/utils_render_html.R
@@ -4,7 +4,7 @@
footnote_mark_to_html <- function(mark) {
as.character(
- htmltools::tagList(htmltools::tags$sup(class = "gt_footnote_marks", mark))
+ htmltools::tags$sup(mark)
)
}
diff --git a/R/utils_render_ir.R b/R/utils_render_ir.R
new file mode 100644
index 0000000000..0fe63e177d
--- /dev/null
+++ b/R/utils_render_ir.R
@@ -0,0 +1,890 @@
+
+create_col_group_ir <- function(data) {
+
+ # Get the table `_boxhead` object, filter the table to
+ # only the visible columns, and then arrange such that
+ # the `stub` (if present) is first in the column series
+ boxh <-
+ dt_boxhead_get(data = data) %>%
+ dplyr::filter(type %in% c("default", "stub")) %>%
+ dplyr::arrange(dplyr::desc(type))
+
+ # Get defult column alignments and column widths (if any)
+ # from the `boxh` table
+ col_alignments <- boxh[["column_align"]]
+ col_widths <- unlist(boxh[["column_width"]])
+
+ # Generate a
element containing
s for each column
+ if (is.null(col_widths)) {
+
+ # Case where column widths aren't expressed: each
+ # element contains the default alignment for each column
+
+ colgroup_element <-
+ htmltools::tags$colgroup(
+ lapply(
+ col_alignments,
+ FUN = function(align) {
+ htmltools::tags$col(
+ align = align
+ )
+ }
+ )
+ )
+
+ } else {
+
+ # Case where column widths *are* expressed: each
+ # element contains the default alignment for each column
+ # and the width as well
+
+ colgroup_element <-
+ htmltools::tags$colgroup(
+ mapply(
+ SIMPLIFY = FALSE,
+ USE.NAMES = FALSE,
+ col_alignments,
+ col_widths,
+ FUN = function(x, width) {
+ htmltools::tags$col(
+ align = x,
+ width = width
+ )
+ }
+ )
+ )
+ }
+
+ htmltools::tagList(colgroup_element)
+}
+
+create_heading_ir <- function(data) {
+
+ # If there is no title or heading component, then return an empty string
+ if (!dt_heading_has_title(data = data)) {
+ return(NULL)
+ }
+
+ heading <- dt_heading_get(data = data)
+ footnotes_tbl <- dt_footnotes_get(data = data)
+ styles_tbl <- dt_styles_get(data = data)
+ stub_components <- dt_stub_components(data = data)
+ subtitle_defined <- dt_heading_has_subtitle(data = data)
+
+ # Get the footnote marks for the title
+ if ("title" %in% footnotes_tbl$locname) {
+
+ footnote_title_marks <-
+ coalesce_marks(
+ fn_tbl = footnotes_tbl,
+ locname = "title"
+ )
+
+ footnote_title_marks <-
+ footnote_mark_to_html(mark = footnote_title_marks$fs_id_c)
+
+ } else {
+ footnote_title_marks <- ""
+ }
+
+ # Get the style attrs for the title
+ if ("title" %in% styles_tbl$locname) {
+
+ title_style_rows <- dplyr::filter(styles_tbl, locname == "title")
+
+ if (nrow(title_style_rows) > 0) {
+ title_styles <- title_style_rows$html_style
+ } else {
+ title_styles <- NULL
+ }
+
+ } else {
+ title_styles <- NA_character_
+ }
+
+ # Get the footnote marks for the subtitle
+ if (subtitle_defined && "subtitle" %in% footnotes_tbl$locname) {
+
+ footnote_subtitle_marks <-
+ coalesce_marks(
+ fn_tbl = footnotes_tbl,
+ locname = "subtitle"
+ )
+
+ footnote_subtitle_marks <-
+ footnote_mark_to_html(mark = footnote_subtitle_marks$fs_id_c)
+
+ } else {
+ footnote_subtitle_marks <- ""
+ }
+
+ # Get the style attrs for the subtitle
+ if (subtitle_defined && "subtitle" %in% styles_tbl$locname) {
+
+ subtitle_style_rows <- dplyr::filter(styles_tbl, locname == "subtitle")
+
+ if (nrow(subtitle_style_rows) > 0) {
+ subtitle_styles <- subtitle_style_rows$html_style
+ } else {
+ subtitle_styles <- NULL
+ }
+
+ } else {
+ subtitle_styles <- NA_character_
+ }
+
+ if (!subtitle_defined) {
+
+ title_component <-
+ htmltools::tagList(
+ htmltools::tags$div(
+ class = "title",
+ style = if (!is.na(title_styles)) title_styles else NULL,
+ htmltools::HTML(paste0(heading$title, footnote_title_marks))
+ )
+ )
+
+ } else {
+
+ title_component <-
+ htmltools::tagList(
+ htmltools::tags$div(
+ class = "title",
+ style = if (!is.na(title_styles)) title_styles else NULL,
+ htmltools::HTML(paste0(heading$title, footnote_title_marks))
+ ),
+ htmltools::tags$div(
+ class = "subtitle",
+ style = if (!is.na(subtitle_styles)) subtitle_styles else NULL,
+ htmltools::HTML(paste0(heading$subtitle, footnote_subtitle_marks))
+ )
+ )
+ }
+
+ title_component
+}
+
+create_columns_ir <- function(data) {
+
+ boxh <- dt_boxhead_get(data = data)
+ stubh <- dt_stubhead_get(data = data)
+
+ styles_tbl <- dt_styles_get(data = data)
+ stub_available <- dt_stub_df_exists(data = data)
+ spanners_present <- dt_spanners_exists(data = data)
+
+ # Get the column alignments for all visible columns
+ col_alignment <- dplyr::pull(subset(boxh, type == "default"), column_align)
+
+ # Get the column headings
+ headings_vars <- dplyr::pull(subset(boxh, type == "default"), var)
+ headings_labels <- dt_boxhead_get_vars_labels_default(data = data)
+
+ # Get the style attrs for the stubhead label
+ stubhead_style_attrs <- subset(styles_tbl, locname == "stubhead")
+
+ # Get the style attrs for the spanner column headings
+ spanner_style_attrs <- subset(styles_tbl, locname == "columns_groups")
+
+ # Get the style attrs for the spanner column headings
+ column_style_attrs <- subset(styles_tbl, locname == "columns_columns")
+
+ # If `stub_available` == TRUE, then replace with a set stubhead
+ # label or nothing
+ if (isTRUE(stub_available) && length(stubh$label) > 0) {
+
+ headings_labels <- prepend_vec(headings_labels, stubh$label)
+ headings_vars <- prepend_vec(headings_vars, "::stub")
+
+ } else if (isTRUE(stub_available)) {
+
+ headings_labels <- prepend_vec(headings_labels, "")
+ headings_vars <- prepend_vec(headings_vars, "::stub")
+ }
+
+ # Initialize empty tagList for the column label
elements
+ column_labels_tagList <- htmltools::tagList()
+
+ for (i in seq_along(headings_vars)) {
+
+ if (stub_available && i == 1) {
+
+ styles_column_labels <- stubhead_style_attrs
+
+ } else {
+
+ styles_column <- subset(column_style_attrs, colnum == i)
+
+ styles_column_labels <-
+ dplyr::filter(
+ column_style_attrs,
+ locname == "columns_columns",
+ colnum == i
+ )
+ }
+
+ column_label_style <-
+ if (nrow(styles_column_labels) > 0) {
+ styles_column_labels$html_style
+ } else {
+ NULL
+ }
+
+ column_labels_th_element <-
+ htmltools::tags$th(
+ class = if (i == 1 && stub_available) "stub" else NULL,
+ style = if (is.null(column_label_style)) NULL else column_label_style,
+ headings_labels[i]
+ )
+
+ # Append the `column_labels_th_element` to the end of the `column_labels_tagList`
+ column_labels_tagList[[i]] <- column_labels_th_element
+ }
+
+ # Create
element for the column labels
+ column_labels_tr_element <-
+ htmltools::tags$tr(class = "column-labels", column_labels_tagList)
+
+ if (spanners_present) {
+
+ # Initialize empty tagList for the spanner
elements
+ spanners_tagList <- htmltools::tagList()
+
+ # Get vector of group labels (spanners)
+ spanners <- dt_spanners_print(data = data, include_hidden = FALSE)
+ spanner_ids <- dt_spanners_print(data = data, include_hidden = FALSE, ids = TRUE)
+
+ if (stub_available) {
+ spanners <- c(NA_character_, spanners)
+ spanner_ids <- c(NA_character_, spanner_ids)
+ }
+
+ spanners_rle <- unclass(rle(spanner_ids))
+
+ # We need a parallel vector of spanner labels and this could
+ # be part of the `spanners_rle` list
+ spanners_rle$labels <- spanners[cumsum(spanners_rle$lengths)]
+
+ for (i in seq_along(spanners_rle$labels)) {
+
+ styles_spanners <-
+ dplyr::filter(
+ spanner_style_attrs,
+ locname == "columns_groups",
+ grpname == spanners_rle$values[i]
+ )
+
+ spanner_style <-
+ if (nrow(styles_spanners) > 0) {
+ styles_spanners$html_style
+ } else {
+ NULL
+ }
+
+ spanners_th_element <-
+ htmltools::tags$th(
+ class = if (i == 1 && stub_available) "stub" else NULL,
+ colspan = if (spanners_rle$lengths[i] > 1) spanners_rle$lengths[i] else NULL,
+ style = if (is.null(spanner_style)) NULL else spanner_style,
+ if (!is.na(spanners_rle$labels[i])) spanners_rle$labels[i] else NULL
+ )
+
+ # Append the `spanners_th_element` to the end of the `spanners_tagList`
+ spanners_tagList[[i]] <- spanners_th_element
+ }
+
+ # Create
element for spanner
+ spanners_tr_element <-
+ htmltools::tags$tr(class = "spanners", spanners_tagList)
+
+ } else {
+ spanners_tr_element <- NULL
+ }
+
+ htmltools::tagList(spanners_tr_element, column_labels_tr_element)
+}
+
+create_body_ir <- function(data) {
+
+ boxh <- dt_boxhead_get(data = data)
+ body <- dt_body_get(data = data)
+
+ summaries_present <- dt_summary_exists(data = data)
+ list_of_summaries <- dt_summary_df_get(data = data)
+ groups_rows_df <- dt_groups_rows_get(data = data)
+ stub_components <- dt_stub_components(data = data)
+
+ styles_tbl <- dt_styles_get(data = data)
+
+ n_data_cols <- length(dt_boxhead_get_vars_default(data = data))
+ n_rows <- nrow(body)
+
+ # Get the column alignments for the data columns (this
+ # doesn't include the stub alignment)
+ col_alignment <- boxh[boxh$type == "default", ][["column_align"]]
+
+ # Determine whether the stub is available through analysis
+ # of the `stub_components`
+ stub_available <- dt_stub_components_has_rowname(stub_components)
+
+ # Obtain all of the visible (`"default"`), non-stub
+ # column names for the table
+ default_vars <- dt_boxhead_get_vars_default(data = data)
+
+ all_default_vals <- unname(as.matrix(body[, default_vars]))
+
+ alignment_classes <- paste0("gt_", col_alignment)
+
+ if (stub_available) {
+
+ n_cols <- n_data_cols + 1
+
+ alignment_classes <- c("gt_left", alignment_classes)
+
+ stub_var <- dt_boxhead_get_var_stub(data = data)
+ all_stub_vals <- as.matrix(body[, stub_var])
+
+ } else {
+ n_cols <- n_data_cols
+ }
+
+ # Define function to get a character vector of formatted cell
+ # data (this includes the stub, if it is present)
+ output_df_row_as_vec <- function(i) {
+
+ default_vals <- all_default_vals[i, ]
+
+ if (stub_available) {
+ default_vals <- c(all_stub_vals[i], default_vals)
+ }
+
+ default_vals
+ }
+
+ # Get the sequence of column numbers in the table body (these
+ # are the visible columns in the table exclusive of the stub)
+ column_series <- seq(n_cols)
+
+ # Replace an NA group with an empty string
+ if (any(is.na(groups_rows_df$group_label))) {
+
+ groups_rows_df <-
+ groups_rows_df %>%
+ dplyr::mutate(group_label = ifelse(is.na(group_label), "", group_label)) %>%
+ dplyr::mutate(group_label = gsub("^NA", "\u2014", group_label))
+ }
+
+ # Is the stub to be striped?
+ table_stub_striped <-
+ dt_options_get_value(
+ data = data,
+ option = "row_striping_include_stub"
+ )
+
+ # Are the rows in the table body to be striped?
+ table_body_striped <-
+ dt_options_get_value(
+ data = data,
+ option = "row_striping_include_table_body"
+ )
+
+ extra_classes_1 <- rep_len(list(NULL), n_cols)
+
+ extra_classes_2 <-
+ rep_len(list(if (table_body_striped) "gt_striped" else NULL), n_cols)
+
+ if (stub_available) {
+
+ extra_classes_1[[1]] <- "gt_stub"
+
+ extra_classes_2[[1]] <-
+ c("gt_stub", if (table_stub_striped) "gt_striped" else NULL)
+ }
+
+ has_tbl_body_styles <- any(c("stub", "data") %in% styles_tbl$locname)
+ has_row_group_styles <- "row_groups" %in% styles_tbl$locname
+
+ if (has_tbl_body_styles) {
+ styles_tbl_body <- subset(styles_tbl, locname %in% c("stub", "data"))
+ } else {
+ row_styles <- rep_len(list(NULL), n_cols)
+ }
+
+ if (has_row_group_styles) {
+ styles_tbl_row_groups <- subset(styles_tbl, locname == "row_groups")
+ }
+
+ body_section <- htmltools::tagList()
+
+ for (i in seq_len(n_rows)) {
+
+ #
+ # Create a group heading row
+ #
+ if (!is.null(groups_rows_df) && i %in% groups_rows_df$row_start) {
+
+ group_id <-
+ groups_rows_df[
+ which(groups_rows_df$row_start %in% i), "group_id"
+ ][[1]]
+
+ group_label <-
+ groups_rows_df[
+ which(groups_rows_df$row_start %in% i), "group_label"
+ ][[1]]
+
+ if (has_row_group_styles) {
+
+ styles_row <-
+ styles_tbl_row_groups[styles_tbl_row_groups$grpname == group_id, ]
+
+ row_style <-
+ if (nrow(styles_row) > 0) {
+ styles_row$html_style
+ } else {
+ NULL
+ }
+
+ } else {
+ row_style <- NULL
+ }
+
+ group_heading_row <-
+ htmltools::tagList(
+ htmltools::tags$tr(
+ class = "row-group-label",
+ htmltools::tagList(
+ htmltools::tags$td(
+ style = row_style,
+ htmltools::HTML(group_label)
+ )
+ )
+ )
+ )
+
+ body_section <- htmltools::tagList(body_section, group_heading_row)
+ }
+
+ #
+ # Create a body row
+ #
+
+ extra_classes <- if (i %% 2 == 0) extra_classes_2 else extra_classes_1
+
+ if (has_tbl_body_styles) {
+
+ styles_row <-
+ styles_tbl_body[styles_tbl_body$rownum == i, ]
+
+ row_styles <-
+ build_row_styles(
+ styles_resolved_row = styles_row,
+ stub_available = stub_available,
+ n_cols = n_cols
+ )
+ }
+
+ body_row <-
+ htmltools::tagList(
+ htmltools::tags$tr(
+ htmltools::HTML(
+ paste0(
+ collapse = "",
+ mapply(
+ SIMPLIFY = FALSE,
+ USE.NAMES = FALSE,
+ seq_along(output_df_row_as_vec(i)),
+ row_styles,
+ FUN = function(x, cell_style) {
+
+ sprintf(
+ if (x == 1 && stub_available) {
+ "\n