Skip to content

Commit

Permalink
[write] protect against overwriting a shared formula reference
Browse files Browse the repository at this point in the history
  • Loading branch information
JanMarvin committed Dec 16, 2024
1 parent bebc5b5 commit 9334e8d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 26 deletions.
36 changes: 36 additions & 0 deletions R/helper-functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -1339,3 +1339,39 @@ wb_upd_custom_pid <- function(wb) {
xml_children = out
)
}

#' replace shared formulas with single cell formulas
#' @param cc the full frame
#' @param cc_shared a subset of the full frame with shared formulas
#' @noRd
shared_as_fml <- function(cc, cc_shared) {
cc_shared <- cc_shared[order(as.integer(cc_shared$f_si)), ]

# carry forward the shared formula
cc_shared$f <- ave2(cc_shared$f, cc_shared$f_si, carry_forward)

# calculate differences from the formula cell, to the shared cells
cc_shared$cols <- ave2(col2int(cc_shared$c_r), cc_shared$f_si, calc_distance)
cc_shared$rows <- ave2(as.integer(cc_shared$row_r), cc_shared$f_si, calc_distance)

# begin updating the formulas. find a1 notion, get the next cell, update formula
cells <- find_a1_notation(cc_shared$f)
repls <- vector("list", length = length(cells))

for (i in seq_along(cells)) {
repls[[i]] <- next_cell(cells[[i]], cc_shared$cols[i], cc_shared$rows[i])
}

cc_shared$f <- replace_a1_notation(cc_shared$f, repls)
cc_shared$cols <- NULL
cc_shared$rows <- NULL
cc_shared$f_t <- ""
cc_shared$f_si <- ""
cc_shared$f_ref <- ""

# reduce and assign
cc_shared <- cc_shared[which(cc_shared$r %in% cc$r), ]

cc[match(cc_shared$r, cc$r), ] <- cc_shared
cc
}
27 changes: 1 addition & 26 deletions R/read.R
Original file line number Diff line number Diff line change
Expand Up @@ -438,37 +438,12 @@ wb_to_df <- function(
if (show_formula) {

if (any(cc$f_t == "shared")) {

# depending on the sheet, this might require updates to many cells
# TODO reduce this to cells, that are part of `cc`. Currently we
# might waste time, updating cells that are not visible to the user
cc_shared <- wb$worksheets[[sheet]]$sheet_data$cc
cc_shared <- cc_shared[cc_shared$f_t == "shared", ]
cc_shared <- cc_shared[order(as.integer(cc_shared$f_si)), ]

# carry forward the shared formula
cc_shared$f <- ave2(cc_shared$f, cc_shared$f_si, carry_forward)

# calculate differences from the formula cell, to the shared cells
cc_shared$cols <- ave2(col2int(cc_shared$c_r), cc_shared$f_si, calc_distance)
cc_shared$rows <- ave2(as.integer(cc_shared$row_r), cc_shared$f_si, calc_distance)

# begin updating the formulas. find a1 notion, get the next cell, update formula
cells <- find_a1_notation(cc_shared$f)
repls <- vector("list", length = length(cells))

for (i in seq_along(cells)) {
repls[[i]] <- next_cell(cells[[i]], cc_shared$cols[i], cc_shared$rows[i])
}

cc_shared$f <- replace_a1_notation(cc_shared$f, repls)
cc_shared$cols <- NULL
cc_shared$rows <- NULL

# reduce and assign
cc_shared <- cc_shared[which(cc_shared$r %in% cc$r), ]
cc[match(cc_shared$r, cc$r), ] <- cc_shared

cc <- shared_as_fml(cc, cc_shared)
}

sel <- cc$f != ""
Expand Down
22 changes: 22 additions & 0 deletions R/write.R
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,28 @@ inner_update <- function(
"f", "f_t", "f_ref", "f_ca", "f_si", "is", "typ")

sel <- match(x$r, cc$r)

# to avoid bricking the worksheet, we make sure that we do not overwrite the
# reference cell of a shared formula. To be on the save side, we replace all
# values with the formula. If the entire cc is replaced with x, we can skip.
if (length(sf <- cc$f_si[sel & cc$f_t[sel] == "shared" & cc$f_ref[sel] != ""]) && !all(cc$r %in% x$r)) {

# collect all the shared formulas that we have to convert
sel_fsi <- cc$f_si %in% unique(sf)

cc_shared <- cc[sel_fsi, , drop = FALSE]

cc <- shared_as_fml(cc, cc_shared)

msg <- paste0(
"A shared formula reference cell was overwritten. To protect the",
" spreadsheet formulas, the impacted cells were converted from shared",
" formulas to normal formulas."
)
warning(msg, call. = FALSE)

}

cc[sel, replacement] <- x[replacement]

# avoid missings in cc
Expand Down
17 changes: 17 additions & 0 deletions tests/testthat/test-write.R
Original file line number Diff line number Diff line change
Expand Up @@ -1476,3 +1476,20 @@ test_that("writing list with sep works", {
expect_equal(exp, got)

})

test_that("guarding against overwriting shared formula reference works", {

wb <- wb_workbook()$add_worksheet()$
add_data(x = 1)$
add_formula(dims = "B1:D1", x = "A1 + 1", shared = TRUE)

expect_warning(
wb$add_data(x = 2, dims = "B1"),
"A shared formula reference cell was overwritten."
)

exp <- c("1", "2", "B1 + 1", "C1 + 1")
got <- unname(unlist(wb$to_df(show_formula = TRUE, col_names = FALSE)))
expect_equal(exp, got)

})

0 comments on commit 9334e8d

Please sign in to comment.