From 4319eb14f71c389e3568648ffde716fe3e386e1a Mon Sep 17 00:00:00 2001 From: Rong-Zh Date: Sun, 5 Apr 2026 15:43:35 +0800 Subject: [PATCH 1/5] Add overwrite support to writeData for clean rewrite workflows --- R/writeData.R | 17 ++++++++++++++++- man/writeData.Rd | 5 ++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/R/writeData.R b/R/writeData.R index 1ac1ab26..08a6eadf 100644 --- a/R/writeData.R +++ b/R/writeData.R @@ -45,6 +45,7 @@ #' @param na.string If not NULL, and if `keepNA` is `TRUE`, NA values are converted to this string in Excel. #' @param name If not NULL, a named region is defined. #' @param sep Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep). +#' @param overwrite If `TRUE`, remove existing cell values in the target worksheet before writing `x`. #' @seealso [writeDataTable()] #' @export writeData #' @details Formulae written using writeFormula to a Workbook object will not get picked up by read.xlsx(). @@ -176,7 +177,8 @@ writeData <- function( name = NULL, sep = ", ", col.names, - row.names + row.names, + overwrite = FALSE ) { x <- force(x) @@ -224,6 +226,7 @@ writeData <- function( assert_class(wb, "Workbook") assert_true_false(colNames) assert_true_false(rowNames) + assert_true_false(overwrite) assert_character1(sep) assert_class(headerStyle, "Style", or_null = TRUE) @@ -296,6 +299,18 @@ writeData <- function( stop("Cannot write to chart sheet.") } + + if (overwrite) { + sheet_data <- wb$worksheets[[sheetX]]$sheet_data + if (sheet_data$n_elements > 0) { + sheet_data$delete( + rows_in = sheet_data$rows, + cols_in = sheet_data$cols, + grid_expand = FALSE + ) + } + } + ## Check not overwriting existing table headers wb$check_overwrite_tables( sheet = sheet, diff --git a/man/writeData.Rd b/man/writeData.Rd index be986f0d..784fa66f 100644 --- a/man/writeData.Rd +++ b/man/writeData.Rd @@ -24,7 +24,8 @@ writeData( name = NULL, sep = ", ", col.names, - row.names + row.names, + overwrite = FALSE ) } \arguments{ @@ -87,6 +88,8 @@ each column. If "\code{all}" all cell borders are drawn.} \item{sep}{Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep).} +\item{overwrite}{If \code{TRUE}, remove existing cell values in the target worksheet before writing \code{x}.} + \item{row.names, col.names}{Deprecated, please use \code{rowNames}, \code{colNames} instead} } \value{ From 48adb3f56a9dbfc344a9cc3a92a4534b16453bf0 Mon Sep 17 00:00:00 2001 From: Rong-Zh <74701628+Rong-Zh@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:00:01 +0800 Subject: [PATCH 2/5] Update man/writeData.Rd Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- man/writeData.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/writeData.Rd b/man/writeData.Rd index 784fa66f..b0e2546e 100644 --- a/man/writeData.Rd +++ b/man/writeData.Rd @@ -88,7 +88,7 @@ each column. If "\code{all}" all cell borders are drawn.} \item{sep}{Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep).} -\item{overwrite}{If \code{TRUE}, remove existing cell values in the target worksheet before writing \code{x}.} +\item{overwrite}{If \code{TRUE}, clear all existing cell contents on the target worksheet before writing \code{x}.} \item{row.names, col.names}{Deprecated, please use \code{rowNames}, \code{colNames} instead} } From 8a61b898a8c6402a0f80268eaccbe1d228e15978 Mon Sep 17 00:00:00 2001 From: Rong-Zh <74701628+Rong-Zh@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:01:16 +0800 Subject: [PATCH 3/5] Update R/writeData.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/writeData.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/writeData.R b/R/writeData.R index 08a6eadf..78b7602c 100644 --- a/R/writeData.R +++ b/R/writeData.R @@ -45,7 +45,7 @@ #' @param na.string If not NULL, and if `keepNA` is `TRUE`, NA values are converted to this string in Excel. #' @param name If not NULL, a named region is defined. #' @param sep Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep). -#' @param overwrite If `TRUE`, remove existing cell values in the target worksheet before writing `x`. +#' @param overwrite If `TRUE`, remove all existing cell values from the entire target worksheet before writing `x` (not just the cells in the target write range). #' @seealso [writeDataTable()] #' @export writeData #' @details Formulae written using writeFormula to a Workbook object will not get picked up by read.xlsx(). From 6505d896e304ad281797b9d06fdd73d2854f0cd0 Mon Sep 17 00:00:00 2001 From: Rong-Zh Date: Sun, 5 Apr 2026 18:43:40 +0800 Subject: [PATCH 4/5] Delay overwrite deletion until after table header validation --- R/writeData.R | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/R/writeData.R b/R/writeData.R index 78b7602c..09822429 100644 --- a/R/writeData.R +++ b/R/writeData.R @@ -299,6 +299,14 @@ writeData <- function( stop("Cannot write to chart sheet.") } + ## Check not overwriting existing table headers + wb$check_overwrite_tables( + sheet = sheet, + new_rows = c(startRow, startRow + nRow - 1L + colNames), + new_cols = c(startCol, startCol + nCol - 1L), + check_table_header_only = TRUE, + error_msg = "Cannot overwrite table headers. Avoid writing over the header row or see getTables() & removeTables() to remove the table object." + ) if (overwrite) { sheet_data <- wb$worksheets[[sheetX]]$sheet_data @@ -311,15 +319,6 @@ writeData <- function( } } - ## Check not overwriting existing table headers - wb$check_overwrite_tables( - sheet = sheet, - new_rows = c(startRow, startRow + nRow - 1L + colNames), - new_cols = c(startCol, startCol + nCol - 1L), - check_table_header_only = TRUE, - error_msg = "Cannot overwrite table headers. Avoid writing over the header row or see getTables() & removeTables() to remove the table object." - ) - ## write autoFilter, can only have a single filter per worksheet if (withFilter) { coords <- data.frame( From 594311e2f325a2e2dc2e23b87cfde991ea8de5a8 Mon Sep 17 00:00:00 2001 From: Rong-Zh Date: Fri, 29 May 2026 16:21:31 +0800 Subject: [PATCH 5/5] Add writeData overwrite NEWS and tests (#536) --- NEWS.md | 1 + man/writeData.Rd | 4 ++-- tests/testthat/test-writeData.R | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 648fa417..c7db769b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ # openxlsx 4.2.8.1 * Fix for upcoming `testthat` release (@hadley, [#530](https://github.com/ycphs/openxlsx/pull/530)) +* Add overwrite support to writeData() to clear stale cells when rewriting with a smaller data range ([#536](https://github.com/ycphs/openxlsx/pull/536)) # openxlsx 4.2.8 diff --git a/man/writeData.Rd b/man/writeData.Rd index b0e2546e..7d552476 100644 --- a/man/writeData.Rd +++ b/man/writeData.Rd @@ -88,9 +88,9 @@ each column. If "\code{all}" all cell borders are drawn.} \item{sep}{Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep).} -\item{overwrite}{If \code{TRUE}, clear all existing cell contents on the target worksheet before writing \code{x}.} - \item{row.names, col.names}{Deprecated, please use \code{rowNames}, \code{colNames} instead} + +\item{overwrite}{If \code{TRUE}, remove all existing cell values from the entire target worksheet before writing \code{x} (not just the cells in the target write range).} } \value{ invisible(0) diff --git a/tests/testthat/test-writeData.R b/tests/testthat/test-writeData.R index 43b3dadc..aa0a823d 100644 --- a/tests/testthat/test-writeData.R +++ b/tests/testthat/test-writeData.R @@ -67,3 +67,35 @@ test_that("as.character.formula() works [312]", { expect_identical(before, middle, ignore.environment = TRUE) expect_identical(before, end, ignore.environment = TRUE) }) + + +test_that("writeData(overwrite = TRUE) works (#536)", { + wb <- createWorkbook() + addWorksheet(wb, "sheet") + + old <- data.frame(a = 1:6, b = letters[1:6], stringsAsFactors = FALSE) + new <- old[1:4, , drop = FALSE] + + writeData(wb, "sheet", old) + writeData(wb, "sheet", new) + out_no_overwrite <- readWorkbook(wb, "sheet") + expect_equal(nrow(out_no_overwrite), 6) + + writeData(wb, "sheet", new, overwrite = TRUE) + out_overwrite <- readWorkbook(wb, "sheet") + expect_equal(out_overwrite, new) + + wb2 <- createWorkbook() + addWorksheet(wb2, "sheet") + tab_df <- data.frame(x = 1:2) + writeDataTable(wb2, "sheet", tab_df) + before <- readWorkbook(wb2, "sheet") + + expect_error( + writeData(wb2, "sheet", data.frame(y = 1), overwrite = TRUE), + "Cannot overwrite table headers" + ) + + after <- readWorkbook(wb2, "sheet") + expect_equal(after, before) +})