From 1660aec07b8ae577cdad48c047bf66333c565211 Mon Sep 17 00:00:00 2001 From: Mark Heckmann Date: Thu, 2 Jan 2025 12:13:23 +0100 Subject: [PATCH] feat: hide slides * Add `slide_visible()`: gets and sets the visibility of slides (#622). --- .github/workflows/R-CMD-check.yaml | 1 + DESCRIPTION | 48 +++++++++- NAMESPACE | 2 + NEWS.md | 1 + R/pptx_slide_manip.R | 82 ++++++++++++++++++ R/utils.R | 53 +++++++++++ inst/examples/example_slide_visible.R | 17 ++++ man/slide-visible.Rd | 43 +++++++++ man/stop_if_not_in_slide_range.Rd | 22 +++++ officer.Rproj | 3 +- .../docs_dir/test-slides-visible.pptx | Bin 0 -> 28230 bytes tests/testthat/test-pptx-misc.R | 81 ++++++++++++++++- tests/testthat/test-utils.R | 60 +++++++++++++ 13 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 inst/examples/example_slide_visible.R create mode 100644 man/slide-visible.Rd create mode 100644 man/stop_if_not_in_slide_range.Rd create mode 100644 tests/testthat/docs_dir/test-slides-visible.pptx diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 8863865c..f89019d5 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -5,6 +5,7 @@ on: pull_request: branches: - master + workflow_dispatch: name: R-CMD-check diff --git a/DESCRIPTION b/DESCRIPTION index b0ef3858..bc9320fe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: officer Title: Manipulation of Microsoft Word and PowerPoint Documents -Version: 0.6.8.003 +Version: 0.6.8.004 Authors@R: c( person("David", "Gohel", , "david.gohel@ardata.fr", role = c("aut", "cre")), person("Stefan", "Moog", , "moogs@gmx.de", role = "aut"), @@ -65,3 +65,49 @@ Suggests: Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 +Collate: + 'core_properties.R' + 'custom_properties.R' + 'defunct.R' + 'dev-utils.R' + 'docx_add.R' + 'docx_comments.R' + 'docx_cursor.R' + 'docx_part.R' + 'docx_replace.R' + 'docx_section.R' + 'docx_settings.R' + 'empty_content.R' + 'formatting_properties.R' + 'fortify_docx.R' + 'fortify_pptx.R' + 'knitr_utils.R' + 'officer.R' + 'ooxml.R' + 'ooxml_block_objects.R' + 'ooxml_run_objects.R' + 'openxml_content_type.R' + 'openxml_document.R' + 'pack_folder.R' + 'ph_location.R' + 'post-proc.R' + 'ppt_class_dir_collection.R' + 'ppt_classes.R' + 'ppt_notes.R' + 'ppt_ph_dedupe_layout.R' + 'ppt_ph_manipulate.R' + 'ppt_ph_rename_layout.R' + 'ppt_ph_with_methods.R' + 'pptx_informations.R' + 'pptx_layout_helper.R' + 'pptx_matrix.R' + 'utils.R' + 'pptx_slide_manip.R' + 'read_docx.R' + 'read_docx_styles.R' + 'read_pptx.R' + 'read_xlsx.R' + 'relationship.R' + 'rtf.R' + 'shape_properties.R' + 'shorcuts.R' diff --git a/NAMESPACE b/NAMESPACE index 89f0a329..4338c78b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -162,6 +162,7 @@ S3method(update,fpar) S3method(update,sp_line) S3method(update,sp_lineend) export("layout_rename_ph_labels<-") +export("slide_visible<-") export(add_sheet) export(add_slide) export(annotate_base) @@ -301,6 +302,7 @@ export(sheet_select) export(shortcuts) export(slide_size) export(slide_summary) +export(slide_visible) export(slip_in_column_break) export(slip_in_footnote) export(slip_in_seqfield) diff --git a/NEWS.md b/NEWS.md index a1f74bc7..a69691bc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ## Issues +- Add `slide_visible()` to get and set the visibility of slides (#622). - debug selector for `ph_remove()` (see #625) that was not working for rvg outputs. diff --git a/R/pptx_slide_manip.R b/R/pptx_slide_manip.R index 22c6a298..416b5e76 100644 --- a/R/pptx_slide_manip.R +++ b/R/pptx_slide_manip.R @@ -1,3 +1,4 @@ + #' @export #' @title Add a slide #' @description Add a slide into a pptx presentation. @@ -255,3 +256,84 @@ ensure_slide_index_exists <- function(x, slide_idx) { ) } } + + +# internal workhorse get/set slide visibility +# x : rpptx object +# slide_idx: id of slide +# value: Use TRUE / FALSE to set visibility. +.slide_visible <- function(x, slide_idx, value = NULL) { + stop_if_not_rpptx(x) + slide <- x$slide$get_slide(slide_idx) + slide_xml <- slide$get() + node <- xml2::xml_find_first(slide_xml, "/p:sld") + if (is.null(value)) { + value <- xml2::xml_attr(node, "show") + value <- as.logical(as.numeric(value)) + ifelse(is.na(value), TRUE, value) # if show is not set, the slide is shown + } else { + stop_if_not_class(value, "logical", arg = "value") + xml2::xml_set_attr(node, "show", value = as.numeric(value)) + slide$save() + invisible(x) + } +} + + +#' Get or set slide visibility +#' +#' PPTX slides can be visible or hidden. This function gets or sets the visibility of slides. +#' @param x An `rpptx` object. +#' @param value Boolean vector with slide visibilities. +#' @rdname slide-visible +#' @export +#' @example inst/examples/example_slide_visible.R +#' @return Boolean vector with slide visibilities or `rpptx` object if changes are made to the object. +`slide_visible<-` <- function(x, value) { + stop_if_not_rpptx(x) + stop_if_not_class(value, "logical", arg = "value") + n_vals <- length(value) + n_slides <- length(x) + if (n_vals > n_slides) { + cli::cli_abort("More values ({.val {n_vals}}) than slides ({.val {n_slides}})") + } + if (n_vals != 1 && n_vals != n_slides) { + cli::cli_warn("Value is not length 1 or same length as number of slides ({.val {n_slides}}). Recycling values.") + } + value <- rep(value, length.out = n_slides) + for (i in seq_along(value)) { + .slide_visible(x, i, value[i]) + } + invisible(x) +} + + +#' @param hide,show Indexes of slides to hide or show. +#' @rdname slide-visible +#' @export +slide_visible <- function(x, hide = NULL, show = NULL) { + stop_if_not_rpptx(x) + idx_in_both <- intersect(as.integer(hide), as.integer(show)) + if (length(idx_in_both) > 1) { + cli::cli_abort( + "Overlap between indexes in {.arg hide} and {.arg show}: {.val {idx_in_both}}", + "x" = "Indexes must be mutually exclusive.") + } + if (!is.null(hide)) { + stop_if_not_integerish(hide, "hide") + stop_if_not_in_slide_range(x, hide, arg = "hide") + slide_visible(x)[hide] <- FALSE + } + if (!is.null(show)) { + stop_if_not_integerish(show, "show") + stop_if_not_in_slide_range(x, show, arg = "show") + slide_visible(x)[show] <- TRUE + } + n_slides <- length(x) + res <- vapply(seq_len(n_slides), function(idx) .slide_visible(x, idx), logical(1)) + if (is.null(hide) && is.null(show)) { + res + } else { + x + } +} diff --git a/R/utils.R b/R/utils.R index 0200f5c2..2b328a9a 100644 --- a/R/utils.R +++ b/R/utils.R @@ -334,6 +334,49 @@ stop_if_not_rpptx <- function(x, arg = NULL) { stop_if_not_class(x, "rpptx", arg) } + +stop_if_not_integerish <- function(x, arg = NULL) { + check <- is_integerish(x) + if (!check) { + msg_arg <- ifelse(is.null(arg), "Incorrect input.", "Incorrect input for {.arg {arg}}") + cli::cli_abort(c( + msg_arg, + "x" = "Expected integerish values but got {.cls {class(x)[1]}}" + ), call = NULL) + } +} + + +#' Ensure valid slide indexes +#' +#' @param x An `rpptx` object. +#' @param idx Slide indexes. +#' @param arg Name of argument to use in error message (optional). +#' @param call Environment to display in error message. Defaults to caller env. +#' Set `NULL` to suppress (see [cli::cli_abort]). +#' @keywords internal +stop_if_not_in_slide_range <- function(x, idx, arg = NULL, call = parent.frame()) { + stop_if_not_rpptx(x) + stop_if_not_integerish(idx) + + n_slides <- length(x) + idx_available <- seq_len(n_slides) + idx_outside <- setdiff(idx, idx_available) + n_outside <- length(idx_outside) + + if (n_outside == 0) { + return(invisible(NULL)) + } + argname <- ifelse(is.null(arg), "", "of {.arg {arg}} ") + part_1 <- paste0("{n_outside} index{?es} ", argname, "outside slide range: {.val {idx_outside}}") + part_2 <- ifelse(n_slides == 0, + "Presentation has no slides!", + "Slide indexes must be in the range [{min(idx_available)}..{max(idx_available)}]" + ) + cli::cli_abort(c(part_1, "x" = part_2), call = call) +} + + check_unit <- function(unit, choices, several.ok = FALSE) { if (!several.ok && length(unit) != 1) { cli::cli_abort( @@ -429,3 +472,13 @@ is_named <- function(x) { detect_void_name <- function(x) { x == "" | is.na(x) } + + +# is_integerish(1) +# is_integerish(1.0) +# is_integerish(c(1.0, 2.0)) +is_integerish <- function(x) { + ii <- all(is.numeric(x) | is.integer(x)) + jj <- all(x == as.integer(x)) + ii && jj +} diff --git a/inst/examples/example_slide_visible.R b/inst/examples/example_slide_visible.R new file mode 100644 index 00000000..1dbd3e47 --- /dev/null +++ b/inst/examples/example_slide_visible.R @@ -0,0 +1,17 @@ +path <- system.file("doc_examples/example.pptx", package = "officer") +x <- read_pptx(path) + +slide_visible(x) # get slide visibilities + +x <- slide_visible(x, hide = 1:2) # hide slides 1 and 2 +x <- slide_visible(x, show = 1:2) # make slides 1 and 2 visible +x <- slide_visible(x, show = 1:2, hide = 3) + +slide_visible(x) <- FALSE # hide all slides +slide_visible(x) <- c(TRUE, FALSE, TRUE) # set each slide separately +slide_visible(x) <- c(TRUE, FALSE) # warns that rhs values are recycled + +slide_visible(x)[2] <- TRUE # set 2nd slide to visible +slide_visible(x)[c(1, 3)] <- FALSE # 1st and 3rd slide +slide_visible(x)[c(1, 3)] <- c(FALSE, FALSE) # identical + diff --git a/man/slide-visible.Rd b/man/slide-visible.Rd new file mode 100644 index 00000000..5d1ae6d3 --- /dev/null +++ b/man/slide-visible.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pptx_slide_manip.R +\name{slide_visible<-} +\alias{slide_visible<-} +\alias{slide_visible} +\title{Get or set slide visibility} +\usage{ +slide_visible(x) <- value + +slide_visible(x, hide = NULL, show = NULL) +} +\arguments{ +\item{x}{An \code{rpptx} object.} + +\item{value}{Boolean vector with slide visibilities.} + +\item{hide, show}{Indexes of slides to hide or show.} +} +\value{ +Boolean vector with slide visibilities or \code{rpptx} object if changes are made to the object. +} +\description{ +PPTX slides can be visible or hidden. This function gets or sets the visibility of slides. +} +\examples{ +path <- system.file("doc_examples/example.pptx", package = "officer") +x <- read_pptx(path) + +slide_visible(x) # get slide visibilities + +x <- slide_visible(x, hide = 1:2) # hide slides 1 and 2 +x <- slide_visible(x, show = 1:2) # make slides 1 and 2 visible +x <- slide_visible(x, show = 1:2, hide = 3) + +slide_visible(x) <- FALSE # hide all slides +slide_visible(x) <- c(TRUE, FALSE, TRUE) # set each slide separately +slide_visible(x) <- c(TRUE, FALSE) # warns that rhs values are recycled + +slide_visible(x)[2] <- TRUE # set 2nd slide to visible +slide_visible(x)[c(1, 3)] <- FALSE # 1st and 3rd slide +slide_visible(x)[c(1, 3)] <- c(FALSE, FALSE) # identical + +} diff --git a/man/stop_if_not_in_slide_range.Rd b/man/stop_if_not_in_slide_range.Rd new file mode 100644 index 00000000..4079eeed --- /dev/null +++ b/man/stop_if_not_in_slide_range.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{stop_if_not_in_slide_range} +\alias{stop_if_not_in_slide_range} +\title{Ensure valid slide indexes} +\usage{ +stop_if_not_in_slide_range(x, idx, arg = NULL, call = parent.frame()) +} +\arguments{ +\item{x}{An \code{rpptx} object.} + +\item{idx}{Slide indexes.} + +\item{arg}{Name of argument to use in error message (optional).} + +\item{call}{Environment to display in error message. Defaults to caller env. +Set \code{NULL} to suppress (see \link[cli:cli_abort]{cli::cli_abort}).} +} +\description{ +Ensure valid slide indexes +} +\keyword{internal} diff --git a/officer.Rproj b/officer.Rproj index cba1b6b7..7f266a65 100644 --- a/officer.Rproj +++ b/officer.Rproj @@ -1,8 +1,9 @@ Version: 1.0 +ProjectId: cf684f77-79cc-4641-8f83-6d6abc3f30bd RestoreWorkspace: No SaveWorkspace: No -AlwaysSaveHistory: Default +AlwaysSaveHistory: No EnableCodeIndexing: Yes UseSpacesForTab: Yes diff --git a/tests/testthat/docs_dir/test-slides-visible.pptx b/tests/testthat/docs_dir/test-slides-visible.pptx new file mode 100644 index 0000000000000000000000000000000000000000..b8702a32914f751b50feeb4b83bb7154224a94d9 GIT binary patch literal 28230 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 literal 0 HcmV?d00001 diff --git a/tests/testthat/test-pptx-misc.R b/tests/testthat/test-pptx-misc.R index 45840fba..44c14aac 100644 --- a/tests/testthat/test-pptx-misc.R +++ b/tests/testthat/test-pptx-misc.R @@ -118,7 +118,86 @@ test_that("no master do not generate an error", { }) - unlink("*.pptx") unlink("*.emf") + +test_that("slide_visible", { + opts <- options(cli.num_colors = 1) # suppress colors for error message check + on.exit(options(opts)) + + x <- read_pptx() + expect_equal(slide_visible(x), logical(0)) # works with 0 slides + + path <- testthat::test_path("docs_dir", "test-slides-visible.pptx") + x <- read_pptx(path) + + expect_equal(slide_visible(x), c(FALSE, TRUE, FALSE)) + x <- slide_visible(x, hide = 1:2) + expect_s3_class(x, "rpptx") + expect_equal(slide_visible(x), c(FALSE, FALSE, FALSE)) + x <- slide_visible(x, show = 1:2) + expect_s3_class(x, "rpptx") + expect_equal(slide_visible(x), c(TRUE, TRUE, FALSE)) + x <- slide_visible(x, hide = 1:2, show = 3) + expect_s3_class(x, "rpptx") + expect_equal(slide_visible(x), c(FALSE, FALSE, TRUE)) + + expect_error( + regex = "Overlap between indexes in `hide` and `show`", + slide_visible(x, hide = 1:2, show = 1:2) + ) + expect_error( + regex = "2 indexes of `hide` outside slide range", + slide_visible(x, hide = 1:5) + ) + expect_error( + regex = "1 index of `show` outside slide range", + slide_visible(x, show = -1) + ) + + slide_visible(x) <- FALSE # hide all slides + expect_false(any(slide_visible(x))) + slide_visible(x) <- c(TRUE, FALSE, TRUE) + expect_equal(slide_visible(x), c(TRUE, FALSE, TRUE)) + expect_warning( + regexp = "Value is not length 1 or same length as number of slides", + slide_visible(x) <- c(TRUE, FALSE) # warns that rhs values are recycled + ) + expect_equal(slide_visible(x), c(TRUE, FALSE, TRUE)) + + slide_visible(x)[2] <- TRUE + expect_equal(slide_visible(x), c(TRUE, TRUE, TRUE)) + slide_visible(x)[c(1, 3)] <- FALSE + expect_equal(slide_visible(x), c(FALSE, TRUE, FALSE)) + + slide_visible(x) <- TRUE + expect_warning( + regexp = "number of items to replace is not a multiple of replacement length", + slide_visible(x)[c(1, 2)] <- rep(FALSE, 4) + ) + expect_equal(slide_visible(x), c(FALSE, FALSE, TRUE)) + + expect_error( + { + slide_visible(x) <- rep(FALSE, 4) + }, + regexp = "More values \\(4\\) than slides \\(3\\)" + ) + + # test that changes are written to file + path <- testthat::test_path("docs_dir", "test-slides-visible.pptx") + x <- read_pptx(path) + + slide_visible(x) <- FALSE + path <- tempfile(fileext = ".pptx") + print(x, path) + x <- read_pptx(path) + expect_equal(slide_visible(x), c(FALSE, FALSE, FALSE)) + + slide_visible(x)[c(1, 3)] <- TRUE + path <- tempfile(fileext = ".pptx") + print(x, path) + x <- read_pptx(path) + expect_equal(slide_visible(x), c(TRUE, FALSE, TRUE)) +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 02511776..efb70879 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -13,6 +13,66 @@ test_that("trailing file index extraction / sorting", { test_that("misc", { + opts <- options(cli.num_colors = 1) # suppress colors for error message check + on.exit(options(opts)) + df <- df_rename(mtcars, c("mpg", "cyl"), c("A", "B")) expect_true(all(names(df)[1:2] == c("A", "B"))) + + expect_true(is_integerish(1)) + expect_true(is_integerish(1:3)) + expect_true(is_integerish(1.0)) + expect_true(is_integerish(c(1.0, 2.0))) + expect_false(is_integerish(1.00001)) + expect_false(is_integerish(c(1.1, 2.0))) + expect_false(is_integerish(TRUE)) + expect_false(is_integerish(FALSE)) + expect_error( + expect_warning(stop_if_not_integerish(LETTERS)), + regex = "Expected integerish values but got " + ) + expect_error( + stop_if_not_integerish(c(1.1, 1.2)), + regex = "Expected integerish values but got " + ) +}) + + +test_that("stop_if_not_in_slide_range", { + opts <- options(cli.num_colors = 1) # suppress colors for error message check + on.exit(options(opts)) + + x <- read_pptx() + expect_error( + regex = "Presentation has no slides", + stop_if_not_in_slide_range(x, 1) + ) + + x <- add_slide(x) + x <- add_slide(x) + expect_no_error(stop_if_not_in_slide_range(x, 1:2)) + expect_error( + regex = "1 index outside slide range", + stop_if_not_in_slide_range(x, -1) + ) + expect_error( + regex = "2 indexes of `my_arg` outside slide range", + stop_if_not_in_slide_range(x, 3:4, arg = "my_arg") + ) + expect_error( + regex = "Expected integerish values but got ", + stop_if_not_in_slide_range(x, 3.1) + ) + + foo <- function() { + stop_if_not_in_slide_range(x, 3:4) + } + error_text <- tryCatch(foo(), error = paste) + grepl("^Error in `foo()`:", error_text, fixed = TRUE) + + foo <- function() { + stop_if_not_in_slide_range(x, 3:4, call = NULL) + } + error_text <- tryCatch(foo(), error = paste) + grepl("^Error:", error_text, fixed = TRUE) })