Skip to content

Commit 2910aaa

Browse files
committed
feat: remove_slide() now supports deletion of multiple slides
thanks to Wahiduzzaman Khan (#691)
1 parent 23275a9 commit 2910aaa

File tree

10 files changed

+224
-30
lines changed

10 files changed

+224
-30
lines changed

DESCRIPTION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Authors@R: c(
2727
person("Greg", "Leleu", , "gregoire.leleu@gmail.com", role = "ctb",
2828
comment = "fields functionality in ppt"),
2929
person("Majid", "Eismann", role = "ctb"),
30+
person("Wahiduzzaman", "Khan", role = "ctb",
31+
comment = "vectorization of remove_slide"),
3032
person("Hongyuan", "Jia", , "hongyuanjia@cqust.edu.cn", role = "ctb",
3133
comment = c(ORCID = "0000-0002-0075-8183")),
3234
person("Michael", "Stackhouse", , "mike.stackhouse@atorusresearch.com", role = "ctb")

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# officer 0.7.3
22

3+
## Features
4+
5+
- function `remove_slide()` now supports deletion of multiple slides
6+
thanks to Wahiduzzaman Khan (#691).
7+
38
## Issues
49

510
- fix feed_from_xml for 'officedown'.

R/ppt_classes.R

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,18 @@ presentation <- R6Class(
4141
},
4242
slide_data = function(){
4343
rel_df <- self$rel_df()
44-
rel_df <- rel_df[, c("id", "target")]
45-
names(rel_df) <- c("slide_rid", "target")
46-
ref <- data.frame(slide_id = private$slide_id,
47-
slide_rid = private$slide_rid,
48-
stringsAsFactors = FALSE)
49-
base::merge(x = ref, y = rel_df, sort = FALSE,
50-
by = "slide_rid", all.x = TRUE, all.y = FALSE)
44+
rel_df <- select(
45+
.data = rel_df,
46+
all_of(setNames(c("id", "target"), c("slide_rid", "target")))
47+
)
48+
49+
ref <- data.frame(
50+
slide_id = private$slide_id,
51+
slide_rid = private$slide_rid,
52+
stringsAsFactors = FALSE
53+
)
54+
55+
left_join(ref, rel_df, by = "slide_rid")
5156
},
5257

5358
move_slide = function(from, to){

R/pptx_slide_manip.R

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,19 @@ on_slide <- function(x, index) {
117117

118118

119119
#' @export
120-
#' @title Remove a slide
121-
#' @description Remove a slide from a pptx presentation.
120+
#' @title Remove slide(s)
121+
#' @description Remove one or more slides from a pptx presentation.
122122
#' @param x an rpptx object
123-
#' @param index slide index, default to current slide position.
123+
#' @param index slide index or a vector of slide indices to remove,
124+
#' default to current slide position.
124125
#' @param rm_images unused anymore.
125126
#' @note cursor is set on the last slide.
126-
#' @examples
127-
#' my_pres <- read_pptx()
128-
#' my_pres <- add_slide(my_pres, "Title and Content")
129-
#' my_pres <- remove_slide(my_pres)
127+
#' @example inst/examples/example_remove_slide.R
130128
#' @family slide_manipulation
131129
#' @seealso [read_pptx()], [ph_with()], [ph_remove()]
132130
remove_slide <- function(x, index = NULL, rm_images = FALSE) {
131+
stop_if_not_rpptx(x)
132+
133133
l_ <- length(x)
134134
if (l_ < 1) {
135135
stop("presentation contains no slide to delete", call. = FALSE)
@@ -139,17 +139,57 @@ remove_slide <- function(x, index = NULL, rm_images = FALSE) {
139139
index <- x$cursor
140140
}
141141

142-
if (!between(index, 1, l_)) {
143-
stop("unvalid index ", index, " (", l_, " slide(s))", call. = FALSE)
142+
if (length(index) == 0) {
143+
return(x)
144+
}
145+
146+
# Validate all indices
147+
indices <- unique(as.integer(index))
148+
invalid_indices <- indices[!between(indices, 1, l_)]
149+
if (length(invalid_indices) > 0) {
150+
stop(
151+
"invalid index(es) ",
152+
paste(invalid_indices, collapse = ", "),
153+
call. = FALSE
154+
)
144155
}
145-
filename <- basename(x$presentation$slide_data()$target[index])
146-
location <- which(x$slide$get_metadata()$name %in% filename)
147156

148-
del_file <- x$slide$remove_slide(location)
157+
# Get slide mapping information using existing slide_data method
158+
slide_map <- x$presentation$slide_data()
159+
slide_map$filename <- basename(slide_map$target)
160+
161+
indices_to_remove <- sort(unique(indices), decreasing = TRUE)
162+
slides_to_remove_df <- slide_map[indices_to_remove, , drop = FALSE]
163+
164+
# Build file paths for deletion
165+
files_to_delete <- file.path(
166+
x$package_dir,
167+
"ppt",
168+
"slides",
169+
slides_to_remove_df$filename
170+
)
171+
rels_to_delete <- file.path(
172+
x$package_dir,
173+
"ppt",
174+
"slides",
175+
"_rels",
176+
paste0(slides_to_remove_df$filename, ".rels")
177+
)
178+
179+
# Delete slide XML files if they exist
180+
unlink(files_to_delete[file.exists(files_to_delete)], force = TRUE)
181+
# Delete slide relationship files if they exist
182+
unlink(rels_to_delete[file.exists(rels_to_delete)], force = TRUE)
183+
184+
# Remove slides from internal collections
185+
for (filename in slides_to_remove_df$filename) {
186+
slide_idx <- x$slide$slide_index(filename)
187+
x$slide$remove_slide(slide_idx)
188+
x$presentation$remove_slide(filename)
189+
x$content_type$remove_slide(partname = filename)
190+
}
149191

150-
# update presentation elements
151-
x$presentation$remove_slide(del_file)
152-
x$content_type$remove_slide(partname = del_file)
192+
# Set cursor to last slide
153193
x$cursor <- x$slide$length()
154194
x
155195
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
library(officer)
2+
3+
x <- read_pptx()
4+
x <- add_slide(x, "Title and Content")
5+
x <- remove_slide(x)
6+
7+
# Remove multiple slides at once
8+
x <- read_pptx()
9+
x <- add_slide(x, "Title and Content")
10+
x <- add_slide(
11+
x,
12+
layout = "Two Content",
13+
`Title 1` = "A title",
14+
dt = "Jan. 26, 2025",
15+
`body[2]` = "Body 2",
16+
left = "Left side",
17+
`6` = "Footer"
18+
)
19+
x <- add_slide(
20+
x,
21+
layout = "Two Content",
22+
`Title 1` = "A title",
23+
dt = "Jan. 26, 2025",
24+
`body[2]` = "Body 2",
25+
left = "Left side",
26+
`6` = "Footer"
27+
)
28+
x <- add_slide(x, "Title and Content")
29+
x <- remove_slide(x, index = c(2, 4))
30+
pptx_file <- print(x, target = tempfile(fileext = ".pptx"))
31+
pptx_file

man/move_slide.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/officer.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/remove_slide.Rd

Lines changed: 35 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-pptx-misc.R

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,86 @@ test_that("slide remove", {
107107
expect_equal(sm[1, ]$text, "Hello world 2")
108108
})
109109

110+
test_that("remove multiple slides at once", {
111+
x <- read_pptx()
112+
x <- add_slide(x, "Title and Content", "Office Theme")
113+
x <- ph_with(x, "Slide 1", location = ph_location_type(type = "body"))
114+
x <- add_slide(x, "Title and Content", "Office Theme")
115+
x <- ph_with(x, "Slide 2", location = ph_location_type(type = "body"))
116+
x <- add_slide(x, "Title and Content", "Office Theme")
117+
x <- ph_with(x, "Slide 3", location = ph_location_type(type = "body"))
118+
x <- add_slide(x, "Title and Content", "Office Theme")
119+
x <- ph_with(x, "Slide 4", location = ph_location_type(type = "body"))
120+
121+
# Remove slides 2 and 4
122+
x <- remove_slide(x, index = c(2, 4))
123+
expect_equal(length(x), 2)
124+
125+
# Check remaining slides are 1 and 3
126+
sm1 <- slide_summary(x, index = 1)
127+
sm2 <- slide_summary(x, index = 2)
128+
expect_equal(sm1[1, ]$text, "Slide 1")
129+
expect_equal(sm2[1, ]$text, "Slide 3")
130+
})
131+
132+
test_that("remove multiple slides - edge cases", {
133+
# Test removing all slides
134+
x <- read_pptx()
135+
x <- add_slide(x, "Title and Content", "Office Theme")
136+
x <- add_slide(x, "Title and Content", "Office Theme")
137+
x <- remove_slide(x, index = c(1, 2))
138+
expect_equal(length(x), 0)
139+
140+
# Test with duplicate indices (should only remove once)
141+
x <- read_pptx()
142+
x <- add_slide(x, "Title and Content", "Office Theme")
143+
x <- ph_with(x, "Slide 1", location = ph_location_type(type = "body"))
144+
x <- add_slide(x, "Title and Content", "Office Theme")
145+
x <- ph_with(x, "Slide 2", location = ph_location_type(type = "body"))
146+
x <- add_slide(x, "Title and Content", "Office Theme")
147+
x <- ph_with(x, "Slide 3", location = ph_location_type(type = "body"))
148+
x <- remove_slide(x, index = c(2, 2, 2))
149+
expect_equal(length(x), 2)
150+
sm <- slide_summary(x, index = 2)
151+
expect_equal(sm[1, ]$text, "Slide 3")
152+
153+
# Test with empty index vector (should return unchanged)
154+
x <- read_pptx()
155+
x <- add_slide(x, "Title and Content", "Office Theme")
156+
x <- add_slide(x, "Title and Content", "Office Theme")
157+
x <- remove_slide(x, index = integer(0))
158+
expect_equal(length(x), 2)
159+
160+
# Test removing slides in non-sequential order
161+
x <- read_pptx()
162+
x <- add_slide(x, "Title and Content", "Office Theme")
163+
x <- ph_with(x, "Slide 1", location = ph_location_type(type = "body"))
164+
x <- add_slide(x, "Title and Content", "Office Theme")
165+
x <- ph_with(x, "Slide 2", location = ph_location_type(type = "body"))
166+
x <- add_slide(x, "Title and Content", "Office Theme")
167+
x <- ph_with(x, "Slide 3", location = ph_location_type(type = "body"))
168+
x <- add_slide(x, "Title and Content", "Office Theme")
169+
x <- ph_with(x, "Slide 4", location = ph_location_type(type = "body"))
170+
x <- remove_slide(x, index = c(4, 1, 3))
171+
expect_equal(length(x), 1)
172+
sm <- slide_summary(x, index = 1)
173+
expect_equal(sm[1, ]$text, "Slide 2")
174+
})
175+
176+
test_that("remove multiple slides - invalid indices", {
177+
x <- read_pptx()
178+
x <- add_slide(x, "Title and Content", "Office Theme")
179+
x <- add_slide(x, "Title and Content", "Office Theme")
180+
181+
# Test with out of range indices
182+
expect_error(remove_slide(x, index = c(1, 5)), "invalid index")
183+
expect_error(remove_slide(x, index = c(0, 1)), "invalid index")
184+
expect_error(remove_slide(x, index = c(-1, 2)), "invalid index")
185+
186+
# Test with all invalid indices
187+
expect_error(remove_slide(x, index = c(5, 6)), "invalid index")
188+
})
189+
110190
test_that("ph remove", {
111191
x <- read_pptx()
112192
x <- add_slide(x, "Title and Content", "Office Theme")

tests/testthat/test-pptx-selections.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test_that("check errors", {
3535
x <- ph_with(x, "my title 2", location = ph_location_type(type = "title"))
3636

3737
expect_error(on_slide(x, index = 3), "unvalid index 3")
38-
expect_error(remove_slide(x, index = 3), "unvalid index 3")
38+
expect_error(remove_slide(x, index = 3), "invalid index\\(es\\) 3")
3939
expect_error(slide_summary(x, index = 3), "unvalid index 3")
4040
})
4141

0 commit comments

Comments
 (0)