Skip to main content

docx_rs/documents/
content_types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use std::io::{Read, Write};
4use xml::reader::{EventReader, XmlEvent};
5
6use crate::documents::BuildXML;
7use crate::reader::{FromXML, ReaderError};
8use crate::xml_builder::*;
9
10#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
11pub struct ContentTypes {
12    types: BTreeMap<String, String>,
13    web_extension_count: usize,
14    custom_xml_count: usize,
15    header_count: usize,
16    footer_count: usize,
17}
18
19impl ContentTypes {
20    pub fn new() -> ContentTypes {
21        Default::default()
22    }
23
24    pub fn add_content(mut self, path: impl Into<String>, namespace: impl Into<String>) -> Self {
25        self.types.insert(path.into(), namespace.into());
26        self
27    }
28
29    pub fn set_default(mut self) -> ContentTypes {
30        self.types.insert(
31            "/_rels/.rels".to_owned(),
32            "application/vnd.openxmlformats-package.relationships+xml".to_owned(),
33        );
34        self.types.insert(
35            "/docProps/app.xml".to_owned(),
36            "application/vnd.openxmlformats-officedocument.extended-properties+xml".to_owned(),
37        );
38        self.types.insert(
39            "/docProps/core.xml".to_owned(),
40            "application/vnd.openxmlformats-package.core-properties+xml".to_owned(),
41        );
42        self.types.insert(
43            "/word/_rels/document.xml.rels".to_owned(),
44            "application/vnd.openxmlformats-package.relationships+xml".to_owned(),
45        );
46        self.types.insert(
47            "/word/settings.xml".to_owned(),
48            "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"
49                .to_owned(),
50        );
51        self.types.insert(
52            "/word/fontTable.xml".to_owned(),
53            "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"
54                .to_owned(),
55        );
56        self.types.insert(
57            "/word/document.xml".to_owned(),
58            "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
59                .to_owned(),
60        );
61        self.types.insert(
62            "/word/styles.xml".to_owned(),
63            "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml".to_owned(),
64        );
65        self.types.insert(
66            "/word/comments.xml".to_owned(),
67            "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"
68                .to_owned(),
69        );
70        self.types.insert(
71            "/word/numbering.xml".to_owned(),
72            "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"
73                .to_owned(),
74        );
75        self.types.insert(
76            "/word/commentsExtended.xml".to_owned(),
77            "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"
78                .to_owned(),
79        );
80        self.types.insert(
81            "/docProps/custom.xml".to_owned(),
82            "application/vnd.openxmlformats-officedocument.custom-properties+xml".to_owned(),
83        );
84        self
85    }
86
87    pub fn add_taskpanes(mut self) -> Self {
88        self.types.insert(
89            "/word/webextensions/taskpanes.xml".to_owned(),
90            "application/vnd.ms-office.webextensiontaskpanes+xml".to_owned(),
91        );
92        self
93    }
94
95    pub fn add_web_extensions(mut self) -> Self {
96        self.types.insert(
97            format!(
98                "/word/webextensions/webextension{}.xml",
99                self.web_extension_count
100            ),
101            "application/vnd.ms-office.webextension+xml".to_owned(),
102        );
103        self.web_extension_count += 1;
104        self
105    }
106
107    pub fn add_custom_xml(mut self) -> Self {
108        self.types.insert(
109            format!("/customXml/itemProps{}.xml", self.web_extension_count),
110            "application/vnd.openxmlformats-officedocument.customXmlProperties+xml".to_owned(),
111        );
112        self.custom_xml_count += 1;
113        self
114    }
115
116    pub fn add_header(mut self) -> Self {
117        self.header_count += 1;
118        self.types.insert(
119            format!("/word/header{}.xml", self.header_count),
120            "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml".to_owned(),
121        );
122        self
123    }
124
125    pub fn add_footer(mut self) -> Self {
126        self.footer_count += 1;
127        self.types.insert(
128            format!("/word/footer{}.xml", self.footer_count),
129            "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml".to_owned(),
130        );
131        self
132    }
133    pub fn add_footnotes(mut self) -> Self {
134        self.types.insert(
135            "/word/footnotes.xml".to_owned(),
136            "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"
137                .to_owned(),
138        );
139        self
140    }
141}
142
143impl Default for ContentTypes {
144    fn default() -> Self {
145        ContentTypes {
146            types: BTreeMap::new(),
147            web_extension_count: 1,
148            custom_xml_count: 1,
149            header_count: 0,
150            footer_count: 0,
151        }
152    }
153}
154
155impl BuildXML for ContentTypes {
156    fn build_to<W: Write>(
157        &self,
158        stream: xml::writer::EventWriter<W>,
159    ) -> xml::writer::Result<xml::writer::EventWriter<W>> {
160        XMLBuilder::from(stream)
161            .declaration(None)?
162            .open_types("http://schemas.openxmlformats.org/package/2006/content-types")?
163            .add_default("png", "image/png")?
164            .add_default("jpeg", "image/jpeg")?
165            .add_default("jpg", "image/jpg")?
166            .add_default("bmp", "image/bmp")?
167            .add_default("gif", "image/gif")?
168            .add_default(
169                "rels",
170                "application/vnd.openxmlformats-package.relationships+xml",
171            )?
172            .add_default("xml", "application/xml")?
173            .apply_each(self.types.iter(), |(k, v), b| b.add_override(k, v))?
174            .close()?
175            .into_inner()
176    }
177}
178
179impl FromXML for ContentTypes {
180    fn from_xml<R: Read>(reader: R) -> Result<Self, ReaderError> {
181        let parser = EventReader::new(reader);
182        let mut s = Self::default();
183        let mut depth = 0;
184        for e in parser {
185            match e {
186                Ok(XmlEvent::StartElement { attributes, .. }) => {
187                    if depth == 1 {
188                        let namespace = attributes[0].value.clone();
189                        let path = attributes[1].value.clone();
190                        s = s.add_content(path, namespace);
191                    }
192                    depth += 1;
193                }
194                Ok(XmlEvent::EndElement { .. }) => {
195                    depth -= 1;
196                }
197                Err(_) => {}
198                _ => {}
199            }
200        }
201        Ok(s)
202    }
203}
204
205#[cfg(test)]
206mod tests {
207
208    use super::*;
209    #[cfg(test)]
210    use pretty_assertions::assert_eq;
211
212    #[test]
213    fn test_from_xml() {
214        let xml = r#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml"></Override></Types>"#;
215        let c = ContentTypes::from_xml(xml.as_bytes()).unwrap();
216        let mut types = BTreeMap::new();
217        types.insert(
218            "/word/document.xml".to_owned(),
219            "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
220                .to_owned(),
221        );
222        assert_eq!(
223            ContentTypes {
224                types,
225                web_extension_count: 1,
226                custom_xml_count: 1,
227                header_count: 0,
228                footer_count: 0,
229            },
230            c
231        );
232    }
233}