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}