1use crate::mce::children;
6use crate::model::Emu;
7use crate::opc::Defect;
8use crate::slide::parse_coordinate;
9use crate::xml::{unescape_attr, Event, Ns, Reader, Start, XmlError};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct SlideId {
14 pub id: Option<u32>,
16 pub rel_id: String,
18 pub offset: usize,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct MasterId {
24 pub id: Option<u32>,
25 pub rel_id: String,
26 pub offset: usize,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct SlideSize {
32 pub cx: Emu,
33 pub cy: Emu,
34 pub kind: Option<String>,
36}
37
38#[derive(Clone, Debug, Default, PartialEq, Eq)]
40pub struct Section {
41 pub name: String,
42 pub id: Option<String>,
44 pub slide_ids: Vec<u32>,
46}
47
48#[derive(Clone, Debug, Default, PartialEq, Eq)]
50pub struct Presentation {
51 pub slides: Vec<SlideId>,
52 pub sections: Vec<Section>,
54 pub masters: Vec<MasterId>,
55 pub notes_master: Option<String>,
57 pub handout_master: Option<String>,
59 pub slide_size: Option<SlideSize>,
60 pub notes_size: Option<(Emu, Emu)>,
61 pub first_slide_num: i32,
63 pub rtl: bool,
64 pub root_ok: bool,
66 pub defects: Vec<Defect>,
67}
68
69impl Presentation {
70 pub fn parse(xml: &[u8]) -> Result<Self, XmlError> {
71 let mut reader = Reader::new(xml);
72 let root = loop {
73 match reader.next()? {
74 Event::Start(start) => break start,
75 Event::Eof => {
76 return Err(XmlError {
77 offset: xml.len(),
78 msg: "no root element",
79 })
80 }
81 _ => {}
82 }
83 };
84 let mut presentation = Presentation {
85 first_slide_num: 1,
86 root_ok: root.name.is(Ns::Pml, b"presentation"),
87 ..Presentation::default()
88 };
89 presentation.first_slide_num = attr_i32(&reader, &root, b"firstSlideNum").unwrap_or(1);
90 presentation.rtl = matches!(reader.attr(&root, Ns::None, b"rtl"), Some(b"1" | b"true"));
91 children(&mut reader, &mut |reader, child| {
92 if child.name.ns != Ns::Pml {
93 return Ok(());
94 }
95 match child.name.local {
96 b"sldIdLst" => children(reader, &mut |reader, item| {
97 if !item.name.is(Ns::Pml, b"sldId") {
98 return Ok(());
99 }
100 match reader
101 .attr(&item, Ns::Rel, b"id")
102 .map(unescape_attr)
103 .filter(|id| !id.is_empty())
104 {
105 Some(rel_id) => presentation.slides.push(SlideId {
106 id: attr_u32(reader, &item, b"id"),
107 rel_id,
108 offset: item.offset,
109 }),
110 None => presentation.defects.push(Defect {
111 offset: item.offset,
112 msg: "sldId without r:id",
113 }),
114 }
115 Ok(())
116 }),
117 b"sldMasterIdLst" => children(reader, &mut |reader, item| {
118 if !item.name.is(Ns::Pml, b"sldMasterId") {
119 return Ok(());
120 }
121 match reader
122 .attr(&item, Ns::Rel, b"id")
123 .map(unescape_attr)
124 .filter(|id| !id.is_empty())
125 {
126 Some(rel_id) => presentation.masters.push(MasterId {
127 id: attr_u32(reader, &item, b"id"),
128 rel_id,
129 offset: item.offset,
130 }),
131 None => presentation.defects.push(Defect {
132 offset: item.offset,
133 msg: "sldMasterId without r:id",
134 }),
135 }
136 Ok(())
137 }),
138 b"notesMasterIdLst" => children(reader, &mut |reader, item| {
139 if item.name.is(Ns::Pml, b"notesMasterId")
140 && presentation.notes_master.is_none()
141 {
142 presentation.notes_master =
143 reader.attr(&item, Ns::Rel, b"id").map(unescape_attr);
144 }
145 Ok(())
146 }),
147 b"handoutMasterIdLst" => children(reader, &mut |reader, item| {
148 if item.name.is(Ns::Pml, b"handoutMasterId")
149 && presentation.handout_master.is_none()
150 {
151 presentation.handout_master =
152 reader.attr(&item, Ns::Rel, b"id").map(unescape_attr);
153 }
154 Ok(())
155 }),
156 b"sldSz" => {
157 let cx = reader
158 .attr(&child, Ns::None, b"cx")
159 .and_then(parse_coordinate);
160 let cy = reader
161 .attr(&child, Ns::None, b"cy")
162 .and_then(parse_coordinate);
163 match (cx, cy) {
164 (Some(cx), Some(cy)) => {
165 presentation.slide_size = Some(SlideSize {
166 cx,
167 cy,
168 kind: reader.attr(&child, Ns::None, b"type").map(unescape_attr),
169 });
170 }
171 _ => presentation.defects.push(Defect {
172 offset: child.offset,
173 msg: "sldSz without numeric cx and cy",
174 }),
175 }
176 Ok(())
177 }
178 b"notesSz" => {
179 let cx = reader
180 .attr(&child, Ns::None, b"cx")
181 .and_then(parse_coordinate);
182 let cy = reader
183 .attr(&child, Ns::None, b"cy")
184 .and_then(parse_coordinate);
185 match (cx, cy) {
186 (Some(cx), Some(cy)) => presentation.notes_size = Some((cx, cy)),
187 _ => presentation.defects.push(Defect {
188 offset: child.offset,
189 msg: "notesSz without numeric cx and cy",
190 }),
191 }
192 Ok(())
193 }
194 b"extLst" => children(reader, &mut |reader, _| {
195 children(reader, &mut |reader, inner| {
196 if !inner.name.is(Ns::P14, b"sectionLst") {
197 return Ok(());
198 }
199 parse_sections(reader, &mut presentation.sections)
200 })
201 }),
202 _ => Ok(()),
203 }
204 })?;
205 Ok(presentation)
206 }
207}
208
209fn parse_sections<'a>(
211 reader: &mut Reader<'a>,
212 sections: &mut Vec<Section>,
213) -> Result<(), XmlError> {
214 children(reader, &mut |reader, section| {
215 if !section.name.is(Ns::P14, b"section") {
216 return Ok(());
217 }
218 let mut item = Section {
219 name: reader
220 .attr(§ion, Ns::None, b"name")
221 .map(unescape_attr)
222 .unwrap_or_default(),
223 id: reader.attr(§ion, Ns::None, b"id").map(unescape_attr),
224 slide_ids: Vec::new(),
225 };
226 children(reader, &mut |reader, list| {
227 if !list.name.is(Ns::P14, b"sldIdLst") {
228 return Ok(());
229 }
230 children(reader, &mut |reader, slide| {
231 if slide.name.is(Ns::P14, b"sldId") {
232 if let Some(id) = attr_u32(reader, &slide, b"id") {
233 item.slide_ids.push(id);
234 }
235 }
236 Ok(())
237 })
238 })?;
239 sections.push(item);
240 Ok(())
241 })
242}
243
244fn attr_u32(reader: &Reader<'_>, start: &Start<'_>, local: &[u8]) -> Option<u32> {
245 std::str::from_utf8(reader.attr(start, Ns::None, local)?)
246 .ok()?
247 .trim()
248 .parse()
249 .ok()
250}
251
252fn attr_i32(reader: &Reader<'_>, start: &Start<'_>, local: &[u8]) -> Option<i32> {
253 std::str::from_utf8(reader.attr(start, Ns::None, local)?)
254 .ok()?
255 .trim()
256 .parse()
257 .ok()
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 const XML: &[u8] = br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
265<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" firstSlideNum="5" rtl="1" saveSubsetFonts="1">
266<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
267<p:notesMasterIdLst><p:notesMasterId r:id="rId6"/></p:notesMasterIdLst>
268<p:handoutMasterIdLst><p:handoutMasterId r:id="rId7"/></p:handoutMasterIdLst>
269<p:sldIdLst><p:sldId id="256" r:id="rId2"/><p:sldId id="257" r:id="rId3"/><p:sldId id="x" r:id="rId4"/><p:sldId id="259"/></p:sldIdLst>
270<p:sldSz cx="12192000" cy="6858000" type="screen16x9"/><p:notesSz cx="6858000" cy="9144000"/>
271<p:defaultTextStyle><a:defPPr/></p:defaultTextStyle>
272</p:presentation>"#;
273
274 #[test]
275 fn slide_order_masters_and_sizes_are_read() {
276 let presentation = Presentation::parse(XML).unwrap();
277 assert!(presentation.root_ok);
278 assert_eq!(presentation.first_slide_num, 5);
279 assert!(presentation.rtl);
280 assert_eq!(presentation.slides.len(), 3);
281 assert_eq!(presentation.slides[0].id, Some(256));
282 assert_eq!(presentation.slides[0].rel_id, "rId2");
283 assert_eq!(presentation.slides[2].id, None);
284 assert_eq!(presentation.slides[2].rel_id, "rId4");
285 assert_eq!(presentation.defects.len(), 1);
286 assert_eq!(presentation.defects[0].msg, "sldId without r:id");
287 assert_eq!(presentation.masters.len(), 1);
288 assert_eq!(presentation.masters[0].id, Some(2147483648));
289 assert_eq!(presentation.notes_master.as_deref(), Some("rId6"));
290 assert_eq!(presentation.handout_master.as_deref(), Some("rId7"));
291 assert_eq!(
292 presentation.slide_size,
293 Some(SlideSize {
294 cx: 12192000,
295 cy: 6858000,
296 kind: Some("screen16x9".into())
297 })
298 );
299 assert_eq!(presentation.notes_size, Some((6858000, 9144000)));
300 }
301
302 #[test]
303 fn a_minimal_presentation_and_a_wrong_root_parse() {
304 let minimal = br#"<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:notesSz cx="913607" cy="913607"/></p:presentation>"#;
305 let presentation = Presentation::parse(minimal).unwrap();
306 assert!(presentation.slides.is_empty());
307 assert_eq!(presentation.first_slide_num, 1);
308 assert_eq!(presentation.notes_size, Some((913607, 913607)));
309 let wrong = Presentation::parse(b"<x/>").unwrap();
310 assert!(!wrong.root_ok);
311 assert!(Presentation::parse(b"").is_err());
312 }
313}