Skip to content

Commit

Permalink
Merge pull request #1402 from thebioengineer/word_respect_group_summa…
Browse files Browse the repository at this point in the history
…ry_location

Word respect group summary location
  • Loading branch information
rich-iannone authored Aug 11, 2023
2 parents a3cc005 + 407f396 commit 16092d7
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 36 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

* Update word processing to htmlEscape all characters before it goes into xml_t. This means now footnotes are escaped too. (#1303)

* Update word processing to handle the new `summary_rows()` and `grand_summary_rows()` `side` argument, as well as handle cases where a table doesn't have rownames. (#1325)


# gt 0.9.0

## New features
Expand Down
93 changes: 58 additions & 35 deletions R/utils_render_xml.R
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ create_body_component_xml <- function(

# Determine whether the stub is available through analysis
# of the `stub_components`
stub_available <- dt_stub_components_has_rowname(stub_components)
stub_available <- dt_stub_components_has_rowname(stub_components) || summaries_present

# Obtain all of the visible (`"default"`), non-stub
# column names for the table
Expand Down Expand Up @@ -1599,6 +1599,7 @@ create_body_component_xml <- function(

body_section <- list()


#
# Create a group heading row
#
Expand Down Expand Up @@ -1660,8 +1661,9 @@ create_body_component_xml <- function(

row_cells <- list()
row_idx <- i
row_vec <- output_df_row_as_vec(i)

for (y in seq_along(output_df_row_as_vec(i))) {
for (y in seq_along(row_vec)) {

style_col_idx <- ifelse(stub_available, y - 1, y)

Expand All @@ -1673,11 +1675,12 @@ create_body_component_xml <- function(
colnum == style_col_idx
) %>%
dplyr::pull("styles") %>%
.[1] %>% .[[1]]
.[1] %>%
.[[1]]

row_cells[[length(row_cells) + 1]] <-
xml_table_cell(
content = output_df_row_as_vec(i)[y],
content = row_vec[y],
font = cell_style[["cell_text"]][["font"]],
size = cell_style[["cell_text"]][["size"]],
color = cell_style[["cell_text"]][["color"]],
Expand Down Expand Up @@ -1712,43 +1715,57 @@ create_body_component_xml <- function(
)
)

body_section <- append(body_section, list(body_row))


#
# Add groupwise summary rows
# Add groupwise summary rows.
#

if (summaries_present && i %in% groups_rows_df$row_end) {
if (summaries_present && nrow(groups_rows_df) > 0){

group_id <-
groups_rows_df[
stats::na.omit(groups_rows_df$row_end == i),
"group_id", drop = TRUE
]
group_info <- groups_rows_df[
i >= groups_rows_df$row_start & i <= groups_rows_df$row_end, ]

summary_styles <-
styles_tbl %>%
dplyr::filter(
locname %in% c("summary_cells"),
grpname %in% group_id
) %>%
dplyr::mutate(rownum = ceiling(rownum * 100 - i * 100))

summary_section <-
summary_rows_xml(
list_of_summaries = list_of_summaries,
boxh = boxh,
group_id = group_id,
locname = "summary_cells",
col_alignment = col_alignment,
table_body_hlines_color = table_body_hlines_color,
table_body_vlines_color = table_body_vlines_color,
styles = summary_styles,
split = split,
keep_with_next = keep_with_next
)
group_summary_row_side <- unique(group_info[, "summary_row_side"])[[1]]

group_row_add_row_loc <- group_info[,ifelse(group_summary_row_side == "top", "row_start","row_end")][[1]]

body_section <- append(body_section, summary_section)
if(i == group_row_add_row_loc) {

summary_styles <-
styles_tbl %>%
dplyr::filter(
locname %in% c("summary_cells"),
grpname %in% group_info[["group_id"]]
) %>%
dplyr::mutate(rownum = ceiling(rownum * 100 - i * 100))

summary_section <-
summary_rows_xml(
list_of_summaries = list_of_summaries,
boxh = boxh,
group_id = group_info[["group_id"]],
locname = "summary_cells",
col_alignment = col_alignment,
table_body_hlines_color = table_body_hlines_color,
table_body_vlines_color = table_body_vlines_color,
styles = summary_styles,
split = split,
keep_with_next = keep_with_next
)

if(group_summary_row_side == "top"){
body_section <- append(body_section, summary_section)
body_section <- append(body_section, list(body_row))
}else{
body_section <- append(body_section, list(body_row))
body_section <- append(body_section, summary_section)
}
}else{
body_section <- append(body_section, list(body_row))
}
}else{
body_section <- append(body_section, list(body_row))
}

body_section
Expand Down Expand Up @@ -1784,7 +1801,13 @@ create_body_component_xml <- function(
keep_with_next = keep_with_next
)

body_rows <- c(body_rows, grand_summary_section)
grand_summary_loc <- unique(list_of_summaries$summary_df_display_list[[grand_summary_col]][["::side::"]])

if(grand_summary_loc == "top"){
body_rows <- c(grand_summary_section, body_rows)
}else{
body_rows <- c(body_rows, grand_summary_section)
}
}

htmltools::tagList(body_rows)
Expand Down
216 changes: 215 additions & 1 deletion tests/testthat/test-as_word.R
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ test_that("word ooxml escapes special characters in gt object footer", {

})


test_that("tables can be added to a word doc", {

check_suggests_xml()
Expand Down Expand Up @@ -807,8 +806,223 @@ test_that("tables with summaries can be added to a word doc", {
c("s.d.", "4,916,123.25", "", "")
)
)


## Now place the summary on the top

## simple table
gt_exibble_min_top <- exibble %>%
dplyr::select(-c(fctr, date, time, datetime)) %>%
gt(rowname_col = "row", groupname_col = "group") %>%
summary_rows(
groups = everything(),
columns = num,
fns = list(
avg = ~mean(., na.rm = TRUE),
total = ~sum(., na.rm = TRUE),
s.d. = ~sd(., na.rm = TRUE)
),
fmt = list(~ fmt_number(.)),
side = "top"
)

## Add table to empty word document
word_doc_top <- officer::read_docx() %>%
body_add_gt(
gt_exibble_min_top,
align = "center"
)

## save word doc to temporary file
temp_word_file_top <- tempfile(fileext = ".docx")
print(word_doc_top,target = temp_word_file_top)

## Manual Review
if (!testthat::is_testing() & interactive()) {
shell.exec(temp_word_file_top)
}

## Programmatic Review
docx_top <- officer::read_docx(temp_word_file_top)

## get docx table contents
docx_contents_top <- docx_top$doc_obj$get() %>%
xml2::xml_children() %>%
xml2::xml_children()

## extract table contents
docx_table_body_header_top <- docx_contents_top[1] %>%
xml2::xml_find_all(".//w:tblHeader/ancestor::w:tr")

docx_table_body_contents_top <- docx_contents_top[1] %>%
xml2::xml_find_all(".//w:tr") %>%
setdiff(docx_table_body_header_top)

## "" at beginning for stubheader
expect_equal(
docx_table_body_header_top %>%
xml2::xml_find_all(".//w:p") %>%
xml2::xml_text(),
c( "", "num", "char", "currency")
)

expect_equal(
lapply(docx_table_body_contents_top, function(x)
x %>% xml2::xml_find_all(".//w:p") %>% xml2::xml_text()),
list(
"grp_a",
c("avg", "120.02", "", ""),
c("total", "480.06", "", ""),
c("s.d.", "216.79", "", ""),
c("row_1", "1.111e-01", "apricot", "49.950"),
c("row_2", "2.222e+00", "banana", "17.950"),
c("row_3", "3.333e+01", "coconut", "1.390"),
c("row_4", "4.444e+02", "durian", "65100.000"),
"grp_b",
c("avg", "3,220,850.00", "", ""),
c("total", "9,662,550.00", "", ""),
c("s.d.", "4,916,123.25", "", ""),
c("row_5", "5.550e+03", "NA", "1325.810"),
c("row_6", "NA", "fig", "13.255"),
c("row_7", "7.770e+05", "grapefruit", "NA"),
c("row_8", "8.880e+06", "honeydew", "0.440")
)
)

})

test_that("tables with grand summaries but no rownames can be added to a word doc", {

check_suggests_xml()

## simple table
gt_exibble_min <- exibble %>%
dplyr::select(-c(fctr, date, time, datetime, row, group)) %>%
dplyr::slice(1:3) %>%
gt() %>%
grand_summary_rows(
c(everything(), -char),
fns = c("Total" = ~length(.))
)

## Add table to empty word document
word_doc <- officer::read_docx() %>%
body_add_gt(
gt_exibble_min,
align = "center"
)

## save word doc to temporary file
temp_word_file <- tempfile(fileext = ".docx")
print(word_doc,target = temp_word_file)

## Manual Review
if (!testthat::is_testing() & interactive()) {
shell.exec(temp_word_file)
}

## Programmatic Review
docx <- officer::read_docx(temp_word_file)

## get docx table contents
docx_contents <- docx$doc_obj$get() %>%
xml2::xml_children() %>%
xml2::xml_children()

## extract table contents
docx_table_body_header <- docx_contents[1] %>%
xml2::xml_find_all(".//w:tblHeader/ancestor::w:tr")

docx_table_body_contents <- docx_contents[1] %>%
xml2::xml_find_all(".//w:tr") %>%
setdiff(docx_table_body_header)

## "" at beginning for stubheader
expect_equal(
docx_table_body_header %>%
xml2::xml_find_all(".//w:p") %>%
xml2::xml_text(),
c( "", "num", "char", "currency")
)

expect_equal(
lapply(docx_table_body_contents, function(x)
x %>% xml2::xml_find_all(".//w:p") %>% xml2::xml_text()),
list(
c("", "0.1111", "apricot", "49.95"),
c("", "2.2220", "banana","17.95"),
c("", "33.3300", "coconut", "1.39"),
c("Total", "3","", "3")
)
)

## simple table
gt_exibble_min_top <- exibble %>%
dplyr::select(-c(fctr, date, time, datetime, row, group)) %>%
dplyr::slice(1:3) %>%
gt() %>%
grand_summary_rows(
c(everything(), -char),
fns = c("Total" = ~length(.)),
side = "top"
)

## Add table to empty word document
word_doc_top <- officer::read_docx() %>%
body_add_gt(
gt_exibble_min_top,
align = "center"
)

## save word doc to temporary file
temp_word_file_top <- tempfile(fileext = ".docx")
print(word_doc_top,target = temp_word_file_top)

## Manual Review
if (!testthat::is_testing() & interactive()) {
shell.exec(temp_word_file_top)
}

## Programmatic Review
docx_top <- officer::read_docx(temp_word_file_top)

## get docx table contents
docx_contents_top <- docx_top$doc_obj$get() %>%
xml2::xml_children() %>%
xml2::xml_children()

## extract table contents
docx_table_body_header_top <- docx_contents_top[1] %>%
xml2::xml_find_all(".//w:tblHeader/ancestor::w:tr")

docx_table_body_contents_top <- docx_contents_top[1] %>%
xml2::xml_find_all(".//w:tr") %>%
setdiff(docx_table_body_header_top)

## "" at beginning for stubheader
expect_equal(
docx_table_body_header_top %>%
xml2::xml_find_all(".//w:p") %>%
xml2::xml_text(),
c( "", "num", "char", "currency")
)

expect_equal(
lapply(docx_table_body_contents_top, function(x)
x %>% xml2::xml_find_all(".//w:p") %>% xml2::xml_text()),
list(
c("Total", "3","", "3"),
c("", "0.1111", "apricot", "49.95"),
c("", "2.2220", "banana","17.95"),
c("", "33.3300", "coconut", "1.39")
)
)


})



test_that("tables with footnotes can be added to a word doc", {

check_suggests_xml()
Expand Down

1 comment on commit 16092d7

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.