1use crate::error::{Error, Result};
6use crate::ext::{CommonSlideDataExt, GroupShapeExt, PictureExt, ShapeExt};
7use crate::parsers::FromXml;
8use crate::types;
9use ooxml_dml::ext::{TextBodyExt, TextParagraphExt, TextRunExt};
10use ooxml_opc::{Package, Relationships};
11use quick_xml::Reader;
12use quick_xml::events::Event;
13use std::fs::File;
14use std::io::{BufReader, Cursor, Read, Seek};
15use std::path::Path;
16
17const REL_OFFICE_DOCUMENT: &str =
19 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
20const REL_SLIDE: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
21const REL_NOTES_SLIDE: &str =
22 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide";
23const REL_SLIDE_MASTER: &str =
24 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster";
25const REL_SLIDE_LAYOUT: &str =
26 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
27
28pub struct Presentation<R: Read + Seek> {
32 package: Package<R>,
33 #[allow(dead_code)]
35 presentation_path: String,
36 #[allow(dead_code)]
38 pres_rels: Relationships,
39 slide_info: Vec<SlideInfo>,
41 slide_masters: Vec<SlideMaster>,
43 slide_layouts: Vec<SlideLayout>,
45}
46
47#[derive(Debug, Clone)]
49struct SlideInfo {
50 #[allow(dead_code)]
51 rel_id: String,
52 path: String,
53 index: usize,
54 layout_rel_id: Option<String>,
56}
57
58#[derive(Debug, Clone)]
60pub struct ImageData {
61 pub data: Vec<u8>,
63 pub content_type: String,
65}
66
67#[derive(Debug, Clone)]
72pub struct SlideMaster {
73 path: String,
75 pub name: Option<String>,
77 layout_ids: Vec<String>,
79 pub color_scheme: Option<String>,
81 pub background_color: Option<String>,
83}
84
85impl SlideMaster {
86 pub fn path(&self) -> &str {
88 &self.path
89 }
90
91 pub fn layout_count(&self) -> usize {
93 self.layout_ids.len()
94 }
95}
96
97#[derive(Debug, Clone)]
102pub struct SlideLayout {
103 path: String,
105 pub name: Option<String>,
107 pub layout_type: SlideLayoutType,
109 #[allow(dead_code)]
111 master_rel_id: Option<String>,
112 pub match_name: bool,
114 pub show_master_shapes: bool,
116}
117
118impl SlideLayout {
119 pub fn path(&self) -> &str {
121 &self.path
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
129pub enum SlideLayoutType {
130 Blank,
132 #[default]
134 Title,
135 TitleAndContent,
137 SectionHeader,
139 TwoContent,
141 TwoContentAndText,
143 TitleOnly,
145 ContentWithCaption,
147 PictureWithCaption,
149 VerticalTitleAndText,
151 VerticalText,
153 Custom,
155 Unknown,
157}
158
159impl SlideLayoutType {
160 fn parse(s: &str) -> Self {
162 match s {
163 "blank" => Self::Blank,
164 "title" | "tx" => Self::Title,
165 "obj" | "objTx" | "twoObj" | "twoObjAndTx" => Self::TitleAndContent,
166 "secHead" => Self::SectionHeader,
167 "twoTxTwoObj" => Self::TwoContent,
168 "objAndTx" => Self::TwoContentAndText,
169 "titleOnly" => Self::TitleOnly,
170 "objOnly" => Self::ContentWithCaption,
171 "picTx" => Self::PictureWithCaption,
172 "vertTx" => Self::VerticalText,
173 "vertTitleAndTx" => Self::VerticalTitleAndText,
174 "cust" => Self::Custom,
175 _ => Self::Unknown,
176 }
177 }
178}
179
180impl Presentation<BufReader<File>> {
181 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
183 let file = File::open(path)?;
184 Self::from_reader(BufReader::new(file))
185 }
186}
187
188impl<R: Read + Seek> Presentation<R> {
189 pub fn from_reader(reader: R) -> Result<Self> {
191 let mut package = Package::open(reader)?;
192
193 let root_rels = package.read_relationships()?;
195 let pres_rel = root_rels
196 .get_by_type(REL_OFFICE_DOCUMENT)
197 .ok_or_else(|| Error::Invalid("Missing presentation relationship".into()))?;
198 let presentation_path = pres_rel.target.clone();
199
200 let pres_rels = package
202 .read_part_relationships(&presentation_path)
203 .unwrap_or_default();
204
205 let pres_xml = package.read_part(&presentation_path)?;
207 let slide_order = parse_presentation_slides(&pres_xml)?;
208
209 let mut slide_masters: Vec<SlideMaster> = Vec::new();
211 let mut slide_layouts: Vec<SlideLayout> = Vec::new();
212
213 for rel in pres_rels.iter() {
214 if rel.relationship_type == REL_SLIDE_MASTER {
215 let path = resolve_path(&presentation_path, &rel.target);
216 if let Ok(master_xml) = package.read_part(&path) {
217 let master = parse_slide_master(&master_xml, &path);
218 let master_path = path.clone();
219
220 if let Ok(master_rels) = package.read_part_relationships(&path) {
222 for layout_rel in master_rels.iter() {
223 if layout_rel.relationship_type == REL_SLIDE_LAYOUT {
224 let layout_path = resolve_path(&master_path, &layout_rel.target);
225 if let Ok(layout_xml) = package.read_part(&layout_path) {
226 let layout = parse_slide_layout(
227 &layout_xml,
228 &layout_path,
229 Some(layout_rel.id.clone()),
230 );
231 slide_layouts.push(layout);
232 }
233 }
234 }
235 }
236
237 slide_masters.push(master);
238 }
239 }
240 }
241
242 let mut slide_info: Vec<SlideInfo> = Vec::new();
244 for rel in pres_rels.iter() {
245 if rel.relationship_type == REL_SLIDE {
246 let path = resolve_path(&presentation_path, &rel.target);
247 let index = slide_order
249 .iter()
250 .position(|id| id == &rel.id)
251 .unwrap_or(slide_info.len());
252
253 let layout_rel_id = if let Ok(slide_rels) = package.read_part_relationships(&path) {
255 slide_rels
256 .get_by_type(REL_SLIDE_LAYOUT)
257 .map(|r| r.id.clone())
258 } else {
259 None
260 };
261
262 slide_info.push(SlideInfo {
263 rel_id: rel.id.clone(),
264 path,
265 index,
266 layout_rel_id,
267 });
268 }
269 }
270
271 slide_info.sort_by_key(|s| s.index);
273
274 Ok(Self {
275 package,
276 presentation_path,
277 pres_rels,
278 slide_info,
279 slide_masters,
280 slide_layouts,
281 })
282 }
283
284 pub fn slide_count(&self) -> usize {
286 self.slide_info.len()
287 }
288
289 pub fn slide_masters(&self) -> &[SlideMaster] {
291 &self.slide_masters
292 }
293
294 pub fn slide_layouts(&self) -> &[SlideLayout] {
296 &self.slide_layouts
297 }
298
299 pub fn layout_by_name(&self, name: &str) -> Option<&SlideLayout> {
301 self.slide_layouts
302 .iter()
303 .find(|l| l.name.as_deref() == Some(name))
304 }
305
306 pub fn slide(&mut self, index: usize) -> Result<Slide> {
308 let info = self
309 .slide_info
310 .get(index)
311 .ok_or_else(|| Error::Invalid(format!("Slide index {} out of range", index)))?
312 .clone();
313
314 self.load_slide(&info)
315 }
316
317 pub fn slides(&mut self) -> Result<Vec<Slide>> {
319 let infos: Vec<_> = self.slide_info.clone();
320 infos.iter().map(|info| self.load_slide(info)).collect()
321 }
322
323 fn load_slide(&mut self, info: &SlideInfo) -> Result<Slide> {
325 let data = self.package.read_part(&info.path)?;
326
327 let inner = parse_slide_xml(&data)?;
329
330 let tables = extract_tables_from_slide(&inner);
332
333 let (chart_rel_ids, smartart_rel_ids) = extract_charts_and_smartart_from_slide(&inner);
335
336 let mut slide = Slide {
338 inner,
339 index: info.index,
340 slide_path: info.path.clone(),
341 notes: None,
342 layout_rel_id: info.layout_rel_id.clone(),
343 tables,
344 chart_rel_ids,
345 smartart_rel_ids,
346 };
347
348 if let Ok(slide_rels) = self.package.read_part_relationships(&info.path)
350 && let Some(notes_rel) = slide_rels.get_by_type(REL_NOTES_SLIDE)
351 {
352 let notes_path = resolve_path(&info.path, ¬es_rel.target);
353 if let Ok(notes_data) = self.package.read_part(¬es_path) {
354 slide.notes = parse_notes_slide(¬es_data);
355 }
356 }
357
358 Ok(slide)
359 }
360
361 pub fn get_image_data(&mut self, slide: &Slide, picture: &types::Picture) -> Result<ImageData> {
365 let rel_id = picture
367 .embed_rel_id()
368 .ok_or_else(|| Error::Invalid("Picture has no embed relationship ID".into()))?;
369
370 let slide_rels = self
372 .package
373 .read_part_relationships(slide.slide_path())
374 .map_err(|_| Error::Invalid("Failed to read slide relationships".into()))?;
375
376 let rel = slide_rels
378 .get(rel_id)
379 .ok_or_else(|| Error::Invalid(format!("Image relationship {} not found", rel_id)))?;
380
381 let image_path = resolve_path(slide.slide_path(), &rel.target);
383
384 let data = self.package.read_part(&image_path)?;
386
387 let content_type = content_type_from_path(&image_path);
389
390 Ok(ImageData { data, content_type })
391 }
392
393 pub fn resolve_hyperlink(&mut self, slide: &Slide, rel_id: &str) -> Result<String> {
402 let slide_rels = self
404 .package
405 .read_part_relationships(slide.slide_path())
406 .map_err(|_| Error::Invalid("Failed to read slide relationships".into()))?;
407
408 let rel = slide_rels.get(rel_id).ok_or_else(|| {
410 Error::Invalid(format!("Hyperlink relationship {} not found", rel_id))
411 })?;
412
413 Ok(rel.target.clone())
414 }
415
416 pub fn get_hyperlinks_with_urls(&mut self, slide: &Slide) -> Result<Vec<(String, String)>> {
420 let hyperlinks = slide.hyperlinks();
421 let mut results = Vec::new();
422
423 for link in hyperlinks {
424 if let Ok(url) = self.resolve_hyperlink(slide, &link.rel_id) {
425 results.push((link.text, url));
426 }
427 }
428
429 Ok(results)
430 }
431
432 #[cfg(feature = "pml-charts")]
441 pub fn get_chart(
442 &mut self,
443 slide: &Slide,
444 rel_id: &str,
445 ) -> Result<ooxml_dml::types::ChartSpace> {
446 let slide_rels = self
448 .package
449 .read_part_relationships(slide.slide_path())
450 .map_err(|_| Error::Invalid("Failed to read slide relationships".into()))?;
451
452 let rel = slide_rels
453 .get(rel_id)
454 .ok_or_else(|| Error::Invalid(format!("Chart relationship {} not found", rel_id)))?;
455
456 let chart_path = resolve_path(slide.slide_path(), &rel.target);
457 let chart_xml = self.package.read_part(&chart_path)?;
458
459 parse_chart(&chart_xml)
460 }
461
462 #[cfg(feature = "pml-charts")]
475 pub fn get_smartart(
476 &mut self,
477 slide: &Slide,
478 rel_ids: &DiagramRelIds,
479 ) -> Result<SmartArtParts> {
480 let slide_rels = self
481 .package
482 .read_part_relationships(slide.slide_path())
483 .map_err(|_| Error::Invalid("Failed to read slide relationships".into()))?;
484
485 let resolve_rel = |rel_id: &str| -> Option<String> {
487 slide_rels
488 .get(rel_id)
489 .map(|r| resolve_path(slide.slide_path(), &r.target))
490 };
491
492 let dm_path = resolve_rel(&rel_ids.dm).ok_or_else(|| {
494 Error::Invalid(format!(
495 "SmartArt data model relationship {} not found",
496 rel_ids.dm
497 ))
498 })?;
499 let dm_xml = self.package.read_part(&dm_path)?;
500 let data = parse_data_model(&dm_xml)?;
501
502 let layout = resolve_rel(&rel_ids.lo)
504 .and_then(|path| self.package.read_part(&path).ok())
505 .and_then(|xml| parse_diagram_definition(&xml).ok());
506
507 let colors = resolve_rel(&rel_ids.cs)
509 .and_then(|path| self.package.read_part(&path).ok())
510 .and_then(|xml| parse_diagram_colors(&xml).ok());
511
512 let style = resolve_rel(&rel_ids.qs)
514 .and_then(|path| self.package.read_part(&path).ok())
515 .and_then(|xml| parse_diagram_style(&xml).ok());
516
517 Ok(SmartArtParts {
518 data,
519 layout,
520 colors,
521 style,
522 })
523 }
524}
525
526#[derive(Debug, Clone)]
535pub struct DiagramRelIds {
536 pub dm: String,
538 pub lo: String,
540 pub qs: String,
542 pub cs: String,
544}
545
546#[cfg(feature = "pml-charts")]
550pub struct SmartArtParts {
551 pub data: ooxml_dml::types::DataModel,
553 pub layout: Option<ooxml_dml::types::DiagramDefinition>,
555 pub colors: Option<ooxml_dml::types::DiagramColorTransform>,
557 pub style: Option<ooxml_dml::types::DiagramStyleDefinition>,
559}
560
561#[derive(Debug, Clone)]
567pub struct Slide {
568 inner: types::Slide,
570 index: usize,
572 slide_path: String,
574 notes: Option<String>,
576 layout_rel_id: Option<String>,
578 tables: Vec<Table>,
580 chart_rel_ids: Vec<String>,
582 smartart_rel_ids: Vec<DiagramRelIds>,
584}
585
586#[derive(Debug, Clone, Default)]
590pub struct Transition {
591 pub transition_type: Option<TransitionType>,
593 pub speed: TransitionSpeed,
595 pub advance_on_click: bool,
597 pub advance_time_ms: Option<u32>,
599}
600
601#[derive(Debug, Clone, PartialEq, Eq)]
603pub enum TransitionType {
604 Fade,
606 Push,
608 Wipe,
610 Split,
612 Blinds,
614 Checker,
616 Circle,
618 Dissolve,
620 Comb,
622 Cover,
624 Cut,
626 Diamond,
628 Plus,
630 Random,
632 Strips,
634 Wedge,
636 Wheel,
638 Zoom,
640 Other(String),
642}
643
644impl TransitionType {
645 pub fn to_xml_value(&self) -> &str {
647 match self {
648 TransitionType::Fade => "fade",
649 TransitionType::Push => "push",
650 TransitionType::Wipe => "wipe",
651 TransitionType::Split => "split",
652 TransitionType::Blinds => "blinds",
653 TransitionType::Checker => "checker",
654 TransitionType::Circle => "circle",
655 TransitionType::Dissolve => "dissolve",
656 TransitionType::Comb => "comb",
657 TransitionType::Cover => "cover",
658 TransitionType::Cut => "cut",
659 TransitionType::Diamond => "diamond",
660 TransitionType::Plus => "plus",
661 TransitionType::Random => "random",
662 TransitionType::Strips => "strips",
663 TransitionType::Wedge => "wedge",
664 TransitionType::Wheel => "wheel",
665 TransitionType::Zoom => "zoom",
666 TransitionType::Other(name) => name.as_str(),
667 }
668 }
669}
670
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
673pub enum TransitionSpeed {
674 Slow,
676 #[default]
678 Medium,
679 Fast,
681}
682
683impl TransitionSpeed {
684 pub fn to_xml_value(self) -> &'static str {
686 match self {
687 TransitionSpeed::Slow => "slow",
688 TransitionSpeed::Medium => "med",
689 TransitionSpeed::Fast => "fast",
690 }
691 }
692}
693
694impl Slide {
695 pub fn index(&self) -> usize {
697 self.index
698 }
699
700 pub fn shapes(&self) -> &[types::Shape] {
705 self.inner.common_slide_data.shape_tree.shapes()
706 }
707
708 pub fn text(&self) -> String {
710 self.inner.common_slide_data.text()
711 }
712
713 pub fn notes(&self) -> Option<&str> {
715 self.notes.as_deref()
716 }
717
718 pub fn has_notes(&self) -> bool {
720 self.notes.as_ref().is_some_and(|n| !n.is_empty())
721 }
722
723 pub fn pictures(&self) -> &[types::Picture] {
728 self.inner.common_slide_data.pictures()
729 }
730
731 pub(crate) fn slide_path(&self) -> &str {
733 &self.slide_path
734 }
735
736 #[cfg(feature = "pml-transitions")]
738 pub fn transition(&self) -> Option<Transition> {
739 self.inner
740 .transition
741 .as_ref()
742 .map(|t| convert_transition(t))
743 }
744
745 #[cfg(not(feature = "pml-transitions"))]
747 pub fn transition(&self) -> Option<Transition> {
748 None
749 }
750
751 #[cfg(feature = "pml-transitions")]
753 pub fn has_transition(&self) -> bool {
754 self.inner.transition.is_some()
755 }
756
757 #[cfg(not(feature = "pml-transitions"))]
759 pub fn has_transition(&self) -> bool {
760 false
761 }
762
763 pub fn layout_rel_id(&self) -> Option<&str> {
767 self.layout_rel_id.as_deref()
768 }
769
770 pub fn hyperlinks(&self) -> Vec<Hyperlink> {
774 let mut links = Vec::new();
775 for shape in self.shapes() {
776 if let Some(text_body) = shape.text_body() {
777 for para in text_body.paragraphs() {
778 for run in para.runs() {
779 if let Some(rel_id) = run.hyperlink_rel_id() {
780 links.push(Hyperlink {
781 text: run.text().to_string(),
782 rel_id: rel_id.to_string(),
783 });
784 }
785 }
786 }
787 }
788 }
789 links
790 }
791
792 pub fn has_hyperlinks(&self) -> bool {
794 self.shapes().iter().any(|s| {
795 s.text_body().is_some_and(|tb| {
796 tb.paragraphs()
797 .iter()
798 .any(|p| p.runs().iter().any(|r| r.has_hyperlink()))
799 })
800 })
801 }
802
803 pub fn tables(&self) -> &[Table] {
805 &self.tables
806 }
807
808 pub fn has_tables(&self) -> bool {
810 !self.tables.is_empty()
811 }
812
813 pub fn table_count(&self) -> usize {
815 self.tables.len()
816 }
817
818 pub fn inner(&self) -> &types::Slide {
822 &self.inner
823 }
824
825 pub fn table(&self, index: usize) -> Option<&Table> {
827 self.tables.get(index)
828 }
829
830 pub fn chart_rel_ids(&self) -> &[String] {
835 &self.chart_rel_ids
836 }
837
838 pub fn smartart_rel_ids(&self) -> &[DiagramRelIds] {
843 &self.smartart_rel_ids
844 }
845}
846
847#[derive(Debug, Clone)]
855pub struct Table {
856 name: Option<String>,
858 inner: ooxml_dml::types::CTTable,
860}
861
862impl Table {
863 pub fn name(&self) -> Option<&str> {
865 self.name.as_deref()
866 }
867
868 pub fn inner(&self) -> &ooxml_dml::types::CTTable {
870 &self.inner
871 }
872
873 pub fn inner_mut(&mut self) -> &mut ooxml_dml::types::CTTable {
875 &mut self.inner
876 }
877
878 pub fn into_inner(self) -> ooxml_dml::types::CTTable {
880 self.inner
881 }
882
883 pub fn rows(&self) -> &[ooxml_dml::types::CTTableRow] {
885 use ooxml_dml::TableExt;
886 self.inner.rows()
887 }
888
889 pub fn row_count(&self) -> usize {
891 use ooxml_dml::TableExt;
892 self.inner.row_count()
893 }
894
895 pub fn col_count(&self) -> usize {
897 use ooxml_dml::TableExt;
898 self.inner.col_count()
899 }
900
901 pub fn cell(&self, row: usize, col: usize) -> Option<&ooxml_dml::types::CTTableCell> {
903 use ooxml_dml::TableExt;
904 self.inner.cell(row, col)
905 }
906
907 pub fn to_text_grid(&self) -> Vec<Vec<String>> {
909 use ooxml_dml::TableExt;
910 self.inner.to_text_grid()
911 }
912
913 pub fn text(&self) -> String {
915 use ooxml_dml::TableExt;
916 self.inner.text()
917 }
918}
919
920#[derive(Debug, Clone)]
922pub struct Hyperlink {
923 pub text: String,
925 pub rel_id: String,
927}
928
929fn parse_presentation_slides(xml: &[u8]) -> Result<Vec<String>> {
935 let mut reader = Reader::from_reader(Cursor::new(xml));
936 let mut buf = Vec::new();
937 let mut slide_ids = Vec::new();
938 let mut in_sld_id_lst = false;
939
940 loop {
941 match reader.read_event_into(&mut buf) {
942 Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
943 let name = e.name();
944 let name = name.as_ref();
945 if name == b"p:sldIdLst" {
946 in_sld_id_lst = true;
947 } else if in_sld_id_lst && name == b"p:sldId" {
948 for attr in e.attributes().filter_map(|a| a.ok()) {
950 if attr.key.as_ref() == b"r:id" {
951 slide_ids.push(String::from_utf8_lossy(&attr.value).into_owned());
952 }
953 }
954 }
955 }
956 Ok(Event::End(e)) => {
957 let name = e.name();
958 if name.as_ref() == b"p:sldIdLst" {
959 in_sld_id_lst = false;
960 }
961 }
962 Ok(Event::Eof) => break,
963 Err(e) => return Err(Error::Xml(e)),
964 _ => {}
965 }
966 buf.clear();
967 }
968
969 Ok(slide_ids)
970}
971
972fn parse_notes_slide(xml: &[u8]) -> Option<String> {
974 use ooxml_dml::parsers::FromXml as DmlFromXml;
975 use ooxml_dml::types::TextBody;
976
977 let mut reader = Reader::from_reader(Cursor::new(xml));
978 let mut buf = Vec::new();
979 let mut all_text = Vec::new();
980
981 loop {
982 match reader.read_event_into(&mut buf) {
983 Ok(Event::Start(e)) => {
984 let local = e.local_name();
985 let local = local.as_ref();
986 if local == b"txBody"
988 && let Ok(text_body) = TextBody::from_xml(&mut reader, &e, false)
989 {
990 for para in &text_body.p {
991 let text = para.text();
992 if !text.is_empty() {
993 all_text.push(text);
994 }
995 }
996 }
997 }
998 Ok(Event::Eof) => break,
999 Err(_) => break,
1000 _ => {}
1001 }
1002 buf.clear();
1003 }
1004
1005 if all_text.is_empty() {
1006 None
1007 } else {
1008 Some(all_text.join("\n"))
1009 }
1010}
1011
1012fn resolve_path(base: &str, target: &str) -> String {
1018 if target.starts_with('/') {
1019 return target.to_string();
1021 }
1022
1023 let base_dir = if let Some(idx) = base.rfind('/') {
1025 &base[..idx + 1]
1026 } else {
1027 ""
1028 };
1029
1030 normalize_path(&format!("{}{}", base_dir, target))
1031}
1032
1033fn normalize_path(path: &str) -> String {
1035 let mut parts: Vec<&str> = Vec::new();
1036 for segment in path.split('/') {
1037 match segment {
1038 ".." => {
1039 parts.pop();
1040 }
1041 "." | "" => {}
1042 _ => parts.push(segment),
1043 }
1044 }
1045 parts.join("/")
1046}
1047
1048fn content_type_from_path(path: &str) -> String {
1050 let ext = path.rsplit('.').next().unwrap_or("").to_ascii_lowercase();
1051
1052 match ext.as_str() {
1053 "png" => "image/png",
1054 "jpg" | "jpeg" => "image/jpeg",
1055 "gif" => "image/gif",
1056 "bmp" => "image/bmp",
1057 "tiff" | "tif" => "image/tiff",
1058 "webp" => "image/webp",
1059 "svg" => "image/svg+xml",
1060 "emf" => "image/x-emf",
1061 "wmf" => "image/x-wmf",
1062 _ => "application/octet-stream",
1063 }
1064 .to_string()
1065}
1066
1067fn parse_slide_master(xml: &[u8], path: &str) -> SlideMaster {
1069 let mut reader = Reader::from_reader(Cursor::new(xml));
1070 let mut buf = Vec::new();
1071 let mut name = None;
1072 let mut color_scheme = None;
1073 let mut background_color = None;
1074 let mut layout_ids = Vec::new();
1075
1076 loop {
1077 match reader.read_event_into(&mut buf) {
1078 Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
1079 let tag = e.name();
1080 let tag = tag.as_ref();
1081 match tag {
1082 b"p:cSld" => {
1083 for attr in e.attributes().filter_map(|a| a.ok()) {
1084 if attr.key.as_ref() == b"name" {
1085 name = Some(String::from_utf8_lossy(&attr.value).into_owned());
1086 }
1087 }
1088 }
1089 b"p:sldLayoutId" => {
1090 for attr in e.attributes().filter_map(|a| a.ok()) {
1091 if attr.key.as_ref() == b"r:id" {
1092 layout_ids.push(String::from_utf8_lossy(&attr.value).into_owned());
1093 }
1094 }
1095 }
1096 b"a:clrScheme" => {
1097 for attr in e.attributes().filter_map(|a| a.ok()) {
1098 if attr.key.as_ref() == b"name" {
1099 color_scheme =
1100 Some(String::from_utf8_lossy(&attr.value).into_owned());
1101 }
1102 }
1103 }
1104 b"a:srgbClr" => {
1105 if background_color.is_none() {
1106 for attr in e.attributes().filter_map(|a| a.ok()) {
1107 if attr.key.as_ref() == b"val" {
1108 background_color =
1109 Some(String::from_utf8_lossy(&attr.value).into_owned());
1110 }
1111 }
1112 }
1113 }
1114 _ => {}
1115 }
1116 }
1117 Ok(Event::Eof) => break,
1118 _ => {}
1119 }
1120 buf.clear();
1121 }
1122
1123 SlideMaster {
1124 path: path.to_string(),
1125 name,
1126 layout_ids,
1127 color_scheme,
1128 background_color,
1129 }
1130}
1131
1132fn parse_slide_layout(xml: &[u8], path: &str, master_rel_id: Option<String>) -> SlideLayout {
1134 let mut reader = Reader::from_reader(Cursor::new(xml));
1135 let mut buf = Vec::new();
1136 let mut name = None;
1137 let mut layout_type = SlideLayoutType::Unknown;
1138 let mut match_name = false;
1139 let mut show_master_shapes = true;
1140
1141 loop {
1142 match reader.read_event_into(&mut buf) {
1143 Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
1144 let tag = e.name();
1145 let tag = tag.as_ref();
1146 match tag {
1147 b"p:sldLayout" => {
1148 for attr in e.attributes().filter_map(|a| a.ok()) {
1149 let val = String::from_utf8_lossy(&attr.value);
1150 match attr.key.as_ref() {
1151 b"type" => layout_type = SlideLayoutType::parse(&val),
1152 b"matchingName" => match_name = val == "1" || val == "true",
1153 b"showMasterSp" => {
1154 show_master_shapes = val != "0" && val != "false"
1155 }
1156 _ => {}
1157 }
1158 }
1159 }
1160 b"p:cSld" => {
1161 for attr in e.attributes().filter_map(|a| a.ok()) {
1162 if attr.key.as_ref() == b"name" {
1163 name = Some(String::from_utf8_lossy(&attr.value).into_owned());
1164 }
1165 }
1166 }
1167 _ => {}
1168 }
1169 }
1170 Ok(Event::Eof) => break,
1171 _ => {}
1172 }
1173 buf.clear();
1174 }
1175
1176 SlideLayout {
1177 path: path.to_string(),
1178 name,
1179 layout_type,
1180 master_rel_id,
1181 match_name,
1182 show_master_shapes,
1183 }
1184}
1185
1186fn parse_slide_xml(xml: &[u8]) -> Result<types::Slide> {
1192 let mut reader = Reader::from_reader(Cursor::new(xml));
1193 let mut buf = Vec::new();
1194
1195 loop {
1197 match reader.read_event_into(&mut buf) {
1198 Ok(Event::Start(e)) => {
1199 let local_name = e.local_name();
1200 if local_name.as_ref() == b"sld" {
1201 return types::Slide::from_xml(&mut reader, &e, false)
1202 .map_err(|e| Error::Invalid(format!("Failed to parse slide: {}", e)));
1203 }
1204 }
1205 Ok(Event::Eof) => break,
1206 Err(e) => return Err(Error::Xml(e)),
1207 _ => {}
1208 }
1209 buf.clear();
1210 }
1211
1212 Err(Error::Invalid("No p:sld element found in slide XML".into()))
1213}
1214
1215#[cfg(feature = "pml-transitions")]
1217fn convert_transition(trans: &types::SlideTransition) -> Transition {
1218 let speed = trans
1220 .spd
1221 .as_ref()
1222 .map_or(TransitionSpeed::Medium, |s| match s {
1223 types::STTransitionSpeed::Slow => TransitionSpeed::Slow,
1224 types::STTransitionSpeed::Med => TransitionSpeed::Medium,
1225 types::STTransitionSpeed::Fast => TransitionSpeed::Fast,
1226 });
1227
1228 let transition_type = if trans.fade.is_some() {
1230 Some(TransitionType::Fade)
1231 } else if trans.push.is_some() {
1232 Some(TransitionType::Push)
1233 } else if trans.wipe.is_some() {
1234 Some(TransitionType::Wipe)
1235 } else if trans.split.is_some() {
1236 Some(TransitionType::Split)
1237 } else if trans.blinds.is_some() {
1238 Some(TransitionType::Blinds)
1239 } else if trans.checker.is_some() {
1240 Some(TransitionType::Checker)
1241 } else if trans.circle.is_some() {
1242 Some(TransitionType::Circle)
1243 } else if trans.dissolve.is_some() {
1244 Some(TransitionType::Dissolve)
1245 } else if trans.comb.is_some() {
1246 Some(TransitionType::Comb)
1247 } else if trans.cover.is_some() {
1248 Some(TransitionType::Cover)
1249 } else if trans.cut.is_some() {
1250 Some(TransitionType::Cut)
1251 } else if trans.diamond.is_some() {
1252 Some(TransitionType::Diamond)
1253 } else if trans.plus.is_some() {
1254 Some(TransitionType::Plus)
1255 } else if trans.random.is_some() {
1256 Some(TransitionType::Random)
1257 } else if trans.strips.is_some() {
1258 Some(TransitionType::Strips)
1259 } else if trans.wedge.is_some() {
1260 Some(TransitionType::Wedge)
1261 } else if trans.wheel.is_some() {
1262 Some(TransitionType::Wheel)
1263 } else if trans.zoom.is_some() {
1264 Some(TransitionType::Zoom)
1265 } else {
1266 None
1267 };
1268
1269 Transition {
1270 transition_type,
1271 speed,
1272 advance_on_click: trans.adv_click.unwrap_or(true),
1273 advance_time_ms: trans.adv_tm,
1274 }
1275}
1276
1277#[cfg(feature = "extra-children")]
1282fn extract_tables_from_slide(slide: &types::Slide) -> Vec<Table> {
1283 use crate::ext::GraphicalObjectFrameExt;
1284
1285 let mut tables = Vec::new();
1286
1287 for frame in &slide.common_slide_data.shape_tree.graphic_frame {
1289 let frame_name = Some(frame.name().to_string()).filter(|s| !s.is_empty());
1291
1292 for node in &frame.extra_children {
1294 if let Some(table) = find_table_in_node(&node.node, frame_name.clone()) {
1295 tables.push(table);
1296 }
1297 }
1298 }
1299
1300 tables
1301}
1302
1303#[cfg(not(feature = "extra-children"))]
1305fn extract_tables_from_slide(_slide: &types::Slide) -> Vec<Table> {
1306 Vec::new()
1307}
1308
1309#[cfg(feature = "extra-children")]
1311fn find_table_in_node(node: &ooxml_xml::RawXmlNode, frame_name: Option<String>) -> Option<Table> {
1312 use ooxml_xml::RawXmlNode;
1313
1314 match node {
1315 RawXmlNode::Element(elem) => {
1316 let local_name = elem.name.split(':').next_back().unwrap_or(&elem.name);
1318 if local_name == "tbl" {
1319 return parse_table_element(elem, frame_name);
1320 }
1321
1322 for child in &elem.children {
1324 if let Some(table) = find_table_in_node(child, frame_name.clone()) {
1325 return Some(table);
1326 }
1327 }
1328 None
1329 }
1330 _ => None,
1331 }
1332}
1333
1334#[cfg(feature = "extra-children")]
1336fn parse_table_element(
1337 elem: &ooxml_xml::RawXmlElement,
1338 frame_name: Option<String>,
1339) -> Option<Table> {
1340 use ooxml_dml::types::CTTable;
1341
1342 elem.parse_as::<CTTable>()
1344 .ok()
1345 .map(|ct_table| wrap_ct_table(ct_table, frame_name))
1346}
1347
1348#[cfg(feature = "extra-children")]
1350fn wrap_ct_table(ct_table: ooxml_dml::types::CTTable, name: Option<String>) -> Table {
1351 Table {
1352 name,
1353 inner: ct_table,
1354 }
1355}
1356
1357#[cfg(feature = "extra-children")]
1366fn extract_charts_and_smartart_from_slide(
1367 slide: &types::Slide,
1368) -> (Vec<String>, Vec<DiagramRelIds>) {
1369 let mut chart_ids = Vec::new();
1370 let mut smartart_ids = Vec::new();
1371
1372 for frame in &slide.common_slide_data.shape_tree.graphic_frame {
1373 for node in &frame.extra_children {
1374 collect_chart_and_smartart_ids(&node.node, &mut chart_ids, &mut smartart_ids);
1375 }
1376 }
1377
1378 (chart_ids, smartart_ids)
1379}
1380
1381#[cfg(not(feature = "extra-children"))]
1383fn extract_charts_and_smartart_from_slide(
1384 _slide: &types::Slide,
1385) -> (Vec<String>, Vec<DiagramRelIds>) {
1386 (Vec::new(), Vec::new())
1387}
1388
1389#[cfg(feature = "extra-children")]
1392fn collect_chart_and_smartart_ids(
1393 node: &ooxml_xml::RawXmlNode,
1394 chart_ids: &mut Vec<String>,
1395 smartart_ids: &mut Vec<DiagramRelIds>,
1396) {
1397 use ooxml_xml::RawXmlNode;
1398
1399 if let RawXmlNode::Element(elem) = node {
1400 let local = elem.name.split(':').next_back().unwrap_or(&elem.name);
1401 match local {
1402 "chart" => {
1404 for (attr_name, attr_val) in &elem.attributes {
1405 let attr_local = attr_name.split(':').next_back().unwrap_or(attr_name);
1406 if attr_local == "id" {
1407 chart_ids.push(attr_val.clone());
1408 }
1409 }
1410 }
1411 "relIds" => {
1413 let mut dm = None;
1414 let mut lo = None;
1415 let mut qs = None;
1416 let mut cs = None;
1417 for (attr_name, attr_val) in &elem.attributes {
1418 let attr_local = attr_name.split(':').next_back().unwrap_or(attr_name);
1419 match attr_local {
1420 "dm" => dm = Some(attr_val.clone()),
1421 "lo" => lo = Some(attr_val.clone()),
1422 "qs" => qs = Some(attr_val.clone()),
1423 "cs" => cs = Some(attr_val.clone()),
1424 _ => {}
1425 }
1426 }
1427 if let (Some(dm), Some(lo), Some(qs), Some(cs)) = (dm, lo, qs, cs) {
1428 smartart_ids.push(DiagramRelIds { dm, lo, qs, cs });
1429 }
1430 }
1431 _ => {
1433 for child in &elem.children {
1434 collect_chart_and_smartart_ids(child, chart_ids, smartart_ids);
1435 }
1436 }
1437 }
1438 }
1439}
1440
1441#[cfg(feature = "pml-charts")]
1450fn parse_chart(xml: &[u8]) -> Result<ooxml_dml::types::ChartSpace> {
1451 use ooxml_dml::parsers::FromXml as DmlFromXml;
1452 let mut reader = Reader::from_reader(Cursor::new(xml));
1453 let mut buf = Vec::new();
1454
1455 loop {
1456 match reader.read_event_into(&mut buf) {
1457 Ok(Event::Start(e)) => {
1458 return ooxml_dml::types::ChartSpace::from_xml(&mut reader, &e, false)
1459 .map_err(|e| Error::Invalid(format!("Failed to parse chartSpace: {}", e)));
1460 }
1461 Ok(Event::Empty(e)) => {
1462 return ooxml_dml::types::ChartSpace::from_xml(&mut reader, &e, true)
1463 .map_err(|e| Error::Invalid(format!("Failed to parse chartSpace: {}", e)));
1464 }
1465 Ok(Event::Eof) => break,
1466 Err(e) => return Err(Error::Xml(e)),
1467 _ => {}
1468 }
1469 buf.clear();
1470 }
1471 Err(Error::Invalid("No chartSpace element found".into()))
1472}
1473
1474#[cfg(feature = "pml-charts")]
1478fn parse_data_model(xml: &[u8]) -> Result<ooxml_dml::types::DataModel> {
1479 use ooxml_dml::parsers::FromXml as DmlFromXml;
1480 let mut reader = Reader::from_reader(Cursor::new(xml));
1481 let mut buf = Vec::new();
1482
1483 loop {
1484 match reader.read_event_into(&mut buf) {
1485 Ok(Event::Start(e)) => {
1486 return ooxml_dml::types::DataModel::from_xml(&mut reader, &e, false)
1487 .map_err(|e| Error::Invalid(format!("Failed to parse dataModel: {}", e)));
1488 }
1489 Ok(Event::Empty(e)) => {
1490 return ooxml_dml::types::DataModel::from_xml(&mut reader, &e, true)
1491 .map_err(|e| Error::Invalid(format!("Failed to parse dataModel: {}", e)));
1492 }
1493 Ok(Event::Eof) => break,
1494 Err(e) => return Err(Error::Xml(e)),
1495 _ => {}
1496 }
1497 buf.clear();
1498 }
1499 Err(Error::Invalid("No dataModel element found".into()))
1500}
1501
1502#[cfg(feature = "pml-charts")]
1506fn parse_diagram_definition(xml: &[u8]) -> Result<ooxml_dml::types::DiagramDefinition> {
1507 use ooxml_dml::parsers::FromXml as DmlFromXml;
1508 let mut reader = Reader::from_reader(Cursor::new(xml));
1509 let mut buf = Vec::new();
1510
1511 loop {
1512 match reader.read_event_into(&mut buf) {
1513 Ok(Event::Start(e)) => {
1514 return ooxml_dml::types::DiagramDefinition::from_xml(&mut reader, &e, false)
1515 .map_err(|e| Error::Invalid(format!("Failed to parse layoutDef: {}", e)));
1516 }
1517 Ok(Event::Empty(e)) => {
1518 return ooxml_dml::types::DiagramDefinition::from_xml(&mut reader, &e, true)
1519 .map_err(|e| Error::Invalid(format!("Failed to parse layoutDef: {}", e)));
1520 }
1521 Ok(Event::Eof) => break,
1522 Err(e) => return Err(Error::Xml(e)),
1523 _ => {}
1524 }
1525 buf.clear();
1526 }
1527 Err(Error::Invalid("No layoutDef element found".into()))
1528}
1529
1530#[cfg(feature = "pml-charts")]
1534fn parse_diagram_colors(xml: &[u8]) -> Result<ooxml_dml::types::DiagramColorTransform> {
1535 use ooxml_dml::parsers::FromXml as DmlFromXml;
1536 let mut reader = Reader::from_reader(Cursor::new(xml));
1537 let mut buf = Vec::new();
1538
1539 loop {
1540 match reader.read_event_into(&mut buf) {
1541 Ok(Event::Start(e)) => {
1542 return ooxml_dml::types::DiagramColorTransform::from_xml(&mut reader, &e, false)
1543 .map_err(|e| Error::Invalid(format!("Failed to parse colorsDef: {}", e)));
1544 }
1545 Ok(Event::Empty(e)) => {
1546 return ooxml_dml::types::DiagramColorTransform::from_xml(&mut reader, &e, true)
1547 .map_err(|e| Error::Invalid(format!("Failed to parse colorsDef: {}", e)));
1548 }
1549 Ok(Event::Eof) => break,
1550 Err(e) => return Err(Error::Xml(e)),
1551 _ => {}
1552 }
1553 buf.clear();
1554 }
1555 Err(Error::Invalid("No colorsDef element found".into()))
1556}
1557
1558#[cfg(feature = "pml-charts")]
1562fn parse_diagram_style(xml: &[u8]) -> Result<ooxml_dml::types::DiagramStyleDefinition> {
1563 use ooxml_dml::parsers::FromXml as DmlFromXml;
1564 let mut reader = Reader::from_reader(Cursor::new(xml));
1565 let mut buf = Vec::new();
1566
1567 loop {
1568 match reader.read_event_into(&mut buf) {
1569 Ok(Event::Start(e)) => {
1570 return ooxml_dml::types::DiagramStyleDefinition::from_xml(&mut reader, &e, false)
1571 .map_err(|e| Error::Invalid(format!("Failed to parse styleDef: {}", e)));
1572 }
1573 Ok(Event::Empty(e)) => {
1574 return ooxml_dml::types::DiagramStyleDefinition::from_xml(&mut reader, &e, true)
1575 .map_err(|e| Error::Invalid(format!("Failed to parse styleDef: {}", e)));
1576 }
1577 Ok(Event::Eof) => break,
1578 Err(e) => return Err(Error::Xml(e)),
1579 _ => {}
1580 }
1581 buf.clear();
1582 }
1583 Err(Error::Invalid("No styleDef element found".into()))
1584}
1585
1586#[cfg(test)]
1587mod tests {
1588 use super::*;
1589
1590 #[test]
1591 fn test_resolve_path() {
1592 assert_eq!(
1593 resolve_path("ppt/presentation.xml", "slides/slide1.xml"),
1594 "ppt/slides/slide1.xml"
1595 );
1596 assert_eq!(
1597 resolve_path("ppt/presentation.xml", "/ppt/slides/slide1.xml"),
1598 "/ppt/slides/slide1.xml"
1599 );
1600 assert_eq!(
1602 resolve_path(
1603 "ppt/slideMasters/slideMaster1.xml",
1604 "../slideLayouts/slideLayout1.xml"
1605 ),
1606 "ppt/slideLayouts/slideLayout1.xml"
1607 );
1608 assert_eq!(
1609 resolve_path(
1610 "ppt/slideLayouts/slideLayout1.xml",
1611 "../slideMasters/slideMaster1.xml"
1612 ),
1613 "ppt/slideMasters/slideMaster1.xml"
1614 );
1615 }
1616
1617 #[cfg(feature = "extra-children")]
1622 fn build_raw_element_from_xml(xml: &str) -> ooxml_xml::RawXmlElement {
1623 use ooxml_xml::{RawXmlElement, RawXmlNode};
1624
1625 let mut reader = Reader::from_reader(Cursor::new(xml.as_bytes()));
1626 reader.config_mut().trim_text(false);
1627 let mut buf = Vec::new();
1628 let mut stack: Vec<RawXmlElement> = Vec::new();
1629 let mut root: Option<RawXmlElement> = None;
1630
1631 loop {
1632 match reader.read_event_into(&mut buf) {
1633 Ok(Event::Start(e)) => {
1634 let name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
1635 let attrs: Vec<(String, String)> = e
1636 .attributes()
1637 .filter_map(|a| a.ok())
1638 .map(|attr| {
1639 let k = String::from_utf8_lossy(attr.key.as_ref()).into_owned();
1640 let v = String::from_utf8_lossy(&attr.value).into_owned();
1641 (k, v)
1642 })
1643 .collect();
1644 stack.push(RawXmlElement {
1645 name,
1646 attributes: attrs,
1647 children: Vec::new(),
1648 self_closing: false,
1649 });
1650 }
1651 Ok(Event::Empty(e)) => {
1652 let name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
1653 let attrs: Vec<(String, String)> = e
1654 .attributes()
1655 .filter_map(|a| a.ok())
1656 .map(|attr| {
1657 let k = String::from_utf8_lossy(attr.key.as_ref()).into_owned();
1658 let v = String::from_utf8_lossy(&attr.value).into_owned();
1659 (k, v)
1660 })
1661 .collect();
1662 let elem = RawXmlElement {
1663 name,
1664 attributes: attrs,
1665 children: Vec::new(),
1666 self_closing: true,
1667 };
1668 if let Some(parent) = stack.last_mut() {
1669 parent.children.push(RawXmlNode::Element(elem));
1670 } else {
1671 root = Some(elem);
1672 }
1673 }
1674 Ok(Event::End(_)) => {
1675 if let Some(finished) = stack.pop() {
1676 if let Some(parent) = stack.last_mut() {
1677 parent.children.push(RawXmlNode::Element(finished));
1678 } else {
1679 root = Some(finished);
1680 }
1681 }
1682 }
1683 Ok(Event::Eof) => break,
1684 _ => {}
1685 }
1686 buf.clear();
1687 }
1688
1689 root.expect("test XML must have a root element")
1690 }
1691
1692 #[cfg(feature = "extra-children")]
1695 #[test]
1696 fn test_extract_chart_rel_id() {
1697 use ooxml_xml::RawXmlNode;
1698
1699 let xml = r#"<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart"><c:chart r:id="rId5"/></a:graphicData></a:graphic>"#;
1700 let elem = build_raw_element_from_xml(xml);
1701 let node = RawXmlNode::Element(elem);
1702
1703 let mut chart_ids = Vec::new();
1704 let mut smartart_ids = Vec::new();
1705 collect_chart_and_smartart_ids(&node, &mut chart_ids, &mut smartart_ids);
1706
1707 assert_eq!(chart_ids, vec!["rId5".to_string()]);
1708 assert!(smartart_ids.is_empty());
1709 }
1710
1711 #[cfg(feature = "extra-children")]
1714 #[test]
1715 fn test_extract_smartart_rel_ids() {
1716 use ooxml_xml::RawXmlNode;
1717
1718 let xml = r#"<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/diagram"><dgm:relIds r:dm="rId4" r:lo="rId5" r:qs="rId6" r:cs="rId7"/></a:graphicData></a:graphic>"#;
1719 let elem = build_raw_element_from_xml(xml);
1720 let node = RawXmlNode::Element(elem);
1721
1722 let mut chart_ids = Vec::new();
1723 let mut smartart_ids = Vec::new();
1724 collect_chart_and_smartart_ids(&node, &mut chart_ids, &mut smartart_ids);
1725
1726 assert!(chart_ids.is_empty());
1727 assert_eq!(smartart_ids.len(), 1);
1728 assert_eq!(smartart_ids[0].dm, "rId4");
1729 assert_eq!(smartart_ids[0].lo, "rId5");
1730 assert_eq!(smartart_ids[0].qs, "rId6");
1731 assert_eq!(smartart_ids[0].cs, "rId7");
1732 }
1733}