1use std::collections::HashMap;
14use std::io::{Read, Seek};
15
16use drawing::{Fill, Geometry, PresetShape, ShapeProperties, TextAnchor, TextBody};
17use opc::{Package, Relationships, TargetMode};
18use xml_core::{BytesStart, Event, Reader};
19
20use crate::error::{Error, Result};
21use crate::model::{
22 AutoShape, ColorScheme, Connector, CustomPropertyValue, DEFAULT_SLIDE_HEIGHT_EMU,
23 DEFAULT_SLIDE_WIDTH_EMU, DocumentProperties, EmbeddedFont, FontScheme, MediaFormat, Picture,
24 PictureFormat, Placeholder, PlaceholderKind, Presentation, Shape, ShapeConnection, ShapeGroup,
25 Slide, SlideChart, SlideComment, SlideHyperlinkTarget, SlideMedia, SlideTable, SlideTableStyle,
26 TableCell, TableCellProperties, TableRow, TableStylePart, Theme,
27};
28
29const OFFICE_DOCUMENT_RELATIONSHIP_TYPE: &str =
30 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
31const NOTES_SLIDE_RELATIONSHIP_TYPE: &str =
32 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide";
33const SLIDE_RELATIONSHIP_TYPE: &str =
38 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
39const SLIDE_MASTER_RELATIONSHIP_TYPE: &str =
40 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster";
41const THEME_RELATIONSHIP_TYPE: &str =
42 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
43const COMMENT_AUTHORS_RELATIONSHIP_TYPE: &str =
44 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors";
45const COMMENT_RELATIONSHIP_TYPE: &str =
46 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
47const TABLE_NAMESPACE: &str = "http://schemas.openxmlformats.org/drawingml/2006/table";
48const CORE_PROPERTIES_RELATIONSHIP_TYPE: &str =
49 "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
50const EXTENDED_PROPERTIES_RELATIONSHIP_TYPE: &str =
51 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
52const CUSTOM_PROPERTIES_RELATIONSHIP_TYPE: &str =
53 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties";
54const TABLE_STYLES_RELATIONSHIP_TYPE: &str =
58 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles";
59const TABLE_STYLE_GUID: &str = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
64
65impl Presentation {
66 pub fn read_from<R: Read + Seek>(reader: R) -> Result<Self> {
68 let package = Package::read_from(reader)?;
69
70 let main_relationship = package
71 .relationships()
72 .iter()
73 .find(|relationship| relationship.rel_type == OFFICE_DOCUMENT_RELATIONSHIP_TYPE)
74 .ok_or(Error::MissingMainPresentation)?;
75 let presentation_part_name = match main_relationship.target_mode {
76 TargetMode::Internal => format!("/{}", main_relationship.target),
77 TargetMode::External => return Err(Error::MissingMainPresentation),
78 };
79 let presentation_part = package
80 .part(&presentation_part_name)
81 .ok_or(Error::MissingMainPresentation)?;
82 let xml = std::str::from_utf8(&presentation_part.data)
83 .map_err(|error| Error::InvalidUtf8(presentation_part_name.clone(), error))?;
84
85 let parsed = parse_presentation_xml(xml)?;
86
87 let mut presentation = Presentation::new();
88 presentation.slide_width_emu = parsed.slide_width_emu;
89 presentation.slide_height_emu = parsed.slide_height_emu;
90 presentation.theme = read_presentation_theme(&package, &presentation_part.relationships)?;
91
92 if let Some(relationship) = presentation_part
95 .relationships
96 .iter()
97 .find(|relationship| relationship.rel_type == TABLE_STYLES_RELATIONSHIP_TYPE)
98 {
99 let part_name = format!("/ppt/{}", relationship.target);
100 if let Some(part) = package.part(&part_name) {
101 let xml = std::str::from_utf8(&part.data)
102 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
103 presentation.table_styles = parse_table_styles_xml(xml)?;
104 }
105 }
106
107 for parsed_font in &parsed.embedded_fonts {
112 presentation.embedded_fonts.push(EmbeddedFont {
113 typeface: parsed_font.typeface.clone(),
114 regular: resolve_embedded_font_variant(
115 &package,
116 &presentation_part.relationships,
117 &parsed_font.regular,
118 ),
119 bold: resolve_embedded_font_variant(
120 &package,
121 &presentation_part.relationships,
122 &parsed_font.bold,
123 ),
124 italic: resolve_embedded_font_variant(
125 &package,
126 &presentation_part.relationships,
127 &parsed_font.italic,
128 ),
129 bold_italic: resolve_embedded_font_variant(
130 &package,
131 &presentation_part.relationships,
132 &parsed_font.bold_italic,
133 ),
134 });
135 }
136
137 presentation.properties = read_document_properties(&package)?;
141
142 let comment_authors = match presentation_part
147 .relationships
148 .iter()
149 .find(|relationship| relationship.rel_type == COMMENT_AUTHORS_RELATIONSHIP_TYPE)
150 {
151 Some(relationship) => {
152 let part_name = format!("/ppt/{}", relationship.target);
153 match package.part(&part_name) {
154 Some(part) => {
155 let xml = std::str::from_utf8(&part.data)
156 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
157 parse_comment_authors_xml(xml)?
158 }
159 None => HashMap::new(),
160 }
161 }
162 None => HashMap::new(),
163 };
164
165 for relationship_id in &parsed.slide_relationship_ids {
166 let relationship = presentation_part
167 .relationships
168 .by_id(relationship_id)
169 .ok_or_else(|| Error::MissingSlideRelationship(relationship_id.clone()))?;
170 let slide_part_name = format!("/ppt/{}", relationship.target);
174 let slide_part = package
175 .part(&slide_part_name)
176 .ok_or_else(|| Error::MissingSlidePart(slide_part_name.clone()))?;
177 let slide_xml = std::str::from_utf8(&slide_part.data)
178 .map_err(|error| Error::InvalidUtf8(slide_part_name.clone(), error))?;
179
180 let context = SlideContext {
181 slide_part_name: &slide_part_name,
182 relationships: &slide_part.relationships,
183 package: &package,
184 };
185 let mut slide = parse_slide_xml(slide_xml, &context)?;
186
187 if let Some(notes_relationship) = slide_part
192 .relationships
193 .iter()
194 .find(|relationship| relationship.rel_type == NOTES_SLIDE_RELATIONSHIP_TYPE)
195 {
196 let notes_part_name =
197 resolve_relative_target(&slide_part_name, ¬es_relationship.target);
198 let notes_part = package
199 .part(¬es_part_name)
200 .ok_or_else(|| Error::MissingNotesPart(notes_part_name.clone()))?;
201 let notes_xml = std::str::from_utf8(¬es_part.data)
202 .map_err(|error| Error::InvalidUtf8(notes_part_name.clone(), error))?;
203 slide.notes = Some(parse_notes_slide_xml(notes_xml)?);
204 }
205
206 if let Some(comments_relationship) = slide_part
211 .relationships
212 .iter()
213 .find(|relationship| relationship.rel_type == COMMENT_RELATIONSHIP_TYPE)
214 {
215 let comments_part_name =
216 resolve_relative_target(&slide_part_name, &comments_relationship.target);
217 if let Some(comments_part) = package.part(&comments_part_name) {
218 let comments_xml = std::str::from_utf8(&comments_part.data)
219 .map_err(|error| Error::InvalidUtf8(comments_part_name.clone(), error))?;
220 slide.comments = parse_comments_xml(comments_xml, &comment_authors)?;
221 }
222 }
223
224 presentation.slides.push(slide);
225 }
226
227 Ok(presentation)
228 }
229}
230
231struct ParsedPresentation {
233 slide_width_emu: i64,
234 slide_height_emu: i64,
235 slide_relationship_ids: Vec<String>,
237 embedded_fonts: Vec<ParsedEmbeddedFont>,
241}
242
243struct ParsedEmbeddedFont {
245 typeface: String,
246 regular: Option<String>,
247 bold: Option<String>,
248 italic: Option<String>,
249 bold_italic: Option<String>,
250}
251
252fn parse_presentation_xml(xml: &str) -> Result<ParsedPresentation> {
258 let mut slide_width_emu = DEFAULT_SLIDE_WIDTH_EMU;
259 let mut slide_height_emu = DEFAULT_SLIDE_HEIGHT_EMU;
260 let mut slide_relationship_ids = Vec::new();
261 let mut embedded_fonts = Vec::new();
262
263 let mut reader = Reader::from_xml_str(xml);
264 loop {
265 match reader.read_event()? {
266 Event::Eof => break,
267
268 Event::Empty(start) if start.local_name().as_ref() == b"sldId" => {
269 if let Some(relationship_id) = attribute_by_qname(&start, b"r:id") {
270 slide_relationship_ids.push(relationship_id);
271 }
272 }
273
274 Event::Empty(start) if start.local_name().as_ref() == b"sldSz" => {
275 if let Some(value) =
276 attribute_by_qname(&start, b"cx").and_then(|value| value.parse().ok())
277 {
278 slide_width_emu = value;
279 }
280 if let Some(value) =
281 attribute_by_qname(&start, b"cy").and_then(|value| value.parse().ok())
282 {
283 slide_height_emu = value;
284 }
285 }
286
287 Event::Start(start) if start.local_name().as_ref() == b"embeddedFontLst" => {
288 embedded_fonts = parse_embedded_font_list(&mut reader)?;
289 }
290
291 _ => {}
292 }
293 }
294
295 Ok(ParsedPresentation {
296 slide_width_emu,
297 slide_height_emu,
298 slide_relationship_ids,
299 embedded_fonts,
300 })
301}
302
303fn parse_embedded_font_list(reader: &mut Reader) -> Result<Vec<ParsedEmbeddedFont>> {
306 let mut fonts = Vec::new();
307
308 loop {
309 match reader.read_event()? {
310 Event::Start(start) if start.local_name().as_ref() == b"embeddedFont" => {
311 fonts.push(parse_embedded_font(reader)?);
312 }
313 Event::End(end) if end.local_name().as_ref() == b"embeddedFontLst" => break,
314 Event::Eof => break,
315 _ => {}
316 }
317 }
318
319 Ok(fonts)
320}
321
322fn parse_embedded_font(reader: &mut Reader) -> Result<ParsedEmbeddedFont> {
325 let mut typeface = String::new();
326 let mut regular = None;
327 let mut bold = None;
328 let mut italic = None;
329 let mut bold_italic = None;
330
331 loop {
332 match reader.read_event()? {
333 Event::Empty(start) if start.local_name().as_ref() == b"font" => {
334 typeface = attribute_by_qname(&start, b"typeface").unwrap_or_default();
335 }
336 Event::Empty(start) if start.local_name().as_ref() == b"regular" => {
337 regular = attribute_by_qname(&start, b"r:id");
338 }
339 Event::Empty(start) if start.local_name().as_ref() == b"bold" => {
340 bold = attribute_by_qname(&start, b"r:id");
341 }
342 Event::Empty(start) if start.local_name().as_ref() == b"italic" => {
343 italic = attribute_by_qname(&start, b"r:id");
344 }
345 Event::Empty(start) if start.local_name().as_ref() == b"boldItalic" => {
346 bold_italic = attribute_by_qname(&start, b"r:id");
347 }
348
349 Event::Start(_) => skip_subtree(reader)?,
350 Event::End(end) if end.local_name().as_ref() == b"embeddedFont" => break,
351 Event::Eof => break,
352 _ => {}
353 }
354 }
355
356 Ok(ParsedEmbeddedFont {
357 typeface,
358 regular,
359 bold,
360 italic,
361 bold_italic,
362 })
363}
364
365struct SlideContext<'a> {
370 slide_part_name: &'a str,
371 relationships: &'a Relationships,
372 package: &'a Package,
373}
374
375fn resolve_relative_target(base_part_name: &str, target: &str) -> String {
386 let base_directory = match base_part_name.rfind('/') {
387 Some(index) => &base_part_name[..index],
388 None => "",
389 };
390 let mut segments: Vec<&str> = base_directory
391 .split('/')
392 .filter(|segment| !segment.is_empty())
393 .collect();
394
395 for segment in target.split('/') {
396 match segment {
397 "" | "." => {}
398 ".." => {
399 segments.pop();
400 }
401 other => segments.push(other),
402 }
403 }
404
405 format!("/{}", segments.join("/"))
406}
407
408fn parse_slide_xml(xml: &str, context: &SlideContext) -> Result<Slide> {
418 let mut slide = Slide::new();
419 let mut reader = Reader::from_xml_str(xml);
420 let content = parse_shapes_until_eof(&mut reader, context)?;
421 slide.shapes = content.shapes;
422 slide.background = content.background;
423 slide.hidden = content.hidden;
424 slide.name = content.name;
425 slide.hide_master_graphics = content.hide_master_graphics;
426 Ok(slide)
427}
428
429struct ParsedSlideContent {
445 shapes: Vec<Shape>,
446 background: Option<Fill>,
447 hidden: bool,
448 name: Option<String>,
449 hide_master_graphics: bool,
450}
451
452fn parse_shapes_until_eof(
453 reader: &mut Reader,
454 context: &SlideContext,
455) -> Result<ParsedSlideContent> {
456 let mut shapes = Vec::new();
457 let mut background = None;
458 let mut hidden = false;
459 let mut name = None;
460 let mut hide_master_graphics = false;
461 loop {
462 match reader.read_event()? {
463 Event::Eof => break,
464 Event::Start(start) if start.local_name().as_ref() == b"sld" => {
465 hidden = attribute_by_qname(&start, b"show").as_deref() == Some("0");
466 hide_master_graphics =
467 attribute_by_qname(&start, b"showMasterSp").as_deref() == Some("0");
468 }
469 Event::Start(start) if start.local_name().as_ref() == b"cSld" => {
470 name = attribute_by_qname(&start, b"name").filter(|value| !value.is_empty());
471 }
472 Event::Start(start) if start.local_name().as_ref() == b"bg" => {
473 background = parse_slide_background(reader)?;
474 }
475 Event::Start(start) if start.local_name().as_ref() == b"sp" => {
476 shapes.push(Shape::AutoShape(parse_auto_shape(reader, Some(context))?));
477 }
478 Event::Start(start) if start.local_name().as_ref() == b"pic" => {
479 shapes.push(parse_pic_shape(reader, context)?);
480 }
481 Event::Start(start) if start.local_name().as_ref() == b"graphicFrame" => {
482 shapes.push(parse_graphic_frame_shape(reader, context)?);
483 }
484 Event::Start(start) if start.local_name().as_ref() == b"grpSp" => {
485 shapes.push(Shape::Group(parse_group_shape(reader, context)?));
486 }
487 Event::Start(start) if start.local_name().as_ref() == b"cxnSp" => {
488 shapes.push(Shape::Connector(parse_connector(reader)?));
489 }
490 _ => {}
491 }
492 }
493 Ok(ParsedSlideContent {
494 shapes,
495 background,
496 hidden,
497 name,
498 hide_master_graphics,
499 })
500}
501
502fn parse_slide_background(reader: &mut Reader) -> Result<Option<Fill>> {
507 let mut fill = None;
508 loop {
509 match reader.read_event()? {
510 Event::Eof => break,
511 Event::End(end) if end.local_name().as_ref() == b"bg" => break,
512 Event::Start(start) if start.local_name().as_ref() == b"bgPr" => {
513 fill = drawing::read_fill(reader)?;
514 }
515 Event::Start(_) => skip_subtree(reader)?,
516 Event::Empty(_) => {}
517 _ => {}
518 }
519 }
520 Ok(fill)
521}
522
523fn parse_group_shape(reader: &mut Reader, context: &SlideContext) -> Result<ShapeGroup> {
528 let mut id: u32 = 0;
529 let mut name = String::new();
530 let mut offset_emu = (0i64, 0i64);
531 let mut extent_emu = (0i64, 0i64);
532 let mut child_offset_emu = (0i64, 0i64);
533 let mut child_extent_emu = (0i64, 0i64);
534 let mut rotation_60000ths: i32 = 0;
535 let mut flip_horizontal = false;
536 let mut flip_vertical = false;
537 let mut shapes = Vec::new();
538
539 loop {
540 match reader.read_event()? {
541 Event::Eof => break,
542 Event::End(end) if end.local_name().as_ref() == b"grpSp" => break,
543
544 Event::Start(start) if start.local_name().as_ref() == b"nvGrpSpPr" => {}
545 Event::End(end) if end.local_name().as_ref() == b"nvGrpSpPr" => {}
546
547 Event::Empty(start) if start.local_name().as_ref() == b"cNvPr" => {
548 id = attribute_by_qname(&start, b"id")
549 .and_then(|value| value.parse().ok())
550 .unwrap_or(0);
551 name = attribute_by_qname(&start, b"name").unwrap_or_default();
552 }
553 Event::Start(start) if start.local_name().as_ref() == b"cNvPr" => {
554 id = attribute_by_qname(&start, b"id")
555 .and_then(|value| value.parse().ok())
556 .unwrap_or(0);
557 name = attribute_by_qname(&start, b"name").unwrap_or_default();
558 skip_subtree(reader)?;
559 }
560
561 Event::Empty(start) if start.local_name().as_ref() == b"cNvGrpSpPr" => {}
562 Event::Start(start) if start.local_name().as_ref() == b"cNvGrpSpPr" => {
563 skip_subtree(reader)?;
565 }
566
567 Event::Empty(start) if start.local_name().as_ref() == b"nvPr" => {}
568 Event::Start(start) if start.local_name().as_ref() == b"nvPr" => skip_subtree(reader)?,
569
570 Event::Start(start) if start.local_name().as_ref() == b"grpSpPr" => {}
571 Event::End(end) if end.local_name().as_ref() == b"grpSpPr" => {}
572
573 Event::Start(start) if start.local_name().as_ref() == b"xfrm" => {
574 rotation_60000ths = attribute_by_qname(&start, b"rot")
575 .and_then(|value| value.parse().ok())
576 .unwrap_or(0);
577 flip_horizontal = attribute_by_qname(&start, b"flipH").as_deref() == Some("1");
578 flip_vertical = attribute_by_qname(&start, b"flipV").as_deref() == Some("1");
579 let transform = parse_group_transform(reader)?;
580 offset_emu = transform.offset;
581 extent_emu = transform.extent;
582 child_offset_emu = transform.child_offset;
583 child_extent_emu = transform.child_extent;
584 }
585
586 Event::Start(start) if start.local_name().as_ref() == b"sp" => {
588 shapes.push(Shape::AutoShape(parse_auto_shape(reader, Some(context))?));
589 }
590 Event::Start(start) if start.local_name().as_ref() == b"pic" => {
591 shapes.push(parse_pic_shape(reader, context)?);
592 }
593 Event::Start(start) if start.local_name().as_ref() == b"graphicFrame" => {
594 shapes.push(parse_graphic_frame_shape(reader, context)?);
595 }
596 Event::Start(start) if start.local_name().as_ref() == b"grpSp" => {
597 shapes.push(Shape::Group(parse_group_shape(reader, context)?));
598 }
599 Event::Start(start) if start.local_name().as_ref() == b"cxnSp" => {
600 shapes.push(Shape::Connector(parse_connector(reader)?));
601 }
602
603 Event::Start(_) => skip_subtree(reader)?,
604 Event::Empty(_) => {}
605 _ => {}
606 }
607 }
608
609 Ok(ShapeGroup {
610 id,
611 name,
612 offset_emu,
613 extent_emu,
614 child_offset_emu,
615 child_extent_emu,
616 rotation_60000ths,
617 flip_horizontal,
618 flip_vertical,
619 shapes,
620 })
621}
622
623struct GroupTransform {
628 offset: (i64, i64),
629 extent: (i64, i64),
630 child_offset: (i64, i64),
631 child_extent: (i64, i64),
632}
633
634fn parse_group_transform(reader: &mut Reader) -> Result<GroupTransform> {
639 let mut offset = (0i64, 0i64);
640 let mut extent = (0i64, 0i64);
641 let mut child_offset = (0i64, 0i64);
642 let mut child_extent = (0i64, 0i64);
643
644 loop {
645 match reader.read_event()? {
646 Event::Eof => break,
647 Event::End(end) if end.local_name().as_ref() == b"xfrm" => break,
648
649 Event::Empty(start) if start.local_name().as_ref() == b"off" => {
650 offset = read_xy_attributes(&start, b"x", b"y");
651 }
652 Event::Empty(start) if start.local_name().as_ref() == b"ext" => {
653 extent = read_xy_attributes(&start, b"cx", b"cy");
654 }
655 Event::Empty(start) if start.local_name().as_ref() == b"chOff" => {
656 child_offset = read_xy_attributes(&start, b"x", b"y");
657 }
658 Event::Empty(start) if start.local_name().as_ref() == b"chExt" => {
659 child_extent = read_xy_attributes(&start, b"cx", b"cy");
660 }
661
662 Event::Start(_) => skip_subtree(reader)?,
663 Event::Empty(_) => {}
664 _ => {}
665 }
666 }
667
668 Ok(GroupTransform {
669 offset,
670 extent,
671 child_offset,
672 child_extent,
673 })
674}
675
676fn read_xy_attributes(start: &BytesStart, first: &[u8], second: &[u8]) -> (i64, i64) {
679 let first_value = attribute_by_qname(start, first)
680 .and_then(|value| value.parse().ok())
681 .unwrap_or(0);
682 let second_value = attribute_by_qname(start, second)
683 .and_then(|value| value.parse().ok())
684 .unwrap_or(0);
685 (first_value, second_value)
686}
687
688fn parse_connector(reader: &mut Reader) -> Result<Connector> {
692 let mut id: u32 = 0;
693 let mut name = String::new();
694 let mut properties = ShapeProperties::new();
695 let mut start_connection = None;
696 let mut end_connection = None;
697
698 loop {
699 match reader.read_event()? {
700 Event::Eof => break,
701 Event::End(end) if end.local_name().as_ref() == b"cxnSp" => break,
702
703 Event::Start(start) if start.local_name().as_ref() == b"nvCxnSpPr" => {}
704 Event::End(end) if end.local_name().as_ref() == b"nvCxnSpPr" => {}
705
706 Event::Empty(start) if start.local_name().as_ref() == b"cNvPr" => {
707 id = attribute_by_qname(&start, b"id")
708 .and_then(|value| value.parse().ok())
709 .unwrap_or(0);
710 name = attribute_by_qname(&start, b"name").unwrap_or_default();
711 }
712 Event::Start(start) if start.local_name().as_ref() == b"cNvPr" => {
713 id = attribute_by_qname(&start, b"id")
714 .and_then(|value| value.parse().ok())
715 .unwrap_or(0);
716 name = attribute_by_qname(&start, b"name").unwrap_or_default();
717 skip_subtree(reader)?;
718 }
719
720 Event::Empty(start) if start.local_name().as_ref() == b"cNvCxnSpPr" => {}
721 Event::Start(start) if start.local_name().as_ref() == b"cNvCxnSpPr" => {}
722 Event::End(end) if end.local_name().as_ref() == b"cNvCxnSpPr" => {}
723
724 Event::Empty(start) if start.local_name().as_ref() == b"stCxn" => {
725 start_connection = Some(parse_shape_connection(&start));
726 }
727 Event::Empty(start) if start.local_name().as_ref() == b"endCxn" => {
728 end_connection = Some(parse_shape_connection(&start));
729 }
730
731 Event::Empty(start) if start.local_name().as_ref() == b"nvPr" => {}
732 Event::Start(start) if start.local_name().as_ref() == b"nvPr" => skip_subtree(reader)?,
733
734 Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {}
735 Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
736 properties = drawing::read_shape_properties(reader)?;
737 }
738
739 Event::Start(_) => skip_subtree(reader)?,
740 Event::Empty(_) => {}
741 _ => {}
742 }
743 }
744
745 Ok(Connector {
746 id,
747 name,
748 properties,
749 start_connection,
750 end_connection,
751 })
752}
753
754fn parse_shape_connection(start: &BytesStart) -> ShapeConnection {
756 ShapeConnection {
757 shape_id: attribute_by_qname(start, b"id")
758 .and_then(|value| value.parse().ok())
759 .unwrap_or(0),
760 index: attribute_by_qname(start, b"idx")
761 .and_then(|value| value.parse().ok())
762 .unwrap_or(0),
763 }
764}
765
766fn parse_auto_shape(reader: &mut Reader, context: Option<&SlideContext>) -> Result<AutoShape> {
774 let mut id: u32 = 0;
775 let mut name = String::new();
776 let mut properties = ShapeProperties::new();
777 let mut text_body = None;
778 let mut placeholder = None;
779 let mut is_text_box = false;
780 let mut hyperlink = None;
781
782 loop {
783 match reader.read_event()? {
784 Event::Eof => break,
785 Event::End(end) if end.local_name().as_ref() == b"sp" => break,
786
787 Event::Start(start) if start.local_name().as_ref() == b"nvSpPr" => {}
791 Event::End(end) if end.local_name().as_ref() == b"nvSpPr" => {}
792
793 Event::Empty(start) if start.local_name().as_ref() == b"cNvPr" => {
794 id = attribute_by_qname(&start, b"id")
795 .and_then(|value| value.parse().ok())
796 .unwrap_or(0);
797 name = attribute_by_qname(&start, b"name").unwrap_or_default();
798 }
799 Event::Start(start) if start.local_name().as_ref() == b"cNvPr" => {
800 id = attribute_by_qname(&start, b"id")
801 .and_then(|value| value.parse().ok())
802 .unwrap_or(0);
803 name = attribute_by_qname(&start, b"name").unwrap_or_default();
804 hyperlink = parse_cnv_pr_hyperlink(reader, context)?;
808 }
809
810 Event::Empty(start) if start.local_name().as_ref() == b"cNvSpPr" => {
811 is_text_box = attribute_by_qname(&start, b"txBox").as_deref() == Some("1");
812 }
813 Event::Start(start) if start.local_name().as_ref() == b"cNvSpPr" => {
814 is_text_box = attribute_by_qname(&start, b"txBox").as_deref() == Some("1");
815 skip_subtree(reader)?;
817 }
818
819 Event::Empty(start) if start.local_name().as_ref() == b"nvPr" => {}
820 Event::Start(start) if start.local_name().as_ref() == b"nvPr" => {
821 placeholder = parse_placeholder_wrapper(reader)?;
822 }
823
824 Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {}
825 Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
826 properties = drawing::read_shape_properties(reader)?;
827 }
828
829 Event::Start(start) if start.local_name().as_ref() == b"txBody" => {
830 text_body = Some(drawing::read_text_body(reader)?);
831 }
832
833 Event::Start(_) => skip_subtree(reader)?,
835 Event::Empty(_) => {}
836
837 _ => {}
838 }
839 }
840
841 Ok(AutoShape {
842 id,
843 name,
844 properties,
845 text_body,
846 placeholder,
847 is_text_box,
848 hyperlink,
849 })
850}
851
852fn parse_cnv_pr_hyperlink(
858 reader: &mut Reader,
859 context: Option<&SlideContext>,
860) -> Result<Option<SlideHyperlinkTarget>> {
861 let mut hyperlink = None;
862
863 loop {
864 match reader.read_event()? {
865 Event::Eof => break,
866 Event::End(end) if end.local_name().as_ref() == b"cNvPr" => break,
867
868 Event::Empty(start) if start.local_name().as_ref() == b"hlinkClick" => {
869 hyperlink = resolve_hyperlink_target(&start, context);
870 }
871 Event::Start(start) if start.local_name().as_ref() == b"hlinkClick" => {
872 hyperlink = resolve_hyperlink_target(&start, context);
873 skip_subtree(reader)?;
875 }
876
877 Event::Start(_) => skip_subtree(reader)?,
878 Event::Empty(_) => {}
879 _ => {}
880 }
881 }
882
883 Ok(hyperlink)
884}
885
886fn resolve_hyperlink_target(
901 start: &BytesStart,
902 context: Option<&SlideContext>,
903) -> Option<SlideHyperlinkTarget> {
904 let action = attribute_by_qname(start, b"action");
905 let relationship_id = attribute_by_qname(start, b"r:id");
906
907 if relationship_id.is_none() {
908 return match action.as_deref() {
909 Some("ppaction://hlinkshowjump?jump=nextslide") => {
910 Some(SlideHyperlinkTarget::NextSlide)
911 }
912 Some("ppaction://hlinkshowjump?jump=previousslide") => {
913 Some(SlideHyperlinkTarget::PreviousSlide)
914 }
915 Some("ppaction://hlinkshowjump?jump=firstslide") => {
916 Some(SlideHyperlinkTarget::FirstSlide)
917 }
918 Some("ppaction://hlinkshowjump?jump=lastslide") => {
919 Some(SlideHyperlinkTarget::LastSlide)
920 }
921 _ => None,
922 };
923 }
924
925 let relationship = context?.relationships.by_id(&relationship_id?)?;
926
927 if action.as_deref() == Some("ppaction://hlinksldjump")
928 && relationship.rel_type == SLIDE_RELATIONSHIP_TYPE
929 {
930 if let Some(index) = parse_slide_relationship_target(&relationship.target) {
931 return Some(SlideHyperlinkTarget::Slide(index));
932 }
933 }
934
935 Some(SlideHyperlinkTarget::External(relationship.target.clone()))
936}
937
938fn parse_slide_relationship_target(target: &str) -> Option<usize> {
942 let number = target
943 .strip_prefix("../slides/slide")?
944 .strip_suffix(".xml")?;
945 let slide_number: usize = number.parse().ok()?;
946 slide_number.checked_sub(1)
947}
948
949fn parse_placeholder_wrapper(reader: &mut Reader) -> Result<Option<Placeholder>> {
954 let mut placeholder = None;
955
956 loop {
957 match reader.read_event()? {
958 Event::Eof => break,
959 Event::End(end) if end.local_name().as_ref() == b"nvPr" => break,
960
961 Event::Empty(start) if start.local_name().as_ref() == b"ph" => {
962 placeholder = Some(parse_placeholder_attributes(&start));
963 }
964 Event::Start(start) if start.local_name().as_ref() == b"ph" => {
965 placeholder = Some(parse_placeholder_attributes(&start));
966 skip_subtree(reader)?;
968 }
969
970 Event::Start(_) => skip_subtree(reader)?,
971 Event::Empty(_) => {}
972 _ => {}
973 }
974 }
975
976 Ok(placeholder)
977}
978
979fn parse_placeholder_attributes(start: &BytesStart) -> Placeholder {
982 let kind = attribute_by_qname(start, b"type")
983 .map(|token| PlaceholderKind::from_xml_token(&token))
984 .unwrap_or(PlaceholderKind::Object);
985 let mut placeholder = Placeholder::new(kind);
986 if let Some(index) = attribute_by_qname(start, b"idx").and_then(|value| value.parse().ok()) {
987 placeholder = placeholder.with_index(index);
988 }
989 placeholder
990}
991
992fn parse_pic_shape(reader: &mut Reader, context: &SlideContext) -> Result<Shape> {
998 let mut id: u32 = 0;
999 let mut name = String::new();
1000 let mut description = String::new();
1001 let mut embed_relationship_id: Option<String> = None;
1002 let mut link_relationship_id: Option<String> = None;
1003 let mut media_relationship_id: Option<String> = None;
1004 let mut properties = ShapeProperties::new();
1005
1006 loop {
1007 match reader.read_event()? {
1008 Event::Eof => break,
1009 Event::End(end) if end.local_name().as_ref() == b"pic" => break,
1010
1011 Event::Start(start) if start.local_name().as_ref() == b"nvPicPr" => {}
1012 Event::End(end) if end.local_name().as_ref() == b"nvPicPr" => {}
1013
1014 Event::Empty(start) if start.local_name().as_ref() == b"cNvPr" => {
1015 id = attribute_by_qname(&start, b"id")
1016 .and_then(|value| value.parse().ok())
1017 .unwrap_or(0);
1018 name = attribute_by_qname(&start, b"name").unwrap_or_default();
1019 description = attribute_by_qname(&start, b"descr").unwrap_or_default();
1020 }
1021 Event::Start(start) if start.local_name().as_ref() == b"cNvPr" => {
1022 id = attribute_by_qname(&start, b"id")
1023 .and_then(|value| value.parse().ok())
1024 .unwrap_or(0);
1025 name = attribute_by_qname(&start, b"name").unwrap_or_default();
1026 description = attribute_by_qname(&start, b"descr").unwrap_or_default();
1027 skip_subtree(reader)?;
1029 }
1030
1031 Event::Empty(start) if start.local_name().as_ref() == b"cNvPicPr" => {}
1032 Event::Start(start) if start.local_name().as_ref() == b"cNvPicPr" => {
1033 skip_subtree(reader)?;
1035 }
1036
1037 Event::Empty(start) if start.local_name().as_ref() == b"nvPr" => {}
1038 Event::Start(start) if start.local_name().as_ref() == b"nvPr" => {
1039 media_relationship_id = parse_media_reference(reader)?;
1043 }
1044
1045 Event::Start(start) if start.local_name().as_ref() == b"blipFill" => {
1046 let (embed, link) = parse_blip_fill_embed(reader)?;
1047 embed_relationship_id = embed;
1048 link_relationship_id = link;
1049 }
1050
1051 Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {}
1052 Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
1053 properties = drawing::read_shape_properties(reader)?;
1054 }
1055
1056 Event::Start(_) => skip_subtree(reader)?,
1057 Event::Empty(_) => {}
1058 _ => {}
1059 }
1060 }
1061
1062 let offset_emu = properties
1066 .transform
1067 .as_ref()
1068 .and_then(|transform| transform.offset)
1069 .unwrap_or((0, 0));
1070 let extent_emu = properties
1071 .transform
1072 .as_ref()
1073 .and_then(|transform| transform.extent)
1074 .unwrap_or((0, 0));
1075
1076 if let Some(media_relationship_id) = media_relationship_id {
1077 let relationship = context
1078 .relationships
1079 .by_id(&media_relationship_id)
1080 .ok_or_else(|| Error::MissingMediaRelationship(media_relationship_id.clone()))?;
1081 let media_part_name =
1082 resolve_relative_target(context.slide_part_name, &relationship.target);
1083 let media_part = context
1084 .package
1085 .part(&media_part_name)
1086 .ok_or_else(|| Error::MissingMediaPart(media_part_name.clone()))?;
1087 let format = MediaFormat::from_extension(&media_part_name)
1088 .ok_or_else(|| Error::UnsupportedMediaFormat(media_part_name.clone()))?;
1089
1090 let poster_image = embed_relationship_id.and_then(|poster_relationship_id| {
1091 let relationship = context.relationships.by_id(&poster_relationship_id)?;
1092 let poster_part_name =
1093 resolve_relative_target(context.slide_part_name, &relationship.target);
1094 let poster_part = context.package.part(&poster_part_name)?;
1095 let poster_format = PictureFormat::from_extension(&poster_part_name)?;
1096 Some((poster_part.data.clone(), poster_format))
1097 });
1098
1099 return Ok(Shape::Media(SlideMedia {
1100 id,
1101 name,
1102 data: media_part.data.clone(),
1103 format,
1104 offset_emu,
1105 extent_emu,
1106 poster_image,
1107 }));
1108 }
1109
1110 properties.transform = None;
1111
1112 let relationship_id =
1113 embed_relationship_id.ok_or_else(|| Error::MissingImageRelationship(String::new()))?;
1114 let relationship = context
1115 .relationships
1116 .by_id(&relationship_id)
1117 .ok_or_else(|| Error::MissingImageRelationship(relationship_id.clone()))?;
1118 let image_part_name = resolve_relative_target(context.slide_part_name, &relationship.target);
1119 let image_part = context
1120 .package
1121 .part(&image_part_name)
1122 .ok_or_else(|| Error::MissingImagePart(image_part_name.clone()))?;
1123 let format = PictureFormat::from_extension(&image_part_name)
1124 .ok_or_else(|| Error::UnsupportedImageFormat(image_part_name.clone()))?;
1125
1126 let is_default_shape = matches!(
1129 properties.geometry,
1130 None | Some(Geometry::Preset(PresetShape::Rectangle))
1131 ) && properties.fill.is_none()
1132 && properties.line.is_none();
1133 let shape_properties = if is_default_shape {
1134 None
1135 } else {
1136 Some(properties)
1137 };
1138
1139 let external_link = link_relationship_id
1143 .and_then(|id| context.relationships.by_id(&id))
1144 .map(|relationship| relationship.target.clone());
1145
1146 Ok(Shape::Picture(Picture {
1147 id,
1148 name,
1149 data: image_part.data.clone(),
1150 format,
1151 offset_emu,
1152 extent_emu,
1153 description,
1154 shape_properties,
1155 external_link,
1156 }))
1157}
1158
1159fn parse_media_reference(reader: &mut Reader) -> Result<Option<String>> {
1165 let mut relationship_id = None;
1166
1167 loop {
1168 match reader.read_event()? {
1169 Event::Eof => break,
1170 Event::End(end) if end.local_name().as_ref() == b"nvPr" => break,
1171
1172 Event::Empty(start) if start.local_name().as_ref() == b"videoFile" => {
1173 relationship_id = attribute_by_qname(&start, b"r:link");
1174 }
1175 Event::Empty(start) if start.local_name().as_ref() == b"audioFile" => {
1176 relationship_id = attribute_by_qname(&start, b"r:link");
1177 }
1178
1179 Event::Start(_) => skip_subtree(reader)?,
1180 Event::Empty(_) => {}
1181 _ => {}
1182 }
1183 }
1184
1185 Ok(relationship_id)
1186}
1187
1188fn parse_blip_fill_embed(reader: &mut Reader) -> Result<(Option<String>, Option<String>)> {
1192 let mut embed_relationship_id = None;
1193 let mut link_relationship_id = None;
1194
1195 loop {
1196 match reader.read_event()? {
1197 Event::Eof => break,
1198 Event::End(end) if end.local_name().as_ref() == b"blipFill" => break,
1199
1200 Event::Empty(start) if start.local_name().as_ref() == b"blip" => {
1201 embed_relationship_id = attribute_by_qname(&start, b"r:embed");
1202 link_relationship_id = attribute_by_qname(&start, b"r:link");
1203 }
1204 Event::Start(start) if start.local_name().as_ref() == b"blip" => {
1205 embed_relationship_id = attribute_by_qname(&start, b"r:embed");
1206 link_relationship_id = attribute_by_qname(&start, b"r:link");
1207 skip_subtree(reader)?;
1209 }
1210
1211 Event::Start(_) => skip_subtree(reader)?,
1212 Event::Empty(_) => {}
1213 _ => {}
1214 }
1215 }
1216
1217 Ok((embed_relationship_id, link_relationship_id))
1218}
1219
1220fn parse_graphic_frame_shape(reader: &mut Reader, context: &SlideContext) -> Result<Shape> {
1228 let mut id: u32 = 0;
1229 let mut name = String::new();
1230 let mut offset_emu: (i64, i64) = (0, 0);
1231 let mut extent_emu: (i64, i64) = (0, 0);
1232
1233 loop {
1234 match reader.read_event()? {
1235 Event::Eof => break,
1236 Event::End(end) if end.local_name().as_ref() == b"graphicFrame" => break,
1237
1238 Event::Start(start) if start.local_name().as_ref() == b"nvGraphicFramePr" => {}
1239 Event::End(end) if end.local_name().as_ref() == b"nvGraphicFramePr" => {}
1240
1241 Event::Empty(start) if start.local_name().as_ref() == b"cNvPr" => {
1242 id = attribute_by_qname(&start, b"id")
1243 .and_then(|value| value.parse().ok())
1244 .unwrap_or(0);
1245 name = attribute_by_qname(&start, b"name").unwrap_or_default();
1246 }
1247 Event::Start(start) if start.local_name().as_ref() == b"cNvPr" => {
1248 id = attribute_by_qname(&start, b"id")
1249 .and_then(|value| value.parse().ok())
1250 .unwrap_or(0);
1251 name = attribute_by_qname(&start, b"name").unwrap_or_default();
1252 skip_subtree(reader)?;
1253 }
1254
1255 Event::Empty(start) if start.local_name().as_ref() == b"cNvGraphicFramePr" => {}
1256 Event::Start(start) if start.local_name().as_ref() == b"cNvGraphicFramePr" => {
1257 skip_subtree(reader)?;
1259 }
1260
1261 Event::Empty(start) if start.local_name().as_ref() == b"nvPr" => {}
1262 Event::Start(start) if start.local_name().as_ref() == b"nvPr" => {
1263 skip_subtree(reader)?;
1264 }
1265
1266 Event::Start(start) if start.local_name().as_ref() == b"xfrm" => {
1267 let (parsed_offset, parsed_extent) = parse_frame_transform(reader)?;
1268 offset_emu = parsed_offset;
1269 extent_emu = parsed_extent;
1270 }
1271
1272 Event::Start(start) if start.local_name().as_ref() == b"graphic" => {}
1274 Event::End(end) if end.local_name().as_ref() == b"graphic" => {}
1275
1276 Event::Start(start) if start.local_name().as_ref() == b"graphicData" => {
1281 let uri = attribute_by_qname(&start, b"uri");
1282 return match uri.as_deref() {
1283 Some(TABLE_NAMESPACE) => Ok(Shape::Table(parse_table(
1284 reader, id, name, offset_emu, extent_emu,
1285 )?)),
1286 _ => Ok(Shape::Chart(Box::new(parse_chart(
1287 reader, context, id, name, offset_emu, extent_emu,
1288 )?))),
1289 };
1290 }
1291
1292 Event::Start(_) => skip_subtree(reader)?,
1293 Event::Empty(_) => {}
1294 _ => {}
1295 }
1296 }
1297
1298 Ok(Shape::Table(SlideTable {
1303 id,
1304 name,
1305 offset_emu,
1306 extent_emu,
1307 column_widths_emu: Vec::new(),
1308 rows: Vec::new(),
1309 style_id: None,
1310 style_first_row: false,
1311 style_first_column: false,
1312 style_last_row: false,
1313 style_last_column: false,
1314 style_band_rows: false,
1315 style_band_columns: false,
1316 }))
1317}
1318
1319fn parse_chart(
1323 reader: &mut Reader,
1324 context: &SlideContext,
1325 id: u32,
1326 name: String,
1327 offset_emu: (i64, i64),
1328 extent_emu: (i64, i64),
1329) -> Result<SlideChart> {
1330 let mut chart_relationship_id: Option<String> = None;
1331
1332 loop {
1333 match reader.read_event()? {
1334 Event::Eof => break,
1335 Event::End(end) if end.local_name().as_ref() == b"graphicFrame" => break,
1336 Event::End(end) if end.local_name().as_ref() == b"graphicData" => {}
1337
1338 Event::Empty(start) if start.local_name().as_ref() == b"chart" => {
1339 chart_relationship_id = attribute_by_qname(&start, b"r:id");
1340 }
1341 Event::Start(start) if start.local_name().as_ref() == b"chart" => {
1342 chart_relationship_id = attribute_by_qname(&start, b"r:id");
1343 skip_subtree(reader)?;
1344 }
1345
1346 Event::Start(_) => skip_subtree(reader)?,
1347 Event::Empty(_) => {}
1348 _ => {}
1349 }
1350 }
1351
1352 let relationship_id =
1353 chart_relationship_id.ok_or_else(|| Error::MissingChartRelationship(String::new()))?;
1354 let relationship = context
1355 .relationships
1356 .by_id(&relationship_id)
1357 .ok_or_else(|| Error::MissingChartRelationship(relationship_id.clone()))?;
1358 let chart_part_name = resolve_relative_target(context.slide_part_name, &relationship.target);
1359 let chart_part = context
1360 .package
1361 .part(&chart_part_name)
1362 .ok_or_else(|| Error::MissingChartPart(chart_part_name.clone()))?;
1363 let chart_xml = std::str::from_utf8(&chart_part.data)
1364 .map_err(|error| Error::InvalidUtf8(chart_part_name.clone(), error))?;
1365 let chart_space = chart::read_chart_space(chart_xml)?;
1366
1367 Ok(SlideChart {
1368 id,
1369 name,
1370 chart_space,
1371 offset_emu,
1372 extent_emu,
1373 })
1374}
1375
1376fn parse_table(
1380 reader: &mut Reader,
1381 id: u32,
1382 name: String,
1383 offset_emu: (i64, i64),
1384 extent_emu: (i64, i64),
1385) -> Result<SlideTable> {
1386 let mut column_widths_emu = Vec::new();
1387 let mut rows = Vec::new();
1388 let mut style_id = None;
1389 let mut style_first_row = false;
1390 let mut style_first_column = false;
1391 let mut style_last_row = false;
1392 let mut style_last_column = false;
1393 let mut style_band_rows = false;
1394 let mut style_band_columns = false;
1395
1396 loop {
1397 match reader.read_event()? {
1398 Event::Eof => break,
1399 Event::End(end) if end.local_name().as_ref() == b"graphicFrame" => break,
1400 Event::End(end) if end.local_name().as_ref() == b"graphicData" => {}
1401
1402 Event::Start(start) if start.local_name().as_ref() == b"tbl" => {}
1403 Event::End(end) if end.local_name().as_ref() == b"tbl" => {}
1404
1405 Event::Empty(start) if start.local_name().as_ref() == b"tblPr" => {
1412 style_first_row = attribute_by_qname(&start, b"firstRow").as_deref() == Some("1");
1413 style_first_column =
1414 attribute_by_qname(&start, b"firstCol").as_deref() == Some("1");
1415 style_last_row = attribute_by_qname(&start, b"lastRow").as_deref() == Some("1");
1416 style_last_column = attribute_by_qname(&start, b"lastCol").as_deref() == Some("1");
1417 style_band_rows = attribute_by_qname(&start, b"bandRow").as_deref() == Some("1");
1418 style_band_columns = attribute_by_qname(&start, b"bandCol").as_deref() == Some("1");
1419 }
1420 Event::Start(start) if start.local_name().as_ref() == b"tblPr" => {
1421 style_first_row = attribute_by_qname(&start, b"firstRow").as_deref() == Some("1");
1422 style_first_column =
1423 attribute_by_qname(&start, b"firstCol").as_deref() == Some("1");
1424 style_last_row = attribute_by_qname(&start, b"lastRow").as_deref() == Some("1");
1425 style_last_column = attribute_by_qname(&start, b"lastCol").as_deref() == Some("1");
1426 style_band_rows = attribute_by_qname(&start, b"bandRow").as_deref() == Some("1");
1427 style_band_columns = attribute_by_qname(&start, b"bandCol").as_deref() == Some("1");
1428 style_id = parse_table_style_id(reader)?;
1429 }
1430
1431 Event::Start(start) if start.local_name().as_ref() == b"tblGrid" => {}
1432 Event::End(end) if end.local_name().as_ref() == b"tblGrid" => {}
1433 Event::Empty(start) if start.local_name().as_ref() == b"gridCol" => {
1434 if let Some(width) =
1435 attribute_by_qname(&start, b"w").and_then(|value| value.parse().ok())
1436 {
1437 column_widths_emu.push(width);
1438 }
1439 }
1440 Event::Start(start) if start.local_name().as_ref() == b"gridCol" => {
1441 if let Some(width) =
1442 attribute_by_qname(&start, b"w").and_then(|value| value.parse().ok())
1443 {
1444 column_widths_emu.push(width);
1445 }
1446 skip_subtree(reader)?;
1448 }
1449
1450 Event::Start(start) if start.local_name().as_ref() == b"tr" => {
1451 rows.push(parse_table_row(reader, &start)?);
1452 }
1453
1454 Event::Start(_) => skip_subtree(reader)?,
1455 Event::Empty(_) => {}
1456 _ => {}
1457 }
1458 }
1459
1460 Ok(SlideTable {
1461 id,
1462 name,
1463 offset_emu,
1464 extent_emu,
1465 column_widths_emu,
1466 rows,
1467 style_id,
1468 style_first_row,
1469 style_first_column,
1470 style_last_row,
1471 style_last_column,
1472 style_band_rows,
1473 style_band_columns,
1474 })
1475}
1476
1477fn parse_table_style_id(reader: &mut Reader) -> Result<Option<String>> {
1483 let mut style_id = None;
1484 let mut in_style_id = false;
1485
1486 loop {
1487 match reader.read_event()? {
1488 Event::Start(start) if start.local_name().as_ref() == b"tableStyleId" => {
1489 in_style_id = true
1490 }
1491 Event::End(end) if end.local_name().as_ref() == b"tableStyleId" => in_style_id = false,
1492 Event::Text(text) if in_style_id => {
1493 let decoded = xml_core::decode_text(&text)?;
1494 if decoded != TABLE_STYLE_GUID {
1495 style_id = Some(decoded);
1496 }
1497 }
1498
1499 Event::Start(_) => skip_subtree(reader)?,
1500 Event::End(end) if end.local_name().as_ref() == b"tblPr" => break,
1501 Event::Eof => break,
1502 _ => {}
1503 }
1504 }
1505
1506 Ok(style_id)
1507}
1508
1509fn parse_table_row(reader: &mut Reader, start: &BytesStart) -> Result<TableRow> {
1512 let height_emu = attribute_by_qname(start, b"h")
1513 .and_then(|value| value.parse().ok())
1514 .unwrap_or(0);
1515 let mut cells = Vec::new();
1516
1517 loop {
1518 match reader.read_event()? {
1519 Event::Eof => break,
1520 Event::End(end) if end.local_name().as_ref() == b"tr" => break,
1521
1522 Event::Start(start) if start.local_name().as_ref() == b"tc" => {
1523 cells.push(parse_table_cell(reader, &start)?);
1524 }
1525 Event::Empty(start) if start.local_name().as_ref() == b"tc" => {
1526 cells.push(TableCell {
1527 horizontal_span: attribute_by_qname(&start, b"gridSpan")
1528 .and_then(|value| value.parse().ok()),
1529 vertical_span: attribute_by_qname(&start, b"rowSpan")
1530 .and_then(|value| value.parse().ok()),
1531 horizontal_merge: attribute_by_qname(&start, b"hMerge").as_deref() == Some("1"),
1532 vertical_merge: attribute_by_qname(&start, b"vMerge").as_deref() == Some("1"),
1533 text_body: None,
1534 properties: None,
1535 });
1536 }
1537
1538 Event::Start(_) => skip_subtree(reader)?,
1539 Event::Empty(_) => {}
1540 _ => {}
1541 }
1542 }
1543
1544 Ok(TableRow { height_emu, cells })
1545}
1546
1547fn parse_table_cell(reader: &mut Reader, start: &BytesStart) -> Result<TableCell> {
1552 let horizontal_span =
1553 attribute_by_qname(start, b"gridSpan").and_then(|value| value.parse().ok());
1554 let vertical_span = attribute_by_qname(start, b"rowSpan").and_then(|value| value.parse().ok());
1555 let horizontal_merge = attribute_by_qname(start, b"hMerge").as_deref() == Some("1");
1556 let vertical_merge = attribute_by_qname(start, b"vMerge").as_deref() == Some("1");
1557 let mut text_body = None;
1558 let mut properties = None;
1559
1560 loop {
1561 match reader.read_event()? {
1562 Event::Eof => break,
1563 Event::End(end) if end.local_name().as_ref() == b"tc" => break,
1564
1565 Event::Start(start) if start.local_name().as_ref() == b"txBody" => {
1566 text_body = Some(drawing::read_text_body(reader)?);
1567 }
1568
1569 Event::Start(start) if start.local_name().as_ref() == b"tcPr" => {
1571 properties = Some(parse_table_cell_properties(reader, &start)?);
1572 }
1573 Event::Empty(start) if start.local_name().as_ref() == b"tcPr" => {
1574 properties = Some(parse_table_cell_properties_attributes(&start));
1575 }
1576
1577 Event::Start(_) => skip_subtree(reader)?,
1578 Event::Empty(_) => {}
1579 _ => {}
1580 }
1581 }
1582
1583 Ok(TableCell {
1584 text_body,
1585 horizontal_span,
1586 vertical_span,
1587 horizontal_merge,
1588 vertical_merge,
1589 properties,
1590 })
1591}
1592
1593fn parse_table_cell_properties_attributes(start: &BytesStart) -> TableCellProperties {
1597 TableCellProperties {
1598 margin_left_emu: attribute_by_qname(start, b"marL").and_then(|value| value.parse().ok()),
1599 margin_top_emu: attribute_by_qname(start, b"marT").and_then(|value| value.parse().ok()),
1600 margin_right_emu: attribute_by_qname(start, b"marR").and_then(|value| value.parse().ok()),
1601 margin_bottom_emu: attribute_by_qname(start, b"marB").and_then(|value| value.parse().ok()),
1602 anchor: attribute_by_qname(start, b"anchor")
1603 .and_then(|value| parse_table_cell_anchor(&value)),
1604 ..TableCellProperties::default()
1605 }
1606}
1607
1608fn parse_table_cell_properties(
1614 reader: &mut Reader,
1615 start: &BytesStart,
1616) -> Result<TableCellProperties> {
1617 let mut properties = parse_table_cell_properties_attributes(start);
1618
1619 loop {
1620 match reader.read_event()? {
1621 Event::Eof => break,
1622 Event::End(end) if end.local_name().as_ref() == b"tcPr" => break,
1623
1624 Event::Empty(start) if start.local_name().as_ref() == b"lnL" => {
1625 properties.border_left = Some(drawing::read_line(reader, &start, false)?);
1626 }
1627 Event::Start(start) if start.local_name().as_ref() == b"lnL" => {
1628 properties.border_left = Some(drawing::read_line(reader, &start, true)?);
1629 }
1630 Event::Empty(start) if start.local_name().as_ref() == b"lnR" => {
1631 properties.border_right = Some(drawing::read_line(reader, &start, false)?);
1632 }
1633 Event::Start(start) if start.local_name().as_ref() == b"lnR" => {
1634 properties.border_right = Some(drawing::read_line(reader, &start, true)?);
1635 }
1636 Event::Empty(start) if start.local_name().as_ref() == b"lnT" => {
1637 properties.border_top = Some(drawing::read_line(reader, &start, false)?);
1638 }
1639 Event::Start(start) if start.local_name().as_ref() == b"lnT" => {
1640 properties.border_top = Some(drawing::read_line(reader, &start, true)?);
1641 }
1642 Event::Empty(start) if start.local_name().as_ref() == b"lnB" => {
1643 properties.border_bottom = Some(drawing::read_line(reader, &start, false)?);
1644 }
1645 Event::Start(start) if start.local_name().as_ref() == b"lnB" => {
1646 properties.border_bottom = Some(drawing::read_line(reader, &start, true)?);
1647 }
1648
1649 Event::Start(start) if drawing::is_fill_choice_element(&start) => {
1656 properties.fill = Some(drawing::read_fill_choice(reader, &start, true)?);
1657 }
1658 Event::Empty(start) if drawing::is_fill_choice_element(&start) => {
1659 properties.fill = Some(drawing::read_fill_choice(reader, &start, false)?);
1660 }
1661
1662 Event::Start(_) => skip_subtree(reader)?,
1663 Event::Empty(_) => {}
1664 _ => {}
1665 }
1666 }
1667
1668 Ok(properties)
1669}
1670
1671fn parse_table_cell_anchor(token: &str) -> Option<TextAnchor> {
1675 Some(match token {
1676 "t" => TextAnchor::Top,
1677 "ctr" => TextAnchor::Center,
1678 "b" => TextAnchor::Bottom,
1679 "just" => TextAnchor::Justified,
1680 "dist" => TextAnchor::Distributed,
1681 _ => return None,
1682 })
1683}
1684
1685fn parse_frame_transform(reader: &mut Reader) -> Result<((i64, i64), (i64, i64))> {
1688 let mut offset = (0i64, 0i64);
1689 let mut extent = (0i64, 0i64);
1690
1691 loop {
1692 match reader.read_event()? {
1693 Event::Eof => break,
1694 Event::End(end) if end.local_name().as_ref() == b"xfrm" => break,
1695
1696 Event::Empty(start) if start.local_name().as_ref() == b"off" => {
1697 let x = attribute_by_qname(&start, b"x")
1698 .and_then(|value| value.parse().ok())
1699 .unwrap_or(0);
1700 let y = attribute_by_qname(&start, b"y")
1701 .and_then(|value| value.parse().ok())
1702 .unwrap_or(0);
1703 offset = (x, y);
1704 }
1705 Event::Empty(start) if start.local_name().as_ref() == b"ext" => {
1706 let cx = attribute_by_qname(&start, b"cx")
1707 .and_then(|value| value.parse().ok())
1708 .unwrap_or(0);
1709 let cy = attribute_by_qname(&start, b"cy")
1710 .and_then(|value| value.parse().ok())
1711 .unwrap_or(0);
1712 extent = (cx, cy);
1713 }
1714
1715 Event::Start(_) => skip_subtree(reader)?,
1716 Event::Empty(_) => {}
1717 _ => {}
1718 }
1719 }
1720
1721 Ok((offset, extent))
1722}
1723
1724fn parse_notes_slide_xml(xml: &str) -> Result<TextBody> {
1731 let mut reader = Reader::from_xml_str(xml);
1732 let mut body_text: Option<TextBody> = None;
1733
1734 loop {
1735 match reader.read_event()? {
1736 Event::Eof => break,
1737 Event::Start(start) if start.local_name().as_ref() == b"sp" => {
1738 let shape = parse_auto_shape(&mut reader, None)?;
1739 let is_body_placeholder = matches!(
1740 shape
1741 .placeholder
1742 .as_ref()
1743 .map(|placeholder| &placeholder.kind),
1744 Some(PlaceholderKind::Body)
1745 );
1746 if is_body_placeholder {
1747 if let Some(text_body) = shape.text_body {
1748 body_text = Some(text_body);
1749 }
1750 }
1751 }
1752 _ => {}
1753 }
1754 }
1755
1756 Ok(body_text.unwrap_or_default())
1757}
1758
1759fn read_presentation_theme(
1766 package: &Package,
1767 presentation_relationships: &Relationships,
1768) -> Result<Option<Theme>> {
1769 let Some(master_relationship) = presentation_relationships
1770 .iter()
1771 .find(|relationship| relationship.rel_type == SLIDE_MASTER_RELATIONSHIP_TYPE)
1772 else {
1773 return Ok(None);
1774 };
1775 let master_part_name = format!("/ppt/{}", master_relationship.target);
1778 let Some(master_part) = package.part(&master_part_name) else {
1779 return Ok(None);
1780 };
1781 let Some(theme_relationship) = master_part
1782 .relationships
1783 .iter()
1784 .find(|relationship| relationship.rel_type == THEME_RELATIONSHIP_TYPE)
1785 else {
1786 return Ok(None);
1787 };
1788 let theme_part_name = resolve_relative_target(&master_part_name, &theme_relationship.target);
1789 let Some(theme_part) = package.part(&theme_part_name) else {
1790 return Ok(None);
1791 };
1792 let xml = std::str::from_utf8(&theme_part.data)
1793 .map_err(|error| Error::InvalidUtf8(theme_part_name.clone(), error))?;
1794 Ok(Some(parse_theme_xml(xml)?))
1795}
1796
1797fn resolve_embedded_font_variant(
1802 package: &Package,
1803 presentation_relationships: &Relationships,
1804 raw_relationship_id: &Option<String>,
1805) -> Option<Vec<u8>> {
1806 let relationship_id = raw_relationship_id.as_ref()?;
1807 let relationship = presentation_relationships.by_id(relationship_id)?;
1808 let part_name = format!("/ppt/{}", relationship.target);
1809 package.part(&part_name).map(|part| part.data.clone())
1810}
1811
1812fn read_document_properties(package: &Package) -> Result<DocumentProperties> {
1816 let mut properties = DocumentProperties::new();
1817
1818 if let Some(relationship) = package
1819 .relationships()
1820 .iter()
1821 .find(|relationship| relationship.rel_type == CORE_PROPERTIES_RELATIONSHIP_TYPE)
1822 {
1823 let part_name = format!("/{}", relationship.target);
1824 if let Some(part) = package.part(&part_name) {
1825 let xml = std::str::from_utf8(&part.data)
1826 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
1827 let core_properties = parse_core_properties_xml(xml)?;
1828 properties.title = core_properties.title;
1829 properties.author = core_properties.author;
1830 properties.subject = core_properties.subject;
1831 properties.keywords = core_properties.keywords;
1832 }
1833 }
1834
1835 if let Some(relationship) = package
1836 .relationships()
1837 .iter()
1838 .find(|relationship| relationship.rel_type == EXTENDED_PROPERTIES_RELATIONSHIP_TYPE)
1839 {
1840 let part_name = format!("/{}", relationship.target);
1841 if let Some(part) = package.part(&part_name) {
1842 let xml = std::str::from_utf8(&part.data)
1843 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
1844 let (company, manager, hyperlink_base) = parse_extended_properties_xml(xml)?;
1845 properties.company = company;
1846 properties.manager = manager;
1847 properties.hyperlink_base = hyperlink_base;
1848 }
1849 }
1850
1851 if let Some(relationship) = package
1852 .relationships()
1853 .iter()
1854 .find(|relationship| relationship.rel_type == CUSTOM_PROPERTIES_RELATIONSHIP_TYPE)
1855 {
1856 let part_name = format!("/{}", relationship.target);
1857 if let Some(part) = package.part(&part_name) {
1858 let xml = std::str::from_utf8(&part.data)
1859 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
1860 properties.custom_properties = parse_custom_properties_xml(xml)?;
1861 }
1862 }
1863
1864 Ok(properties)
1865}
1866
1867struct ParsedCoreProperties {
1871 title: Option<String>,
1872 author: Option<String>,
1873 subject: Option<String>,
1874 keywords: Option<String>,
1875}
1876
1877fn parse_core_properties_xml(xml: &str) -> Result<ParsedCoreProperties> {
1881 let mut reader = Reader::from_xml_str(xml);
1882 let mut title = None;
1883 let mut author = None;
1884 let mut subject = None;
1885 let mut keywords = None;
1886 let mut in_element: Option<&'static str> = None;
1887 let mut buffer = String::new();
1888
1889 loop {
1890 match reader.read_event()? {
1891 Event::Eof => break,
1892 Event::Start(start) if start.local_name().as_ref() == b"title" => {
1893 in_element = Some("title");
1894 buffer.clear();
1895 }
1896 Event::Start(start) if start.local_name().as_ref() == b"creator" => {
1897 in_element = Some("creator");
1898 buffer.clear();
1899 }
1900 Event::Start(start) if start.local_name().as_ref() == b"subject" => {
1901 in_element = Some("subject");
1902 buffer.clear();
1903 }
1904 Event::Start(start) if start.local_name().as_ref() == b"keywords" => {
1905 in_element = Some("keywords");
1906 buffer.clear();
1907 }
1908 Event::Text(text) if in_element.is_some() => {
1909 buffer.push_str(&xml_core::decode_text(&text)?);
1910 }
1911 Event::GeneralRef(reference) if in_element.is_some() => {
1912 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
1913 buffer.push_str(&resolved);
1914 }
1915 }
1916 Event::End(end)
1917 if in_element.is_some()
1918 && matches!(
1919 end.local_name().as_ref(),
1920 b"title" | b"creator" | b"subject" | b"keywords"
1921 ) =>
1922 {
1923 let text = std::mem::take(&mut buffer);
1924 match in_element.take() {
1925 Some("title") => title = Some(text),
1926 Some("creator") => author = Some(text),
1927 Some("subject") => subject = Some(text),
1928 Some("keywords") => keywords = Some(text),
1929 _ => {}
1930 }
1931 }
1932 _ => {}
1933 }
1934 }
1935
1936 Ok(ParsedCoreProperties {
1937 title,
1938 author,
1939 subject,
1940 keywords,
1941 })
1942}
1943
1944fn parse_extended_properties_xml(
1947 xml: &str,
1948) -> Result<(Option<String>, Option<String>, Option<String>)> {
1949 let mut reader = Reader::from_xml_str(xml);
1950 let mut company = None;
1951 let mut manager = None;
1952 let mut hyperlink_base = None;
1953 let mut in_element: Option<&'static str> = None;
1954 let mut buffer = String::new();
1955
1956 loop {
1957 match reader.read_event()? {
1958 Event::Eof => break,
1959 Event::Start(start) if start.local_name().as_ref() == b"Company" => {
1960 in_element = Some("Company");
1961 buffer.clear();
1962 }
1963 Event::Start(start) if start.local_name().as_ref() == b"Manager" => {
1964 in_element = Some("Manager");
1965 buffer.clear();
1966 }
1967 Event::Start(start) if start.local_name().as_ref() == b"HyperlinkBase" => {
1968 in_element = Some("HyperlinkBase");
1969 buffer.clear();
1970 }
1971 Event::Text(text) if in_element.is_some() => {
1972 buffer.push_str(&xml_core::decode_text(&text)?);
1973 }
1974 Event::GeneralRef(reference) if in_element.is_some() => {
1975 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
1976 buffer.push_str(&resolved);
1977 }
1978 }
1979 Event::End(end)
1980 if in_element.is_some()
1981 && matches!(
1982 end.local_name().as_ref(),
1983 b"Company" | b"Manager" | b"HyperlinkBase"
1984 ) =>
1985 {
1986 let text = std::mem::take(&mut buffer);
1987 match in_element.take() {
1988 Some("Company") => company = Some(text),
1989 Some("Manager") => manager = Some(text),
1990 Some("HyperlinkBase") => hyperlink_base = Some(text),
1991 _ => {}
1992 }
1993 }
1994 _ => {}
1995 }
1996 }
1997
1998 Ok((company, manager, hyperlink_base))
1999}
2000
2001fn parse_custom_properties_xml(xml: &str) -> Result<Vec<(String, CustomPropertyValue)>> {
2005 let mut reader = Reader::from_xml_str(xml);
2006 let mut custom_properties = Vec::new();
2007 let mut current_name: Option<String> = None;
2008 let mut current_variant: Option<Vec<u8>> = None;
2009 let mut buffer = String::new();
2010
2011 loop {
2012 match reader.read_event()? {
2013 Event::Eof => break,
2014
2015 Event::Start(start) if start.local_name().as_ref() == b"property" => {
2016 current_name = attribute_by_qname(&start, b"name");
2017 }
2018
2019 Event::Start(start) | Event::Empty(start)
2020 if current_name.is_some()
2021 && matches!(
2022 start.local_name().as_ref(),
2023 b"lpwstr" | b"bool" | b"i4" | b"r8"
2024 ) =>
2025 {
2026 current_variant = Some(start.local_name().as_ref().to_vec());
2027 buffer.clear();
2028 }
2029
2030 Event::Text(text) if current_variant.is_some() => {
2031 buffer.push_str(&xml_core::decode_text(&text)?);
2032 }
2033
2034 Event::GeneralRef(reference) if current_variant.is_some() => {
2035 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
2036 buffer.push_str(&resolved);
2037 }
2038 }
2039
2040 Event::End(end) => {
2041 if let Some(variant) = current_variant.take() {
2042 if end.local_name().as_ref() == variant.as_slice() {
2043 if let Some(name) = current_name.clone() {
2044 let value = std::mem::take(&mut buffer);
2045 let parsed = match variant.as_slice() {
2046 b"lpwstr" => Some(CustomPropertyValue::Text(value)),
2047 b"bool" => {
2048 Some(CustomPropertyValue::Bool(value == "true" || value == "1"))
2049 }
2050 b"i4" => value.parse::<i32>().ok().map(CustomPropertyValue::Int),
2051 b"r8" => value.parse::<f64>().ok().map(CustomPropertyValue::Number),
2052 _ => None,
2053 };
2054 if let Some(value) = parsed {
2055 custom_properties.push((name, value));
2056 }
2057 }
2058 } else {
2059 current_variant = Some(variant);
2060 }
2061 } else if end.local_name().as_ref() == b"property" {
2062 current_name = None;
2063 }
2064 }
2065
2066 _ => {}
2067 }
2068 }
2069
2070 Ok(custom_properties)
2071}
2072
2073fn parse_table_styles_xml(xml: &str) -> Result<Vec<SlideTableStyle>> {
2077 let mut styles = Vec::new();
2078
2079 let mut reader = Reader::from_xml_str(xml);
2080 loop {
2081 match reader.read_event()? {
2082 Event::Eof => break,
2083 Event::Start(start) if start.local_name().as_ref() == b"tblStyle" => {
2084 let id = attribute_by_qname(&start, b"styleId").unwrap_or_default();
2085 let name = attribute_by_qname(&start, b"styleName").unwrap_or_default();
2086 styles.push(parse_table_style(&mut reader, id, name)?);
2087 }
2088 Event::Empty(start) if start.local_name().as_ref() == b"tblStyle" => {
2089 let id = attribute_by_qname(&start, b"styleId").unwrap_or_default();
2090 let name = attribute_by_qname(&start, b"styleName").unwrap_or_default();
2091 styles.push(SlideTableStyle {
2092 id,
2093 name,
2094 ..Default::default()
2095 });
2096 }
2097 _ => {}
2098 }
2099 }
2100
2101 Ok(styles)
2102}
2103
2104fn parse_table_style(reader: &mut Reader, id: String, name: String) -> Result<SlideTableStyle> {
2107 let mut style = SlideTableStyle {
2108 id,
2109 name,
2110 ..Default::default()
2111 };
2112
2113 loop {
2114 match reader.read_event()? {
2115 Event::Start(start) => {
2116 let wrapper_name = start.local_name().as_ref().to_vec();
2117 let part = Some(parse_table_style_part(reader, &wrapper_name)?);
2118 match wrapper_name.as_slice() {
2119 b"wholeTbl" => style.whole_table = part,
2120 b"band1H" => style.band1_horizontal = part,
2121 b"band2H" => style.band2_horizontal = part,
2122 b"band1V" => style.band1_vertical = part,
2123 b"band2V" => style.band2_vertical = part,
2124 b"firstRow" => style.first_row = part,
2125 b"lastRow" => style.last_row = part,
2126 b"firstCol" => style.first_column = part,
2127 b"lastCol" => style.last_column = part,
2128 _ => {}
2131 }
2132 }
2133 Event::Empty(_) => {}
2134 Event::End(end) if end.local_name().as_ref() == b"tblStyle" => break,
2135 Event::Eof => break,
2136 _ => {}
2137 }
2138 }
2139
2140 Ok(style)
2141}
2142
2143fn parse_table_style_part(
2154 reader: &mut Reader,
2155 wrapper_local_name: &[u8],
2156) -> Result<TableStylePart> {
2157 let mut part = TableStylePart::new();
2158
2159 loop {
2160 match reader.read_event()? {
2161 Event::Empty(start) if start.local_name().as_ref() == b"tcTxStyle" => {
2162 part.bold = attribute_by_qname(&start, b"b").as_deref() == Some("on");
2163 part.italic = attribute_by_qname(&start, b"i").as_deref() == Some("on");
2164 }
2165 Event::Start(start) if start.local_name().as_ref() == b"tcTxStyle" => {
2166 part.bold = attribute_by_qname(&start, b"b").as_deref() == Some("on");
2167 part.italic = attribute_by_qname(&start, b"i").as_deref() == Some("on");
2168 part.text_color = drawing::read_color(reader)?;
2169 }
2170
2171 Event::Start(start) if start.local_name().as_ref() == b"tcStyle" => {
2172 part.fill = parse_table_style_cell_fill(reader)?;
2173 }
2174 Event::Empty(start) if start.local_name().as_ref() == b"tcStyle" => {}
2175
2176 Event::Start(_) => skip_subtree(reader)?,
2177 Event::Empty(_) => {}
2178 Event::End(end) if end.local_name().as_ref() == wrapper_local_name => break,
2179 Event::Eof => break,
2180 _ => {}
2181 }
2182 }
2183
2184 Ok(part)
2185}
2186
2187fn parse_table_style_cell_fill(reader: &mut Reader) -> Result<Option<Fill>> {
2192 let mut fill = None;
2193
2194 loop {
2195 match reader.read_event()? {
2196 Event::Start(start) if start.local_name().as_ref() == b"fill" => {
2197 fill = drawing::read_fill(reader)?;
2198 }
2199 Event::Empty(start) if start.local_name().as_ref() == b"fill" => {}
2200
2201 Event::Start(_) => skip_subtree(reader)?,
2202 Event::Empty(_) => {}
2203 Event::End(end) if end.local_name().as_ref() == b"tcStyle" => break,
2204 Event::Eof => break,
2205 _ => {}
2206 }
2207 }
2208
2209 Ok(fill)
2210}
2211
2212fn parse_theme_xml(xml: &str) -> Result<Theme> {
2216 let mut reader = Reader::from_xml_str(xml);
2217 let mut name = String::new();
2218 let mut colors = ColorScheme::office_default();
2219 let mut fonts = FontScheme::office_default();
2220
2221 loop {
2222 match reader.read_event()? {
2223 Event::Eof => break,
2224
2225 Event::Start(start) if start.local_name().as_ref() == b"theme" => {
2226 if let Some(value) = attribute_by_qname(&start, b"name") {
2227 name = value;
2228 }
2229 }
2230 Event::Start(start) if start.local_name().as_ref() == b"clrScheme" => {
2231 colors = parse_color_scheme(&mut reader)?;
2232 }
2233 Event::Start(start) if start.local_name().as_ref() == b"fontScheme" => {
2234 fonts = parse_font_scheme(&mut reader)?;
2235 }
2236
2237 _ => {}
2238 }
2239 }
2240
2241 Ok(Theme {
2242 name,
2243 colors,
2244 fonts,
2245 })
2246}
2247
2248fn parse_color_scheme(reader: &mut Reader) -> Result<ColorScheme> {
2254 let mut colors = ColorScheme::office_default();
2255
2256 loop {
2257 match reader.read_event()? {
2258 Event::Eof => break,
2259 Event::End(end) if end.local_name().as_ref() == b"clrScheme" => break,
2260
2261 Event::Start(start) => {
2262 let slot = start.local_name().as_ref().to_vec();
2263 if let Some(value) = read_color_slot_value(reader, &slot)? {
2264 assign_color_slot(&mut colors, &slot, value);
2265 }
2266 }
2267
2268 _ => {}
2269 }
2270 }
2271
2272 Ok(colors)
2273}
2274
2275fn read_color_slot_value(reader: &mut Reader, wrapper_local_name: &[u8]) -> Result<Option<String>> {
2279 let mut value = None;
2280
2281 loop {
2282 match reader.read_event()? {
2283 Event::Eof => break,
2284 Event::End(end) if end.local_name().as_ref() == wrapper_local_name => break,
2285
2286 Event::Empty(start) | Event::Start(start) => match start.local_name().as_ref() {
2287 b"srgbClr" => value = attribute_by_qname(&start, b"val"),
2288 b"sysClr" => {
2289 value = attribute_by_qname(&start, b"lastClr")
2290 .or_else(|| attribute_by_qname(&start, b"val"))
2291 }
2292 _ => {}
2293 },
2294
2295 _ => {}
2296 }
2297 }
2298
2299 Ok(value)
2300}
2301
2302fn assign_color_slot(colors: &mut ColorScheme, slot: &[u8], value: String) {
2305 match slot {
2306 b"dk1" => colors.dark1 = value,
2307 b"lt1" => colors.light1 = value,
2308 b"dk2" => colors.dark2 = value,
2309 b"lt2" => colors.light2 = value,
2310 b"accent1" => colors.accent1 = value,
2311 b"accent2" => colors.accent2 = value,
2312 b"accent3" => colors.accent3 = value,
2313 b"accent4" => colors.accent4 = value,
2314 b"accent5" => colors.accent5 = value,
2315 b"accent6" => colors.accent6 = value,
2316 b"hlink" => colors.hyperlink = value,
2317 b"folHlink" => colors.followed_hyperlink = value,
2318 _ => {}
2319 }
2320}
2321
2322fn parse_font_scheme(reader: &mut Reader) -> Result<FontScheme> {
2326 let mut fonts = FontScheme::office_default();
2327
2328 loop {
2329 match reader.read_event()? {
2330 Event::Eof => break,
2331 Event::End(end) if end.local_name().as_ref() == b"fontScheme" => break,
2332
2333 Event::Start(start) if start.local_name().as_ref() == b"majorFont" => {
2334 if let Some(typeface) = parse_font_collection_latin(reader, b"majorFont")? {
2335 fonts.major_latin = typeface;
2336 }
2337 }
2338 Event::Start(start) if start.local_name().as_ref() == b"minorFont" => {
2339 if let Some(typeface) = parse_font_collection_latin(reader, b"minorFont")? {
2340 fonts.minor_latin = typeface;
2341 }
2342 }
2343
2344 _ => {}
2345 }
2346 }
2347
2348 Ok(fonts)
2349}
2350
2351fn parse_font_collection_latin(
2355 reader: &mut Reader,
2356 wrapper_local_name: &[u8],
2357) -> Result<Option<String>> {
2358 let mut typeface = None;
2359
2360 loop {
2361 match reader.read_event()? {
2362 Event::Eof => break,
2363 Event::End(end) if end.local_name().as_ref() == wrapper_local_name => break,
2364
2365 Event::Empty(start) | Event::Start(start)
2366 if start.local_name().as_ref() == b"latin" =>
2367 {
2368 typeface = attribute_by_qname(&start, b"typeface");
2369 }
2370
2371 Event::Start(_) => skip_subtree(reader)?,
2372 Event::Empty(_) => {}
2373 _ => {}
2374 }
2375 }
2376
2377 Ok(typeface)
2378}
2379
2380fn parse_comment_authors_xml(xml: &str) -> Result<HashMap<u32, (String, String)>> {
2383 let mut authors = HashMap::new();
2384 let mut reader = Reader::from_xml_str(xml);
2385
2386 loop {
2387 match reader.read_event()? {
2388 Event::Eof => break,
2389 Event::Empty(start) | Event::Start(start)
2390 if start.local_name().as_ref() == b"cmAuthor" =>
2391 {
2392 if let Some(id) =
2393 attribute_by_qname(&start, b"id").and_then(|value| value.parse().ok())
2394 {
2395 let name = attribute_by_qname(&start, b"name").unwrap_or_default();
2396 let initials = attribute_by_qname(&start, b"initials").unwrap_or_default();
2397 authors.insert(id, (name, initials));
2398 }
2399 }
2400 _ => {}
2401 }
2402 }
2403
2404 Ok(authors)
2405}
2406
2407fn parse_comments_xml(
2413 xml: &str,
2414 authors: &HashMap<u32, (String, String)>,
2415) -> Result<Vec<SlideComment>> {
2416 let mut comments = Vec::new();
2417 let mut reader = Reader::from_xml_str(xml);
2418
2419 loop {
2420 match reader.read_event()? {
2421 Event::Eof => break,
2422 Event::Start(start) if start.local_name().as_ref() == b"cm" => {
2423 let author_id: u32 = attribute_by_qname(&start, b"authorId")
2424 .and_then(|value| value.parse().ok())
2425 .unwrap_or(0);
2426 let (author, initials) = authors.get(&author_id).cloned().unwrap_or_default();
2427 let date = attribute_by_qname(&start, b"dt").unwrap_or_default();
2428 comments.push(parse_comment_body(&mut reader, author, initials, date)?);
2429 }
2430 _ => {}
2431 }
2432 }
2433
2434 Ok(comments)
2435}
2436
2437fn parse_comment_body(
2440 reader: &mut Reader,
2441 author: String,
2442 initials: String,
2443 date: String,
2444) -> Result<SlideComment> {
2445 let mut position_emu = (0i64, 0i64);
2446 let mut text = String::new();
2447
2448 loop {
2449 match reader.read_event()? {
2450 Event::Eof => break,
2451 Event::End(end) if end.local_name().as_ref() == b"cm" => break,
2452
2453 Event::Empty(start) if start.local_name().as_ref() == b"pos" => {
2454 position_emu = read_xy_attributes(&start, b"x", b"y");
2455 }
2456 Event::Start(start) if start.local_name().as_ref() == b"text" => {
2457 text = read_element_text(reader)?;
2458 }
2459
2460 Event::Start(_) => skip_subtree(reader)?,
2461 Event::Empty(_) => {}
2462 _ => {}
2463 }
2464 }
2465
2466 Ok(SlideComment {
2467 author,
2468 initials,
2469 date,
2470 position_emu,
2471 text,
2472 })
2473}
2474
2475fn read_element_text(reader: &mut Reader) -> Result<String> {
2481 let mut text = String::new();
2482
2483 loop {
2484 match reader.read_event()? {
2485 Event::Eof | Event::End(_) => break,
2486 Event::Text(bytes_text) => {
2487 text.push_str(&xml_core::decode_text(&bytes_text)?);
2488 }
2489 Event::GeneralRef(bytes_ref) => {
2490 if let Some(decoded) = xml_core::decode_general_ref(&bytes_ref)? {
2491 text.push_str(&decoded);
2492 }
2493 }
2494 _ => {}
2495 }
2496 }
2497
2498 Ok(text)
2499}
2500
2501fn attribute_by_qname(start: &BytesStart, qname: &[u8]) -> Option<String> {
2507 start
2508 .attributes()
2509 .flatten()
2510 .find(|attribute| attribute.key.as_ref() == qname)
2511 .and_then(|attribute| {
2512 xml_core::decode_attribute_value(attribute.value.as_ref())
2513 .ok()
2514 .flatten()
2515 })
2516}
2517
2518fn skip_subtree(reader: &mut Reader) -> Result<()> {
2522 let mut depth: u32 = 0;
2523 loop {
2524 match reader.read_event()? {
2525 Event::Start(_) => depth += 1,
2526 Event::End(_) => {
2527 if depth == 0 {
2528 return Ok(());
2529 }
2530 depth -= 1;
2531 }
2532 Event::Eof => return Ok(()),
2533 _ => {}
2534 }
2535 }
2536}