ppt_rs/
slide.rs

1//! Slide-related types and functionality
2
3use crate::error::Result;
4use crate::parts::presentation::PresentationPart;
5use crate::parts::slide::SlidePart;
6use crate::shapes::Shape;
7
8/// Collection of slides in a presentation
9pub struct Slides<'a> {
10    presentation_part: &'a mut PresentationPart,
11}
12
13impl<'a> Slides<'a> {
14    pub fn new(presentation_part: &'a mut PresentationPart) -> Self {
15        Self {
16            presentation_part,
17        }
18    }
19
20    /// Add a new slide
21    pub fn add_slide(&mut self, slide_layout: &SlidePart) -> Result<Slide> {
22        use crate::opc::part::Part;
23        use crate::opc::packuri::PackURI;
24        // Use PresentationPart's add_slide method
25        let r_id = self.presentation_part.add_slide(slide_layout as &dyn Part)?;
26        
27        // Get the slide partname from the relationship
28        let rels = self.presentation_part.relationships();
29        if let Some(rel) = rels.get(&r_id) {
30            let slide_uri = PackURI::new(&format!("/{}", rel.target))?;
31            let slide_part = crate::parts::slide::SlidePart::new(slide_uri, slide_layout as &dyn Part)?;
32            Ok(Slide::with_part(slide_part))
33        } else {
34            Ok(Slide::new())
35        }
36    }
37
38    /// Get a slide by index
39    pub fn get(&self, index: usize) -> Option<Slide> {
40        use crate::opc::part::Part;
41        
42        // Parse presentation.xml to get slide relationships
43        if let Ok(blob) = Part::blob(self.presentation_part) {
44            if let Ok(xml) = String::from_utf8(blob) {
45                // Extract all r:id values from sldId entries
46                let re = regex::Regex::new(r#"<p:sldId\s+[^>]*r:id="([^"]+)""#).ok()?;
47                let r_ids: Vec<&str> = re.captures_iter(&xml)
48                    .map(|cap| cap.get(1).map(|m| m.as_str()))
49                    .collect::<Option<Vec<_>>>()?;
50                
51                if index < r_ids.len() {
52                    let r_id = r_ids[index];
53                    let rels = self.presentation_part.relationships();
54                    if let Some(rel) = rels.get(r_id) {
55                        use crate::opc::packuri::PackURI;
56                        if let Ok(slide_uri) = PackURI::new(&format!("/{}", rel.target)) {
57                            // Create a minimal SlidePart - in full implementation, would load from package
58                            if let Ok(slide_part) = crate::parts::slide::SlidePart::new(slide_uri, self.presentation_part as &dyn Part) {
59                                return Some(Slide::with_part(slide_part));
60                            }
61                        }
62                    }
63                }
64            }
65        }
66        None
67    }
68
69    /// Get the number of slides
70    pub fn len(&self) -> usize {
71        use crate::opc::part::Part;
72        // Parse presentation.xml to count slides in sldIdLst
73        if let Ok(blob) = Part::blob(self.presentation_part) {
74            if let Ok(xml) = String::from_utf8(blob) {
75                // Count occurrences of <p:sldId> tags (not <p:sldIdLst>)
76                // Match <p:sldId with attributes, not just the list container
77                let pattern = r#"<p:sldId\s"#;
78                return xml.matches(pattern).count();
79            }
80        }
81        0
82    }
83
84    /// Check if slides collection is empty
85    pub fn is_empty(&self) -> bool {
86        self.len() == 0
87    }
88
89    /// Iterate over slides
90    pub fn iter(&self) -> SlideIterator {
91        SlideIterator {
92            slides: self,
93            index: 0,
94        }
95    }
96}
97
98/// Iterator over slides
99pub struct SlideIterator<'a> {
100    slides: &'a Slides<'a>,
101    index: usize,
102}
103
104impl<'a> Iterator for SlideIterator<'a> {
105    type Item = Slide;
106
107    fn next(&mut self) -> Option<Self::Item> {
108        if self.index < self.slides.len() {
109            let slide = self.slides.get(self.index);
110            self.index += 1;
111            slide
112        } else {
113            None
114        }
115    }
116}
117
118/// Individual slide
119pub struct Slide {
120    part: Option<SlidePart>,
121    name: String,
122}
123
124impl Slide {
125    pub fn new() -> Self {
126        Self {
127            part: None,
128            name: String::new(),
129        }
130    }
131
132    pub fn with_part(part: SlidePart) -> Self {
133        Self {
134            part: Some(part),
135            name: String::new(),
136        }
137    }
138
139    /// Get the slide name
140    pub fn name(&self) -> &str {
141        &self.name
142    }
143
144    /// Set the slide name
145    pub fn set_name(&mut self, name: String) {
146        self.name = name;
147    }
148
149    /// Get shapes on this slide
150    pub fn shapes(&self) -> Vec<Box<dyn Shape>> {
151        use crate::opc::part::Part;
152        use crate::shapes::xml::parse_shapes_from_xml;
153        
154        // Parse slide XML to extract shapes
155        if let Some(ref part) = self.part {
156            if let Ok(blob) = Part::blob(part) {
157                if let Ok(xml) = String::from_utf8(blob) {
158                    // Parse shapes from XML spTree
159                    if let Ok(shapes) = parse_shapes_from_xml(&xml) {
160                        return shapes;
161                    }
162                }
163            }
164        }
165        Vec::new()
166    }
167
168    /// Add a shape to the slide
169    pub fn add_shape(&mut self, shape: Box<dyn Shape>) -> Result<()> {
170        use crate::opc::part::Part;
171        use crate::shapes::xml::{shape_to_xml, next_shape_id};
172        
173        if let Some(ref mut part) = self.part {
174            // Get current XML
175            let mut xml = Part::to_xml(part)?;
176            
177            // Find next shape ID
178            let next_id = next_shape_id(&xml);
179            
180            // Generate shape XML
181            let shape_xml = shape_to_xml(shape.as_ref(), next_id);
182            
183            // Insert shape XML into spTree (before closing </p:spTree>)
184            if let Some(pos) = xml.find("</p:spTree>") {
185                xml.insert_str(pos, &format!("      {}\n    ", shape_xml));
186            } else {
187                // If spTree doesn't exist, add it
188                if let Some(pos) = xml.find("<p:cSld>") {
189                    let sp_tree = format!(
190                        r#"<p:spTree>
191      <p:nvGrpSpPr>
192        <p:cNvPr id="1" name=""/>
193        <p:cNvGrpSpPr/>
194        <p:nvPr/>
195      </p:nvGrpSpPr>
196      <p:grpSpPr/>
197      {}
198    </p:spTree>"#,
199                        shape_xml
200                    );
201                    xml.insert_str(pos + 8, &format!("\n    {}", sp_tree));
202                }
203            }
204            
205            // Update slide part with new XML
206            part.update_xml(xml)?;
207        }
208        Ok(())
209    }
210}
211
212/// Slide masters collection
213pub struct SlideMasters {
214    presentation_part: *const PresentationPart, // Using raw pointer to avoid lifetime issues
215}
216
217impl SlideMasters {
218    pub fn new(_presentation_part: &PresentationPart) -> Self {
219        Self {
220            presentation_part: std::ptr::null(),
221        }
222    }
223    
224    /// Get the number of slide masters
225    pub fn len(&self) -> usize {
226        // Parse presentation.xml to count slide masters
227        // For now, return 0 as this requires XML parsing
228        0
229    }
230    
231    /// Check if empty
232    pub fn is_empty(&self) -> bool {
233        self.len() == 0
234    }
235}
236
237/// Slide layouts collection
238pub struct SlideLayouts {
239    slide_master_part: *const crate::parts::slide::SlideMasterPart,
240}
241
242impl SlideLayouts {
243    pub fn new(_slide_master_part: &crate::parts::slide::SlideMasterPart) -> Self {
244        Self {
245            slide_master_part: std::ptr::null(),
246        }
247    }
248
249    /// Get a layout by name
250    pub fn get_by_name(&self, _name: &str) -> Option<crate::parts::slide::SlideLayoutPart> {
251        // Parse slide master XML to find layout by name
252        // For now, return None as this requires XML parsing
253        None
254    }
255    
256    /// Get the number of layouts
257    pub fn len(&self) -> usize {
258        // Parse slide master XML to count layouts
259        // For now, return 0
260        0
261    }
262    
263    /// Check if empty
264    pub fn is_empty(&self) -> bool {
265        self.len() == 0
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use crate::parts::presentation::PresentationPart;
273
274    #[test]
275    fn test_slide_new() {
276        let slide = Slide::new();
277        assert_eq!(slide.name(), "");
278        assert_eq!(slide.shapes().len(), 0);
279    }
280
281    #[test]
282    fn test_slide_name() {
283        let mut slide = Slide::new();
284        slide.set_name("Test Slide".to_string());
285        assert_eq!(slide.name(), "Test Slide");
286    }
287
288    #[test]
289    fn test_slide_masters_new() {
290        let part = PresentationPart::new().unwrap();
291        let masters = SlideMasters::new(&part);
292        assert_eq!(masters.len(), 0);
293        assert!(masters.is_empty());
294    }
295
296    #[test]
297    fn test_slide_layouts_new() {
298        use crate::parts::slide::SlideMasterPart;
299        use crate::opc::packuri::PackURI;
300        
301        let master_part = SlideMasterPart::new(PackURI::new("/ppt/slideMasters/slideMaster1.xml").unwrap()).unwrap();
302        let layouts = SlideLayouts::new(&master_part);
303        assert_eq!(layouts.len(), 0);
304        assert!(layouts.is_empty());
305    }
306
307    #[test]
308    fn test_slides_len_empty() {
309        let mut part = PresentationPart::new().unwrap();
310        let slides = Slides::new(&mut part);
311        assert_eq!(slides.len(), 0);
312        assert!(slides.is_empty());
313    }
314}
315