Skip to main content

ppt_rs/parts/
content_types.rs

1//! Content types part
2//!
3//! Represents [Content_Types].xml which defines MIME types for all parts.
4
5use super::base::{ContentType, Part, PartType};
6use crate::exc::PptxError;
7
8/// Default content type mapping (by extension)
9#[derive(Debug, Clone)]
10pub struct DefaultType {
11    pub extension: String,
12    pub content_type: String,
13}
14
15impl DefaultType {
16    pub fn new(extension: impl Into<String>, content_type: impl Into<String>) -> Self {
17        DefaultType {
18            extension: extension.into(),
19            content_type: content_type.into(),
20        }
21    }
22}
23
24/// Override content type mapping (by part name)
25#[derive(Debug, Clone)]
26pub struct OverrideType {
27    pub part_name: String,
28    pub content_type: String,
29}
30
31impl OverrideType {
32    pub fn new(part_name: impl Into<String>, content_type: impl Into<String>) -> Self {
33        OverrideType {
34            part_name: part_name.into(),
35            content_type: content_type.into(),
36        }
37    }
38}
39
40/// Content types part ([Content_Types].xml)
41#[derive(Debug, Clone)]
42pub struct ContentTypesPart {
43    defaults: Vec<DefaultType>,
44    overrides: Vec<OverrideType>,
45}
46
47impl ContentTypesPart {
48    /// Create a new content types part with standard defaults
49    pub fn new() -> Self {
50        ContentTypesPart {
51            defaults: vec![
52                DefaultType::new(
53                    "rels",
54                    "application/vnd.openxmlformats-package.relationships+xml",
55                ),
56                DefaultType::new("xml", "application/xml"),
57                DefaultType::new("jpeg", "image/jpeg"),
58                DefaultType::new("jpg", "image/jpeg"),
59                DefaultType::new("png", "image/png"),
60                DefaultType::new("gif", "image/gif"),
61                DefaultType::new("bmp", "image/bmp"),
62                DefaultType::new("tiff", "image/tiff"),
63                DefaultType::new("svg", "image/svg+xml"),
64                DefaultType::new("mp4", "video/mp4"),
65                DefaultType::new("mp3", "audio/mpeg"),
66                DefaultType::new("wav", "audio/wav"),
67            ],
68            overrides: vec![],
69        }
70    }
71
72    /// Add a default type
73    pub fn add_default(&mut self, extension: impl Into<String>, content_type: impl Into<String>) {
74        self.defaults
75            .push(DefaultType::new(extension, content_type));
76    }
77
78    /// Add an override type
79    pub fn add_override(&mut self, part_name: impl Into<String>, content_type: impl Into<String>) {
80        self.overrides
81            .push(OverrideType::new(part_name, content_type));
82    }
83
84    /// Add presentation part
85    pub fn add_presentation(&mut self) {
86        self.add_override(
87            "/ppt/presentation.xml",
88            "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml",
89        );
90    }
91
92    /// Add slide part
93    pub fn add_slide(&mut self, slide_number: usize) {
94        self.add_override(
95            format!("/ppt/slides/slide{}.xml", slide_number),
96            "application/vnd.openxmlformats-officedocument.presentationml.slide+xml",
97        );
98    }
99
100    /// Add slide layout part
101    pub fn add_slide_layout(&mut self, layout_number: usize) {
102        self.add_override(
103            format!("/ppt/slideLayouts/slideLayout{}.xml", layout_number),
104            "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml",
105        );
106    }
107
108    /// Add slide master part
109    pub fn add_slide_master(&mut self, master_number: usize) {
110        self.add_override(
111            format!("/ppt/slideMasters/slideMaster{}.xml", master_number),
112            "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml",
113        );
114    }
115
116    /// Add theme part
117    pub fn add_theme(&mut self, theme_number: usize) {
118        self.add_override(
119            format!("/ppt/theme/theme{}.xml", theme_number),
120            "application/vnd.openxmlformats-officedocument.theme+xml",
121        );
122    }
123
124    /// Add notes slide part
125    pub fn add_notes_slide(&mut self, notes_number: usize) {
126        self.add_override(
127            format!("/ppt/notesSlides/notesSlide{}.xml", notes_number),
128            "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml",
129        );
130    }
131
132    /// Add chart part
133    pub fn add_chart(&mut self, chart_number: usize) {
134        self.add_override(
135            format!("/ppt/charts/chart{}.xml", chart_number),
136            "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
137        );
138    }
139
140    /// Add core properties
141    pub fn add_core_properties(&mut self) {
142        self.add_override(
143            "/docProps/core.xml",
144            "application/vnd.openxmlformats-package.core-properties+xml",
145        );
146    }
147
148    /// Add app properties
149    pub fn add_app_properties(&mut self) {
150        self.add_override(
151            "/docProps/app.xml",
152            "application/vnd.openxmlformats-officedocument.extended-properties+xml",
153        );
154    }
155
156    fn generate_xml(&self) -> String {
157        let defaults_xml: String = self
158            .defaults
159            .iter()
160            .map(|d| {
161                format!(
162                    r#"<Default Extension="{}" ContentType="{}"/>"#,
163                    d.extension, d.content_type
164                )
165            })
166            .collect::<Vec<_>>()
167            .join("\n  ");
168
169        let overrides_xml: String = self
170            .overrides
171            .iter()
172            .map(|o| {
173                format!(
174                    r#"<Override PartName="{}" ContentType="{}"/>"#,
175                    o.part_name, o.content_type
176                )
177            })
178            .collect::<Vec<_>>()
179            .join("\n  ");
180
181        format!(
182            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
183<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
184  {}
185  {}
186</Types>"#,
187            defaults_xml, overrides_xml
188        )
189    }
190}
191
192impl Default for ContentTypesPart {
193    fn default() -> Self {
194        Self::new()
195    }
196}
197
198impl Part for ContentTypesPart {
199    fn path(&self) -> &str {
200        "[Content_Types].xml"
201    }
202
203    fn part_type(&self) -> PartType {
204        PartType::Relationships
205    }
206
207    fn content_type(&self) -> ContentType {
208        ContentType::Xml
209    }
210
211    fn to_xml(&self) -> Result<String, PptxError> {
212        Ok(self.generate_xml())
213    }
214
215    fn from_xml(_xml: &str) -> Result<Self, PptxError> {
216        // TODO: Parse XML
217        Ok(ContentTypesPart::new())
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_content_types_new() {
227        let ct = ContentTypesPart::new();
228        assert!(!ct.defaults.is_empty());
229    }
230
231    #[test]
232    fn test_content_types_add_slide() {
233        let mut ct = ContentTypesPart::new();
234        ct.add_slide(1);
235        ct.add_slide(2);
236        assert_eq!(ct.overrides.len(), 2);
237    }
238
239    #[test]
240    fn test_content_types_to_xml() {
241        let mut ct = ContentTypesPart::new();
242        ct.add_presentation();
243        ct.add_slide(1);
244        let xml = ct.to_xml().unwrap();
245        assert!(xml.contains("<Types"));
246        assert!(xml.contains("Default Extension"));
247        assert!(xml.contains("Override PartName"));
248    }
249
250    #[test]
251    fn test_content_types_path() {
252        let ct = ContentTypesPart::new();
253        assert_eq!(ct.path(), "[Content_Types].xml");
254    }
255
256    #[test]
257    fn test_content_types_add_all() {
258        let mut ct = ContentTypesPart::new();
259        ct.add_presentation();
260        ct.add_slide(1);
261        ct.add_slide_layout(1);
262        ct.add_slide_master(1);
263        ct.add_theme(1);
264        ct.add_core_properties();
265        ct.add_app_properties();
266        assert_eq!(ct.overrides.len(), 7);
267    }
268}