oxml_opc/
content_types.rs1use std::collections::HashMap;
4
5use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
6use quick_xml::{Reader, Writer};
7
8use crate::error::{OpcError, Result};
9
10pub const RELATIONSHIPS: &str = "application/vnd.openxmlformats-package.relationships+xml";
11pub const XML: &str = "application/xml";
12
13pub const CORE_PROPERTIES: &str = "application/vnd.openxmlformats-package.core-properties+xml";
14pub const EXTENDED_PROPERTIES: &str =
15 "application/vnd.openxmlformats-officedocument.extended-properties+xml";
16pub const CUSTOM_PROPERTIES: &str =
17 "application/vnd.openxmlformats-officedocument.custom-properties+xml";
18pub const THEME: &str = "application/vnd.openxmlformats-officedocument.theme+xml";
19pub const CHART: &str = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
20
21pub const PRESENTATION: &str =
22 "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
23pub const SLIDESHOW: &str =
24 "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml";
25pub const SLIDE: &str = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
26pub const SLIDE_LAYOUT: &str =
27 "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
28pub const SLIDE_MASTER: &str =
29 "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
30pub const NOTES_SLIDE: &str =
31 "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
32pub const NOTES_MASTER: &str =
33 "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
34pub const PRES_PROPS: &str =
35 "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml";
36pub const VIEW_PROPS: &str =
37 "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml";
38pub const TABLE_STYLES: &str =
39 "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
40pub const HANDOUT_MASTER: &str =
41 "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml";
42
43pub const WORKBOOK: &str =
44 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
45pub const EMBEDDED_WORKBOOK: &str =
46 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
47pub const WORKSHEET: &str =
48 "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
49pub const SHARED_STRINGS: &str =
50 "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
51pub const STYLES: &str = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
52
53#[derive(Debug, Clone, PartialEq)]
55pub enum ContentType {
56 Default {
57 extension: String,
58 content_type: String,
59 },
60 Override {
61 part_name: String,
62 content_type: String,
63 },
64}
65
66#[derive(Debug, Clone)]
68pub struct ContentTypes {
69 pub defaults: HashMap<String, String>,
70 pub overrides: HashMap<String, String>,
71}
72
73impl ContentTypes {
74 pub fn from_xml(xml: &[u8]) -> Result<Self> {
76 let mut reader = Reader::from_reader(xml);
77 reader.config_mut().trim_text(true);
78
79 let mut defaults = HashMap::new();
80 let mut overrides = HashMap::new();
81 let mut buf = Vec::new();
82
83 loop {
84 match reader.read_event_into(&mut buf) {
85 Ok(Event::Empty(ref e)) => match e.name().as_ref() {
86 b"Default" => {
87 let mut ext = None;
88 let mut ct = None;
89 for attr in e.attributes() {
90 let attr = attr?;
91 match attr.key.as_ref() {
92 b"Extension" => {
93 ext = Some(std::str::from_utf8(&attr.value)?.to_string());
94 }
95 b"ContentType" => {
96 ct = Some(std::str::from_utf8(&attr.value)?.to_string());
97 }
98 _ => {}
99 }
100 }
101 match (ext, ct) {
102 (Some(e), Some(c)) => {
103 defaults.insert(e, c);
104 }
105 _ => return Err(OpcError::InvalidContentTypes),
106 }
107 }
108 b"Override" => {
109 let mut pn = None;
110 let mut ct = None;
111 for attr in e.attributes() {
112 let attr = attr?;
113 match attr.key.as_ref() {
114 b"PartName" => {
115 pn = Some(std::str::from_utf8(&attr.value)?.to_string());
116 }
117 b"ContentType" => {
118 ct = Some(std::str::from_utf8(&attr.value)?.to_string());
119 }
120 _ => {}
121 }
122 }
123 match (pn, ct) {
124 (Some(p), Some(c)) => {
125 overrides.insert(p, c);
126 }
127 _ => return Err(OpcError::InvalidContentTypes),
128 }
129 }
130 _ => {}
131 },
132 Ok(Event::Eof) => break,
133 Err(e) => return Err(e.into()),
134 _ => {}
135 }
136 buf.clear();
137 }
138
139 Ok(ContentTypes {
140 defaults,
141 overrides,
142 })
143 }
144
145 pub fn to_xml(&self) -> Result<Vec<u8>> {
147 let mut writer = Writer::new_with_indent(Vec::new(), b' ', 2);
148
149 writer.write_event(Event::Decl(BytesDecl::new(
150 "1.0",
151 Some("UTF-8"),
152 Some("yes"),
153 )))?;
154
155 let mut types_start = BytesStart::new("Types");
156 types_start.push_attribute((
157 "xmlns",
158 "http://schemas.openxmlformats.org/package/2006/content-types",
159 ));
160 writer.write_event(Event::Start(types_start))?;
161
162 let mut sorted_defaults: Vec<_> = self.defaults.iter().collect();
164 sorted_defaults.sort_by_key(|(k, _)| (*k).clone());
165 for (ext, ct) in sorted_defaults {
166 let mut elem = BytesStart::new("Default");
167 elem.push_attribute(("Extension", ext.as_str()));
168 elem.push_attribute(("ContentType", ct.as_str()));
169 writer.write_event(Event::Empty(elem))?;
170 }
171
172 let mut sorted_overrides: Vec<_> = self.overrides.iter().collect();
174 sorted_overrides.sort_by_key(|(k, _)| (*k).clone());
175 for (pn, ct) in sorted_overrides {
176 let mut elem = BytesStart::new("Override");
177 elem.push_attribute(("PartName", pn.as_str()));
178 elem.push_attribute(("ContentType", ct.as_str()));
179 writer.write_event(Event::Empty(elem))?;
180 }
181
182 writer.write_event(Event::End(BytesEnd::new("Types")))?;
183
184 Ok(writer.into_inner())
185 }
186
187 pub fn content_type_for(&self, part_name: &str) -> Option<&str> {
189 if let Some(ct) = self.overrides.get(part_name) {
191 return Some(ct.as_str());
192 }
193 if let Some(dot_pos) = part_name.rfind('.') {
195 let ext = &part_name[dot_pos + 1..];
196 if let Some(ct) = self.defaults.get(ext) {
197 return Some(ct.as_str());
198 }
199 }
200 None
201 }
202
203 pub fn add_default(&mut self, extension: &str, content_type: &str) {
205 self.defaults
206 .entry(extension.to_string())
207 .or_insert_with(|| content_type.to_string());
208 }
209
210 pub fn add_override(&mut self, part_name: &str, content_type: &str) {
212 self.overrides
213 .insert(part_name.to_string(), content_type.to_string());
214 }
215
216 pub fn minimal() -> Self {
218 let mut defaults = HashMap::new();
219 defaults.insert("rels".to_string(), RELATIONSHIPS.to_string());
220 defaults.insert("xml".to_string(), XML.to_string());
221
222 ContentTypes {
223 defaults,
224 overrides: HashMap::new(),
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 fn docx_content_types() -> ContentTypes {
234 let mut content_types = ContentTypes::minimal();
235 content_types.add_override(
236 "/word/document.xml",
237 "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
238 );
239 content_types.add_override(
240 "/word/styles.xml",
241 "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
242 );
243 content_types
244 }
245
246 #[test]
247 fn minimal_content_types_contain_only_universal_defaults() {
248 let content_types = ContentTypes::minimal();
249
250 assert_eq!(content_types.defaults.len(), 2);
251 assert_eq!(
252 content_types.defaults.get("rels").map(String::as_str),
253 Some("application/vnd.openxmlformats-package.relationships+xml")
254 );
255 assert_eq!(
256 content_types.defaults.get("xml").map(String::as_str),
257 Some("application/xml")
258 );
259 assert!(content_types.overrides.is_empty());
260 }
261
262 #[test]
263 fn round_trip_content_types() {
264 let ct = docx_content_types();
265 let xml = ct.to_xml().unwrap();
266 let parsed = ContentTypes::from_xml(&xml).unwrap();
267 assert_eq!(parsed.defaults.len(), ct.defaults.len());
268 assert_eq!(parsed.overrides.len(), ct.overrides.len());
269 assert_eq!(
270 parsed.content_type_for("/word/document.xml"),
271 Some(
272 "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
273 )
274 );
275 }
276
277 #[test]
278 fn lookup_by_extension() {
279 let ct = docx_content_types();
280 assert_eq!(
281 ct.content_type_for("/word/_rels/document.xml.rels"),
282 Some("application/vnd.openxmlformats-package.relationships+xml")
283 );
284 }
285}