Skip to main content

ppt_rs/parts/
presentation.rs

1//! Presentation part
2//!
3//! Represents the main presentation.xml part.
4
5use super::base::{ContentType, Part, PartType};
6use super::relationships::{RelationshipType, Relationships};
7use crate::exc::PptxError;
8use crate::oxml::XmlParser;
9
10/// Slide reference in presentation
11#[derive(Debug, Clone)]
12pub struct SlideRef {
13    pub id: u32,
14    pub r_id: String,
15}
16
17/// Presentation part (presentation.xml)
18#[derive(Debug, Clone)]
19pub struct PresentationPart {
20    path: String,
21    slide_refs: Vec<SlideRef>,
22    slide_master_r_id: Option<String>,
23    theme_r_id: Option<String>,
24    slide_width: u32,
25    slide_height: u32,
26}
27
28impl PresentationPart {
29    /// Create a new presentation part
30    pub fn new() -> Self {
31        PresentationPart {
32            path: "ppt/presentation.xml".to_string(),
33            slide_refs: Vec::new(),
34            slide_master_r_id: None,
35            theme_r_id: None,
36            slide_width: 9144000,  // 10 inches
37            slide_height: 6858000, // 7.5 inches
38        }
39    }
40
41    /// Set slide master relationship ID
42    pub fn set_slide_master(&mut self, r_id: &str) {
43        self.slide_master_r_id = Some(r_id.to_string());
44    }
45
46    /// Set theme relationship ID
47    pub fn set_theme(&mut self, r_id: &str) {
48        self.theme_r_id = Some(r_id.to_string());
49    }
50
51    /// Add a slide reference
52    pub fn add_slide(&mut self, r_id: &str) -> u32 {
53        let id = 256 + self.slide_refs.len() as u32 + 1;
54        self.slide_refs.push(SlideRef {
55            id,
56            r_id: r_id.to_string(),
57        });
58        id
59    }
60
61    /// Get slide count
62    pub fn slide_count(&self) -> usize {
63        self.slide_refs.len()
64    }
65
66    /// Get slide references
67    pub fn slides(&self) -> &[SlideRef] {
68        &self.slide_refs
69    }
70
71    /// Set slide dimensions
72    pub fn set_dimensions(&mut self, width: u32, height: u32) {
73        self.slide_width = width;
74        self.slide_height = height;
75    }
76
77    /// Create default relationships for presentation
78    pub fn create_relationships(&self) -> Relationships {
79        let mut rels = Relationships::new();
80
81        // Add slide master
82        if let Some(ref r_id) = self.slide_master_r_id {
83            rels.add_with_id(
84                r_id,
85                RelationshipType::SlideMaster,
86                "slideMasters/slideMaster1.xml",
87            );
88        } else {
89            rels.add(
90                RelationshipType::SlideMaster,
91                "slideMasters/slideMaster1.xml",
92            );
93        }
94
95        // Add theme
96        if let Some(ref r_id) = self.theme_r_id {
97            rels.add_with_id(r_id, RelationshipType::Theme, "theme/theme1.xml");
98        } else {
99            rels.add(RelationshipType::Theme, "theme/theme1.xml");
100        }
101
102        // Add slides
103        for (i, slide_ref) in self.slide_refs.iter().enumerate() {
104            rels.add_with_id(
105                &slide_ref.r_id,
106                RelationshipType::Slide,
107                &format!("slides/slide{}.xml", i + 1),
108            );
109        }
110
111        rels
112    }
113}
114
115impl Default for PresentationPart {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl Part for PresentationPart {
122    fn path(&self) -> &str {
123        &self.path
124    }
125
126    fn part_type(&self) -> PartType {
127        PartType::Presentation
128    }
129
130    fn content_type(&self) -> ContentType {
131        ContentType::Presentation
132    }
133
134    fn to_xml(&self) -> Result<String, PptxError> {
135        let mut slide_id_list = String::new();
136        for slide_ref in &self.slide_refs {
137            slide_id_list.push_str(&format!(
138                "\n<p:sldId id=\"{}\" r:id=\"{}\"/>",
139                slide_ref.id, slide_ref.r_id
140            ));
141        }
142
143        let master_r_id = self.slide_master_r_id.as_deref().unwrap_or("rId1");
144
145        let xml = format!(
146            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
147<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">
148<p:sldMasterIdLst>
149<p:sldMasterId id="2147483648" r:id="{}"/>
150</p:sldMasterIdLst>
151<p:sldIdLst>{}
152</p:sldIdLst>
153<p:sldSz cx="{}" cy="{}" type="screen4x3"/>
154<p:notesSz cx="{}" cy="{}"/>
155</p:presentation>"#,
156            master_r_id,
157            slide_id_list,
158            self.slide_width,
159            self.slide_height,
160            self.slide_height,
161            self.slide_width,
162        );
163
164        Ok(xml)
165    }
166
167    fn from_xml(xml: &str) -> Result<Self, PptxError> {
168        let root = XmlParser::parse_str(xml)?;
169        let mut part = PresentationPart::new();
170
171        // Parse slide references
172        for sld_id in root.find_all_descendants("sldId") {
173            if let (Some(id), Some(r_id)) = (sld_id.attr("id"), sld_id.attr("r:id")) {
174                if let Ok(id_num) = id.parse::<u32>() {
175                    part.slide_refs.push(SlideRef {
176                        id: id_num,
177                        r_id: r_id.to_string(),
178                    });
179                }
180            }
181        }
182
183        // Parse slide master reference
184        if let Some(master_id) = root.find_descendant("sldMasterId") {
185            part.slide_master_r_id = master_id.attr("r:id").map(|s| s.to_string());
186        }
187
188        // Parse slide size
189        if let Some(sld_sz) = root.find_descendant("sldSz") {
190            if let Some(cx) = sld_sz.attr("cx").and_then(|s| s.parse().ok()) {
191                part.slide_width = cx;
192            }
193            if let Some(cy) = sld_sz.attr("cy").and_then(|s| s.parse().ok()) {
194                part.slide_height = cy;
195            }
196        }
197
198        Ok(part)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_presentation_part_new() {
208        let part = PresentationPart::new();
209        assert_eq!(part.slide_count(), 0);
210        assert_eq!(part.path(), "ppt/presentation.xml");
211    }
212
213    #[test]
214    fn test_add_slides() {
215        let mut part = PresentationPart::new();
216        part.add_slide("rId3");
217        part.add_slide("rId4");
218
219        assert_eq!(part.slide_count(), 2);
220        assert_eq!(part.slides()[0].r_id, "rId3");
221    }
222
223    #[test]
224    fn test_to_xml() {
225        let mut part = PresentationPart::new();
226        part.set_slide_master("rId1");
227        part.add_slide("rId3");
228
229        let xml = part.to_xml().unwrap();
230        assert!(xml.contains("p:presentation"));
231        assert!(xml.contains("p:sldId"));
232        assert!(xml.contains("rId3"));
233    }
234
235    #[test]
236    fn test_from_xml() {
237        let xml = r#"<?xml version="1.0"?>
238        <p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" 
239                        xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
240            <p:sldMasterIdLst>
241                <p:sldMasterId id="2147483648" r:id="rId1"/>
242            </p:sldMasterIdLst>
243            <p:sldIdLst>
244                <p:sldId id="257" r:id="rId3"/>
245                <p:sldId id="258" r:id="rId4"/>
246            </p:sldIdLst>
247            <p:sldSz cx="9144000" cy="6858000"/>
248        </p:presentation>"#;
249
250        let part = PresentationPart::from_xml(xml).unwrap();
251        assert_eq!(part.slide_count(), 2);
252        assert_eq!(part.slides()[0].id, 257);
253    }
254}