Skip to main content

sheetkit_xml/
content_types.rs

1//! [Content_Types].xml schema structures.
2//!
3//! Defines the content types for all parts in the OOXML package.
4
5use serde::{Deserialize, Serialize};
6
7use crate::namespaces;
8
9/// `[Content_Types].xml` root element.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename = "Types")]
12pub struct ContentTypes {
13    #[serde(rename = "@xmlns")]
14    pub xmlns: String,
15
16    #[serde(rename = "Default", default)]
17    pub defaults: Vec<ContentTypeDefault>,
18
19    #[serde(rename = "Override", default)]
20    pub overrides: Vec<ContentTypeOverride>,
21}
22
23/// Extension-based default content type mapping.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct ContentTypeDefault {
26    #[serde(rename = "@Extension")]
27    pub extension: String,
28
29    #[serde(rename = "@ContentType")]
30    pub content_type: String,
31}
32
33/// Path-specific content type override.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct ContentTypeOverride {
36    #[serde(rename = "@PartName")]
37    pub part_name: String,
38
39    #[serde(rename = "@ContentType")]
40    pub content_type: String,
41}
42
43impl Default for ContentTypes {
44    fn default() -> Self {
45        Self {
46            xmlns: namespaces::CONTENT_TYPES.to_string(),
47            defaults: vec![
48                ContentTypeDefault {
49                    extension: "rels".to_string(),
50                    content_type: mime_types::RELS.to_string(),
51                },
52                ContentTypeDefault {
53                    extension: "xml".to_string(),
54                    content_type: mime_types::XML.to_string(),
55                },
56            ],
57            overrides: vec![
58                ContentTypeOverride {
59                    part_name: "/xl/workbook.xml".to_string(),
60                    content_type: mime_types::WORKBOOK.to_string(),
61                },
62                ContentTypeOverride {
63                    part_name: "/xl/worksheets/sheet1.xml".to_string(),
64                    content_type: mime_types::WORKSHEET.to_string(),
65                },
66                ContentTypeOverride {
67                    part_name: "/xl/styles.xml".to_string(),
68                    content_type: mime_types::STYLES.to_string(),
69                },
70                ContentTypeOverride {
71                    part_name: "/xl/sharedStrings.xml".to_string(),
72                    content_type: mime_types::SHARED_STRINGS.to_string(),
73                },
74            ],
75        }
76    }
77}
78
79/// Standard content type MIME string constants.
80pub mod mime_types {
81    // Default extensions
82    pub const RELS: &str = "application/vnd.openxmlformats-package.relationships+xml";
83    pub const XML: &str = "application/xml";
84    pub const PNG: &str = "image/png";
85    pub const JPEG: &str = "image/jpeg";
86    pub const GIF: &str = "image/gif";
87    pub const BMP: &str = "image/bmp";
88    pub const ICO: &str = "image/x-icon";
89    pub const TIFF: &str = "image/tiff";
90    pub const SVG: &str = "image/svg+xml";
91    pub const EMF: &str = "image/x-emf";
92    pub const EMZ: &str = "image/x-emz";
93    pub const WMF: &str = "image/x-wmf";
94    pub const WMZ: &str = "image/x-wmz";
95
96    // Workbook
97    pub const WORKBOOK: &str =
98        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
99    pub const WORKBOOK_MACRO: &str = "application/vnd.ms-excel.sheet.macroEnabled.main+xml";
100    pub const WORKBOOK_TEMPLATE: &str =
101        "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml";
102    pub const WORKBOOK_TEMPLATE_MACRO: &str =
103        "application/vnd.ms-excel.template.macroEnabled.main+xml";
104    pub const WORKBOOK_ADDIN_MACRO: &str = "application/vnd.ms-excel.addin.macroEnabled.main+xml";
105
106    // Worksheet
107    pub const WORKSHEET: &str =
108        "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
109    pub const CHARTSHEET: &str =
110        "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml";
111
112    // Shared elements
113    pub const SHARED_STRINGS: &str =
114        "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
115    pub const STYLES: &str =
116        "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
117    pub const THEME: &str = "application/vnd.openxmlformats-officedocument.theme+xml";
118
119    // Charts and drawings
120    pub const CHART: &str = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
121    pub const DRAWING: &str = "application/vnd.openxmlformats-officedocument.drawing+xml";
122
123    // Table
124    pub const TABLE: &str = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml";
125
126    // VML Drawing
127    pub const VML_DRAWING: &str = "application/vnd.openxmlformats-officedocument.vmlDrawing";
128
129    // Comments
130    pub const COMMENTS: &str =
131        "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml";
132
133    // Pivot tables
134    pub const PIVOT_TABLE: &str =
135        "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml";
136    pub const PIVOT_CACHE_DEFINITION: &str =
137        "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml";
138    pub const PIVOT_CACHE_RECORDS: &str =
139        "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml";
140
141    // Slicers
142    pub const SLICER: &str = "application/vnd.ms-excel.slicer+xml";
143    pub const SLICER_CACHE: &str = "application/vnd.ms-excel.slicerCache+xml";
144
145    // Document properties
146    pub const CORE_PROPERTIES: &str = "application/vnd.openxmlformats-package.core-properties+xml";
147    pub const EXTENDED_PROPERTIES: &str =
148        "application/vnd.openxmlformats-officedocument.extended-properties+xml";
149    pub const CUSTOM_PROPERTIES: &str =
150        "application/vnd.openxmlformats-officedocument.custom-properties+xml";
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_content_types_default() {
159        let ct = ContentTypes::default();
160        assert_eq!(ct.xmlns, namespaces::CONTENT_TYPES);
161        assert_eq!(ct.defaults.len(), 2);
162        assert_eq!(ct.overrides.len(), 4);
163
164        // Check default extensions
165        assert_eq!(ct.defaults[0].extension, "rels");
166        assert_eq!(ct.defaults[1].extension, "xml");
167
168        // Check overrides contain workbook, worksheet, styles, shared strings
169        let part_names: Vec<&str> = ct.overrides.iter().map(|o| o.part_name.as_str()).collect();
170        assert!(part_names.contains(&"/xl/workbook.xml"));
171        assert!(part_names.contains(&"/xl/worksheets/sheet1.xml"));
172        assert!(part_names.contains(&"/xl/styles.xml"));
173        assert!(part_names.contains(&"/xl/sharedStrings.xml"));
174    }
175
176    #[test]
177    fn test_content_types_roundtrip() {
178        let ct = ContentTypes::default();
179        let xml = quick_xml::se::to_string(&ct).unwrap();
180        let parsed: ContentTypes = quick_xml::de::from_str(&xml).unwrap();
181        assert_eq!(ct.defaults.len(), parsed.defaults.len());
182        assert_eq!(ct.overrides.len(), parsed.overrides.len());
183        assert_eq!(ct.xmlns, parsed.xmlns);
184    }
185
186    #[test]
187    fn test_content_types_serialize_structure() {
188        let ct = ContentTypes::default();
189        let xml = quick_xml::se::to_string(&ct).unwrap();
190
191        // Should contain Types as root element
192        assert!(xml.contains("<Types"));
193        assert!(xml.contains("xmlns="));
194        // Should contain Default elements
195        assert!(xml.contains("<Default"));
196        assert!(xml.contains("Extension="));
197        assert!(xml.contains("ContentType="));
198        // Should contain Override elements
199        assert!(xml.contains("<Override"));
200        assert!(xml.contains("PartName="));
201    }
202
203    #[test]
204    fn test_parse_real_excel_content_types() {
205        let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
206<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
207  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
208  <Default Extension="xml" ContentType="application/xml"/>
209  <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
210  <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
211  <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
212  <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
213</Types>"#;
214
215        let parsed: ContentTypes = quick_xml::de::from_str(xml).unwrap();
216        assert_eq!(parsed.defaults.len(), 2);
217        assert_eq!(parsed.overrides.len(), 4);
218        assert_eq!(parsed.defaults[0].extension, "rels");
219        assert_eq!(parsed.overrides[0].part_name, "/xl/workbook.xml");
220    }
221
222    #[test]
223    fn test_content_type_default_fields() {
224        let default = ContentTypeDefault {
225            extension: "png".to_string(),
226            content_type: mime_types::PNG.to_string(),
227        };
228        let xml = quick_xml::se::to_string(&default).unwrap();
229        assert!(xml.contains("Extension=\"png\""));
230        assert!(xml.contains("ContentType=\"image/png\""));
231    }
232}