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                && 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        // Parse slide master reference
183        if let Some(master_id) = root.find_descendant("sldMasterId") {
184            part.slide_master_r_id = master_id.attr("r:id").map(|s| s.to_string());
185        }
186
187        // Parse slide size
188        if let Some(sld_sz) = root.find_descendant("sldSz") {
189            if let Some(cx) = sld_sz.attr("cx").and_then(|s| s.parse().ok()) {
190                part.slide_width = cx;
191            }
192            if let Some(cy) = sld_sz.attr("cy").and_then(|s| s.parse().ok()) {
193                part.slide_height = cy;
194            }
195        }
196
197        Ok(part)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_presentation_part_new() {
207        let part = PresentationPart::new();
208        assert_eq!(part.slide_count(), 0);
209        assert_eq!(part.path(), "ppt/presentation.xml");
210    }
211
212    #[test]
213    fn test_add_slides() {
214        let mut part = PresentationPart::new();
215        part.add_slide("rId3");
216        part.add_slide("rId4");
217
218        assert_eq!(part.slide_count(), 2);
219        assert_eq!(part.slides()[0].r_id, "rId3");
220    }
221
222    #[test]
223    fn test_to_xml() {
224        let mut part = PresentationPart::new();
225        part.set_slide_master("rId1");
226        part.add_slide("rId3");
227
228        let xml = part.to_xml().unwrap();
229        assert!(xml.contains("p:presentation"));
230        assert!(xml.contains("p:sldId"));
231        assert!(xml.contains("rId3"));
232    }
233
234    #[test]
235    fn test_from_xml() {
236        let xml = r#"<?xml version="1.0"?>
237        <p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" 
238                        xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
239            <p:sldMasterIdLst>
240                <p:sldMasterId id="2147483648" r:id="rId1"/>
241            </p:sldMasterIdLst>
242            <p:sldIdLst>
243                <p:sldId id="257" r:id="rId3"/>
244                <p:sldId id="258" r:id="rId4"/>
245            </p:sldIdLst>
246            <p:sldSz cx="9144000" cy="6858000"/>
247        </p:presentation>"#;
248
249        let part = PresentationPart::from_xml(xml).unwrap();
250        assert_eq!(part.slide_count(), 2);
251        assert_eq!(part.slides()[0].id, 257);
252    }
253}