Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance CSS-inlining within as_raw_html() #1114

Merged
merged 13 commits into from
Nov 10, 2022
10 changes: 10 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,20 @@ tests/testthat/test-cols_align_decimal.R
tests/testthat/test-cols_width_rtf.R
tests/testthat/test-data_color.R
tests/testthat/test-extract_cells.R
tests/testthat/test-fmt_bytes.R
tests/testthat/test-fmt_currency.R
tests/testthat/test-fmt_datetime.R
tests/testthat/test-fmt_duration.R
tests/testthat/test-fmt_engineering.R
tests/testthat/test-fmt_fraction.R
tests/testthat/test-fmt_integer.R
tests/testthat/test-fmt_markdown.R
tests/testthat/test-fmt_number.R
tests/testthat/test-fmt_partsper.R
tests/testthat/test-fmt_passthrough.R
tests/testthat/test-fmt_percent.R
tests/testthat/test-fmt_roman.R
tests/testthat/test-fmt_scientific.R
tests/testthat/test-footer.R
tests/testthat/test-group_column_label.R
tests/testthat/test-gtsave.R
Expand Down
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ Imports:
base64enc (>= 0.1-3),
bigD (>= 0.1),
bitops (>= 1.0-7),
cli (>= 3.3.0),
cli (>= 3.4.1),
commonmark (>= 1.8),
dplyr (>= 1.0.8),
fs (>= 1.5.2),
ggplot2 (>= 3.3.5),
glue (>= 1.6.1),
htmltools (>= 0.5.2),
juicyjuice (>= 0.1.0),
magrittr (>= 2.0.2),
rlang (>= 1.0.2),
sass (>= 0.4.1),
Expand Down
26 changes: 18 additions & 8 deletions R/export.R
Original file line number Diff line number Diff line change
Expand Up @@ -395,20 +395,30 @@ as_raw_html <- function(
# Perform input object validation
stop_if_not_gt(data = data)

html_table <- as.character(as.tags.gt_tbl(data))

if (inline_css) {

# Generation of the HTML table
html_table <- render_as_html(data = data)
font_vec <- unique(dt_options_get_value(data = data, option = "table_font_names"))
font_family_attr <- as_css_font_family_attr(font_vec = font_vec)

# Create inline styles
html_table <-
inline_html_styles(
html = html_table,
css_tbl = get_css_tbl(data = data)
gsub(
pattern = "<style>html \\{.*?\\}",
replacement = "<style>",
x = html_table
)

} else {
html_table <- as.character(as.tags.gt_tbl(data))
html_table <-
gsub(
pattern = ".gt_table {\n",
replacement = paste0(".gt_table { \n ", font_family_attr, "\n"),
x = html_table,
fixed = TRUE
)

# Create inline styles
html_table <- juicyjuice::css_inline(html = html_table)
}

htmltools::HTML(html_table)
Expand Down
140 changes: 0 additions & 140 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -1467,150 +1467,10 @@ remove_html <- function(text) {
gsub("<.+?>", "", text)
}

#' Transform a CSS stylesheet to a tibble representation
#'
#' @noRd
get_css_tbl <- function(data) {

raw_css_vec <- unlist(strsplit(as.character(compile_scss(data)), "\n"))

ruleset_start <- which(grepl("\\{\\s*", raw_css_vec))
ruleset_end <- which(grepl("\\s*\\}\\s*", raw_css_vec))

css_tbl <- dplyr::tibble()

for (i in seq(ruleset_start)) {

css_tbl <-
dplyr::bind_rows(
css_tbl,
dplyr::tibble(
selector = rep(
str_single_replace(raw_css_vec[ruleset_start[i]], "\\s*\\{\\s*$", ""),
(ruleset_end[i] - ruleset_start[i] - 1)),
property = raw_css_vec[(ruleset_start[i] + 1):(ruleset_end[i] - 1)] %>%
str_single_extract("[a-zA-z-]*?(?=:)") %>%
str_trim_sides(),
value = raw_css_vec[(ruleset_start[i] + 1):(ruleset_end[i] - 1)] %>%
str_single_extract("(?<=:).*") %>%
str_single_replace(pattern = ";\\s*", "") %>%
str_single_replace(pattern = "\\/\\*.*", "") %>%
str_trim_sides()
) %>%
dplyr::filter(!is.na(property))
)
}

# Add a column that has the selector type for each row
# For anything other than a class selector, the class type
# will entered as NA
css_tbl <-
dplyr::mutate(
css_tbl,
type = dplyr::case_when(
str_has_match(selector, "^\\.") ~ "class",
!str_has_match(selector, "^\\.") ~ NA_character_
)
)
css_tbl <- dplyr::select(css_tbl, selector, type, property, value)

# Stop function if any NA values found while inspecting the
# selector names (e.g., not determined to be class selectors)
if (any(is.na(css_tbl$type))) {
cli::cli_abort("All selectors must be class selectors.")
}

css_tbl
}

#' Create an inlined style block from a CSS tibble
#'
#' @noRd
create_inline_styles <- function(
class_names,
css_tbl,
extra_style = ""
) {

class_names <- unlist(strsplit(class_names, "\\s+"))

paste0(
"style=\"",
css_tbl %>%
dplyr::filter(selector %in% paste0(".", class_names)) %>%
dplyr::select(property, value) %>%
dplyr::distinct() %>%
dplyr::mutate(property_value = paste0(property, ": ", value, "; ")) %>%
dplyr::pull(property_value) %>%
paste(collapse = ""),
extra_style,
"\"") %>%
tidy_gsub(" \\\"$", "\\\"")
}

extract_strings <- function(text, pattern, perl = TRUE) {
sapply(regmatches(text, regexec(pattern, text, perl = perl)), "[", 1)
}

#' Transform HTML to inlined HTML using a CSS tibble
#'
#' @noRd
inline_html_styles <- function(html, css_tbl) {

cls_sty_pattern <- "class=\\\"(.*?)\\\"\\s+style=\\\"(.*?)\\\""
cls_names_pattern <- "(?<=\\\").*?(?=\\\")"
sty_exist_pattern <- "style=\\\"(.*?)\\\""
cls_pattern <- "class=\\\"(.*?)\\\""

repeat {

matching_css_style <- extract_strings(html, cls_sty_pattern)

if (is.na(matching_css_style)) break

class_names <- extract_strings(matching_css_style, cls_names_pattern)

existing_style <- str_get_match(matching_css_style, sty_exist_pattern)[, 2]

inline_styles <-
create_inline_styles(
class_names = class_names,
css_tbl = css_tbl,
extra_style = existing_style
)

html <-
str_single_replace(
html,
pattern = cls_sty_pattern,
replacement = inline_styles
)
}

repeat {

class_names <- str_single_extract(html, pattern = cls_pattern)
class_names <- str_single_extract(class_names, pattern = cls_names_pattern)

if (is.na(class_names)) break

inline_styles <-
create_inline_styles(
class_names = class_names,
css_tbl = css_tbl
)

html <-
str_single_replace(
html,
pattern = cls_pattern,
replacement = inline_styles
)
}

html
}

#' Split any strings that are values in scientific notation
#'
#' @param x_str The input character vector of values formatted in scientific
Expand Down
2 changes: 1 addition & 1 deletion R/utils_render_html.R
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ create_body_component_h <- function(data) {
} else {
paste0(
" style=\"",
htmltools::htmlEscape(cell_style, attribute = TRUE),
htmltools::htmlEscape(cell_style, attribute = FALSE),
"\""
)
},
Expand Down
10 changes: 6 additions & 4 deletions tests/testthat/test-as_raw_html.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ test_that("`as_raw_html()` produces the same table every time", {

gt_html_1 <-
gt(exibble) %>%
as_raw_html(inline_css = TRUE)
as_raw_html(inline_css = TRUE) %>%
gsub("id=\"[a-z]*?\"", "", .)

gt_html_1_sha1 <- digest::sha1(gt_html_1)
expect_equal(gt_html_1_sha1, "ea4301a8442752cf8321bad80db2f1f726e66c50")
expect_equal(gt_html_1_sha1, "5344de4ed3bbff521ddaa77db44242b7d7000074")

gt_html_2 <-
gt(
Expand Down Expand Up @@ -108,8 +109,9 @@ test_that("`as_raw_html()` produces the same table every time", {
rows = "Mazda RX4"
)
) %>%
as_raw_html(inline_css = TRUE)
as_raw_html(inline_css = TRUE) %>%
gsub("id=\"[a-z]*?\"", "", .)

gt_html_2_sha1 <- digest::sha1(gt_html_2)
expect_equal(gt_html_2_sha1, "d0b881eaa690047bf58877d4de65883ca3d0ba39")
expect_equal(gt_html_2_sha1, "ead3b201f06d98f304d23d9c04a2eba0b18bbdf5")
})
8 changes: 4 additions & 4 deletions tests/testthat/test-cols_merge.R
Original file line number Diff line number Diff line change
Expand Up @@ -469,8 +469,8 @@ test_that("the `cols_merge_range()` function works correctly", {

# Expect that the HTML produced from the two tables is the same
expect_identical(
tbl_html_1 %>% as_raw_html(),
tbl_html_2 %>% as_raw_html()
tbl_html_1 %>% as_raw_html() %>% gsub("id=\"[a-z]*?\"", "", .),
tbl_html_2 %>% as_raw_html() %>% gsub("id=\"[a-z]*?\"", "", .)
)

# Create another variant that renames `col_2` as `1`, which
Expand All @@ -487,8 +487,8 @@ test_that("the `cols_merge_range()` function works correctly", {
# Expect that the HTML produced from `tbl_html_2` and
# `tbl_html_3` is the same
expect_identical(
tbl_html_2 %>% as_raw_html(),
tbl_html_3 %>% as_raw_html()
tbl_html_2 %>% as_raw_html() %>% gsub("id=\"[a-z]*?\"", "", .),
tbl_html_3 %>% as_raw_html() %>% gsub("id=\"[a-z]*?\"", "", .)
)
})

Expand Down
7 changes: 5 additions & 2 deletions tests/testthat/test-summary_rows.R
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,9 @@ test_that("`groups = FALSE` returns data unchanged", {
# Expect that using `groups = FALSE` with
# `summary_rows()` creates no summary rows
expect_equal(
tbl %>% as_raw_html(),
tbl %>%
as_raw_html() %>%
gsub("id=\"[a-z]*?\"", "", .),
tbl %>%
summary_rows(
groups = FALSE,
Expand All @@ -711,7 +713,8 @@ test_that("`groups = FALSE` returns data unchanged", {
`std dev` = ~sd(., na.rm = TRUE)
)
) %>%
as_raw_html()
as_raw_html() %>%
gsub("id=\"[a-z]*?\"", "", .)
)
})

Expand Down
14 changes: 8 additions & 6 deletions tests/testthat/test-tab_style.R
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ test_that("using fonts in `cell_text()` works", {
) %>%
as_raw_html() %>%
expect_match(
"<td headers=\"time\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums; font-family: &#39;Comic Sans MS&#39;, Menlo, -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Helvetica Neue&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, Arial, sans-serif;\">13:35</td>"
"<td headers=\"time\" class=\"gt_row gt_right\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums; font-family: 'Comic Sans MS', Menlo, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;\" valign=\"middle\" align=\"right\">13:35</td>"
)

# Expect that a Google Fonts and system fonts can be combined
Expand All @@ -449,7 +449,7 @@ test_that("using fonts in `cell_text()` works", {
) %>%
as_raw_html() %>%
expect_match(
"<td headers=\"time\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums; font-family: &#39;Dancing Script&#39;, -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Helvetica Neue&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, Arial, sans-serif;\">13:35</td>"
"<td headers=\"time\" class=\"gt_row gt_right\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums; font-family: 'Dancing Script', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;\" valign=\"middle\" align=\"right\">13:35</td>"
)

tbl %>%
Expand All @@ -459,7 +459,7 @@ test_that("using fonts in `cell_text()` works", {
) %>%
as_raw_html() %>%
expect_match(
"<td headers=\"time\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums; font-family: &#39;Dancing Script&#39;, -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Helvetica Neue&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, Arial, sans-serif;\">13:35</td>"
"<td headers=\"time\" class=\"gt_row gt_right\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums; font-family: 'Dancing Script', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;\" valign=\"middle\" align=\"right\">13:35</td>"
)

gtcars_tbl <-
Expand All @@ -480,7 +480,8 @@ test_that("using fonts in `cell_text()` works", {
),
locations = cells_body(columns = hp, rows = 1:2)
) %>%
as_raw_html(),
as_raw_html() %>%
gsub("id=\"[a-z]*?\"", "", .),
gtcars_tbl %>%
tab_style(
style =
Expand All @@ -493,7 +494,8 @@ test_that("using fonts in `cell_text()` works", {
),
locations = cells_body(columns = hp, rows = 1:2)
) %>%
as_raw_html()
as_raw_html() %>%
gsub("id=\"[a-z]*?\"", "", .)
)

# Don't expect any errors when styling with different fonts
Expand Down Expand Up @@ -568,7 +570,7 @@ test_that("setting white-space options in `cell_text()` works", {
) %>%
as_raw_html() %>%
expect_match(
"<td headers=\"ws\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left; white-space: pre;\"> space </td>"
"<td headers=\"ws\" class=\"gt_row gt_left\" style=\"padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left; white-space: pre;\" valign=\"middle\" align=\"left\"> space </td>"
)
})

Expand Down
Loading