Skip to main content

ppt_rs/oxml/
editor.rs

1//! Presentation editing capabilities
2//!
3//! Provides functionality to modify existing PPTX files:
4//! - Add new slides
5//! - Update slide content
6//! - Remove slides
7//! - Modify presentation properties
8
9use super::slide::{ParsedSlide, SlideParser};
10use crate::exc::{messages, PptxError};
11use crate::generator::slide_content::SlideContent;
12use crate::generator::package_xml::{
13    create_presentation_rels_xml_full, slide_id_value, slide_rel_id,
14};
15use crate::generator::slide_xml::{create_slide_rels_xml, create_slide_xml_with_content};
16use crate::opc::Package;
17
18/// Presentation editor for modifying PPTX files
19pub struct PresentationEditor {
20    package: Package,
21    slide_count: usize,
22}
23
24impl PresentationEditor {
25    /// Open a PPTX file for editing
26    pub fn open(path: &str) -> Result<Self, PptxError> {
27        let package = Package::open(path)?;
28        let slide_count = Self::count_slides(&package);
29
30        Ok(PresentationEditor {
31            package,
32            slide_count,
33        })
34    }
35
36    /// Create a new presentation for editing
37    pub fn new() -> Self {
38        PresentationEditor {
39            package: Package::new(),
40            slide_count: 0,
41        }
42    }
43
44    /// Get number of slides
45    pub fn slide_count(&self) -> usize {
46        self.slide_count
47    }
48
49    /// Get a parsed slide by index (0-based)
50    pub fn get_slide(&self, index: usize) -> Result<ParsedSlide, PptxError> {
51        let slide_num = index + 1;
52        let path = format!("ppt/slides/slide{slide_num}.xml");
53        let xml = self
54            .package
55            .get_part(&path)
56            .ok_or_else(|| PptxError::NotFound(messages::slide_not_found(index)))?;
57
58        let xml_str = String::from_utf8_lossy(xml);
59        SlideParser::parse(&xml_str)
60    }
61
62    /// Add a new slide at the end
63    pub fn add_slide(&mut self, content: SlideContent) -> Result<usize, PptxError> {
64        let new_index = self.slide_count + 1;
65
66        // Generate slide XML
67        let slide_xml = create_slide_xml_with_content(new_index, &content, &[], None);
68        let slide_rels_xml = create_slide_rels_xml();
69
70        // Add slide file
71        let slide_path = format!("ppt/slides/slide{new_index}.xml");
72        self.package.add_part(slide_path, slide_xml.into_bytes());
73
74        // Add slide relationships
75        let rels_path = format!("ppt/slides/_rels/slide{new_index}.xml.rels");
76        self.package
77            .add_part(rels_path, slide_rels_xml.into_bytes());
78
79        // Update presentation.xml to include new slide
80        self.update_presentation_xml(new_index)?;
81
82        // Update presentation.xml.rels
83        self.update_presentation_rels(new_index)?;
84
85        // Update [Content_Types].xml
86        self.update_content_types(new_index)?;
87
88        self.slide_count = new_index;
89        Ok(new_index - 1) // Return 0-based index
90    }
91
92    /// Update slide content at index
93    pub fn update_slide(&mut self, index: usize, content: SlideContent) -> Result<(), PptxError> {
94        if index >= self.slide_count {
95            return Err(PptxError::NotFound(messages::slide_not_found(index)));
96        }
97
98        let slide_num = index + 1;
99        let slide_xml = create_slide_xml_with_content(slide_num, &content, &[], None);
100        let slide_path = format!("ppt/slides/slide{slide_num}.xml");
101
102        self.package.add_part(slide_path, slide_xml.into_bytes());
103        Ok(())
104    }
105
106    /// Remove a slide by index
107    pub fn remove_slide(&mut self, index: usize) -> Result<(), PptxError> {
108        if index >= self.slide_count {
109            return Err(PptxError::NotFound(messages::slide_not_found(index)));
110        }
111
112        let slide_num = index + 1;
113
114        // Remove slide file
115        let slide_path = format!("ppt/slides/slide{slide_num}.xml");
116        self.package.remove_part(&slide_path);
117
118        // Remove slide relationships
119        let rels_path = format!("ppt/slides/_rels/slide{slide_num}.xml.rels");
120        self.package.remove_part(&rels_path);
121
122        // Renumber remaining slides
123        for i in (slide_num + 1)..=self.slide_count {
124            self.renumber_slide(i, i - 1)?;
125        }
126
127        self.slide_count -= 1;
128
129        // Update presentation files
130        self.rebuild_presentation_xml()?;
131        self.rebuild_presentation_rels()?;
132        self.rebuild_content_types()?;
133
134        Ok(())
135    }
136
137    /// Duplicate an existing slide, inserting the copy immediately after the original.
138    pub fn duplicate_slide(&mut self, index: usize) -> Result<usize, PptxError> {
139        if index >= self.slide_count {
140            return Err(PptxError::NotFound(messages::slide_not_found(index)));
141        }
142
143        let slide_num = index + 1;
144        let new_index = index + 1;
145        let new_num = new_index + 1;
146
147        // Copy original slide content and relationships.
148        let slide_path = format!("ppt/slides/slide{slide_num}.xml");
149        let rels_path = format!("ppt/slides/_rels/slide{slide_num}.xml.rels");
150
151        let slide_data = self
152            .package
153            .get_part(&slide_path)
154            .ok_or_else(|| PptxError::NotFound(messages::slide_not_found(index)))?
155            .to_vec();
156        let rels_data = self
157            .package
158            .get_part(&rels_path)
159            .unwrap_or_default()
160            .to_vec();
161
162        // Shift slides after the original to make room for the duplicate.
163        for i in ((new_num + 1)..=(self.slide_count + 1)).rev() {
164            self.renumber_slide(i - 1, i)?;
165        }
166
167        // Insert duplicate at the newly vacated position.
168        self.package
169            .add_part(format!("ppt/slides/slide{new_num}.xml"), slide_data);
170        self.package
171            .add_part(format!("ppt/slides/_rels/slide{new_num}.xml.rels"), rels_data);
172
173        self.slide_count += 1;
174
175        self.rebuild_presentation_xml()?;
176        self.rebuild_presentation_rels()?;
177        self.rebuild_content_types()?;
178
179        Ok(new_index)
180    }
181
182    /// Insert a new slide at the specified index.
183    pub fn insert_slide(&mut self, index: usize, content: SlideContent) -> Result<usize, PptxError> {
184        if index > self.slide_count {
185            return Err(PptxError::NotFound(messages::slide_not_found(index)));
186        }
187
188        let new_num = index + 1;
189
190        // Shift slides at and after the insertion point upward.
191        for i in ((new_num + 1)..=(self.slide_count + 1)).rev() {
192            self.renumber_slide(i - 1, i)?;
193        }
194
195        // Generate and insert the new slide.
196        let slide_xml = create_slide_xml_with_content(new_num, &content, &[], None);
197        let slide_rels_xml = create_slide_rels_xml();
198
199        self.package
200            .add_part(format!("ppt/slides/slide{new_num}.xml"), slide_xml.into_bytes());
201        self.package.add_part(
202            format!("ppt/slides/_rels/slide{new_num}.xml.rels"),
203            slide_rels_xml.into_bytes(),
204        );
205
206        self.slide_count += 1;
207
208        self.rebuild_presentation_xml()?;
209        self.rebuild_presentation_rels()?;
210        self.rebuild_content_types()?;
211
212        Ok(index)
213    }
214
215    /// Reorder a slide from one position to another.
216    pub fn reorder_slide(&mut self, from_index: usize, to_index: usize) -> Result<(), PptxError> {
217        if from_index >= self.slide_count || to_index >= self.slide_count {
218            return Err(PptxError::NotFound(messages::slide_not_found(
219                from_index.max(to_index),
220            )));
221        }
222
223        if from_index == to_index {
224            return Ok(());
225        }
226
227        let from_num = from_index + 1;
228        let to_num = to_index + 1;
229
230        // Extract the slide being moved.
231        let temp_slide_path = "ppt/slides/_temp_slide.xml".to_string();
232        let temp_rels_path = "ppt/slides/_rels/_temp_slide.xml.rels".to_string();
233
234        if let Some(data) = self.package.remove_part(&format!("ppt/slides/slide{from_num}.xml")) {
235            self.package.add_part(temp_slide_path.clone(), data);
236        }
237        if let Some(data) =
238            self.package.remove_part(&format!("ppt/slides/_rels/slide{from_num}.xml.rels"))
239        {
240            self.package.add_part(temp_rels_path.clone(), data);
241        }
242
243        if from_index < to_index {
244            // Shift intermediate slides left.
245            for i in from_num..to_num {
246                self.renumber_slide(i + 1, i)?;
247            }
248        } else {
249            // Shift intermediate slides right.
250            for i in (to_num..from_num).rev() {
251                self.renumber_slide(i, i + 1)?;
252            }
253        }
254
255        // Place the moved slide at the target position.
256        if let Some(data) = self.package.remove_part(&temp_slide_path) {
257            self.package
258                .add_part(format!("ppt/slides/slide{to_num}.xml"), data);
259        }
260        if let Some(data) = self.package.remove_part(&temp_rels_path) {
261            self.package
262                .add_part(format!("ppt/slides/_rels/slide{to_num}.xml.rels"), data);
263        }
264
265        self.rebuild_presentation_xml()?;
266        self.rebuild_presentation_rels()?;
267        self.rebuild_content_types()?;
268
269        Ok(())
270    }
271
272    /// Save the modified presentation
273    pub fn save(&self, path: &str) -> Result<(), PptxError> {
274        self.package.save(path)?;
275        Ok(())
276    }
277
278    /// Get the underlying package for advanced operations
279    pub fn package(&self) -> &Package {
280        &self.package
281    }
282
283    /// Get mutable reference to package
284    pub fn package_mut(&mut self) -> &mut Package {
285        &mut self.package
286    }
287
288    // Helper methods
289
290    fn count_slides(package: &Package) -> usize {
291        package
292            .part_paths()
293            .iter()
294            .filter(|p| {
295                p.starts_with("ppt/slides/slide") && p.ends_with(".xml") && !p.contains("_rels")
296            })
297            .count()
298    }
299
300    fn presentation_has_notes(xml: &str) -> bool {
301        xml.contains("<p:notesMasterIdLst>")
302    }
303
304    fn presentation_has_handout(xml: &str) -> bool {
305        xml.contains("<p:handoutMasterIdLst>")
306    }
307
308    fn update_presentation_xml(&mut self, new_slide_count: usize) -> Result<(), PptxError> {
309        if let Some(xml) = self.package.get_part_string("ppt/presentation.xml") {
310            // Parse and update the presentation XML
311            let updated = self.add_slide_to_presentation_xml(&xml, new_slide_count)?;
312            self.package
313                .add_part("ppt/presentation.xml".to_string(), updated.into_bytes());
314        }
315        Ok(())
316    }
317
318    fn add_slide_to_presentation_xml(
319        &self,
320        xml: &str,
321        slide_num: usize,
322    ) -> Result<String, PptxError> {
323        // Find </p:sldIdLst> and insert new slide reference before it
324        let has_notes = Self::presentation_has_notes(xml);
325        let has_handout = Self::presentation_has_handout(xml);
326        let slide_id = slide_id_value(slide_num);
327        let r_id = slide_rel_id(slide_num, has_notes, has_handout);
328
329        let new_slide_ref = format!("\n<p:sldId id=\"{slide_id}\" r:id=\"rId{r_id}\"/>");
330
331        if let Some(pos) = xml.find("</p:sldIdLst>") {
332            let mut result = xml.to_string();
333            result.insert_str(pos, &new_slide_ref);
334            Ok(result)
335        } else {
336            // If no sldIdLst, return as-is (shouldn't happen for valid PPTX)
337            Ok(xml.to_string())
338        }
339    }
340
341    fn update_presentation_rels(&mut self, new_slide_count: usize) -> Result<(), PptxError> {
342        if let Some(xml) = self
343            .package
344            .get_part_string("ppt/_rels/presentation.xml.rels")
345        {
346            let updated = self.add_slide_to_presentation_rels(&xml, new_slide_count)?;
347            self.package.add_part(
348                "ppt/_rels/presentation.xml.rels".to_string(),
349                updated.into_bytes(),
350            );
351        }
352        Ok(())
353    }
354
355    fn add_slide_to_presentation_rels(
356        &self,
357        xml: &str,
358        slide_num: usize,
359    ) -> Result<String, PptxError> {
360        let has_notes = Self::presentation_has_notes(xml);
361        let has_handout = Self::presentation_has_handout(xml);
362        let r_id = slide_rel_id(slide_num, has_notes, has_handout);
363        let new_rel = format!(
364            "\n<Relationship Id=\"rId{r_id}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide\" Target=\"slides/slide{slide_num}.xml\"/>"
365        );
366
367        if let Some(pos) = xml.find("</Relationships>") {
368            let mut result = xml.to_string();
369            result.insert_str(pos, &new_rel);
370            Ok(result)
371        } else {
372            Ok(xml.to_string())
373        }
374    }
375
376    fn update_content_types(&mut self, new_slide_count: usize) -> Result<(), PptxError> {
377        if let Some(xml) = self.package.get_part_string("[Content_Types].xml") {
378            let updated = self.add_slide_to_content_types(&xml, new_slide_count)?;
379            self.package
380                .add_part("[Content_Types].xml".to_string(), updated.into_bytes());
381        }
382        Ok(())
383    }
384
385    fn add_slide_to_content_types(&self, xml: &str, slide_num: usize) -> Result<String, PptxError> {
386        let new_override = format!(
387            "\n<Override PartName=\"/ppt/slides/slide{slide_num}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>"
388        );
389
390        if let Some(pos) = xml.find("</Types>") {
391            let mut result = xml.to_string();
392            result.insert_str(pos, &new_override);
393            Ok(result)
394        } else {
395            Ok(xml.to_string())
396        }
397    }
398
399    fn renumber_slide(&mut self, old_num: usize, new_num: usize) -> Result<(), PptxError> {
400        // Move slide file
401        let old_path = format!("ppt/slides/slide{old_num}.xml");
402        let new_path = format!("ppt/slides/slide{new_num}.xml");
403
404        if let Some(content) = self.package.remove_part(&old_path) {
405            self.package.add_part(new_path, content);
406        }
407
408        // Move slide rels
409        let old_rels = format!("ppt/slides/_rels/slide{old_num}.xml.rels");
410        let new_rels = format!("ppt/slides/_rels/slide{new_num}.xml.rels");
411
412        if let Some(content) = self.package.remove_part(&old_rels) {
413            self.package.add_part(new_rels, content);
414        }
415
416        Ok(())
417    }
418
419    fn rebuild_presentation_xml(&mut self) -> Result<(), PptxError> {
420        let existing = self
421            .package
422            .get_part_string("ppt/presentation.xml")
423            .unwrap_or_default();
424        let has_notes = Self::presentation_has_notes(&existing);
425        let has_handout = Self::presentation_has_handout(&existing);
426
427        let mut slide_refs = String::new();
428        for i in 1..=self.slide_count {
429            let slide_id = slide_id_value(i);
430            let r_id = slide_rel_id(i, has_notes, has_handout);
431            slide_refs.push_str(&format!(
432                "\n<p:sldId id=\"{slide_id}\" r:id=\"rId{r_id}\"/>"
433            ));
434        }
435
436        let mut xml = String::from(
437            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
438<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" saveSubsetFonts="1" autoCompressPictures="0">
439<p:sldMasterIdLst>
440<p:sldMasterId id="2147483648" r:id="rId1"/>
441</p:sldMasterIdLst>"#,
442        );
443
444        if has_notes {
445            xml.push_str(
446                r#"
447<p:notesMasterIdLst>
448<p:notesMasterId id="2147483650" r:id="rId6"/>
449</p:notesMasterIdLst>"#,
450            );
451        }
452
453        if has_handout {
454            let handout_rid = if has_notes { 7 } else { 6 };
455            xml.push_str(&format!(
456                r#"
457<p:handoutMasterIdLst>
458<p:handoutMasterId id="2147483651" r:id="rId{handout_rid}"/>
459</p:handoutMasterIdLst>"#
460            ));
461        }
462
463        xml.push_str(&format!(
464            r#"
465<p:sldIdLst>{slide_refs}
466</p:sldIdLst>
467<p:sldSz cx="9144000" cy="6858000" type="screen4x3"/>
468<p:notesSz cx="6858000" cy="9144000"/>
469</p:presentation>"#
470        ));
471
472        self.package
473            .add_part("ppt/presentation.xml".to_string(), xml.into_bytes());
474        Ok(())
475    }
476
477    fn rebuild_presentation_rels(&mut self) -> Result<(), PptxError> {
478        let existing = self
479            .package
480            .get_part_string("ppt/presentation.xml")
481            .unwrap_or_default();
482        let has_notes = Self::presentation_has_notes(&existing);
483        let has_handout = Self::presentation_has_handout(&existing);
484
485        let xml = create_presentation_rels_xml_full(self.slide_count, has_notes, has_handout);
486
487        self.package.add_part(
488            "ppt/_rels/presentation.xml.rels".to_string(),
489            xml.into_bytes(),
490        );
491        Ok(())
492    }
493
494    fn rebuild_content_types(&mut self) -> Result<(), PptxError> {
495        let mut slide_overrides = String::new();
496        for i in 1..=self.slide_count {
497            slide_overrides.push_str(&format!(
498                "\n<Override PartName=\"/ppt/slides/slide{i}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>"
499            ));
500        }
501
502        let xml = format!(
503            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
504<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
505<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
506<Default Extension="xml" ContentType="application/xml"/>
507<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>{slide_overrides}
508<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>
509<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>
510<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
511<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
512<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
513</Types>"#
514        );
515
516        self.package
517            .add_part("[Content_Types].xml".to_string(), xml.into_bytes());
518        Ok(())
519    }
520}
521
522impl Default for PresentationEditor {
523    fn default() -> Self {
524        Self::new()
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531    use crate::generator::create_pptx_with_content;
532    use crate::oxml::PresentationReader;
533    use std::fs;
534
535    #[test]
536    fn test_open_and_add_slide() {
537        // Create initial presentation
538        let slides = vec![SlideContent::new("Original Slide 1").add_bullet("Original content")];
539        let pptx_data = create_pptx_with_content("Test", slides).unwrap();
540        fs::write("test_edit.pptx", &pptx_data).unwrap();
541
542        // Open and add a slide
543        let mut editor = PresentationEditor::open("test_edit.pptx").unwrap();
544        assert_eq!(editor.slide_count(), 1);
545
546        let new_slide = SlideContent::new("New Slide").add_bullet("Added via editor");
547        editor.add_slide(new_slide).unwrap();
548
549        assert_eq!(editor.slide_count(), 2);
550
551        // Save and verify
552        editor.save("test_edit_modified.pptx").unwrap();
553
554        let reader = PresentationReader::open("test_edit_modified.pptx").unwrap();
555        assert_eq!(reader.slide_count(), 2);
556
557        // Cleanup
558        fs::remove_file("test_edit.pptx").ok();
559        fs::remove_file("test_edit_modified.pptx").ok();
560    }
561
562    #[test]
563    fn test_update_slide() {
564        let slides = vec![SlideContent::new("Original Title").add_bullet("Original bullet")];
565        let pptx_data = create_pptx_with_content("Test", slides).unwrap();
566        fs::write("test_update.pptx", &pptx_data).unwrap();
567
568        let mut editor = PresentationEditor::open("test_update.pptx").unwrap();
569
570        let updated = SlideContent::new("Updated Title").add_bullet("Updated bullet");
571        editor.update_slide(0, updated).unwrap();
572
573        editor.save("test_update_modified.pptx").unwrap();
574
575        let reader = PresentationReader::open("test_update_modified.pptx").unwrap();
576        let slide = reader.get_slide(0).unwrap();
577        assert_eq!(slide.title, Some("Updated Title".to_string()));
578
579        fs::remove_file("test_update.pptx").ok();
580        fs::remove_file("test_update_modified.pptx").ok();
581    }
582
583    #[test]
584    fn test_duplicate_slide() {
585        let slides = vec![
586            SlideContent::new("Slide 1").add_bullet("one"),
587            SlideContent::new("Slide 2").add_bullet("two"),
588        ];
589        let pptx_data = create_pptx_with_content("Test", slides).unwrap();
590        fs::write("test_duplicate.pptx", &pptx_data).unwrap();
591
592        let mut editor = PresentationEditor::open("test_duplicate.pptx").unwrap();
593        let new_index = editor.duplicate_slide(0).unwrap();
594        assert_eq!(new_index, 1);
595        assert_eq!(editor.slide_count(), 3);
596
597        editor.save("test_duplicate_modified.pptx").unwrap();
598        let reader = PresentationReader::open("test_duplicate_modified.pptx").unwrap();
599        assert_eq!(reader.slide_count(), 3);
600
601        fs::remove_file("test_duplicate.pptx").ok();
602        fs::remove_file("test_duplicate_modified.pptx").ok();
603    }
604
605    #[test]
606    fn test_insert_slide() {
607        let slides = vec![
608            SlideContent::new("Slide 1").add_bullet("one"),
609            SlideContent::new("Slide 2").add_bullet("two"),
610        ];
611        let pptx_data = create_pptx_with_content("Test", slides).unwrap();
612        fs::write("test_insert.pptx", &pptx_data).unwrap();
613
614        let mut editor = PresentationEditor::open("test_insert.pptx").unwrap();
615        let inserted = editor
616            .insert_slide(1, SlideContent::new("Inserted").add_bullet("new"))
617            .unwrap();
618        assert_eq!(inserted, 1);
619        assert_eq!(editor.slide_count(), 3);
620
621        editor.save("test_insert_modified.pptx").unwrap();
622        let reader = PresentationReader::open("test_insert_modified.pptx").unwrap();
623        assert_eq!(reader.slide_count(), 3);
624        let titles: Vec<_> = (0..reader.slide_count())
625            .map(|i| reader.get_slide(i).unwrap().title.unwrap_or_default())
626            .collect();
627        assert_eq!(titles, vec!["Slide 1", "Inserted", "Slide 2"]);
628
629        fs::remove_file("test_insert.pptx").ok();
630        fs::remove_file("test_insert_modified.pptx").ok();
631    }
632
633    #[test]
634    fn test_reorder_slide() {
635        let slides = vec![
636            SlideContent::new("Slide A").add_bullet("a"),
637            SlideContent::new("Slide B").add_bullet("b"),
638            SlideContent::new("Slide C").add_bullet("c"),
639        ];
640        let pptx_data = create_pptx_with_content("Test", slides).unwrap();
641        fs::write("test_reorder.pptx", &pptx_data).unwrap();
642
643        let mut editor = PresentationEditor::open("test_reorder.pptx").unwrap();
644        editor.reorder_slide(2, 0).unwrap();
645        assert_eq!(editor.slide_count(), 3);
646
647        editor.save("test_reorder_modified.pptx").unwrap();
648        let reader = PresentationReader::open("test_reorder_modified.pptx").unwrap();
649        let titles: Vec<_> = (0..reader.slide_count())
650            .map(|i| reader.get_slide(i).unwrap().title.unwrap_or_default())
651            .collect();
652        assert_eq!(titles, vec!["Slide C", "Slide A", "Slide B"]);
653
654        fs::remove_file("test_reorder.pptx").ok();
655        fs::remove_file("test_reorder_modified.pptx").ok();
656    }
657}