Skip to content

Commit

Permalink
[hl] hyperlink rewrite (#1137)
Browse files Browse the repository at this point in the history
* [hyperlinks] begin hyperlink rewrite.

1) do not remove hyperlinks from `wb$worksheet_rels`
2) do not replace `wb$worksheets[[sheet]]$hyperlinks` with hyperlink objects
3) save what is available

* [clone] fix cloning hyperlinks

* [hl] add `wb_add_hyperlink()` wrapper to create shared hyperlinks

* [hl] another approach, this time w/o implicit wb_add_data()/wb_add_data_table()

* [docs] fix spelling

* [hl] switch to col_names = FALSE

* [hl] cleanups and updated documentation

* [hl] another round of cleanups
  • Loading branch information
JanMarvin authored Sep 18, 2024
1 parent b9df555 commit b752ffb
Show file tree
Hide file tree
Showing 43 changed files with 571 additions and 121 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export(wb_add_filter)
export(wb_add_font)
export(wb_add_form_control)
export(wb_add_formula)
export(wb_add_hyperlink)
export(wb_add_ignore_error)
export(wb_add_image)
export(wb_add_mips)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@

* `wb_to_df()` gained a new argument `show_hyperlinks` which returns the target or location of a hyperlink, instead of the links description. [1136](https://github.com/JanMarvin/openxlsx2/pull/1136)

* A new wrapper function `wb_add_hyperlink()` extends the capabilities of writing hyperlinks to shared hyperlinks. Shared hyperlinks bring along internal changes that are noted below. [1137](https://github.com/JanMarvin/openxlsx2/pull/1137)

## Fixes

* The integration of the shared formula feature in the previous release broke the silent extension of dims, if a single cell `dims` was provided for an `x` that was larger than a single cell in `wb_add_formula()`. [1131](https://github.com/JanMarvin/openxlsx2/pull/1131)

* Fixed a regression in the previous release, where `wb_dims()` would pass column names passed via `cols` to `col2int()` which could cause overflow errors resulting in a failing check. [1133](https://github.com/JanMarvin/openxlsx2/pull/1133)

## Internal changes

* The handling of shared hyperlinks has been updated. Previously, when loading a file with shared hyperlinks, they were converted into `wbHyperlink` objects (a legacy from `openxlsx`). With recent internal changes, hyperlinks are no longer automatically transformed into `wbHyperlink` objects. If you still require these objects, you can use the internal function `wb_to_hyperlink(wb, sheet = 1)`. However, please note that this class is not essential for `openxlsx2` and may be further simplified or removed in the future without notice. [1137](https://github.com/JanMarvin/openxlsx2/pull/1137)


***************************************************************************

Expand Down
1 change: 1 addition & 0 deletions R/class-chart-sheet.R
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ wbChartSheet <- R6::R6Class(
self$relships <- list(
comments = integer(),
drawing = integer(),
hyperlink = integer(),
pivotTable = integer(),
slicer = integer(),
table = integer(),
Expand Down
61 changes: 23 additions & 38 deletions R/class-hyperlink.R
Original file line number Diff line number Diff line change
Expand Up @@ -77,51 +77,36 @@ wb_hyperlink <- function() {
wbHyperlink$new(ref = character(), target = character(), location = character())
}

#' Helper function to create `wbHyperlink` objects from a workbook
#'
#' This function is used only in wb_to_df(show_hyperlinks = TRUE) to construct ref, target and location
#' @param wb a workbook
#' @param sheet a sheet index
#' @noRd
wb_to_hyperlink <- function(wb, sheet = 1) {

xml_to_hyperlink <- function(xml) {
# xml_to_hyperlink() is used once in wb_load()
xml <- wb$worksheets[[sheet]]$hyperlinks
relship <- wb$worksheets_rels[[sheet]]

# TODO allow wbHyperlink$new(xml = xml)
# prepare relships data frame
relships <- rbindlist(xml_attr(relship, "Relationship"))
relships <- relships[basename(relships$Type) == "hyperlink", ]

# xml <- c('<hyperlink ref="A1" r:id="rId1" location="Authority"/>',
# '<hyperlink ref="B1" r:id="rId2"/>',
# '<hyperlink ref="A1" location="Sheet2!A1" display="Sheet2!A1"/>')
# prepare hyperlinks data frame
hlinks <- rbindlist(xml_attr(xml, "hyperlink"))

if (length(xml) == 0) {
return(xml)
}
# merge both
hl_df <- merge(hlinks, relships, by.x = "r:id", by.y = "Id", all.x = TRUE, all.y = FALSE)

targets <- names(xml) %||% rep(NA, length(xml))
xml <- unname(xml)
lapply(seq_len(nrow(hl_df)), function(i) {

# TODO a, names, and vals could be moved within the larger lapply()
a <- unapply(xml, function(i) regmatches(i, gregexpr('[a-zA-Z]+=".*?"', i)), .recurse = FALSE)
names <- lapply(a, function(i) regmatches(i, regexpr('[a-zA-Z]+(?=\\=".*?")', i, perl = TRUE)))
vals <- lapply(a, function(i) {
res <- regmatches(i, regexpr('(?<=").*?(?=")', i, perl = TRUE))
res
})
x <- hl_df[i, ]

lapply(seq_along(xml), function(i) {
tmp_vals <- vals[[i]]
tmp_nms <- names[[i]]
names(tmp_vals) <- tmp_nms

## ref
ref <- tmp_vals[["ref"]]

## location
location <- if ("location" %in% tmp_nms) tmp_vals[["location"]]
display <- if ("display" %in% tmp_nms) tmp_vals[["display"]]

## target/external
if (is.na(targets[i])) {
target <- NULL
is_external <- FALSE
} else {
is_external <- TRUE
target <- targets[i]
}
ref <- x$ref
target <- x$Target
location <- x$location
display <- x$display
is_external <- x$TargetMode == "External"

wbHyperlink$new(
ref = ref,
Expand Down
47 changes: 47 additions & 0 deletions R/class-workbook-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,53 @@ wb_add_formula <- function(
)
}

#' wb_add_hyperlink
#'
#' Helper to add shared hyperlinks into a worksheet
#'
#' @details
#' There are multiple ways to add hyperlinks into a worksheet. One way is to construct a formula with [create_hyperlink()] another is to assign a class `hyperlink` to a column of a data frame.
#' Contrary to the previous method, shared hyperlinks are not cell formulas in the worksheet, but references in the worksheet relationship and hyperlinks in the worksheet xml structure.
#' These shared hyperlinks can be reused and they are not visible to spreadsheet users as `HYPERLINK()` formulas.
#'
#' @param wb A Workbook object containing a worksheet.
#' @param sheet The worksheet to write to. (either as index or name)
#' @param dims Spreadsheet dimensions that will determine where the hyperlink reference spans: "A1", "A1:B2", "A:B"
#' @param target An optional target, if no target is specified, it is assumed that the cell already contains a reference (the cell could be a url or a filename)
#' @param tooltip An optional description for a variable that will be visible when hovering over the link text in the spreadsheet
#' @param is_external A logical indicating if the hyperlink is external (a url, a mail address, a file) or internal (a reference to worksheet cells)
#' @param col_names Whether or not the object contains column names. If yes the first column of the dimension will be ignored
#' @export
#' @family workbook wrappers
#' @family worksheet content functions
#' @examples
#' wb <- wb_workbook()$add_worksheet()$
#' add_data(x = "openxlsx2 on CRAN")$
#' add_hyperlink(target = "https://cran.r-project.org/package=openxlsx2",
#' tooltip = "The canonical form to link to our CRAN page.")
wb_add_hyperlink <- function(
wb,
sheet = current_sheet(),
dims = "A1",
target = NULL,
tooltip = NULL,
is_external = TRUE,
col_names = FALSE
) {

assert_workbook(wb)

wb$clone()$add_hyperlink(
sheet = sheet,
dims = dims,
target = target,
tooltip = tooltip,
is_external = is_external,
col_names = col_names
)
}


#' Update a data table position in a worksheet
#'
#' Update the position of a data table, possibly written using [wb_add_data_table()]
Expand Down
Loading

0 comments on commit b752ffb

Please sign in to comment.