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

Word respect group summary location #1402

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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