diff --git a/R/write.R b/R/write.R
index 3e4b24ed6..78496516b 100644
--- a/R/write.R
+++ b/R/write.R
@@ -1,721 +1,278 @@
-
-# `write_data()` ---------------------------------------------------------------
-#' Write an object to a worksheet
-#'
-#' Write an object to worksheet with optional styling.
+#' function to add missing cells to cc and rows
#'
-#' Formulae written using write_formula to a Workbook object will not get picked up by read_xlsx().
-#' This is because only the formula is written and left to Excel to evaluate the formula when the file is opened in Excel.
-#' The string `"_openxlsx_NA"` is reserved for `openxlsx2`. If the data frame contains this string, the output will be broken.
+#' Create a cell in the workbook
#'
-#' @param wb A Workbook object containing a worksheet.
-#' @param sheet The worksheet to write to. Can be the worksheet index or name.
-#' @param x Object to be written. For classes supported look at the examples.
-#' @param start_col A vector specifying the starting column to write to.
-#' @param start_row A vector specifying the starting row to write to.
-#' @param dims Spreadsheet dimensions that will determine startCol and startRow: "A1", "A1:B2", "A:B"
-#' @param array A bool if the function written is of type array
-#' @param col_names If `TRUE`, column names of x are written.
-#' @param row_names If `TRUE`, data.frame row names of x are written.
-#' @param with_filter If `TRUE`, add filters to the column name row. NOTE can only have one filter per worksheet.
-#' @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 name If not NULL, a named region is defined.
-#' @param apply_cell_style apply styles when writing on the sheet
-#' @param remove_cell_style if writing into existing cells, should the cell style be removed?
+#' @param wb the workbook update
+#' @param sheet_id the sheet to update
+#' @param x the newly filled cc frame
+#' @param rows the rows needed
+#' @param cells_needed the cells needed
+#' @param colNames has colNames (only in update_cell)
+#' @param removeCellStyle remove the cell style (only in update_cell)
#' @param na.strings Value used for replacing `NA` values from `x`. Default
-#' `na_strings()` uses the special `#N/A` value within the workbook.
-#' @param inline_strings write characters as inline strings
-#' @param ... additional arguments
-#' @seealso [write_datatable()]
-#' @return invisible(0)
-#' @examples
-#' ## See formatting vignette for further examples.
-#'
-#' ## Options for default styling (These are the defaults)
-#' options("openxlsx2.dateFormat" = "mm/dd/yyyy")
-#' options("openxlsx2.datetimeFormat" = "yyyy-mm-dd hh:mm:ss")
-#' options("openxlsx2.numFmt" = NULL)
+#' `na_strings()` uses the special `#N/A` value within the workbook.#' @keywords internal
+#' @noRd
+inner_update <- function(
+ wb,
+ sheet_id,
+ x,
+ rows,
+ cells_needed,
+ colNames = FALSE,
+ removeCellStyle = FALSE,
+ na.strings = na_strings()
+) {
+
+ # 1) pull sheet to modify from workbook; 2) modify it; 3) push it back
+ cc <- wb$worksheets[[sheet_id]]$sheet_data$cc
+ row_attr <- wb$worksheets[[sheet_id]]$sheet_data$row_attr
+
+ # workbooks contain only entries for values currently present.
+ # if A1 is filled, B1 is not filled and C1 is filled the sheet will only
+ # contain fields A1 and C1.
+ cells_in_wb <- cc$r
+ rows_in_wb <- row_attr$r
+
+ # check if there are rows not available
+ if (!all(rows %in% rows_in_wb)) {
+ # message("row(s) not in workbook")
+
+ missing_rows <- rows[!rows %in% rows_in_wb]
+
+ # new row_attr
+ row_attr_missing <- empty_row_attr(n = length(missing_rows))
+ row_attr_missing$r <- missing_rows
+
+ row_attr <- rbind(row_attr, row_attr_missing)
+
+ # order
+ row_attr <- row_attr[order(as.numeric(row_attr$r)), ]
+
+ wb$worksheets[[sheet_id]]$sheet_data$row_attr <- row_attr
+ # provide output
+ rows_in_wb <- row_attr$r
+
+ }
+
+ if (!all(cells_needed %in% cells_in_wb)) {
+ # message("cell(s) not in workbook")
+
+ missing_cells <- cells_needed[!cells_needed %in% cells_in_wb]
+
+ # create missing cells
+ cc_missing <- create_char_dataframe(names(cc), length(missing_cells))
+ cc_missing$r <- missing_cells
+ cc_missing$row_r <- gsub("[[:upper:]]", "", cc_missing$r)
+ cc_missing$c_r <- gsub("[[:digit:]]", "", cc_missing$r)
+
+ # assign to cc
+ cc <- rbind(cc, cc_missing)
+
+ # order cc (not really necessary, will be done when saving)
+ cc <- cc[order(as.integer(cc[, "row_r"]), col2int(cc[, "c_r"])), ]
+
+ # update dimensions (only required if new cols and rows are added) ------
+ all_rows <- as.numeric(unique(cc$row_r))
+ all_cols <- col2int(unique(cc$c_r))
+
+ min_cell <- trimws(paste0(int2col(min(all_cols, na.rm = TRUE)), min(all_rows, na.rm = TRUE)))
+ max_cell <- trimws(paste0(int2col(max(all_cols, na.rm = TRUE)), max(all_rows, na.rm = TRUE)))
+
+ # i know, i know, i'm lazy
+ wb$worksheets[[sheet_id]]$dimension <- paste0("")
+ }
+
+ if (is_na_strings(na.strings)) {
+ na.strings <- NULL
+ }
+
+ if (removeCellStyle) {
+ cell_style <- "c_s"
+ } else {
+ cell_style <- NULL
+ }
+
+ replacement <- c("r", cell_style, "c_t", "c_cm", "c_ph", "c_vm", "v",
+ "f", "f_t", "f_ref", "f_ca", "f_si", "is", "typ")
+
+ sel <- match(x$r, cc$r)
+ cc[sel, replacement] <- x[replacement]
+
+ # avoid missings in cc
+ if (any(is.na(cc)))
+ cc[is.na(cc)] <- ""
+
+ # push everything back to workbook
+ wb$worksheets[[sheet_id]]$sheet_data$cc <- cc
+
+ wb
+}
+
+#' Initialize data cell(s)
#'
-#' #############################################################################
-#' ## Create Workbook object and add worksheets
-#' wb <- wb_workbook()
+#' Create a cell in the workbook
#'
-#' ## Add worksheets
-#' wb$add_worksheet("Cars")
-#' wb$add_worksheet("Formula")
+#' @param wb the workbook you want to update
+#' @param sheet the sheet you want to update
+#' @param new_cells the cell you want to update in Excel connotation e.g. "A1"
#'
-#' x <- mtcars[1:6, ]
-#' wb$add_data("Cars", x, startCol = 2, startRow = 3, rowNames = TRUE)
+#' @keywords internal
+#' @noRd
+initialize_cell <- function(wb, sheet, new_cells) {
+
+ sheet_id <- wb$validate_sheet(sheet)
+
+ # create artificial cc for the missing cells
+ x <- empty_sheet_data_cc(n = length(new_cells))
+ x$r <- new_cells
+ x$row_r <- gsub("[[:upper:]]", "", new_cells)
+ x$c_r <- gsub("[[:digit:]]", "", new_cells)
+
+ rows <- x$row_r
+ cells_needed <- new_cells
+
+ inner_update(wb, sheet_id, x, rows, cells_needed)
+}
+
+#' Replace data cell(s)
#'
-#' #############################################################################
-#' ## Hyperlinks
-#' ## - vectors/columns with class 'hyperlink' are written as hyperlinks'
+#' Minimal invasive update of cell(s) inside of imported workbooks.
#'
-#' v <- rep("https://CRAN.R-project.org/", 4)
-#' names(v) <- paste0("Hyperlink", 1:4) # Optional: names will be used as display text
-#' class(v) <- "hyperlink"
-#' wb$add_data("Cars", x = v, dims = c("B32"))
+#' @param x cc dataframe of the updated cells
+#' @param wb the workbook you want to update
+#' @param sheet the sheet you want to update
+#' @param cell the cell you want to update in Excel connotation e.g. "A1"
+#' @param colNames if TRUE colNames are passed down
+#' @param removeCellStyle keep the cell style?
+#' @param na.strings optional na.strings argument. if missing #N/A is used. If NULL no cell value is written, if character or numeric this is written (even if NA is part of numeric data)
#'
-#' #############################################################################
-#' ## Formulas
-#' ## - vectors/columns with class 'formula' are written as formulas'
+#' @keywords internal
+#' @noRd
+update_cell <- function(x, wb, sheet, cell, colNames = FALSE,
+ removeCellStyle = FALSE, na.strings) {
+
+ if (missing(na.strings))
+ na.strings <- substitute()
+
+ sheet_id <- wb$validate_sheet(sheet)
+
+ dims <- dims_to_dataframe(cell, fill = TRUE)
+ rows <- rownames(dims)
+
+ cells_needed <- unname(unlist(dims))
+
+ inner_update(wb, sheet_id, x, rows, cells_needed, colNames, removeCellStyle, na.strings)
+}
+
+#' dummy function to write data
+#' @param wb workbook
+#' @param sheet sheet
+#' @param data data to export
+#' @param name If not NULL, a named region is defined.
+#' @param colNames include colnames?
+#' @param rowNames include rownames?
+#' @param startRow row to place it
+#' @param startCol col to place it
+#' @param applyCellStyle apply styles when writing on the sheet
+#' @param removeCellStyle keep the cell style?
+#' @param na.strings Value used for replacing `NA` values from `x`. Default
+#' `na_strings()` uses the special `#N/A` value within the workbook.
+#' @param data_table logical. if `TRUE` and `rowNames = TRUE`, do not write the cell containing `"_rowNames_"`
+#' @param inline_strings write characters as inline strings
+#' @details
+#' The string `"_openxlsx_NA"` is reserved for `openxlsx2`. If the data frame
+#' contains this string, the output will be broken.
#'
-#' df <- data.frame(
-#' x = 1:3, y = 1:3,
-#' z = paste(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = "+"),
-#' stringsAsFactors = FALSE
-#' )
+#' @examples
+#' # create a workbook and add some sheets
+#' wb <- wb_workbook()
#'
-#' class(df$z) <- c(class(df$z), "formula")
+#' wb$add_worksheet("sheet1")
+#' write_data2(wb, "sheet1", mtcars, colNames = TRUE, rowNames = TRUE)
#'
-#' wb$add_data(sheet = "Formula", x = df)
+#' wb$add_worksheet("sheet2")
+#' write_data2(wb, "sheet2", cars, colNames = FALSE)
#'
-#' #############################################################################
-#' # update cell range and add mtcars
-#' xlsxFile <- system.file("extdata", "openxlsx2_example.xlsx", package = "openxlsx2")
-#' wb2 <- wb_load(xlsxFile)
+#' wb$add_worksheet("sheet3")
+#' write_data2(wb, "sheet3", letters)
#'
-#' # read dataset with inlinestr
-#' wb_to_df(wb2)
-#' write_data(wb2, 1, mtcars, startCol = 4, startRow = 4)
-#' wb_to_df(wb2)
-#' @export
-write_data <- function(
+#' wb$add_worksheet("sheet4")
+#' write_data2(wb, "sheet4", as.data.frame(Titanic), startRow = 2, startCol = 2)
+#' @keywords internal
+#' @noRd
+write_data2 <- function(
wb,
sheet,
- x,
- dims = rowcol_to_dims(start_row, start_col),
- start_col = 1,
- start_row = 1,
- array = FALSE,
- col_names = TRUE,
- row_names = FALSE,
- with_filter = FALSE,
- sep = ", ",
- name = NULL,
- apply_cell_style = TRUE,
- remove_cell_style = FALSE,
- na.strings = na_strings(),
- inline_strings = TRUE,
- ...
+ data,
+ name = NULL,
+ colNames = TRUE,
+ rowNames = FALSE,
+ startRow = 1,
+ startCol = 1,
+ applyCellStyle = TRUE,
+ removeCellStyle = FALSE,
+ na.strings = na_strings(),
+ data_table = FALSE,
+ inline_strings = TRUE
) {
- standardize_case_names(...)
+ is_data_frame <- FALSE
+ #### prepare the correct data formats for openxml
+ dc <- openxlsx2_type(data)
- write_data_table(
- wb = wb,
- sheet = sheet,
- x = x,
- dims = dims,
- startCol = start_col,
- startRow = start_row,
- array = array,
- colNames = col_names,
- rowNames = row_names,
- tableStyle = NULL,
- tableName = NULL,
- withFilter = with_filter,
- sep = sep,
- firstColumn = FALSE,
- lastColumn = FALSE,
- bandedRows = FALSE,
- bandedCols = FALSE,
- name = name,
- applyCellStyle = apply_cell_style,
- removeCellStyle = remove_cell_style,
- data_table = FALSE,
- na.strings = na.strings,
- inline_strings = inline_strings
- )
-}
+ # convert factor to character
+ if (any(dc == openxlsx2_celltype[["factor"]])) {
+ is_factor <- dc == openxlsx2_celltype[["factor"]]
+ fcts <- names(dc[is_factor])
+ data[fcts] <- lapply(data[fcts], to_string)
+ # dc <- openxlsx2_type(data)
+ }
-# write_formula() -------------------------------------------
-#' Write a character vector as an Excel Formula
-#'
-#' Write a a character vector containing Excel formula to a worksheet.
-#'
-#' @details
-#' Currently only the English version of functions are supported. Please don't use the local translation.
-#' The examples below show a small list of possible formulas:
-#' * SUM(B2:B4)
-#' * AVERAGE(B2:B4)
-#' * MIN(B2:B4)
-#' * MAX(B2:B4)
-#' * ...
-#'
-#' @param wb A Workbook object containing a worksheet.
-#' @param sheet The worksheet to write to. Can be the worksheet index or name.
-#' @param x A character vector.
-#' @param dims Spreadsheet dimensions that will determine startCol and startRow: "A1", "A1:B2", "A:B"
-#' @param start_col A vector specifying the starting column to write to.
-#' @param start_row A vector specifying the starting row to write to.
-#' @param array A bool if the function written is of type array
-#' @param cm A bool if the function is of type cm (array with hidden curly braces)
-#' @param apply_cell_style apply styles when writing on the sheet
-#' @param remove_cell_style if writing into existing cells, should the cell style be removed?
-#' @param ... additional arguments
-#' @seealso [write_data()]
-#' @examples
-#' ## There are 3 ways to write a formula
-#'
-#' wb <- wb_workbook()
-#' wb$add_worksheet("Sheet 1")
-#' wb$add_data("Sheet 1", x = iris)
-#'
-#' ## SEE `int2col()` to convert int to Excel column label
-#'
-#' ## 1. - As a character vector using write_formula
-#'
-#' v <- c("SUM(A2:A151)", "AVERAGE(B2:B151)") ## skip header row
-#' write_formula(wb, sheet = 1, x = v, startCol = 10, startRow = 2)
-#' write_formula(wb, 1, x = "A2 + B2", startCol = 10, startRow = 10)
-#'
-#'
-#' ## 2. - As a data.frame column with class "formula" using write_data
-#'
-#' df <- data.frame(
-#' x = 1:3,
-#' y = 1:3,
-#' z = paste(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = " + "),
-#' z2 = sprintf("ADDRESS(1,%s)", 1:3),
-#' stringsAsFactors = FALSE
-#' )
-#'
-#' class(df$z) <- c(class(df$z), "formula")
-#' class(df$z2) <- c(class(df$z2), "formula")
-#'
-#' wb$add_worksheet("Sheet 2")
-#' wb$add_data(sheet = 2, x = df)
-#'
-#'
-#' ## 3. - As a vector with class "formula" using write_data
-#'
-#' v2 <- c("SUM(A2:A4)", "AVERAGE(B2:B4)", "MEDIAN(C2:C4)")
-#' class(v2) <- c(class(v2), "formula")
-#'
-#' wb$add_data(sheet = 2, x = v2, startCol = 10, startRow = 2)
-#'
-#'
-#' ## 4. - Writing internal hyperlinks
-#'
-#' wb <- wb_workbook()
-#' wb$add_worksheet("Sheet1")
-#' wb$add_worksheet("Sheet2")
-#' write_formula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")')
-#'
-#'
-#' ## 5. - Writing array formulas
-#'
-#' set.seed(123)
-#' df <- data.frame(C = rnorm(10), D = rnorm(10))
-#'
-#' wb <- wb_workbook()
-#' wb <- wb_add_worksheet(wb, "df")
-#'
-#' wb$add_data("df", df, startCol = "C")
-#'
-#' write_formula(wb, "df", startCol = "E", startRow = "2",
-#' x = "SUM(C2:C11*D2:D11)",
-#' array = TRUE)
-#' @export
-write_formula <- function(
- wb,
- sheet,
- x,
- dims = rowcol_to_dims(start_row, start_col),
- start_col = 1,
- start_row = 1,
- array = FALSE,
- cm = FALSE,
- apply_cell_style = TRUE,
- remove_cell_style = FALSE,
- ...
-) {
+ hconvert_date1904 <- grepl('date1904="1"|date1904="true"',
+ stri_join(unlist(wb$workbook), collapse = ""),
+ ignore.case = TRUE)
- standardize_case_names(...)
+ # TODO need to tell excel that we have a date, apply some kind of numFmt
+ data <- convert_to_excel_date(df = data, date1904 = hconvert_date1904)
- assert_class(x, "character")
- # remove xml encoding and reapply it afterwards. until v0.3 encoding was not enforced
- x <- replaceXMLEntities(x)
- x <- vapply(x, function(val) xml_value(xml_node_create("fml", val, escapes = TRUE), "fml"), NA_character_)
- dfx <- data.frame("X" = x, stringsAsFactors = FALSE)
+ # backward compatible
+ if (!inherits(data, "data.frame") || inherits(data, "matrix")) {
+ data <- as.data.frame(data)
+ colNames <- FALSE
+ }
- formula <- "formula"
- if (array) formula <- "array_formula"
- if (cm) {
- # need to set cell metadata in wb$metadata
- if (is.null(wb$metadata)) {
+ if (inherits(data, "data.frame") || inherits(data, "matrix")) {
+ is_data_frame <- TRUE
- wb$append("Content_Types", "")
+ if (is.data.frame(data)) data <- as.data.frame(data)
- wb$metadata <- # danger danger no clue what this means!
- xml_node_create(
- "metadata",
- xml_attributes = c(
- xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
- "xmlns:xda" = "http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray"
- ),
- xml_children = read_xml(
- "
-
-
-
-
-
-
-
-
-
-
- ,
- ",
- pointer = FALSE
- )
- )
+ sel <- !dc %in% c(4, 5, 10)
+ data[sel] <- lapply(data[sel], as.character)
+
+ # add rownames
+ if (rowNames) {
+ data <- cbind("_rowNames_" = rownames(data), data)
+ dc <- c(c("_rowNames_" = openxlsx2_celltype[["character"]]), dc)
}
- ## TODO Not sure if there are more cases
- # add new cell metadata record
- cM <- xml_node(wb$metadata, "metadata", "cellMetadata")
- cM <- xml_add_child(cM, xml_child = "")
+ # add colnames
+ if (colNames)
+ data <- rbind(colnames(data), data)
+ }
- # we need to update count
- cnt <- as_xml_attr(length(xml_node(cM, "cellMetadata", "bk")))
- cM <- xml_attr_mod(cM, xml_attributes = c(count = cnt))
- # remove current cellMetadata update new
- wb$metadata <- xml_rm_child(wb$metadata, "cellMetadata")
- wb$metadata <- xml_add_child(wb$metadata, cM)
+ sheetno <- wb_validate_sheet(wb, sheet)
+ # message("sheet no: ", sheetno)
- attr(dfx, "c_cm") <- cnt
- formula <- "cm_formula"
- }
+ # create a data frame
+ if (!is_data_frame)
+ data <- as.data.frame(t(data))
- class(dfx$X) <- c("character", formula)
- if (!is.null(dims)) {
- if (array || cm) {
- attr(dfx, "f_ref") <- dims
- }
- }
+ data_nrow <- NROW(data)
+ data_ncol <- NCOL(data)
- if (any(grepl("=([\\s]*?)HYPERLINK\\(", x, perl = TRUE))) {
- class(dfx$X) <- c("character", "formula", "hyperlink")
- }
-
- write_data(
- wb = wb,
- sheet = sheet,
- x = dfx,
- start_col = start_col,
- start_row = start_row,
- dims = dims,
- array = array,
- col_names = FALSE,
- row_names = FALSE,
- apply_cell_style = apply_cell_style,
- remove_cell_style = remove_cell_style
- )
-
-}
-
-# `write_datatable()` ----------------------
-#' Write to a worksheet as an Excel table
-#'
-#' Write to a worksheet and format as an Excel table
-#'
-#' @param wb A Workbook object containing a worksheet.
-#' @param sheet The worksheet to write to. Can be the worksheet index or name.
-#' @param x A data frame.
-#' @param start_col A vector specifying the starting column to write df
-#' @param start_row A vector specifying the starting row to write df
-#' @param dims Spreadsheet dimensions that will determine startCol and startRow: "A1", "A1:B2", "A:B"
-#' @param col_names If `TRUE`, column names of x are written.
-#' @param row_names If `TRUE`, row names of x are written.
-#' @param table_style Any excel table style name or "none" (see "formatting" vignette).
-#' @param table_name name of table in workbook. The table name must be unique.
-#' @param with_filter If `TRUE`, columns with have filters in the first row.
-#' @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).
-#' \cr\cr
-#' \cr**The below options correspond to Excel table options:**
-#' \cr
-#' \if{html}{\figure{tableoptions.png}{options: width="40\%" alt="Figure: table_options.png"}}
-#' \if{latex}{\figure{tableoptions.pdf}{options: width=7cm}}
-#'
-#' @param first_column logical. If TRUE, the first column is bold
-#' @param last_column logical. If TRUE, the last column is bold
-#' @param banded_rows logical. If TRUE, rows are color banded
-#' @param banded_cols logical. If TRUE, the columns are color banded
-#' @param apply_cell_style apply styles when writing on the sheet
-#' @param remove_cell_style if writing into existing cells, should the cell style be removed?
-#' @param na.strings Value used for replacing `NA` values from `x`. Default
-#' `na_strings()` uses the special `#N/A` value within the workbook.
-#' @param inline_strings write characters as inline strings
-#' @param ... additional arguments
-#' @details columns of x with class Date/POSIXt, currency, accounting,
-#' hyperlink, percentage are automatically styled as dates, currency, accounting,
-#' hyperlinks, percentages respectively.
-#' The string `"_openxlsx_NA"` is reserved for `openxlsx2`. If the data frame
-#' contains this string, the output will be broken.
-#' @seealso
-#' [wb_add_worksheet()]
-#' [write_data()]
-#' [wb_remove_tables()]
-#' [wb_get_tables()]
-#' @examples
-#' ## see package vignettes for further examples.
-#'
-#' #####################################################################################
-#' ## Create Workbook object and add worksheets
-#' wb <- wb_workbook()
-#' wb$add_worksheet("S1")
-#' wb$add_worksheet("S2")
-#' wb$add_worksheet("S3")
-#'
-#' #####################################################################################
-#' ## -- write data.frame as an Excel table with column filters
-#' ## -- default table style is "TableStyleMedium2"
-#'
-#' wb$add_data_table("S1", x = iris)
-#'
-#' wb$add_data_table("S2",
-#' x = mtcars, dims = c("B3"), rowNames = TRUE,
-#' tableStyle = "TableStyleLight9"
-#' )
-#'
-#' df <- data.frame(
-#' "Date" = Sys.Date() - 0:19,
-#' "T" = TRUE, "F" = FALSE,
-#' "Time" = Sys.time() - 0:19 * 60 * 60,
-#' "Cash" = paste("$", 1:20), "Cash2" = 31:50,
-#' "hLink" = "https://CRAN.R-project.org/",
-#' "Percentage" = seq(0, 1, length.out = 20),
-#' "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE
-#' )
-#'
-#' ## openxlsx will apply default Excel styling for these classes
-#' class(df$Cash) <- c(class(df$Cash), "currency")
-#' class(df$Cash2) <- c(class(df$Cash2), "accounting")
-#' class(df$hLink) <- "hyperlink"
-#' class(df$Percentage) <- c(class(df$Percentage), "percentage")
-#' class(df$TinyNumbers) <- c(class(df$TinyNumbers), "scientific")
-#'
-#' wb$add_data_table("S3", x = df, startRow = 4, rowNames = TRUE, tableStyle = "TableStyleMedium9")
-#'
-#' #####################################################################################
-#' ## Additional Header Styling and remove column filters
-#'
-#' write_datatable(wb,
-#' sheet = 1,
-#' x = iris,
-#' startCol = 7,
-#' withFilter = FALSE,
-#' firstColumn = TRUE,
-#' lastColumn = TRUE,
-#' bandedRows = TRUE,
-#' bandedCols = TRUE
-#' )
-#'
-#' #####################################################################################
-#' ## Pre-defined table styles gallery
-#'
-#' wb <- wb_workbook(paste0("tableStylesGallery.xlsx"))
-#' wb$add_worksheet("Style Samples")
-#' for (i in 1:21) {
-#' style <- paste0("TableStyleLight", i)
-#' write_datatable(wb,
-#' x = data.frame(style), sheet = 1,
-#' tableStyle = style, startRow = 1, startCol = i * 3 - 2
-#' )
-#' }
-#'
-#' for (i in 1:28) {
-#' style <- paste0("TableStyleMedium", i)
-#' write_datatable(wb,
-#' x = data.frame(style), sheet = 1,
-#' tableStyle = style, startRow = 4, startCol = i * 3 - 2
-#' )
-#' }
-#'
-#' for (i in 1:11) {
-#' style <- paste0("TableStyleDark", i)
-#' write_datatable(wb,
-#' x = data.frame(style), sheet = 1,
-#' tableStyle = style, startRow = 7, startCol = i * 3 - 2
-#' )
-#' }
-#' @export
-write_datatable <- function(
- wb,
- sheet,
- x,
- dims = rowcol_to_dims(start_row, start_col),
- start_col = 1,
- start_row = 1,
- col_names = TRUE,
- row_names = FALSE,
- table_style = "TableStyleLight9",
- table_name = NULL,
- with_filter = TRUE,
- sep = ", ",
- first_column = FALSE,
- last_column = FALSE,
- banded_rows = TRUE,
- banded_cols = FALSE,
- apply_cell_style = TRUE,
- remove_cell_style = FALSE,
- na.strings = na_strings(),
- inline_strings = TRUE,
- ...
-) {
-
- standardize_case_names(...)
-
- write_data_table(
- wb = wb,
- sheet = sheet,
- x = x,
- startCol = start_col,
- startRow = start_row,
- dims = dims,
- array = FALSE,
- colNames = col_names,
- rowNames = row_names,
- tableStyle = table_style,
- tableName = table_name,
- withFilter = with_filter,
- sep = sep,
- firstColumn = first_column,
- lastColumn = last_column,
- bandedRows = banded_rows,
- bandedCols = banded_cols,
- name = NULL,
- data_table = TRUE,
- applyCellStyle = apply_cell_style,
- removeCellStyle = remove_cell_style,
- na.strings = na.strings,
- inline_strings = inline_strings
- )
-}
-# Internal helpers of `write_data*` --------------------------------------------
-#' function to add missing cells to cc and rows
-#'
-#' Create a cell in the workbook
-#'
-#' @param wb the workbook update
-#' @param sheet_id the sheet to update
-#' @param x the newly filled cc frame
-#' @param rows the rows needed
-#' @param cells_needed the cells needed
-#' @param colNames has colNames (only in update_cell)
-#' @param removeCellStyle remove the cell style (only in update_cell)
-#' @param na.strings Value used for replacing `NA` values from `x`. Default
-#' `na_strings()` uses the special `#N/A` value within the workbook.
-#' @noRd
-inner_update <- function(
- wb,
- sheet_id,
- x,
- rows,
- cells_needed,
- colNames = FALSE,
- removeCellStyle = FALSE,
- na.strings = na_strings()
-) {
-
- # 1) pull sheet to modify from workbook; 2) modify it; 3) push it back
- cc <- wb$worksheets[[sheet_id]]$sheet_data$cc
- row_attr <- wb$worksheets[[sheet_id]]$sheet_data$row_attr
-
- # workbooks contain only entries for values currently present.
- # if A1 is filled, B1 is not filled and C1 is filled the sheet will only
- # contain fields A1 and C1.
- cells_in_wb <- cc$r
- rows_in_wb <- row_attr$r
-
- # check if there are rows not available
- if (!all(rows %in% rows_in_wb)) {
- # message("row(s) not in workbook")
-
- missing_rows <- rows[!rows %in% rows_in_wb]
-
- # new row_attr
- row_attr_missing <- empty_row_attr(n = length(missing_rows))
- row_attr_missing$r <- missing_rows
-
- row_attr <- rbind(row_attr, row_attr_missing)
-
- # order
- row_attr <- row_attr[order(as.numeric(row_attr$r)), ]
-
- wb$worksheets[[sheet_id]]$sheet_data$row_attr <- row_attr
- # provide output
- rows_in_wb <- row_attr$r
-
- }
-
- if (!all(cells_needed %in% cells_in_wb)) {
- # message("cell(s) not in workbook")
-
- missing_cells <- cells_needed[!cells_needed %in% cells_in_wb]
-
- # create missing cells
- cc_missing <- create_char_dataframe(names(cc), length(missing_cells))
- cc_missing$r <- missing_cells
- cc_missing$row_r <- gsub("[[:upper:]]", "", cc_missing$r)
- cc_missing$c_r <- gsub("[[:digit:]]", "", cc_missing$r)
-
- # assign to cc
- cc <- rbind(cc, cc_missing)
-
- # order cc (not really necessary, will be done when saving)
- cc <- cc[order(as.integer(cc[, "row_r"]), col2int(cc[, "c_r"])), ]
-
- # update dimensions (only required if new cols and rows are added) ------
- all_rows <- as.numeric(unique(cc$row_r))
- all_cols <- col2int(unique(cc$c_r))
-
- min_cell <- trimws(paste0(int2col(min(all_cols, na.rm = TRUE)), min(all_rows, na.rm = TRUE)))
- max_cell <- trimws(paste0(int2col(max(all_cols, na.rm = TRUE)), max(all_rows, na.rm = TRUE)))
-
- # i know, i know, i'm lazy
- wb$worksheets[[sheet_id]]$dimension <- paste0("")
- }
-
- if (is_na_strings(na.strings)) {
- na.strings <- NULL
- }
-
- if (removeCellStyle) {
- cell_style <- "c_s"
- } else {
- cell_style <- NULL
- }
-
- replacement <- c("r", cell_style, "c_t", "c_cm", "c_ph", "c_vm", "v",
- "f", "f_t", "f_ref", "f_ca", "f_si", "is", "typ")
-
- sel <- match(x$r, cc$r)
- cc[sel, replacement] <- x[replacement]
-
- # avoid missings in cc
- if (any(is.na(cc)))
- cc[is.na(cc)] <- ""
-
- # push everything back to workbook
- wb$worksheets[[sheet_id]]$sheet_data$cc <- cc
-
- wb
-}
-# `write_data2()` -------------------------------------
-#' dummy function to write data
-#'
-#' @param wb workbook
-#' @param sheet sheet
-#' @param data data to export
-#' @param name If not NULL, a named region is defined.
-#' @param colNames include colnames?
-#' @param rowNames include rownames?
-#' @param startRow row to place it
-#' @param startCol col to place it
-#' @param applyCellStyle apply styles when writing on the sheet
-#' @param removeCellStyle keep the cell style?
-#' @param na.strings Value used for replacing `NA` values from `x`. Default
-#' `na_strings()` uses the special `#N/A` value within the workbook.
-#' @param data_table logical. if `TRUE` and `rowNames = TRUE`, do not write the cell containing `"_rowNames_"`
-#' @param inline_strings write characters as inline strings
-#' @details
-#' The string `"_openxlsx_NA"` is reserved for `openxlsx2`. If the data frame
-#' contains this string, the output will be broken.
-#'
-#' @examples
-#' # create a workbook and add some sheets
-#' wb <- wb_workbook()
-#'
-#' wb$add_worksheet("sheet1")
-#' write_data2(wb, "sheet1", mtcars, colNames = TRUE, rowNames = TRUE)
-#'
-#' wb$add_worksheet("sheet2")
-#' write_data2(wb, "sheet2", cars, colNames = FALSE)
-#'
-#' wb$add_worksheet("sheet3")
-#' write_data2(wb, "sheet3", letters)
-#'
-#' wb$add_worksheet("sheet4")
-#' write_data2(wb, "sheet4", as.data.frame(Titanic), startRow = 2, startCol = 2)
-#' @noRd
-write_data2 <- function(
- wb,
- sheet,
- data,
- name = NULL,
- colNames = TRUE,
- rowNames = FALSE,
- startRow = 1,
- startCol = 1,
- applyCellStyle = TRUE,
- removeCellStyle = FALSE,
- na.strings = na_strings(),
- data_table = FALSE,
- inline_strings = TRUE
-) {
-
- is_data_frame <- FALSE
- #### prepare the correct data formats for openxml
- dc <- openxlsx2_type(data)
-
- # convert factor to character
- if (any(dc == openxlsx2_celltype[["factor"]])) {
- is_factor <- dc == openxlsx2_celltype[["factor"]]
- fcts <- names(dc[is_factor])
- data[fcts] <- lapply(data[fcts], to_string)
- # dc <- openxlsx2_type(data)
- }
-
- hconvert_date1904 <- grepl('date1904="1"|date1904="true"',
- stri_join(unlist(wb$workbook), collapse = ""),
- ignore.case = TRUE)
-
- # TODO need to tell excel that we have a date, apply some kind of numFmt
- data <- convert_to_excel_date(df = data, date1904 = hconvert_date1904)
-
- # backward compatible
- if (!inherits(data, "data.frame") || inherits(data, "matrix")) {
- data <- as.data.frame(data)
- colNames <- FALSE
- }
-
- if (inherits(data, "data.frame") || inherits(data, "matrix")) {
- is_data_frame <- TRUE
-
- if (is.data.frame(data)) data <- as.data.frame(data)
-
- sel <- !dc %in% c(4, 5, 10)
- data[sel] <- lapply(data[sel], as.character)
-
- # add rownames
- if (rowNames) {
- data <- cbind("_rowNames_" = rownames(data), data)
- dc <- c(c("_rowNames_" = openxlsx2_celltype[["character"]]), dc)
- }
-
- # add colnames
- if (colNames)
- data <- rbind(colnames(data), data)
- }
-
-
- sheetno <- wb_validate_sheet(wb, sheet)
- # message("sheet no: ", sheetno)
-
- # create a data frame
- if (!is_data_frame)
- data <- as.data.frame(t(data))
-
- data_nrow <- NROW(data)
- data_ncol <- NCOL(data)
-
- endRow <- (startRow - 1) + data_nrow
- endCol <- (startCol - 1) + data_ncol
+ endRow <- (startRow - 1) + data_nrow
+ endCol <- (startCol - 1) + data_ncol
dims <- paste0(
int2col(startCol), startRow,
@@ -1098,9 +655,9 @@ write_data2 <- function(
return(wb)
}
# `write_data_table()` ---------------------------------------------------------
+# `write_data_table()` an internal driver function to `write_data` and `write_data_table` ----
#' Write to a worksheet as an Excel table
#'
-#' `write_data_table()` an internal driver function to `write_data` and `write_datatable`
#' Write to a worksheet and format as an Excel table
#'
#' @param wb A Workbook object containing a worksheet.
@@ -1128,6 +685,7 @@ write_data2 <- function(
#' `na_strings()` uses the special `#N/A` value within the workbook.
#' @param inline_strings optional write strings as inline strings
#' @noRd
+#' @keywords internal
write_data_table <- function(
wb,
sheet,
@@ -1284,156 +842,600 @@ write_data_table <- function(
}
### End: Only in data --------------------------------------------------------
- if (data_table) {
- overwrite_nrows <- 1L
- check_tab_head_only <- FALSE
- error_msg <- "Cannot overwrite existing table with another table"
- } else {
- overwrite_nrows <- colNames
- check_tab_head_only <- TRUE
- error_msg <- "Cannot overwrite table headers. Avoid writing over the header row or see wb_get_tables() & wb_remove_tabless() to remove the table object."
- }
+ if (data_table) {
+ overwrite_nrows <- 1L
+ check_tab_head_only <- FALSE
+ error_msg <- "Cannot overwrite existing table with another table"
+ } else {
+ overwrite_nrows <- colNames
+ check_tab_head_only <- TRUE
+ error_msg <- "Cannot overwrite table headers. Avoid writing over the header row or see wb_get_tables() & wb_remove_tabless() to remove the table object."
+ }
+
+ ## Check not overwriting existing table headers
+ wb_check_overwrite_tables(
+ wb = wb,
+ sheet = sheet,
+ new_rows = c(startRow, startRow + nRow - 1L + overwrite_nrows),
+ new_cols = c(startCol, startCol + nCol - 1L),
+ check_table_header_only = check_tab_head_only,
+ error_msg = error_msg
+ )
+
+ ## actual driver, the rest should not create data used for writing
+ wb <- write_data2(
+ wb = wb,
+ sheet = sheet,
+ data = x,
+ name = name,
+ colNames = colNames,
+ rowNames = rowNames,
+ startRow = startRow,
+ startCol = startCol,
+ applyCellStyle = applyCellStyle,
+ removeCellStyle = removeCellStyle,
+ na.strings = na.strings,
+ data_table = data_table,
+ inline_strings = inline_strings
+ )
+
+ ### Beg: Only in datatable ---------------------------------------------------
+
+ # if rowNames is set, write_data2 has added a rowNames column to the sheet.
+ # This has to be handled in colnames and in ref.
+ if (data_table) {
+
+ ## replace invalid XML characters
+ col_names <- replace_legal_chars(colnames(x))
+ if (rowNames) col_names <- c("_rowNames_", col_names)
+
+ ## Table name validation
+ if (is.null(tableName)) {
+ tableName <- paste0("Table", as.character(NROW(wb$tables) + 1L))
+ } else {
+ tableName <- wb_validate_table_name(wb, tableName)
+ }
+
+ ## If 0 rows append a blank row
+ cstm_tableStyles <- wb$styles_mgr$tableStyle$name
+ validNames <- c("none", paste0("TableStyleLight", seq_len(21)), paste0("TableStyleMedium", seq_len(28)), paste0("TableStyleDark", seq_len(11)), cstm_tableStyles)
+ if (!tolower(tableStyle) %in% tolower(validNames)) {
+ stop("Invalid table style.")
+ } else {
+ tableStyle <- grep(paste0("^", tableStyle, "$"), validNames, ignore.case = TRUE, value = TRUE)
+ }
+
+ tableStyle <- tableStyle[!is.na(tableStyle)]
+ if (length(tableStyle) == 0) {
+ stop("Unknown table style.")
+ }
+
+ ## If zero rows, append an empty row (prevent XML from corrupting)
+ if (nrow(x) == 0) {
+ x <- rbind(as.data.frame(x), matrix("", nrow = 1, ncol = nCol, dimnames = list(character(), colnames(x))))
+ names(x) <- colNames
+ }
+
+ ref1 <- paste0(int2col(startCol), startRow)
+ ref2 <- paste0(int2col(startCol + nCol - !rowNames), startRow + nRow)
+ ref <- paste(ref1, ref2, sep = ":")
+
+ ## create table.xml and assign an id to worksheet tables
+ wb$buildTable(
+ sheet = sheet,
+ colNames = col_names,
+ ref = ref,
+ showColNames = colNames,
+ tableStyle = tableStyle,
+ tableName = tableName,
+ withFilter = withFilter,
+ totalsRowCount = 0L,
+ showFirstColumn = firstColumn,
+ showLastColumn = lastColumn,
+ showRowStripes = bandedRows,
+ showColumnStripes = bandedCols
+ )
+ }
+
+ ### End: Only in datatable ---------------------------------------------------
+
+ return(wb)
+}
+
+# `write_data()` ---------------------------------------------------------------
+#' Write an object to a worksheet
+#'
+#' Write an object to worksheet with optional styling.
+#'
+#' Formulae written using write_formula to a Workbook object will not get picked up by read_xlsx().
+#' This is because only the formula is written and left to Excel to evaluate the formula when the file is opened in Excel.
+#' The string `"_openxlsx_NA"` is reserved for `openxlsx2`. If the data frame contains this string, the output will be broken.
+#'
+#' @param wb A Workbook object containing a worksheet.
+#' @param sheet The worksheet to write to. Can be the worksheet index or name.
+#' @param x Object to be written. For classes supported look at the examples.
+#' @param start_col A vector specifying the starting column to write to.
+#' @param start_row A vector specifying the starting row to write to.
+#' @param dims Spreadsheet dimensions that will determine startCol and startRow: "A1", "A1:B2", "A:B"
+#' @param array A bool if the function written is of type array
+#' @param col_names If `TRUE`, column names of x are written.
+#' @param row_names If `TRUE`, data.frame row names of x are written.
+#' @param with_filter If `TRUE`, add filters to the column name row. NOTE can only have one filter per worksheet.
+#' @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 name If not NULL, a named region is defined.
+#' @param apply_cell_style apply styles when writing on the sheet
+#' @param remove_cell_style if writing into existing cells, should the cell style be removed?
+#' @param na.strings Value used for replacing `NA` values from `x`. Default
+#' `na_strings()` uses the special `#N/A` value within the workbook.
+#' @param inline_strings write characters as inline strings
+#' @param ... additional arguments
+#' @seealso [write_datatable()]
+#' @return invisible(0)
+#' @examples
+#' ## See formatting vignette for further examples.
+#'
+#' ## Options for default styling (These are the defaults)
+#' options("openxlsx2.dateFormat" = "mm/dd/yyyy")
+#' options("openxlsx2.datetimeFormat" = "yyyy-mm-dd hh:mm:ss")
+#' options("openxlsx2.numFmt" = NULL)
+#'
+#' #############################################################################
+#' ## Create Workbook object and add worksheets
+#' wb <- wb_workbook()
+#'
+#' ## Add worksheets
+#' wb$add_worksheet("Cars")
+#' wb$add_worksheet("Formula")
+#'
+#' x <- mtcars[1:6, ]
+#' wb$add_data("Cars", x, startCol = 2, startRow = 3, rowNames = TRUE)
+#'
+#' #############################################################################
+#' ## Hyperlinks
+#' ## - vectors/columns with class 'hyperlink' are written as hyperlinks'
+#'
+#' v <- rep("https://CRAN.R-project.org/", 4)
+#' names(v) <- paste0("Hyperlink", 1:4) # Optional: names will be used as display text
+#' class(v) <- "hyperlink"
+#' wb$add_data("Cars", x = v, dims = c("B32"))
+#'
+#' #############################################################################
+#' ## Formulas
+#' ## - vectors/columns with class 'formula' are written as formulas'
+#'
+#' df <- data.frame(
+#' x = 1:3, y = 1:3,
+#' z = paste(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = "+"),
+#' stringsAsFactors = FALSE
+#' )
+#'
+#' class(df$z) <- c(class(df$z), "formula")
+#'
+#' wb$add_data(sheet = "Formula", x = df)
+#'
+#' #############################################################################
+#' # update cell range and add mtcars
+#' xlsxFile <- system.file("extdata", "openxlsx2_example.xlsx", package = "openxlsx2")
+#' wb2 <- wb_load(xlsxFile)
+#'
+#' # read dataset with inlinestr
+#' wb_to_df(wb2)
+#' write_data(wb2, 1, mtcars, startCol = 4, startRow = 4)
+#' wb_to_df(wb2)
+#' @export
+write_data <- function(
+ wb,
+ sheet,
+ x,
+ dims = rowcol_to_dims(start_row, start_col),
+ start_col = 1,
+ start_row = 1,
+ array = FALSE,
+ col_names = TRUE,
+ row_names = FALSE,
+ with_filter = FALSE,
+ sep = ", ",
+ name = NULL,
+ apply_cell_style = TRUE,
+ remove_cell_style = FALSE,
+ na.strings = na_strings(),
+ inline_strings = TRUE,
+ ...
+) {
+
+ standardize_case_names(...)
- ## Check not overwriting existing table headers
- wb_check_overwrite_tables(
- wb = wb,
- sheet = sheet,
- new_rows = c(startRow, startRow + nRow - 1L + overwrite_nrows),
- new_cols = c(startCol, startCol + nCol - 1L),
- check_table_header_only = check_tab_head_only,
- error_msg = error_msg
+ write_data_table(
+ wb = wb,
+ sheet = sheet,
+ x = x,
+ dims = dims,
+ startCol = start_col,
+ startRow = start_row,
+ array = array,
+ colNames = col_names,
+ rowNames = row_names,
+ tableStyle = NULL,
+ tableName = NULL,
+ withFilter = with_filter,
+ sep = sep,
+ firstColumn = FALSE,
+ lastColumn = FALSE,
+ bandedRows = FALSE,
+ bandedCols = FALSE,
+ name = name,
+ applyCellStyle = apply_cell_style,
+ removeCellStyle = remove_cell_style,
+ data_table = FALSE,
+ na.strings = na.strings,
+ inline_strings = inline_strings
)
+}
- ## actual driver, the rest should not create data used for writing
- wb <- write_data2(
- wb = wb,
- sheet = sheet,
- data = x,
- name = name,
- colNames = colNames,
- rowNames = rowNames,
- startRow = startRow,
- startCol = startCol,
- applyCellStyle = applyCellStyle,
- removeCellStyle = removeCellStyle,
- na.strings = na.strings,
- data_table = data_table,
- inline_strings = inline_strings
- )
+# write_formula() -------------------------------------------
+#' Write a character vector as an Excel Formula
+#'
+#' Write a a character vector containing Excel formula to a worksheet.
+#'
+#' @details
+#' Currently only the English version of functions are supported. Please don't use the local translation.
+#' The examples below show a small list of possible formulas:
+#' * SUM(B2:B4)
+#' * AVERAGE(B2:B4)
+#' * MIN(B2:B4)
+#' * MAX(B2:B4)
+#' * ...
+#'
+#' @param wb A Workbook object containing a worksheet.
+#' @param sheet The worksheet to write to. Can be the worksheet index or name.
+#' @param x A character vector.
+#' @param dims Spreadsheet dimensions that will determine startCol and startRow: "A1", "A1:B2", "A:B"
+#' @param start_col A vector specifying the starting column to write to.
+#' @param start_row A vector specifying the starting row to write to.
+#' @param array A bool if the function written is of type array
+#' @param cm A bool if the function is of type cm (array with hidden curly braces)
+#' @param apply_cell_style apply styles when writing on the sheet
+#' @param remove_cell_style if writing into existing cells, should the cell style be removed?
+#' @param ... additional arguments
+#' @seealso [write_data()]
+#' @examples
+#' ## There are 3 ways to write a formula
+#'
+#' wb <- wb_workbook()
+#' wb$add_worksheet("Sheet 1")
+#' wb$add_data("Sheet 1", x = iris)
+#'
+#' ## SEE `int2col()` to convert int to Excel column label
+#'
+#' ## 1. - As a character vector using write_formula
+#'
+#' v <- c("SUM(A2:A151)", "AVERAGE(B2:B151)") ## skip header row
+#' write_formula(wb, sheet = 1, x = v, startCol = 10, startRow = 2)
+#' write_formula(wb, 1, x = "A2 + B2", startCol = 10, startRow = 10)
+#'
+#'
+#' ## 2. - As a data.frame column with class "formula" using write_data
+#'
+#' df <- data.frame(
+#' x = 1:3,
+#' y = 1:3,
+#' z = paste(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = " + "),
+#' z2 = sprintf("ADDRESS(1,%s)", 1:3),
+#' stringsAsFactors = FALSE
+#' )
+#'
+#' class(df$z) <- c(class(df$z), "formula")
+#' class(df$z2) <- c(class(df$z2), "formula")
+#'
+#' wb$add_worksheet("Sheet 2")
+#' wb$add_data(sheet = 2, x = df)
+#'
+#'
+#' ## 3. - As a vector with class "formula" using write_data
+#'
+#' v2 <- c("SUM(A2:A4)", "AVERAGE(B2:B4)", "MEDIAN(C2:C4)")
+#' class(v2) <- c(class(v2), "formula")
+#'
+#' wb$add_data(sheet = 2, x = v2, startCol = 10, startRow = 2)
+#'
+#'
+#' ## 4. - Writing internal hyperlinks
+#'
+#' wb <- wb_workbook()
+#' wb$add_worksheet("Sheet1")
+#' wb$add_worksheet("Sheet2")
+#' write_formula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")')
+#'
+#'
+#' ## 5. - Writing array formulas
+#'
+#' set.seed(123)
+#' df <- data.frame(C = rnorm(10), D = rnorm(10))
+#'
+#' wb <- wb_workbook()
+#' wb <- wb_add_worksheet(wb, "df")
+#'
+#' wb$add_data("df", df, startCol = "C")
+#'
+#' write_formula(wb, "df", startCol = "E", startRow = "2",
+#' x = "SUM(C2:C11*D2:D11)",
+#' array = TRUE)
+#' @export
+write_formula <- function(
+ wb,
+ sheet,
+ x,
+ dims = rowcol_to_dims(start_row, start_col),
+ start_col = 1,
+ start_row = 1,
+ array = FALSE,
+ cm = FALSE,
+ apply_cell_style = TRUE,
+ remove_cell_style = FALSE,
+ ...
+) {
- ### Beg: Only in datatable ---------------------------------------------------
+ standardize_case_names(...)
- # if rowNames is set, write_data2 has added a rowNames column to the sheet.
- # This has to be handled in colnames and in ref.
- if (data_table) {
+ assert_class(x, "character")
+ # remove xml encoding and reapply it afterwards. until v0.3 encoding was not enforced
+ x <- replaceXMLEntities(x)
+ x <- vapply(x, function(val) xml_value(xml_node_create("fml", val, escapes = TRUE), "fml"), NA_character_)
+ dfx <- data.frame("X" = x, stringsAsFactors = FALSE)
- ## replace invalid XML characters
- col_names <- replace_legal_chars(colnames(x))
- if (rowNames) col_names <- c("_rowNames_", col_names)
+ formula <- "formula"
+ if (array) formula <- "array_formula"
+ if (cm) {
+ # need to set cell metadata in wb$metadata
+ if (is.null(wb$metadata)) {
- ## Table name validation
- if (is.null(tableName)) {
- tableName <- paste0("Table", as.character(NROW(wb$tables) + 1L))
- } else {
- tableName <- wb_validate_table_name(wb, tableName)
- }
+ wb$append("Content_Types", "")
- ## If 0 rows append a blank row
- cstm_tableStyles <- wb$styles_mgr$tableStyle$name
- validNames <- c("none", paste0("TableStyleLight", seq_len(21)), paste0("TableStyleMedium", seq_len(28)), paste0("TableStyleDark", seq_len(11)), cstm_tableStyles)
- if (!tolower(tableStyle) %in% tolower(validNames)) {
- stop("Invalid table style.")
- } else {
- tableStyle <- grep(paste0("^", tableStyle, "$"), validNames, ignore.case = TRUE, value = TRUE)
+ wb$metadata <- # danger danger no clue what this means!
+ xml_node_create(
+ "metadata",
+ xml_attributes = c(
+ xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
+ "xmlns:xda" = "http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray"
+ ),
+ xml_children = read_xml(
+ "
+
+
+
+
+
+
+
+
+
+
+ ,
+ ",
+ pointer = FALSE
+ )
+ )
}
- tableStyle <- tableStyle[!is.na(tableStyle)]
- if (length(tableStyle) == 0) {
- stop("Unknown table style.")
- }
+ ## TODO Not sure if there are more cases
+ # add new cell metadata record
+ cM <- xml_node(wb$metadata, "metadata", "cellMetadata")
+ cM <- xml_add_child(cM, xml_child = "")
- ## If zero rows, append an empty row (prevent XML from corrupting)
- if (nrow(x) == 0) {
- x <- rbind(as.data.frame(x), matrix("", nrow = 1, ncol = nCol, dimnames = list(character(), colnames(x))))
- names(x) <- colNames
- }
+ # we need to update count
+ cnt <- as_xml_attr(length(xml_node(cM, "cellMetadata", "bk")))
+ cM <- xml_attr_mod(cM, xml_attributes = c(count = cnt))
+
+ # remove current cellMetadata update new
+ wb$metadata <- xml_rm_child(wb$metadata, "cellMetadata")
+ wb$metadata <- xml_add_child(wb$metadata, cM)
+
+ attr(dfx, "c_cm") <- cnt
+ formula <- "cm_formula"
+ }
- ref1 <- paste0(int2col(startCol), startRow)
- ref2 <- paste0(int2col(startCol + nCol - !rowNames), startRow + nRow)
- ref <- paste(ref1, ref2, sep = ":")
+ class(dfx$X) <- c("character", formula)
+ if (!is.null(dims)) {
+ if (array || cm) {
+ attr(dfx, "f_ref") <- dims
+ }
+ }
- ## create table.xml and assign an id to worksheet tables
- wb$buildTable(
- sheet = sheet,
- colNames = col_names,
- ref = ref,
- showColNames = colNames,
- tableStyle = tableStyle,
- tableName = tableName,
- withFilter = withFilter,
- totalsRowCount = 0L,
- showFirstColumn = firstColumn,
- showLastColumn = lastColumn,
- showRowStripes = bandedRows,
- showColumnStripes = bandedCols
- )
+ if (any(grepl("=([\\s]*?)HYPERLINK\\(", x, perl = TRUE))) {
+ class(dfx$X) <- c("character", "formula", "hyperlink")
}
- ### End: Only in datatable ---------------------------------------------------
+ write_data(
+ wb = wb,
+ sheet = sheet,
+ x = dfx,
+ start_col = start_col,
+ start_row = start_row,
+ dims = dims,
+ array = array,
+ col_names = FALSE,
+ row_names = FALSE,
+ apply_cell_style = apply_cell_style,
+ remove_cell_style = remove_cell_style
+ )
- return(wb)
}
-# Cell(s) helpers --------------------------------------------
-#' Initialize data cell(s)
+
+# `write_datatable()` ----------------------
+#' Write to a worksheet as an Excel table
#'
-#' Create a cell in the workbook
+#' Write to a worksheet and format as an Excel table
#'
-#' @param wb the workbook you want to update
-#' @param sheet the sheet you want to update
-#' @param new_cells the cell you want to update in Excel connotation e.g. "A1"
+#' @param wb A Workbook object containing a worksheet.
+#' @param sheet The worksheet to write to. Can be the worksheet index or name.
+#' @param x A data frame.
+#' @param start_col A vector specifying the starting column to write df
+#' @param start_row A vector specifying the starting row to write df
+#' @param dims Spreadsheet dimensions that will determine startCol and startRow: "A1", "A1:B2", "A:B"
+#' @param col_names If `TRUE`, column names of x are written.
+#' @param row_names If `TRUE`, row names of x are written.
+#' @param table_style Any excel table style name or "none" (see "formatting" vignette).
+#' @param table_name name of table in workbook. The table name must be unique.
+#' @param with_filter If `TRUE`, columns with have filters in the first row.
+#' @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).
+#' \cr\cr
+#' \cr**The below options correspond to Excel table options:**
+#' \cr
+#' \if{html}{\figure{tableoptions.png}{options: width="40\%" alt="Figure: table_options.png"}}
+#' \if{latex}{\figure{tableoptions.pdf}{options: width=7cm}}
#'
-#' @noRd
-initialize_cell <- function(wb, sheet, new_cells) {
-
- sheet_id <- wb$validate_sheet(sheet)
-
- # create artificial cc for the missing cells
- x <- empty_sheet_data_cc(n = length(new_cells))
- x$r <- new_cells
- x$row_r <- gsub("[[:upper:]]", "", new_cells)
- x$c_r <- gsub("[[:digit:]]", "", new_cells)
-
- rows <- x$row_r
- cells_needed <- new_cells
-
- inner_update(wb, sheet_id, x, rows, cells_needed)
-}
-
-#' Replace data cell(s)
+#' @param first_column logical. If TRUE, the first column is bold
+#' @param last_column logical. If TRUE, the last column is bold
+#' @param banded_rows logical. If TRUE, rows are color banded
+#' @param banded_cols logical. If TRUE, the columns are color banded
+#' @param apply_cell_style apply styles when writing on the sheet
+#' @param remove_cell_style if writing into existing cells, should the cell style be removed?
+#' @param na.strings Value used for replacing `NA` values from `x`. Default
+#' `na_strings()` uses the special `#N/A` value within the workbook.
+#' @param inline_strings write characters as inline strings
+#' @param ... additional arguments
+#' @details columns of x with class Date/POSIXt, currency, accounting,
+#' hyperlink, percentage are automatically styled as dates, currency, accounting,
+#' hyperlinks, percentages respectively.
+#' The string `"_openxlsx_NA"` is reserved for `openxlsx2`. If the data frame
+#' contains this string, the output will be broken.
+#' @seealso
+#' [wb_add_worksheet()]
+#' [write_data()]
+#' [wb_remove_tables()]
+#' [wb_get_tables()]
+#' @examples
+#' ## see package vignettes for further examples.
#'
-#' Minimal invasive update of cell(s) inside of imported workbooks.
+#' #####################################################################################
+#' ## Create Workbook object and add worksheets
+#' wb <- wb_workbook()
+#' wb$add_worksheet("S1")
+#' wb$add_worksheet("S2")
+#' wb$add_worksheet("S3")
#'
-#' @param x cc dataframe of the updated cells
-#' @param wb the workbook you want to update
-#' @param sheet the sheet you want to update
-#' @param cell the cell you want to update in Excel connotation e.g. "A1"
-#' @param colNames if TRUE colNames are passed down
-#' @param removeCellStyle keep the cell style?
-#' @param na.strings optional na.strings argument. if missing #N/A is used. If NULL no cell value is written, if character or numeric this is written (even if NA is part of numeric data)
+#' #####################################################################################
+#' ## -- write data.frame as an Excel table with column filters
+#' ## -- default table style is "TableStyleMedium2"
#'
-#' @noRd
-update_cell <- function(x, wb, sheet, cell, colNames = FALSE,
- removeCellStyle = FALSE, na.strings) {
-
- if (missing(na.strings))
- na.strings <- substitute()
-
- sheet_id <- wb$validate_sheet(sheet)
-
- dims <- dims_to_dataframe(cell, fill = TRUE)
- rows <- rownames(dims)
+#' wb$add_data_table("S1", x = iris)
+#'
+#' wb$add_data_table("S2",
+#' x = mtcars, dims = c("B3"), rowNames = TRUE,
+#' tableStyle = "TableStyleLight9"
+#' )
+#'
+#' df <- data.frame(
+#' "Date" = Sys.Date() - 0:19,
+#' "T" = TRUE, "F" = FALSE,
+#' "Time" = Sys.time() - 0:19 * 60 * 60,
+#' "Cash" = paste("$", 1:20), "Cash2" = 31:50,
+#' "hLink" = "https://CRAN.R-project.org/",
+#' "Percentage" = seq(0, 1, length.out = 20),
+#' "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE
+#' )
+#'
+#' ## openxlsx will apply default Excel styling for these classes
+#' class(df$Cash) <- c(class(df$Cash), "currency")
+#' class(df$Cash2) <- c(class(df$Cash2), "accounting")
+#' class(df$hLink) <- "hyperlink"
+#' class(df$Percentage) <- c(class(df$Percentage), "percentage")
+#' class(df$TinyNumbers) <- c(class(df$TinyNumbers), "scientific")
+#'
+#' wb$add_data_table("S3", x = df, startRow = 4, rowNames = TRUE, tableStyle = "TableStyleMedium9")
+#'
+#' #####################################################################################
+#' ## Additional Header Styling and remove column filters
+#'
+#' write_datatable(wb,
+#' sheet = 1,
+#' x = iris,
+#' startCol = 7,
+#' withFilter = FALSE,
+#' firstColumn = TRUE,
+#' lastColumn = TRUE,
+#' bandedRows = TRUE,
+#' bandedCols = TRUE
+#' )
+#'
+#' #####################################################################################
+#' ## Pre-defined table styles gallery
+#'
+#' wb <- wb_workbook(paste0("tableStylesGallery.xlsx"))
+#' wb$add_worksheet("Style Samples")
+#' for (i in 1:21) {
+#' style <- paste0("TableStyleLight", i)
+#' write_datatable(wb,
+#' x = data.frame(style), sheet = 1,
+#' tableStyle = style, startRow = 1, startCol = i * 3 - 2
+#' )
+#' }
+#'
+#' for (i in 1:28) {
+#' style <- paste0("TableStyleMedium", i)
+#' write_datatable(wb,
+#' x = data.frame(style), sheet = 1,
+#' tableStyle = style, startRow = 4, startCol = i * 3 - 2
+#' )
+#' }
+#'
+#' for (i in 1:11) {
+#' style <- paste0("TableStyleDark", i)
+#' write_datatable(wb,
+#' x = data.frame(style), sheet = 1,
+#' tableStyle = style, startRow = 7, startCol = i * 3 - 2
+#' )
+#' }
+#' @export
+write_datatable <- function(
+ wb,
+ sheet,
+ x,
+ dims = rowcol_to_dims(start_row, start_col),
+ start_col = 1,
+ start_row = 1,
+ col_names = TRUE,
+ row_names = FALSE,
+ table_style = "TableStyleLight9",
+ table_name = NULL,
+ with_filter = TRUE,
+ sep = ", ",
+ first_column = FALSE,
+ last_column = FALSE,
+ banded_rows = TRUE,
+ banded_cols = FALSE,
+ apply_cell_style = TRUE,
+ remove_cell_style = FALSE,
+ na.strings = na_strings(),
+ inline_strings = TRUE,
+ ...
+) {
- cells_needed <- unname(unlist(dims))
+ standardize_case_names(...)
- inner_update(wb, sheet_id, x, rows, cells_needed, colNames, removeCellStyle, na.strings)
+ write_data_table(
+ wb = wb,
+ sheet = sheet,
+ x = x,
+ startCol = start_col,
+ startRow = start_row,
+ dims = dims,
+ array = FALSE,
+ colNames = col_names,
+ rowNames = row_names,
+ tableStyle = table_style,
+ tableName = table_name,
+ withFilter = with_filter,
+ sep = sep,
+ firstColumn = first_column,
+ lastColumn = last_column,
+ bandedRows = banded_rows,
+ bandedCols = banded_cols,
+ name = NULL,
+ data_table = TRUE,
+ applyCellStyle = apply_cell_style,
+ removeCellStyle = remove_cell_style,
+ na.strings = na.strings,
+ inline_strings = inline_strings
+ )
}