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