1#![allow(clippy::field_reassign_with_default)]
34
35use quick_xml::events::Event;
36use quick_xml::reader::Reader;
37
38use crate::oxml::color::{Color, PresetColor, SchemeColor};
39use crate::oxml::shape::{
40 Connector, GraphicFrame, Group, GroupChild, Pic, ShapeLocks, ShapeStyle, Sp, StyleRef,
41};
42use crate::oxml::simpletypes::{
43 Alignment, MsoAnchor, PresetGeometry, TabAlignment, TextWrapping, Underline,
44};
45use crate::oxml::slide::Sld;
46use crate::oxml::slide::{
47 MorphOption, SplitOrientation, Transition, TransitionDirection, TransitionSpeed, TransitionType,
48};
49use crate::oxml::sppr::{
50 ArrowHead, ArrowSize, ArrowType, Backdrop, Bevel, Camera, CameraPreset, CustomGeometry,
51 EffectList, Fill, GeomRect, Geometry, GlowEffect, GradientFill, GradientPath, GradientStop,
52 GradientType, LightRig, LightRigDirection, LightRigType, Line, LineJoin, MaterialPreset, Path,
53 PathSegment, PatternFill, Point3d, ReflectionEffect, Rotation3d, Scene3d, ShadowEffect,
54 ShapeProperties, SoftEdgeEffect, Sp3d,
55};
56use crate::oxml::txbody::{
57 BodyProperties, BulletStyle, Field, FieldType, Hyperlink, Paragraph, ParagraphProperties, Run,
58 RunProperties, TabStop, TextBody,
59};
60use crate::oxml::SlideShape as OxmlSlideShape;
61use crate::units::{Emu, Pt, RGBColor};
62
63pub fn parse_sld(xml: &str) -> crate::Result<Sld> {
73 let mut rd = Reader::from_str(xml);
74 rd.config_mut().trim_text(true);
75 let mut buf = Vec::new();
76 let mut sld = Sld::default();
77 let mut state: u8 = 0;
82 let mut sp_bufs: Vec<String> = Vec::new();
84 let mut pic_bufs: Vec<String> = Vec::new();
85 let mut cxn_bufs: Vec<String> = Vec::new();
86 let mut grp_bufs: Vec<String> = Vec::new();
87 let mut gfx_bufs: Vec<String> = Vec::new();
88
89 loop {
90 match rd.read_event_into(&mut buf) {
91 Ok(Event::Start(e)) => {
92 let name = e.name();
93 let local = local_name(name.as_ref());
94 match state {
95 0 if local == b"sld" => {
96 for a in e.attributes().flatten() {
99 if a.key.as_ref() == b"id" {
100 if let Ok(v) = a
101 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
102 .unwrap_or_default()
103 .parse::<u32>()
104 {
105 sld.id = v;
106 }
107 }
108 }
109 state = 1;
110 }
111 1 if local == b"cSld" => {
112 for a in e.attributes().flatten() {
114 if a.key.as_ref() == b"name" {
115 sld.name = a
116 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
117 .unwrap_or_default()
118 .to_string();
119 }
120 }
121 }
122 1 if local == b"spTree" => {
123 state = 2;
124 }
125 1 if local == b"transition" => {
126 let inner = collect_full_element(&mut rd, e.into_owned())?;
129 if let Ok(tr) = parse_transition(&inner) {
130 sld.transition = Some(tr);
131 }
132 }
133 2 if local == b"sp" => {
134 let inner = collect_full_element(&mut rd, e.into_owned())?;
136 sp_bufs.push(inner);
137 }
138 2 if local == b"pic" => {
139 let inner = collect_full_element(&mut rd, e.into_owned())?;
140 pic_bufs.push(inner);
141 }
142 2 if local == b"cxnSp" => {
143 let inner = collect_full_element(&mut rd, e.into_owned())?;
144 cxn_bufs.push(inner);
145 }
146 2 if local == b"grpSp" => {
147 let inner = collect_full_element(&mut rd, e.into_owned())?;
148 grp_bufs.push(inner);
149 }
150 2 if local == b"graphicFrame" => {
151 let inner = collect_full_element(&mut rd, e.into_owned())?;
152 gfx_bufs.push(inner);
153 }
154 _ => {
155 let _ = collect_full_element(&mut rd, e.into_owned());
157 }
158 }
159 }
160 Ok(Event::Empty(e)) => {
161 let name = e.name();
162 let local = local_name(name.as_ref());
163 if state == 2 {
164 if local == b"sp" {
165 } else if local == b"pic" {
167 }
169 }
170 }
171 Ok(Event::End(e)) => {
172 let name = e.name();
173 let local = local_name(name.as_ref());
174 if state == 1 && local == b"sld" {
175 break;
176 } else if state == 2 && local == b"spTree" {
177 state = 1;
178 }
179 }
180 Ok(Event::Eof) => break,
181 Err(e) => return Err(crate::Error::Xml(format!("sld parse: {e}"))),
182 _ => {}
183 }
184 buf.clear();
185 }
186 for s in sp_bufs {
188 match parse_sp(&s) {
189 Ok(sp) => sld.shapes.push(OxmlSlideShape::Sp(sp)),
190 Err(e) => {
191 return Err(e);
193 }
194 }
195 }
196 for p in pic_bufs {
198 if let Ok(pic) = parse_pic(&p) {
199 sld.shapes.push(OxmlSlideShape::Pic(pic));
200 }
201 }
203 for c in cxn_bufs {
205 if let Ok(cxn) = parse_cxn_sp(&c) {
206 sld.shapes.push(OxmlSlideShape::CxnSp(cxn));
207 }
208 }
209 for g in grp_bufs {
211 if let Ok(grp) = parse_grp_sp(&g) {
212 sld.shapes.push(OxmlSlideShape::Group(Box::new(grp)));
213 }
214 }
215 for f in gfx_bufs {
217 if let Ok(frame) = parse_graphic_frame(&f) {
218 sld.shapes.push(OxmlSlideShape::GraphicFrame(frame));
219 }
220 }
221 Ok(sld)
222}
223
224pub fn parse_transition(xml: &str) -> crate::Result<Transition> {
240 let mut rd = Reader::from_str(xml);
241 rd.config_mut().trim_text(true);
242 let mut buf = Vec::new();
243 let mut tr = Transition::default();
244 tr.advance_click = true;
246 let mut in_transition = false;
247
248 loop {
249 match rd.read_event_into(&mut buf) {
250 Ok(Event::Start(e)) => {
251 let name = e.name();
252 let local = local_name(name.as_ref());
253 if !in_transition && local == b"transition" {
254 in_transition = true;
255 for a in e.attributes().flatten() {
257 let key = a.key.as_ref();
258 let v = a
259 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
260 .unwrap_or_default();
261 match key {
262 b"spd" => {
263 tr.speed = match v.as_ref() {
264 "slow" => TransitionSpeed::Slow,
265 "fast" => TransitionSpeed::Fast,
266 _ => TransitionSpeed::Medium,
267 };
268 }
269 b"advClick" => {
270 tr.advance_click = v == "1" || v == "true";
271 }
272 b"advTm" => {
273 tr.advance_after_ms = v.parse::<u32>().ok();
274 }
275 _ => {}
276 }
277 }
278 } else if in_transition {
279 parse_transition_type_child(local, &e, &mut tr, false)?;
281 }
282 }
283 Ok(Event::Empty(e)) => {
284 let name = e.name();
285 let local = local_name(name.as_ref());
286 if !in_transition && local == b"transition" {
287 in_transition = true;
289 for a in e.attributes().flatten() {
290 let key = a.key.as_ref();
291 let v = a
292 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
293 .unwrap_or_default();
294 match key {
295 b"spd" => {
296 tr.speed = match v.as_ref() {
297 "slow" => TransitionSpeed::Slow,
298 "fast" => TransitionSpeed::Fast,
299 _ => TransitionSpeed::Medium,
300 };
301 }
302 b"advClick" => {
303 tr.advance_click = v == "1" || v == "true";
304 }
305 b"advTm" => {
306 tr.advance_after_ms = v.parse::<u32>().ok();
307 }
308 _ => {}
309 }
310 }
311 } else if in_transition {
312 parse_transition_type_child(local, &e, &mut tr, true)?;
314 }
315 }
316 Ok(Event::End(e)) => {
317 let name = e.name();
318 let local = local_name(name.as_ref());
319 if in_transition && local == b"transition" {
320 break;
321 }
322 }
323 Ok(Event::Eof) => break,
324 Err(e) => return Err(crate::Error::Xml(format!("transition parse: {e}"))),
325 _ => {}
326 }
327 buf.clear();
328 }
329 Ok(tr)
330}
331
332fn parse_transition_type_child(
340 local: &[u8],
341 e: &quick_xml::events::BytesStart<'_>,
342 tr: &mut Transition,
343 _is_empty: bool,
344) -> crate::Result<()> {
345 let attr = |key: &[u8]| -> Option<String> {
347 for a in e.attributes().flatten() {
348 if a.key.as_ref() == key {
349 return Some(
350 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
351 .unwrap_or_default()
352 .to_string(),
353 );
354 }
355 }
356 None
357 };
358
359 match local {
360 b"fade" => {
361 let thru_blk = attr(b"thruBlk")
362 .map(|v| v == "1" || v == "true")
363 .unwrap_or(false);
364 tr.transition_type = TransitionType::Fade { thru_blk };
365 }
366 b"push" => {
367 let dir = parse_transition_dir(&attr(b"dir").unwrap_or_default());
368 tr.transition_type = TransitionType::Push { dir };
369 }
370 b"wipe" => {
371 let dir = parse_transition_dir(&attr(b"dir").unwrap_or_default());
372 tr.transition_type = TransitionType::Wipe { dir };
373 }
374 b"split" => {
375 let orient = match attr(b"orient").as_deref() {
376 Some("vert") => SplitOrientation::Vertical,
377 _ => SplitOrientation::Horizontal,
378 };
379 let dir = parse_transition_dir(&attr(b"dir").unwrap_or_default());
380 tr.transition_type = TransitionType::Split { orient, dir };
381 }
382 b"cover" => {
383 let dir = parse_transition_dir(&attr(b"dir").unwrap_or_default());
384 tr.transition_type = TransitionType::Cover { dir };
385 }
386 b"pull" => {
387 let dir = parse_transition_dir(&attr(b"dir").unwrap_or_default());
388 tr.transition_type = TransitionType::Pull { dir };
389 }
390 b"cut" => {
391 let thru_blk = attr(b"thruBlk")
392 .map(|v| v == "1" || v == "true")
393 .unwrap_or(false);
394 tr.transition_type = TransitionType::Cut { thru_blk };
395 }
396 b"zoom" => {
397 let dir = parse_transition_dir(&attr(b"dir").unwrap_or_default());
398 tr.transition_type = TransitionType::Zoom { dir };
399 }
400 b"morph" => {
401 let option = match attr(b"option").as_deref() {
402 Some("byWord") => MorphOption::ByWord,
403 Some("byChar") => MorphOption::ByChar,
404 _ => MorphOption::ByObject,
405 };
406 tr.transition_type = TransitionType::Morph { option };
407 }
408 _ => {
409 }
411 }
412 Ok(())
413}
414
415fn parse_transition_dir(s: &str) -> TransitionDirection {
417 match s {
418 "l" => TransitionDirection::Left,
419 "r" => TransitionDirection::Right,
420 "u" => TransitionDirection::Up,
421 "d" => TransitionDirection::Down,
422 "lu" => TransitionDirection::LeftUp,
423 "ld" => TransitionDirection::LeftDown,
424 "ru" => TransitionDirection::RightUp,
425 "rd" => TransitionDirection::RightDown,
426 _ => TransitionDirection::Right,
427 }
428}
429
430pub fn find_relationship_target(rels_xml: &str, rid: &str) -> crate::Result<String> {
436 let rels = crate::opc::rels::Relationships::from_xml(rels_xml)?;
437 rels.get(rid)
438 .map(|r| r.target.as_str().to_string())
439 .ok_or_else(|| crate::Error::opc(format!("relationship not found: {rid}")))
440}
441
442pub fn all_relationships(rels_xml: &str) -> crate::Result<Vec<(String, String, String)>> {
444 use crate::opc::rels::Relationships;
446 let rels = Relationships::from_xml(rels_xml)?;
447 Ok(rels
448 .iter()
449 .map(|r| {
450 (
451 r.id.clone(),
452 r.reltype.uri().to_string(),
453 r.target.as_str().to_string(),
454 )
455 })
456 .collect())
457}
458
459pub fn parse_sp(xml: &str) -> crate::Result<Sp> {
461 let mut rd = Reader::from_str(xml);
462 rd.config_mut().trim_text(true);
463 let mut buf = Vec::new();
464 let mut sp = Sp::default();
465 let mut state = 0u8; loop {
468 match rd.read_event_into(&mut buf) {
469 Ok(Event::Start(e)) => {
470 let name = e.name();
471 let local = local_name(name.as_ref());
472 match state {
473 0 if local == b"sp" => {
474 state = 1;
475 }
476 1 if local == b"nvSpPr" => {
477 let mut nv_depth = 1i32;
481 loop {
482 match rd.read_event_into(&mut buf) {
483 Ok(Event::Start(e2)) => {
484 nv_depth += 1;
485 let name2 = e2.name();
488 let local2 = local_name(name2.as_ref());
489 if local2 == b"cNvSpPr" {
490 for a in e2.attributes().flatten() {
491 if a.key.as_ref() == b"txBox" {
492 let v = a
493 .normalized_value(
494 quick_xml::XmlVersion::Implicit1_0,
495 )
496 .unwrap_or_default();
497 if v == "1" || v == "true" {
498 sp.c_nv_sp_pr_tx_box = true;
499 }
500 }
501 }
502 }
503 }
504 Ok(Event::End(_)) => {
505 nv_depth -= 1;
506 if nv_depth == 0 {
507 break;
508 }
509 }
510 Ok(Event::Empty(e2)) => {
511 let name2 = e2.name();
512 let local2 = local_name(name2.as_ref());
513 if local2 == b"cNvPr" {
514 for a in e2.attributes().flatten() {
515 match a.key.as_ref() {
516 b"id" => {
517 if let Ok(v) = a
518 .normalized_value(
519 quick_xml::XmlVersion::Implicit1_0,
520 )
521 .unwrap_or_default()
522 .parse::<u32>()
523 {
524 sp.id = v;
525 }
526 }
527 b"name" => {
528 sp.name = a
529 .normalized_value(
530 quick_xml::XmlVersion::Implicit1_0,
531 )
532 .unwrap_or_default()
533 .to_string();
534 }
535 _ => {}
536 }
537 }
538 } else if local2 == b"cNvSpPr" {
539 for a in e2.attributes().flatten() {
540 if a.key.as_ref() == b"txBox" {
541 let v = a
542 .normalized_value(
543 quick_xml::XmlVersion::Implicit1_0,
544 )
545 .unwrap_or_default();
546 if v == "1" || v == "true" {
547 sp.c_nv_sp_pr_tx_box = true;
548 }
549 }
550 }
551 } else if local2 == b"spLocks" {
552 sp.locks = Some(parse_sp_locks_attrs(&e2));
554 } else if local2 == b"ph" {
555 sp.is_placeholder = true;
556 for a in e2.attributes().flatten() {
557 match a.key.as_ref() {
558 b"type" => {
559 sp.ph_type = Some(
560 String::from_utf8_lossy(a.value.as_ref())
561 .into_owned(),
562 )
563 }
564 b"idx" => {
565 if let Ok(v) = a
566 .normalized_value(
567 quick_xml::XmlVersion::Implicit1_0,
568 )
569 .unwrap_or_default()
570 .parse::<u32>()
571 {
572 sp.ph_idx = Some(v);
573 }
574 }
575 _ => {}
576 }
577 }
578 }
579 }
580 Ok(Event::Eof) => break,
581 Err(_) => break,
582 _ => {}
583 }
584 buf.clear();
585 }
586 }
587 1 if local == b"spPr" => {
588 let inner = collect_full_element(&mut rd, e.into_owned())?;
589 sp.properties = parse_sppr(&inner)?;
591 state = 2;
592 }
593 _ if state < 2 && local == b"spPr" => {
594 let inner = collect_full_element(&mut rd, e.into_owned())?;
595 sp.properties = parse_sppr(&inner)?;
596 state = 2;
597 }
598 2 if local == b"txBody" => {
599 let inner = collect_full_element(&mut rd, e.into_owned())?;
600 sp.text = parse_txbody(&inner)?;
602 state = 3;
603 }
604 _ if state >= 2 && local == b"style" => {
605 let inner = collect_full_element(&mut rd, e.into_owned())?;
607 sp.style = Some(parse_shape_style(&inner)?);
608 }
609 _ => {
610 let _ = collect_full_element(&mut rd, e.into_owned());
612 }
613 }
614 }
615 Ok(Event::Empty(e)) => {
616 let name = e.name();
617 let local = local_name(name.as_ref());
618 if state == 1 && local == b"ph" {
619 sp.is_placeholder = true;
621 for a in e.attributes().flatten() {
622 match a.key.as_ref() {
623 b"type" => {
624 sp.ph_type =
625 Some(String::from_utf8_lossy(a.value.as_ref()).into_owned())
626 }
627 b"idx" => {
628 if let Ok(v) = a
629 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
630 .unwrap_or_default()
631 .parse::<u32>()
632 {
633 sp.ph_idx = Some(v);
634 }
635 }
636 _ => {}
637 }
638 }
639 } else if state == 1 && local == b"cNvPr" {
640 for a in e.attributes().flatten() {
642 match a.key.as_ref() {
643 b"id" => {
644 if let Ok(v) = a
645 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
646 .unwrap_or_default()
647 .parse::<u32>()
648 {
649 sp.id = v;
650 }
651 }
652 b"name" => {
653 sp.name = a
654 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
655 .unwrap_or_default()
656 .to_string();
657 }
658 _ => {}
659 }
660 }
661 } else if state == 1 && local == b"cNvSpPr" {
662 for a in e.attributes().flatten() {
664 if a.key.as_ref() == b"txBox" {
665 let v = a
666 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
667 .unwrap_or_default();
668 if v == "1" || v == "true" {
669 sp.c_nv_sp_pr_tx_box = true;
670 }
671 }
672 }
673 } else if state == 1 && local == b"spPr" {
674 state = 2;
677 } else if state == 2 && local == b"prstGeom" {
678 for a in e.attributes().flatten() {
679 if a.key.as_ref() == b"prst" {
680 let v = a
681 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
682 .unwrap_or_default();
683 if let Ok(g) = v.parse::<PresetGeometry>() {
684 sp.properties.geometry = Some(Geometry::preset(g));
685 }
686 }
687 }
688 } else if state == 2 && local == b"custGeom" {
689 let inner = collect_full_element(&mut rd, e.into_owned())?;
691 if let Ok(cg) = parse_custom_geometry(&inner) {
692 sp.properties.geometry = Some(Geometry::Custom(cg));
693 }
694 } else if state >= 2 && local == b"style" {
695 sp.style = Some(ShapeStyle::default());
697 }
698 }
699 Ok(Event::End(e)) => {
700 let name = e.name();
701 let local = local_name(name.as_ref());
702 if state == 3 && local == b"sp" {
703 break;
704 }
705 if state == 2 && local == b"sp" {
706 break;
708 }
709 if state == 1 && local == b"sp" {
710 break;
711 }
712 }
713 Ok(Event::Eof) => break,
714 Err(e) => return Err(crate::Error::Xml(format!("sp parse: {e}"))),
715 _ => {}
716 }
717 buf.clear();
718 }
719 Ok(sp)
721}
722
723pub fn parse_sppr(xml: &str) -> crate::Result<ShapeProperties> {
725 let mut rd = Reader::from_str(xml);
726 rd.config_mut().trim_text(true);
727 let mut buf = Vec::new();
728 let mut sp = ShapeProperties::default();
729 loop {
730 match rd.read_event_into(&mut buf) {
731 Ok(Event::Start(e)) => {
732 let name = e.name();
733 let local = local_name(name.as_ref());
734 if local == b"spPr" {
735 } else if local == b"xfrm" {
738 for a in e.attributes().flatten() {
740 match a.key.as_ref() {
741 b"rot" => {
742 if let Ok(v) = a
743 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
744 .unwrap_or_default()
745 .parse::<i32>()
746 {
747 sp.xfrm.rot = Some(v);
748 }
749 }
750 b"flipH" => {
751 let v = a
752 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
753 .unwrap_or_default();
754 if v == "1" {
755 sp.xfrm.flip_h = true;
756 }
757 }
758 b"flipV" => {
759 let v = a
760 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
761 .unwrap_or_default();
762 if v == "1" {
763 sp.xfrm.flip_v = true;
764 }
765 }
766 _ => {}
767 }
768 }
769 parse_xfrm_into(&mut rd, &mut sp);
771 } else if local == b"prstGeom" {
772 let mut prst_val: Option<String> = None;
774 for a in e.attributes().flatten() {
775 if a.key.as_ref() == b"prst" {
776 prst_val = Some(
777 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
778 .unwrap_or_default()
779 .to_string(),
780 );
781 }
782 }
783 let inner = collect_full_element(&mut rd, e.into_owned())?;
784 if let Some(v) = prst_val {
785 if let Ok(g) = v.parse::<PresetGeometry>() {
786 let adjustments = parse_av_lst(&inner);
787 sp.geometry = Some(Geometry::Preset(g, adjustments));
788 }
789 }
790 } else if local == b"custGeom" {
791 let inner = collect_full_element(&mut rd, e.into_owned())?;
793 if let Ok(cg) = parse_custom_geometry(&inner) {
794 sp.geometry = Some(Geometry::Custom(cg));
795 }
796 } else if local == b"noFill" {
797 sp.fill = Fill::None;
798 } else if local == b"solidFill" {
799 let color = parse_solid_fill(&mut rd, e.into_owned())?;
801 sp.fill = Fill::Solid(color);
802 } else if local == b"gradFill" {
803 let grad = parse_grad_fill(&mut rd, e.into_owned())?;
805 sp.fill = Fill::Gradient(grad);
806 } else if local == b"pattFill" {
807 let patt = parse_patt_fill(&mut rd, e.into_owned())?;
809 sp.fill = Fill::Pattern(patt);
810 } else if local == b"blipFill" {
811 let (rid, mode) = parse_blip_fill(&mut rd, e.into_owned())?;
815 if !rid.is_empty() {
816 sp.fill = Fill::Blip { rid, mode };
817 } else {
818 }
820 } else if local == b"ln" {
821 let ln = parse_ln(&mut rd, e.into_owned())?;
822 sp.line = Some(ln);
823 } else if local == b"effectLst" {
824 let effects = parse_effect_lst(&mut rd, e.into_owned())?;
826 sp.effects = Some(effects);
827 } else if local == b"scene3d" {
828 let scene = parse_scene_3d(&mut rd, e.into_owned())?;
830 sp.scene3d = Some(scene);
831 } else if local == b"sp3d" {
832 let sp3d = parse_sp_3d(&mut rd, e.into_owned())?;
834 sp.sp3d = Some(sp3d);
835 } else {
836 let _ = collect_full_element(&mut rd, e.into_owned());
837 }
838 }
839 Ok(Event::Empty(e)) => {
840 let name = e.name();
841 let local = local_name(name.as_ref());
842 if local == b"prstGeom" {
843 for a in e.attributes().flatten() {
845 if a.key.as_ref() == b"prst" {
846 let v = a
847 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
848 .unwrap_or_default();
849 if let Ok(g) = v.parse::<PresetGeometry>() {
850 sp.geometry = Some(Geometry::preset(g));
851 }
852 }
853 }
854 } else if local == b"noFill" {
855 sp.fill = Fill::None;
856 } else if local == b"custGeom" {
857 sp.geometry = Some(Geometry::Custom(CustomGeometry::default()));
859 }
860 }
861 Ok(Event::Eof) => break,
862 Err(e) => return Err(crate::Error::Xml(format!("spPr parse: {e}"))),
863 _ => {}
864 }
865 buf.clear();
866 }
867 Ok(sp)
868}
869
870fn parse_xfrm_into<R: std::io::BufRead>(rd: &mut Reader<R>, sp: &mut ShapeProperties) {
898 let mut buf = Vec::new();
899 loop {
901 match rd.read_event_into(&mut buf) {
902 Ok(Event::Empty(e)) => {
903 let name = e.name();
904 let local = local_name(name.as_ref());
905 if local == b"off" {
906 let (mut x, mut y) = (None, None);
907 for a in e.attributes().flatten() {
908 match a.key.as_ref() {
909 b"x" => {
910 x = a
911 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
912 .unwrap_or_default()
913 .parse()
914 .ok()
915 }
916 b"y" => {
917 y = a
918 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
919 .unwrap_or_default()
920 .parse()
921 .ok()
922 }
923 _ => {}
924 }
925 }
926 sp.xfrm.off_x = x.map(Emu);
927 sp.xfrm.off_y = y.map(Emu);
928 } else if local == b"ext" {
929 let (mut cx, mut cy) = (None, None);
930 for a in e.attributes().flatten() {
931 match a.key.as_ref() {
932 b"cx" => {
933 cx = a
934 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
935 .unwrap_or_default()
936 .parse()
937 .ok()
938 }
939 b"cy" => {
940 cy = a
941 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
942 .unwrap_or_default()
943 .parse()
944 .ok()
945 }
946 _ => {}
947 }
948 }
949 sp.xfrm.ext_cx = cx.map(Emu);
950 sp.xfrm.ext_cy = cy.map(Emu);
951 }
952 }
954 Ok(Event::End(e)) => {
955 if local_name(e.name().as_ref()) == b"xfrm" {
956 return;
957 }
958 }
959 Ok(Event::Eof) => return,
960 _ => {}
961 }
962 buf.clear();
963 }
964}
965
966fn parse_av_lst(xml: &str) -> Vec<crate::oxml::sppr::AdjustmentValue> {
976 use crate::oxml::sppr::AdjustmentValue;
977
978 let mut result = Vec::new();
979 let mut rd = Reader::from_str(xml);
980 let mut buf = Vec::new();
981 loop {
982 match rd.read_event_into(&mut buf) {
983 Ok(Event::Empty(e)) => {
984 if local_name(e.name().as_ref()) == b"gd" {
985 let mut name = String::new();
986 let mut fmla = String::new();
987 for a in e.attributes().flatten() {
988 let v = a
989 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
990 .unwrap_or_default();
991 match a.key.as_ref() {
992 b"name" => name = v.to_string(),
993 b"fmla" => fmla = v.to_string(),
994 _ => {}
995 }
996 }
997 if let Some(raw) = parse_fmla_val(&fmla) {
999 result.push(AdjustmentValue::new(name, raw));
1000 }
1001 }
1002 }
1003 Ok(Event::Start(e)) => {
1004 if local_name(e.name().as_ref()) == b"gd" {
1005 let mut name = String::new();
1006 let mut fmla = String::new();
1007 for a in e.attributes().flatten() {
1008 let v = a
1009 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1010 .unwrap_or_default();
1011 match a.key.as_ref() {
1012 b"name" => name = v.to_string(),
1013 b"fmla" => fmla = v.to_string(),
1014 _ => {}
1015 }
1016 }
1017 let _ = collect_full_element(&mut rd, e.into_owned());
1019 if let Some(raw) = parse_fmla_val(&fmla) {
1020 result.push(AdjustmentValue::new(name, raw));
1021 }
1022 }
1023 }
1024 Ok(Event::Eof) => break,
1025 _ => {}
1026 }
1027 buf.clear();
1028 }
1029 result
1030}
1031
1032fn parse_fmla_val(fmla: &str) -> Option<i64> {
1037 let trimmed = fmla.trim();
1038 if let Some(rest) = trimmed.strip_prefix("val ") {
1039 rest.trim().parse::<i64>().ok()
1040 } else if let Some(rest) = trimmed.strip_prefix("val") {
1041 rest.trim().parse::<i64>().ok()
1042 } else {
1043 None
1044 }
1045}
1046
1047fn parse_custom_geometry(xml: &str) -> crate::Result<CustomGeometry> {
1071 let mut rd = Reader::from_str(xml);
1072 rd.config_mut().trim_text(true);
1073 let mut buf = Vec::new();
1074 let mut geom = CustomGeometry::default();
1075 let mut state: u8 = 0;
1077 let mut current_path: Option<Path> = None;
1078 let mut bez_pts: Vec<(i64, i64)> = Vec::new();
1080
1081 loop {
1082 match rd.read_event_into(&mut buf) {
1083 Ok(Event::Start(e)) => {
1084 let ev_name = e.name();
1085 let local = local_name(ev_name.as_ref());
1086 match state {
1087 0 => match local {
1088 b"fill" => {
1089 let text = read_element_text(&mut rd, b"fill");
1091 geom.fill = Some(text);
1092 }
1093 b"stroke" => {
1094 let text = read_element_text(&mut rd, b"stroke");
1095 geom.stroke = Some(text);
1096 }
1097 b"rect" => {
1098 geom.rect = Some(parse_geom_rect(&e));
1100 }
1101 b"pathLst" => {
1102 state = 1;
1103 }
1104 b"avLst" => {
1105 }
1109 _ => {}
1110 },
1111 1 => {
1112 if local == b"path" {
1113 let mut p = Path::default();
1115 for a in e.attributes().flatten() {
1116 let key = a.key.as_ref();
1117 let v = a
1118 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1119 .unwrap_or_default()
1120 .to_string();
1121 match key {
1122 b"w" => p.width = v.parse().unwrap_or(0),
1123 b"h" => p.height = v.parse().unwrap_or(0),
1124 b"fill" => p.fill = Some(v),
1125 b"stroke" => p.stroke = Some(v),
1126 _ => {}
1127 }
1128 }
1129 current_path = Some(p);
1130 state = 2;
1131 }
1132 }
1133 2 => {
1134 match local {
1136 b"moveTo" | b"lnTo" => {
1137 let pt = read_single_pt(&mut rd, local);
1139 if let Some((x, y)) = pt {
1140 let seg = if local == b"moveTo" {
1141 PathSegment::MoveTo { x, y }
1142 } else {
1143 PathSegment::LineTo { x, y }
1144 };
1145 if let Some(p) = &mut current_path {
1146 p.segments.push(seg);
1147 }
1148 }
1149 }
1150 b"cubicBezTo" => {
1151 bez_pts.clear();
1153 read_multiple_pts(&mut rd, b"cubicBezTo", &mut bez_pts);
1154 if bez_pts.len() >= 3 {
1155 let seg = PathSegment::CubicBezTo {
1156 x1: bez_pts[0].0,
1157 y1: bez_pts[0].1,
1158 x2: bez_pts[1].0,
1159 y2: bez_pts[1].1,
1160 x3: bez_pts[2].0,
1161 y3: bez_pts[2].1,
1162 };
1163 if let Some(p) = &mut current_path {
1164 p.segments.push(seg);
1165 }
1166 }
1167 }
1168 b"quadBezTo" => {
1169 bez_pts.clear();
1171 read_multiple_pts(&mut rd, b"quadBezTo", &mut bez_pts);
1172 if bez_pts.len() >= 2 {
1173 let seg = PathSegment::QuadBezTo {
1174 x1: bez_pts[0].0,
1175 y1: bez_pts[0].1,
1176 x2: bez_pts[1].0,
1177 y2: bez_pts[1].1,
1178 };
1179 if let Some(p) = &mut current_path {
1180 p.segments.push(seg);
1181 }
1182 }
1183 }
1184 b"arcTo" => {
1185 let seg = parse_arc_to(&e);
1187 if let Some(p) = &mut current_path {
1188 p.segments.push(seg);
1189 }
1190 }
1191 _ => {}
1192 }
1193 }
1194 _ => {}
1195 }
1196 }
1197 Ok(Event::Empty(e)) => {
1198 let ev_name = e.name();
1199 let local = local_name(ev_name.as_ref());
1200 match state {
1201 0 => {
1202 if local == b"rect" {
1203 geom.rect = Some(parse_geom_rect(&e));
1204 }
1205 }
1207 2 => {
1208 if local == b"close" {
1209 if let Some(p) = &mut current_path {
1210 p.segments.push(PathSegment::Close);
1211 }
1212 } else if local == b"arcTo" {
1213 let seg = parse_arc_to(&e);
1214 if let Some(p) = &mut current_path {
1215 p.segments.push(seg);
1216 }
1217 } else if local == b"moveTo" || local == b"lnTo" {
1218 }
1221 }
1222 _ => {}
1223 }
1224 }
1225 Ok(Event::End(e)) => {
1226 let ev_name = e.name();
1227 let local = local_name(ev_name.as_ref());
1228 match state {
1229 2 => {
1230 if local == b"path" {
1231 if let Some(p) = current_path.take() {
1233 geom.path_list.push(p);
1234 }
1235 state = 1;
1236 }
1237 }
1238 1 => {
1239 if local == b"pathLst" {
1240 state = 0;
1241 }
1242 }
1243 0 if local == b"custGeom" => {
1244 return Ok(geom);
1245 }
1246 _ => {}
1247 }
1248 }
1249 Ok(Event::Eof) => return Ok(geom),
1250 Err(e) => return Err(crate::Error::Xml(format!("parse_custom_geometry: {e}"))),
1251 _ => {}
1252 }
1253 buf.clear();
1254 }
1255}
1256
1257fn read_element_text<R: std::io::BufRead>(rd: &mut Reader<R>, expected_end: &[u8]) -> String {
1261 let mut buf = Vec::new();
1262 let mut text = String::new();
1263 loop {
1264 match rd.read_event_into(&mut buf) {
1265 Ok(Event::Text(t)) => {
1266 text.push_str(&t.decode().unwrap_or_default());
1267 }
1268 Ok(Event::End(e)) => {
1269 if local_name(e.name().as_ref()) == expected_end {
1270 return text;
1271 }
1272 }
1273 Ok(Event::Eof) => return text,
1274 _ => {}
1275 }
1276 buf.clear();
1277 }
1278}
1279
1280fn parse_geom_rect(e: &quick_xml::events::BytesStart<'_>) -> GeomRect {
1282 let mut r = GeomRect::default();
1283 for a in e.attributes().flatten() {
1284 let v = a
1285 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1286 .unwrap_or_default()
1287 .to_string();
1288 match a.key.as_ref() {
1289 b"l" => r.l = v,
1290 b"t" => r.t = v,
1291 b"r" => r.r = v,
1292 b"b" => r.b = v,
1293 _ => {}
1294 }
1295 }
1296 r
1297}
1298
1299fn read_single_pt<R: std::io::BufRead>(rd: &mut Reader<R>, parent: &[u8]) -> Option<(i64, i64)> {
1301 let mut buf = Vec::new();
1302 let mut result = None;
1303 loop {
1304 match rd.read_event_into(&mut buf) {
1305 Ok(Event::Empty(e)) => {
1306 let ev_name = e.name();
1307 if local_name(ev_name.as_ref()) == b"pt" {
1308 result = parse_pt_attrs(&e);
1309 }
1310 }
1311 Ok(Event::Start(e)) => {
1312 let ev_name = e.name();
1313 if local_name(ev_name.as_ref()) == b"pt" {
1314 result = parse_pt_attrs(&e);
1316 }
1317 }
1318 Ok(Event::End(e)) => {
1319 if local_name(e.name().as_ref()) == parent {
1320 return result;
1321 }
1322 }
1323 Ok(Event::Eof) => return result,
1324 _ => {}
1325 }
1326 buf.clear();
1327 }
1328}
1329
1330fn read_multiple_pts<R: std::io::BufRead>(
1332 rd: &mut Reader<R>,
1333 parent: &[u8],
1334 pts: &mut Vec<(i64, i64)>,
1335) {
1336 let mut buf = Vec::new();
1337 loop {
1338 match rd.read_event_into(&mut buf) {
1339 Ok(Event::Empty(e)) => {
1340 let ev_name = e.name();
1341 if local_name(ev_name.as_ref()) == b"pt" {
1342 if let Some(p) = parse_pt_attrs(&e) {
1343 pts.push(p);
1344 }
1345 }
1346 }
1347 Ok(Event::Start(e)) => {
1348 let ev_name = e.name();
1349 if local_name(ev_name.as_ref()) == b"pt" {
1350 if let Some(p) = parse_pt_attrs(&e) {
1351 pts.push(p);
1352 }
1353 }
1354 }
1355 Ok(Event::End(e)) => {
1356 if local_name(e.name().as_ref()) == parent {
1357 return;
1358 }
1359 }
1360 Ok(Event::Eof) => return,
1361 _ => {}
1362 }
1363 buf.clear();
1364 }
1365}
1366
1367fn parse_pt_attrs(e: &quick_xml::events::BytesStart<'_>) -> Option<(i64, i64)> {
1369 let mut x: Option<i64> = None;
1370 let mut y: Option<i64> = None;
1371 for a in e.attributes().flatten() {
1372 let v = a
1373 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1374 .unwrap_or_default();
1375 match a.key.as_ref() {
1376 b"x" => x = v.parse().ok(),
1377 b"y" => y = v.parse().ok(),
1378 _ => {}
1379 }
1380 }
1381 match (x, y) {
1382 (Some(x), Some(y)) => Some((x, y)),
1383 _ => None,
1384 }
1385}
1386
1387fn parse_arc_to(e: &quick_xml::events::BytesStart<'_>) -> PathSegment {
1389 let mut w_r: i64 = 0;
1390 let mut h_r: i64 = 0;
1391 let mut st_ang: i32 = 0;
1392 let mut sw_ang: i32 = 0;
1393 for a in e.attributes().flatten() {
1394 let v = a
1395 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1396 .unwrap_or_default();
1397 match a.key.as_ref() {
1398 b"wR" => w_r = v.parse().unwrap_or(0),
1399 b"hR" => h_r = v.parse().unwrap_or(0),
1400 b"stAng" => st_ang = v.parse().unwrap_or(0),
1401 b"swAng" => sw_ang = v.parse().unwrap_or(0),
1402 _ => {}
1403 }
1404 }
1405 PathSegment::ArcTo {
1406 w_r,
1407 h_r,
1408 st_ang,
1409 sw_ang,
1410 }
1411}
1412
1413fn parse_solid_fill<R: std::io::BufRead>(
1415 rd: &mut Reader<R>,
1416 _start: quick_xml::events::BytesStart<'static>,
1417) -> crate::Result<Color> {
1418 let mut buf = Vec::new();
1419 loop {
1421 match rd.read_event_into(&mut buf) {
1422 Ok(Event::Empty(e)) => {
1423 let name = e.name();
1424 let local = local_name(name.as_ref());
1425 if local == b"srgbClr" {
1426 for a in e.attributes().flatten() {
1427 if a.key.as_ref() == b"val" {
1428 let v = a
1429 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1430 .unwrap_or_default();
1431 if v.len() == 6 {
1432 let r = u8::from_str_radix(&v[0..2], 16).unwrap_or(0);
1433 let g = u8::from_str_radix(&v[2..4], 16).unwrap_or(0);
1434 let b = u8::from_str_radix(&v[4..6], 16).unwrap_or(0);
1435 return Ok(Color::RGB(RGBColor(r, g, b)));
1436 }
1437 }
1438 }
1439 } else if local == b"schemeClr" {
1440 for a in e.attributes().flatten() {
1441 if a.key.as_ref() == b"val" {
1442 let v = a
1443 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1444 .unwrap_or_default();
1445 if let Ok(sc) = v.parse::<SchemeColor>() {
1446 return Ok(Color::Scheme(sc));
1447 }
1448 }
1449 }
1450 } else if local == b"prstClr" {
1451 for a in e.attributes().flatten() {
1452 if a.key.as_ref() == b"val" {
1453 let v = a
1454 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1455 .unwrap_or_default();
1456 if let Ok(p) = v.parse::<PresetColor>() {
1457 return Ok(Color::Preset(p));
1458 }
1459 }
1460 }
1461 }
1462 }
1463 Ok(Event::End(e)) => {
1464 let name = e.name();
1465 if local_name(name.as_ref()) == b"solidFill" {
1466 return Ok(Color::None);
1467 }
1468 }
1469 Ok(Event::Eof) => return Ok(Color::None),
1470 _ => {}
1471 }
1472 buf.clear();
1473 }
1474}
1475
1476fn parse_grad_fill<R: std::io::BufRead>(
1496 rd: &mut Reader<R>,
1497 start: quick_xml::events::BytesStart<'static>,
1498) -> crate::Result<GradientFill> {
1499 let mut flip = None;
1501 let mut rot_with_shape = None;
1502 for a in start.attributes().flatten() {
1503 match a.key.as_ref() {
1504 b"flip" => {
1505 flip = Some(
1506 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1507 .unwrap_or_default()
1508 .to_string(),
1509 );
1510 }
1511 b"rotWithShape" => {
1512 let v = a
1513 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1514 .unwrap_or_default();
1515 rot_with_shape = Some(v == "1");
1516 }
1517 _ => {}
1518 }
1519 }
1520
1521 let mut stops: Vec<GradientStop> = Vec::new();
1522 let mut gradient_type = GradientType::Linear(0);
1523 let mut buf = Vec::new();
1524
1525 loop {
1526 match rd.read_event_into(&mut buf) {
1527 Ok(Event::Start(e)) => {
1528 let name = e.name();
1529 let local = local_name(name.as_ref());
1530 if local == b"gsLst" {
1531 let mut gs_depth = 1i32;
1533 loop {
1534 match rd.read_event_into(&mut buf) {
1535 Ok(Event::Start(ref e2)) => {
1536 if local_name(e2.name().as_ref()) == b"gs" && gs_depth == 1 {
1537 let mut pos: u32 = 0;
1539 for a in e2.attributes().flatten() {
1540 if a.key.as_ref() == b"pos" {
1541 pos = a
1542 .normalized_value(
1543 quick_xml::XmlVersion::Implicit1_0,
1544 )
1545 .unwrap_or_default()
1546 .parse()
1547 .unwrap_or(0);
1548 }
1549 }
1550 gs_depth += 1;
1551 let color = parse_color_child(rd, b"gs")?;
1553 stops.push(GradientStop { pos, color });
1554 } else {
1555 gs_depth += 1;
1556 }
1557 }
1558 Ok(Event::End(ref e2)) => {
1559 let n2 = e2.name();
1560 let ln = local_name(n2.as_ref());
1561 if ln == b"gs" {
1562 gs_depth -= 1;
1563 } else if ln == b"gsLst" {
1564 break;
1565 }
1566 }
1567 Ok(Event::Eof) => break,
1568 Err(_) => break,
1569 _ => {}
1570 }
1571 buf.clear();
1572 }
1573 } else if local == b"lin" {
1574 let mut ang: i32 = 0;
1576 for a in e.attributes().flatten() {
1577 if a.key.as_ref() == b"ang" {
1578 ang = a
1579 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1580 .unwrap_or_default()
1581 .parse()
1582 .unwrap_or(0);
1583 }
1584 }
1585 gradient_type = GradientType::Linear(ang);
1586 if e.is_empty() {
1588 } else {
1590 let _ = collect_full_element(rd, e.into_owned())?;
1592 }
1593 } else if local == b"path" {
1594 let mut path_type = GradientPath::Circle;
1596 for a in e.attributes().flatten() {
1597 if a.key.as_ref() == b"path" {
1598 let v = a
1599 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1600 .unwrap_or_default();
1601 path_type = match v.as_ref() {
1602 "circle" => GradientPath::Circle,
1603 "rect" => GradientPath::Rect,
1604 "shape" => GradientPath::Shape,
1605 _ => GradientPath::Circle,
1606 };
1607 }
1608 }
1609 gradient_type = GradientType::Path(path_type);
1610 let _ = collect_full_element(rd, e.into_owned())?;
1612 } else {
1613 let _ = collect_full_element(rd, e.into_owned())?;
1614 }
1615 }
1616 Ok(Event::Empty(e)) => {
1617 let name = e.name();
1618 let local = local_name(name.as_ref());
1619 if local == b"lin" {
1620 let mut ang: i32 = 0;
1622 for a in e.attributes().flatten() {
1623 if a.key.as_ref() == b"ang" {
1624 ang = a
1625 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1626 .unwrap_or_default()
1627 .parse()
1628 .unwrap_or(0);
1629 }
1630 }
1631 gradient_type = GradientType::Linear(ang);
1632 } else if local == b"path" {
1633 let mut path_type = GradientPath::Circle;
1634 for a in e.attributes().flatten() {
1635 if a.key.as_ref() == b"path" {
1636 let v = a
1637 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1638 .unwrap_or_default();
1639 path_type = match v.as_ref() {
1640 "circle" => GradientPath::Circle,
1641 "rect" => GradientPath::Rect,
1642 "shape" => GradientPath::Shape,
1643 _ => GradientPath::Circle,
1644 };
1645 }
1646 }
1647 gradient_type = GradientType::Path(path_type);
1648 }
1649 }
1650 Ok(Event::End(e)) => {
1651 if local_name(e.name().as_ref()) == b"gradFill" {
1652 break;
1653 }
1654 }
1655 Ok(Event::Eof) => break,
1656 Err(e) => return Err(crate::Error::Xml(format!("gradFill parse: {e}"))),
1657 _ => {}
1658 }
1659 buf.clear();
1660 }
1661
1662 Ok(GradientFill {
1663 stops,
1664 gradient_type,
1665 flip,
1666 rot_with_shape,
1667 })
1668}
1669
1670fn parse_patt_fill<R: std::io::BufRead>(
1681 rd: &mut Reader<R>,
1682 start: quick_xml::events::BytesStart<'static>,
1683) -> crate::Result<PatternFill> {
1684 let mut prst = String::new();
1685 for a in start.attributes().flatten() {
1686 if a.key.as_ref() == b"prst" {
1687 prst = a
1688 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1689 .unwrap_or_default()
1690 .to_string();
1691 }
1692 }
1693
1694 let mut fg_color = Color::None;
1695 let mut bg_color = Color::None;
1696 let mut buf = Vec::new();
1697
1698 loop {
1699 match rd.read_event_into(&mut buf) {
1700 Ok(Event::Start(e)) => {
1701 let name = e.name();
1702 let local = local_name(name.as_ref());
1703 if local == b"fgClr" {
1704 fg_color = parse_color_child(rd, b"fgClr")?;
1705 } else if local == b"bgClr" {
1706 bg_color = parse_color_child(rd, b"bgClr")?;
1707 } else {
1708 let _ = collect_full_element(rd, e.into_owned())?;
1709 }
1710 }
1711 Ok(Event::End(e)) => {
1712 if local_name(e.name().as_ref()) == b"pattFill" {
1713 break;
1714 }
1715 }
1716 Ok(Event::Eof) => break,
1717 Err(e) => return Err(crate::Error::Xml(format!("pattFill parse: {e}"))),
1718 _ => {}
1719 }
1720 buf.clear();
1721 }
1722
1723 Ok(PatternFill {
1724 prst,
1725 fg_color,
1726 bg_color,
1727 })
1728}
1729
1730fn parse_blip_fill<R: std::io::BufRead>(
1754 rd: &mut Reader<R>,
1755 _start: quick_xml::events::BytesStart<'static>,
1756) -> crate::Result<(String, crate::oxml::sppr::BlipFillMode)> {
1757 let mut rid = String::new();
1758 let mut mode = crate::oxml::sppr::BlipFillMode::default(); let mut buf = Vec::new();
1760
1761 loop {
1762 match rd.read_event_into(&mut buf) {
1763 Ok(Event::Start(e)) => {
1764 let name = e.name();
1765 let local = local_name(name.as_ref());
1766 if local == b"blip" {
1767 for a in e.attributes().flatten() {
1769 let key = a.key.as_ref();
1770 if key == b"r:embed" || key.ends_with(b":embed") {
1771 rid = a
1772 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1773 .unwrap_or_default()
1774 .to_string();
1775 }
1776 }
1777 let _ = collect_full_element(rd, e.into_owned())?;
1779 } else if local == b"stretch" {
1780 let _ = collect_full_element(rd, e.into_owned())?;
1782 mode = crate::oxml::sppr::BlipFillMode::Stretch;
1783 } else if local == b"tile" {
1784 mode = parse_tile_attrs(&e);
1786 let _ = collect_full_element(rd, e.into_owned())?;
1787 } else if local == b"srcRect" {
1788 let _ = collect_full_element(rd, e.into_owned())?;
1790 } else {
1791 let _ = collect_full_element(rd, e.into_owned())?;
1792 }
1793 }
1794 Ok(Event::Empty(e)) => {
1795 let name = e.name();
1796 let local = local_name(name.as_ref());
1797 if local == b"blip" {
1798 for a in e.attributes().flatten() {
1800 let key = a.key.as_ref();
1801 if key == b"r:embed" || key.ends_with(b":embed") {
1802 rid = a
1803 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1804 .unwrap_or_default()
1805 .to_string();
1806 }
1807 }
1808 } else if local == b"stretch" {
1809 mode = crate::oxml::sppr::BlipFillMode::Stretch;
1810 } else if local == b"tile" {
1811 mode = parse_tile_attrs(&e);
1812 }
1813 }
1815 Ok(Event::End(e)) => {
1816 if local_name(e.name().as_ref()) == b"blipFill" {
1817 break;
1818 }
1819 }
1820 Ok(Event::Eof) => break,
1821 Err(e) => return Err(crate::Error::Xml(format!("blipFill parse: {e}"))),
1822 _ => {}
1823 }
1824 buf.clear();
1825 }
1826
1827 Ok((rid, mode))
1828}
1829
1830fn parse_color_child<R: std::io::BufRead>(
1838 rd: &mut Reader<R>,
1839 parent: &[u8],
1840) -> crate::Result<Color> {
1841 let mut buf = Vec::new();
1842 loop {
1843 match rd.read_event_into(&mut buf) {
1844 Ok(Event::Empty(e)) => {
1845 let name = e.name();
1846 let local = local_name(name.as_ref());
1847 if let Some(c) = parse_color_from_event(&e) {
1848 return Ok(c);
1849 }
1850 let _ = local;
1852 }
1853 Ok(Event::Start(e)) => {
1854 let name = e.name();
1855 let local = local_name(name.as_ref());
1856 if local == b"srgbClr" || local == b"schemeClr" || local == b"prstClr" {
1857 if let Some(c) = parse_color_from_event(&e) {
1859 let _ = collect_full_element(rd, e.into_owned())?;
1861 return Ok(c);
1862 }
1863 } else {
1864 let _ = collect_full_element(rd, e.into_owned())?;
1865 }
1866 }
1867 Ok(Event::End(e)) => {
1868 if local_name(e.name().as_ref()) == parent {
1869 return Ok(Color::None);
1870 }
1871 }
1872 Ok(Event::Eof) => return Ok(Color::None),
1873 Err(_) => return Ok(Color::None),
1874 _ => {}
1875 }
1876 buf.clear();
1877 }
1878}
1879
1880fn parse_color_from_event(e: &quick_xml::events::BytesStart<'_>) -> Option<Color> {
1884 let name = e.name();
1885 let local = local_name(name.as_ref());
1886 for a in e.attributes().flatten() {
1887 if a.key.as_ref() == b"val" {
1888 let v = a
1889 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1890 .unwrap_or_default();
1891 if local == b"srgbClr" && v.len() == 6 {
1892 let r = u8::from_str_radix(&v[0..2], 16).unwrap_or(0);
1893 let g = u8::from_str_radix(&v[2..4], 16).unwrap_or(0);
1894 let b = u8::from_str_radix(&v[4..6], 16).unwrap_or(0);
1895 return Some(Color::RGB(RGBColor(r, g, b)));
1896 } else if local == b"schemeClr" {
1897 if let Ok(sc) = v.parse::<SchemeColor>() {
1898 return Some(Color::Scheme(sc));
1899 }
1900 } else if local == b"prstClr" {
1901 if let Ok(p) = v.parse::<PresetColor>() {
1902 return Some(Color::Preset(p));
1903 }
1904 }
1905 }
1906 }
1907 None
1908}
1909
1910fn parse_ln<R: std::io::BufRead>(
1912 rd: &mut Reader<R>,
1913 start: quick_xml::events::BytesStart<'static>,
1914) -> crate::Result<Line> {
1915 let mut ln = Line::default();
1916 for a in start.attributes().flatten() {
1918 match a.key.as_ref() {
1919 b"w" => {
1920 if let Ok(v) = a
1921 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1922 .unwrap_or_default()
1923 .parse::<i64>()
1924 {
1925 ln.width = Some(Emu(v));
1926 }
1927 }
1928 b"cap" => {
1929 ln.cap = Some(
1930 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1931 .unwrap_or_default()
1932 .to_string(),
1933 );
1934 }
1935 b"cmpd" => {
1936 ln.compound = Some(
1937 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1938 .unwrap_or_default()
1939 .to_string(),
1940 );
1941 }
1942 _ => {}
1943 }
1944 }
1945 let mut buf = Vec::new();
1946 loop {
1947 match rd.read_event_into(&mut buf) {
1948 Ok(Event::Start(e)) => {
1949 let name = e.name();
1950 let local = local_name(name.as_ref());
1951 if local == b"solidFill" {
1952 ln.color = parse_solid_fill(rd, e.into_owned())?;
1953 } else if local == b"noFill" {
1954 ln.no_fill = true;
1955 let _ = collect_full_element(rd, e.into_owned());
1956 } else if local == b"prstDash" {
1957 parse_prstdash_into(rd, &mut ln);
1959 } else if local == b"gradFill" {
1960 let grad = parse_grad_fill(rd, e.into_owned())?;
1962 ln.fill = Fill::Gradient(grad);
1963 } else if local == b"pattFill" {
1964 let patt = parse_patt_fill(rd, e.into_owned())?;
1966 ln.fill = Fill::Pattern(patt);
1967 } else if local == b"headEnd" {
1968 ln.head_end = Some(parse_arrow_head(&e));
1969 let _ = collect_full_element(rd, e.into_owned());
1970 } else if local == b"tailEnd" {
1971 ln.tail_end = Some(parse_arrow_head(&e));
1972 let _ = collect_full_element(rd, e.into_owned());
1973 } else if local == b"round" {
1974 ln.join = Some(LineJoin::Round);
1975 let _ = collect_full_element(rd, e.into_owned());
1976 } else if local == b"miter" {
1977 let mut lim = 800000i32;
1978 for a in e.attributes().flatten() {
1979 if a.key.as_ref() == b"lim" {
1980 lim = a
1981 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
1982 .unwrap_or_default()
1983 .parse()
1984 .unwrap_or(800000);
1985 }
1986 }
1987 ln.join = Some(LineJoin::Miter(lim));
1988 let _ = collect_full_element(rd, e.into_owned());
1989 } else if local == b"bevel" {
1990 ln.join = Some(LineJoin::Bevel);
1991 let _ = collect_full_element(rd, e.into_owned());
1992 } else {
1993 let _ = collect_full_element(rd, e.into_owned());
1994 }
1995 }
1996 Ok(Event::Empty(e)) => {
1997 let name = e.name();
1998 let local = local_name(name.as_ref());
1999 if local == b"noFill" {
2000 ln.no_fill = true;
2001 } else if local == b"headEnd" {
2002 ln.head_end = Some(parse_arrow_head(&e));
2003 } else if local == b"tailEnd" {
2004 ln.tail_end = Some(parse_arrow_head(&e));
2005 } else if local == b"round" {
2006 ln.join = Some(LineJoin::Round);
2007 } else if local == b"miter" {
2008 let mut lim = 800000i32;
2009 for a in e.attributes().flatten() {
2010 if a.key.as_ref() == b"lim" {
2011 lim = a
2012 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2013 .unwrap_or_default()
2014 .parse()
2015 .unwrap_or(800000);
2016 }
2017 }
2018 ln.join = Some(LineJoin::Miter(lim));
2019 } else if local == b"bevel" {
2020 ln.join = Some(LineJoin::Bevel);
2021 }
2022 }
2024 Ok(Event::End(e)) => {
2025 if local_name(e.name().as_ref()) == b"ln" {
2026 return Ok(ln);
2027 }
2028 }
2029 Ok(Event::Eof) => return Ok(ln),
2030 _ => {}
2031 }
2032 buf.clear();
2033 }
2034}
2035
2036fn parse_arrow_head(e: &quick_xml::events::BytesStart<'_>) -> ArrowHead {
2040 let mut arrow_type = ArrowType::None;
2041 let mut width = ArrowSize::Medium;
2042 let mut length = ArrowSize::Medium;
2043 for a in e.attributes().flatten() {
2044 match a.key.as_ref() {
2045 b"type" => {
2046 let v = a
2047 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2048 .unwrap_or_default();
2049 arrow_type = match v.as_ref() {
2050 "none" => ArrowType::None,
2051 "triangle" => ArrowType::Triangle,
2052 "stealth" => ArrowType::Stealth,
2053 "diamond" => ArrowType::Diamond,
2054 "oval" => ArrowType::Oval,
2055 "arrow" => ArrowType::Arrow,
2056 _ => ArrowType::None,
2057 };
2058 }
2059 b"w" => {
2060 let v = a
2061 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2062 .unwrap_or_default();
2063 width = match v.as_ref() {
2064 "sm" => ArrowSize::Small,
2065 "lg" => ArrowSize::Large,
2066 _ => ArrowSize::Medium,
2067 };
2068 }
2069 b"len" => {
2070 let v = a
2071 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2072 .unwrap_or_default();
2073 length = match v.as_ref() {
2074 "sm" => ArrowSize::Small,
2075 "lg" => ArrowSize::Large,
2076 _ => ArrowSize::Medium,
2077 };
2078 }
2079 _ => {}
2080 }
2081 }
2082 ArrowHead {
2083 arrow_type,
2084 width,
2085 length,
2086 }
2087}
2088
2089fn parse_prstdash_into<R: std::io::BufRead>(rd: &mut Reader<R>, ln: &mut Line) {
2090 let mut buf = Vec::new();
2091 loop {
2092 match rd.read_event_into(&mut buf) {
2093 Ok(Event::Empty(e)) => {
2094 let name = e.name();
2095 let local = local_name(name.as_ref());
2096 if local == b"prst" {
2097 for a in e.attributes().flatten() {
2098 if a.key.as_ref() == b"val" {
2099 let v = a
2100 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2101 .unwrap_or_default();
2102 if let Ok(d) = v.parse::<crate::oxml::sppr::Dash>() {
2103 ln.dash = Some(d);
2104 }
2105 }
2106 }
2107 }
2108 }
2109 Ok(Event::End(e)) => {
2110 let name = e.name();
2111 if local_name(name.as_ref()) == b"prstDash" {
2112 return;
2113 }
2114 }
2115 Ok(Event::Eof) => return,
2116 _ => {}
2117 }
2118 buf.clear();
2119 }
2120}
2121
2122pub fn parse_txbody(xml: &str) -> crate::Result<TextBody> {
2124 let mut tb = TextBody::new();
2125 let mut rd = Reader::from_str(xml);
2126 rd.config_mut().trim_text(true);
2127 let mut buf = Vec::new();
2128 let mut p_bufs: Vec<String> = Vec::new();
2129 loop {
2130 match rd.read_event_into(&mut buf) {
2131 Ok(Event::Start(e)) => {
2132 let name = e.name();
2133 let local = local_name(name.as_ref());
2134 if local == b"txBody" {
2135 } else if local == b"p" {
2138 let inner = collect_full_element(&mut rd, e.into_owned())?;
2139 p_bufs.push(inner);
2140 } else if local == b"bodyPr" {
2141 let inner = collect_full_element(&mut rd, e.into_owned())?;
2144 let bp = parse_body_pr(&inner)?;
2145 tb.body_properties = Some(bp);
2146 } else {
2147 let _ = collect_full_element(&mut rd, e.into_owned());
2149 }
2150 }
2151 Ok(Event::Empty(e)) => {
2152 let name = e.name();
2153 let local = local_name(name.as_ref());
2154 if local == b"bodyPr" {
2155 let bp = parse_body_pr_attrs(&e);
2157 tb.body_properties = Some(bp);
2158 }
2159 }
2160 Ok(Event::Eof) => break,
2161 Err(e) => return Err(crate::Error::Xml(format!("txBody parse: {e}"))),
2162 _ => {}
2163 }
2164 buf.clear();
2165 }
2166 for p_xml in p_bufs {
2167 if let Ok(p) = parse_paragraph(&p_xml) {
2168 tb.paragraphs.push(p);
2169 }
2170 }
2171 Ok(tb)
2172}
2173
2174fn parse_effect_lst<R: std::io::BufRead>(
2183 rd: &mut Reader<R>,
2184 start: quick_xml::events::BytesStart<'static>,
2185) -> crate::Result<EffectList> {
2186 let mut effects = EffectList::default();
2187 let mut buf = Vec::new();
2188 let _ = start;
2190 loop {
2191 match rd.read_event_into(&mut buf) {
2192 Ok(Event::Start(e)) => {
2193 let name = e.name();
2194 let local = local_name(name.as_ref());
2195 match local {
2196 b"outerShdw" => {
2197 let shdw = parse_shadow_attrs(&e, false);
2198 let color = parse_color_child(rd, b"outerShdw")?;
2200 let mut shdw = shdw;
2201 shdw.color = color;
2202 effects.outer_shadow = Some(shdw);
2203 }
2204 b"innerShdw" => {
2205 let shdw = parse_shadow_attrs(&e, true);
2206 let color = parse_color_child(rd, b"innerShdw")?;
2207 let mut shdw = shdw;
2208 shdw.color = color;
2209 effects.inner_shadow = Some(shdw);
2210 }
2211 b"glow" => {
2212 let mut glow = GlowEffect::default();
2213 for a in e.attributes().flatten() {
2214 if a.key.as_ref() == b"rad" {
2215 glow.rad = a
2216 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2217 .unwrap_or_default()
2218 .parse()
2219 .unwrap_or(0);
2220 }
2221 }
2222 glow.color = parse_color_child(rd, b"glow")?;
2223 effects.glow = Some(glow);
2224 }
2225 b"softEdge" => {
2226 let mut se = SoftEdgeEffect::default();
2227 for a in e.attributes().flatten() {
2228 if a.key.as_ref() == b"rad" {
2229 se.rad = a
2230 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2231 .unwrap_or_default()
2232 .parse()
2233 .unwrap_or(0);
2234 }
2235 }
2236 let _ = collect_full_element(rd, e.into_owned());
2238 effects.soft_edge = Some(se);
2239 }
2240 b"reflection" => {
2241 let mut refl = ReflectionEffect::default();
2242 for a in e.attributes().flatten() {
2243 let key = a.key.as_ref();
2244 let v = a
2245 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2246 .unwrap_or_default();
2247 match key {
2248 b"blurRad" => refl.blur_rad = v.parse().ok(),
2249 b"stA" => refl.st_a = v.parse().ok(),
2250 b"stPos" => refl.st_pos = v.parse().ok(),
2251 b"endA" => refl.end_a = v.parse().ok(),
2252 b"endPos" => refl.end_pos = v.parse().ok(),
2253 b"dist" => refl.dist = v.parse().ok(),
2254 b"dir" => refl.dir = v.parse().ok(),
2255 b"rotWithShape" => {
2256 refl.rot_with_shape = Some(v == "1" || v == "true")
2257 }
2258 _ => {}
2259 }
2260 }
2261 let _ = collect_full_element(rd, e.into_owned());
2263 effects.reflection = Some(refl);
2264 }
2265 _ => {
2266 let _ = collect_full_element(rd, e.into_owned());
2268 }
2269 }
2270 }
2271 Ok(Event::Empty(e)) => {
2272 let name = e.name();
2273 let local = local_name(name.as_ref());
2274 if local == b"outerShdw" {
2275 let mut shdw = parse_shadow_attrs(&e, false);
2276 shdw.color = Color::None;
2277 effects.outer_shadow = Some(shdw);
2278 } else if local == b"innerShdw" {
2279 let mut shdw = parse_shadow_attrs(&e, true);
2280 shdw.color = Color::None;
2281 effects.inner_shadow = Some(shdw);
2282 } else if local == b"glow" {
2283 let mut glow = GlowEffect::default();
2284 for a in e.attributes().flatten() {
2285 if a.key.as_ref() == b"rad" {
2286 glow.rad = a
2287 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2288 .unwrap_or_default()
2289 .parse()
2290 .unwrap_or(0);
2291 }
2292 }
2293 effects.glow = Some(glow);
2294 } else if local == b"softEdge" {
2295 let mut se = SoftEdgeEffect::default();
2296 for a in e.attributes().flatten() {
2297 if a.key.as_ref() == b"rad" {
2298 se.rad = a
2299 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2300 .unwrap_or_default()
2301 .parse()
2302 .unwrap_or(0);
2303 }
2304 }
2305 effects.soft_edge = Some(se);
2306 } else if local == b"reflection" {
2307 let mut refl = ReflectionEffect::default();
2308 for a in e.attributes().flatten() {
2309 let key = a.key.as_ref();
2310 let v = a
2311 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2312 .unwrap_or_default();
2313 match key {
2314 b"blurRad" => refl.blur_rad = v.parse().ok(),
2315 b"stA" => refl.st_a = v.parse().ok(),
2316 b"stPos" => refl.st_pos = v.parse().ok(),
2317 b"endA" => refl.end_a = v.parse().ok(),
2318 b"endPos" => refl.end_pos = v.parse().ok(),
2319 b"dist" => refl.dist = v.parse().ok(),
2320 b"dir" => refl.dir = v.parse().ok(),
2321 b"rotWithShape" => refl.rot_with_shape = Some(v == "1" || v == "true"),
2322 _ => {}
2323 }
2324 }
2325 effects.reflection = Some(refl);
2326 }
2327 }
2328 Ok(Event::End(e)) => {
2329 if local_name(e.name().as_ref()) == b"effectLst" {
2330 return Ok(effects);
2331 }
2332 }
2333 Ok(Event::Eof) => return Ok(effects),
2334 Err(e) => return Err(crate::Error::Xml(format!("effectLst parse: {e}"))),
2335 _ => {}
2336 }
2337 buf.clear();
2338 }
2339}
2340
2341fn parse_rotation_3d(e: &quick_xml::events::BytesStart<'_>) -> Rotation3d {
2345 let mut rot = Rotation3d::default();
2346 for a in e.attributes().flatten() {
2347 let v = a
2348 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2349 .unwrap_or_default();
2350 match a.key.as_ref() {
2351 b"lat" => rot.lat = v.parse().unwrap_or(0),
2352 b"lon" => rot.lon = v.parse().unwrap_or(0),
2353 b"rev" => rot.rev = v.parse().unwrap_or(0),
2354 _ => {}
2355 }
2356 }
2357 rot
2358}
2359
2360fn parse_scene_3d<R: std::io::BufRead>(
2374 rd: &mut Reader<R>,
2375 start: quick_xml::events::BytesStart<'static>,
2376) -> crate::Result<Scene3d> {
2377 let mut scene = Scene3d::default();
2378 let mut buf = Vec::new();
2379 let _ = start;
2380 loop {
2381 match rd.read_event_into(&mut buf) {
2382 Ok(Event::Start(e)) => {
2383 let name = e.name();
2385 let local = local_name(name.as_ref());
2386 if local == b"camera" {
2387 let mut camera = Camera::default();
2388 for a in e.attributes().flatten() {
2389 let v = a
2390 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2391 .unwrap_or_default();
2392 match a.key.as_ref() {
2393 b"prst" => camera.preset = CameraPreset::parse(&v),
2395 b"fov" => camera.fov = v.parse().unwrap_or(0),
2396 b"zoom" => camera.zoom = v.parse().unwrap_or(0),
2397 _ => {}
2398 }
2399 }
2400 let mut buf2 = Vec::new();
2402 loop {
2403 match rd.read_event_into(&mut buf2) {
2404 Ok(Event::Start(e2)) => {
2405 let n2 = e2.name();
2406 if local_name(n2.as_ref()) == b"rot" {
2407 camera.rotation = Some(parse_rotation_3d(&e2));
2408 let _ = collect_full_element(rd, e2.into_owned());
2409 }
2410 }
2411 Ok(Event::End(e2)) => {
2412 let n2 = e2.name();
2413 if local_name(n2.as_ref()) == b"camera" {
2414 break;
2415 }
2416 }
2417 Ok(Event::Eof) => break,
2418 _ => {}
2419 }
2420 buf2.clear();
2421 }
2422 scene.camera = camera;
2423 } else if local == b"lightRig" {
2424 let mut rig = LightRig::default();
2425 for a in e.attributes().flatten() {
2426 let v = a
2427 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2428 .unwrap_or_default();
2429 match a.key.as_ref() {
2430 b"rig" => rig.rig = LightRigType::parse(&v),
2432 b"dir" => rig.dir = LightRigDirection::parse(&v),
2433 _ => {}
2434 }
2435 }
2436 let mut buf2 = Vec::new();
2437 loop {
2438 match rd.read_event_into(&mut buf2) {
2439 Ok(Event::Start(e2)) => {
2440 let n2 = e2.name();
2441 if local_name(n2.as_ref()) == b"rot" {
2442 rig.rotation = Some(parse_rotation_3d(&e2));
2443 let _ = collect_full_element(rd, e2.into_owned());
2444 }
2445 }
2446 Ok(Event::End(e2)) => {
2447 let n2 = e2.name();
2448 if local_name(n2.as_ref()) == b"lightRig" {
2449 break;
2450 }
2451 }
2452 Ok(Event::Eof) => break,
2453 _ => {}
2454 }
2455 buf2.clear();
2456 }
2457 scene.light_rig = rig;
2458 } else if local == b"backdrop" {
2459 let backdrop = parse_backdrop(rd, e.into_owned())?;
2461 scene.backdrop = Some(backdrop);
2462 } else {
2463 let _ = collect_full_element(rd, e.into_owned());
2464 }
2465 }
2466 Ok(Event::End(e)) => {
2467 let name = e.name();
2468 if local_name(name.as_ref()) == b"scene3d" {
2469 return Ok(scene);
2470 }
2471 }
2472 Ok(Event::Eof) => return Ok(scene),
2473 Err(e) => return Err(crate::Error::Xml(format!("scene3d parse: {e}"))),
2474 _ => {}
2475 }
2476 buf.clear();
2477 }
2478}
2479
2480fn parse_backdrop<R: std::io::BufRead>(
2495 rd: &mut Reader<R>,
2496 start: quick_xml::events::BytesStart<'static>,
2497) -> crate::Result<Backdrop> {
2498 let mut bd = Backdrop::default();
2499 let mut buf = Vec::new();
2500 let _ = start;
2501 loop {
2502 match rd.read_event_into(&mut buf) {
2503 Ok(Event::Empty(e)) => {
2504 let name = e.name();
2506 let local = local_name(name.as_ref());
2507 if local == b"anchor" {
2508 let mut pt = Point3d::default();
2509 for a in e.attributes().flatten() {
2510 let v = a
2511 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2512 .unwrap_or_default();
2513 match a.key.as_ref() {
2514 b"x" => pt.x = v.parse().unwrap_or(0),
2515 b"y" => pt.y = v.parse().unwrap_or(0),
2516 b"z" => pt.z = v.parse().unwrap_or(0),
2517 _ => {}
2518 }
2519 }
2520 bd.anchor = Some(pt);
2521 } else if local == b"floor" {
2522 bd.floor = true;
2523 } else if local == b"wall" {
2524 bd.wall = true;
2525 } else if local == b"l" {
2526 bd.left = true;
2527 } else if local == b"r" {
2528 bd.right = true;
2529 } else if local == b"t" {
2530 bd.top = true;
2531 } else if local == b"b" {
2532 bd.bottom = true;
2533 }
2534 }
2535 Ok(Event::Start(e)) => {
2536 let name = e.name();
2539 let local = local_name(name.as_ref());
2540 if local == b"floor" {
2541 bd.floor = true;
2542 } else if local == b"wall" {
2543 bd.wall = true;
2544 } else if local == b"l" {
2545 bd.left = true;
2546 } else if local == b"r" {
2547 bd.right = true;
2548 } else if local == b"t" {
2549 bd.top = true;
2550 } else if local == b"b" {
2551 bd.bottom = true;
2552 }
2553 let _ = collect_full_element(rd, e.into_owned());
2554 }
2555 Ok(Event::End(e)) => {
2556 let name = e.name();
2557 if local_name(name.as_ref()) == b"backdrop" {
2558 return Ok(bd);
2559 }
2560 }
2561 Ok(Event::Eof) => return Ok(bd),
2562 Err(e) => return Err(crate::Error::Xml(format!("backdrop parse: {e}"))),
2563 _ => {}
2564 }
2565 buf.clear();
2566 }
2567}
2568
2569fn parse_sp_3d<R: std::io::BufRead>(
2581 rd: &mut Reader<R>,
2582 start: quick_xml::events::BytesStart<'static>,
2583) -> crate::Result<Sp3d> {
2584 let mut sp3d = Sp3d::default();
2585 for a in start.attributes().flatten() {
2587 let v = a
2588 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2589 .unwrap_or_default();
2590 match a.key.as_ref() {
2591 b"extrusionH" => sp3d.extrusion_h = v.parse().unwrap_or(0),
2592 b"contourW" => sp3d.contour_w = v.parse().unwrap_or(0),
2593 b"prstMaterial" => sp3d.prst_material = MaterialPreset::parse(&v),
2595 _ => {}
2596 }
2597 }
2598 let mut buf = Vec::new();
2599 loop {
2600 match rd.read_event_into(&mut buf) {
2601 Ok(Event::Empty(e)) => {
2602 let name = e.name();
2605 let local = local_name(name.as_ref());
2606 match local {
2607 b"bevelT" => {
2608 sp3d.bevel_top = Some(parse_bevel_attrs(&e));
2609 }
2610 b"bevelB" => {
2611 sp3d.bevel_bottom = Some(parse_bevel_attrs(&e));
2612 }
2613 _ => {}
2614 }
2615 }
2616 Ok(Event::Start(e)) => {
2617 let name = e.name();
2618 let local = local_name(name.as_ref());
2619 match local {
2620 b"bevelT" => {
2621 let bvl = parse_bevel_attrs(&e);
2622 let _ = collect_full_element(rd, e.into_owned());
2623 sp3d.bevel_top = Some(bvl);
2624 }
2625 b"bevelB" => {
2626 let bvl = parse_bevel_attrs(&e);
2627 let _ = collect_full_element(rd, e.into_owned());
2628 sp3d.bevel_bottom = Some(bvl);
2629 }
2630 b"extrusionClr" => {
2631 let color = parse_color_child(rd, b"extrusionClr")?;
2633 sp3d.extrusion_color = Some(color);
2634 }
2635 b"contourClr" => {
2636 let color = parse_color_child(rd, b"contourClr")?;
2637 sp3d.contour_color = Some(color);
2638 }
2639 _ => {
2640 let _ = collect_full_element(rd, e.into_owned());
2641 }
2642 }
2643 }
2644 Ok(Event::End(e)) => {
2645 let name = e.name();
2646 if local_name(name.as_ref()) == b"sp3d" {
2647 return Ok(sp3d);
2648 }
2649 }
2650 Ok(Event::Eof) => return Ok(sp3d),
2651 Err(e) => return Err(crate::Error::Xml(format!("sp3d parse: {e}"))),
2652 _ => {}
2653 }
2654 buf.clear();
2655 }
2656}
2657
2658fn parse_bevel_attrs(e: &quick_xml::events::BytesStart<'_>) -> Bevel {
2660 let mut bvl = Bevel::default();
2661 for a in e.attributes().flatten() {
2662 let v = a
2663 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2664 .unwrap_or_default();
2665 match a.key.as_ref() {
2666 b"w" => bvl.w = v.parse().unwrap_or(0),
2667 b"h" => bvl.h = v.parse().unwrap_or(0),
2668 _ => {}
2669 }
2670 }
2671 bvl
2672}
2673
2674fn parse_shadow_attrs(e: &quick_xml::events::BytesStart<'_>, is_inner: bool) -> ShadowEffect {
2678 let mut shdw = ShadowEffect::default();
2679 for a in e.attributes().flatten() {
2680 let key = a.key.as_ref();
2681 let v = a
2682 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2683 .unwrap_or_default();
2684 match key {
2685 b"dir" => shdw.dir = v.parse().unwrap_or(0),
2686 b"dist" => shdw.dist = v.parse().unwrap_or(0),
2687 b"blurRad" => shdw.blur_rad = v.parse().unwrap_or(0),
2688 b"rotWithShape" if !is_inner => {
2689 shdw.rot_with_shape = Some(v == "1" || v == "true");
2690 }
2691 _ => {}
2692 }
2693 }
2694 shdw
2695}
2696
2697fn parse_body_pr(xml: &str) -> crate::Result<BodyProperties> {
2702 let mut bp = BodyProperties::default();
2703 let mut rd = Reader::from_str(xml);
2704 rd.config_mut().trim_text(true);
2705 let mut buf = Vec::new();
2706 loop {
2707 match rd.read_event_into(&mut buf) {
2708 Ok(Event::Start(e)) => {
2709 let name = e.name();
2710 let local = local_name(name.as_ref());
2711 if local == b"bodyPr" {
2712 apply_body_pr_attrs(&mut bp, &e);
2714 } else if local == b"spAutoFit" {
2715 bp.sp_auto_fit = true;
2716 let _ = collect_full_element(&mut rd, e.into_owned());
2717 } else if local == b"normAutofit" {
2718 bp.norm_autofit = true;
2719 let _ = collect_full_element(&mut rd, e.into_owned());
2720 } else {
2721 let _ = collect_full_element(&mut rd, e.into_owned());
2722 }
2723 }
2724 Ok(Event::Empty(e)) => {
2725 let name = e.name();
2726 let local = local_name(name.as_ref());
2727 if local == b"bodyPr" {
2728 apply_body_pr_attrs(&mut bp, &e);
2729 } else if local == b"spAutoFit" {
2730 bp.sp_auto_fit = true;
2731 } else if local == b"normAutofit" {
2732 bp.norm_autofit = true;
2733 }
2734 }
2735 Ok(Event::Eof) => break,
2736 Err(e) => return Err(crate::Error::Xml(format!("bodyPr parse: {e}"))),
2737 _ => {}
2738 }
2739 buf.clear();
2740 }
2741 Ok(bp)
2742}
2743
2744fn parse_body_pr_attrs(e: &quick_xml::events::BytesStart<'_>) -> BodyProperties {
2746 let mut bp = BodyProperties::default();
2747 apply_body_pr_attrs(&mut bp, e);
2748 bp
2749}
2750
2751fn apply_body_pr_attrs(bp: &mut BodyProperties, e: &quick_xml::events::BytesStart<'_>) {
2756 for a in e.attributes().flatten() {
2757 match a.key.as_ref() {
2758 b"numCol" => {
2759 let v = a
2760 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2761 .unwrap_or_default();
2762 bp.num_cols = v.parse::<u32>().ok();
2763 }
2764 b"spcCol" => {
2765 let v = a
2766 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2767 .unwrap_or_default();
2768 if let Ok(n) = v.parse::<i64>() {
2769 bp.col_spacing = Some(Emu(n));
2770 }
2771 }
2772 b"anchor" => {
2773 let v = a
2774 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2775 .unwrap_or_default();
2776 bp.anchor = match v.as_ref() {
2777 "t" => Some(MsoAnchor::Top),
2778 "ctr" => Some(MsoAnchor::Middle),
2779 "b" => Some(MsoAnchor::Bottom),
2780 _ => None,
2781 };
2782 }
2783 b"wrap" => {
2784 let v = a
2785 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2786 .unwrap_or_default();
2787 bp.wrap = match v.as_ref() {
2788 "square" => Some(TextWrapping::Square),
2789 "none" => Some(TextWrapping::None),
2790 _ => None,
2791 };
2792 }
2793 b"vert" => {
2794 let v = a
2795 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2796 .unwrap_or_default()
2797 .to_string();
2798 bp.vertical = Some(v);
2799 }
2800 b"rot" => {
2801 let v = a
2802 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2803 .unwrap_or_default();
2804 bp.rotation = v.parse::<i32>().ok();
2805 }
2806 b"lIns" => {
2807 let v = a
2808 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2809 .unwrap_or_default();
2810 if let Ok(n) = v.parse::<i64>() {
2811 bp.insets.get_or_insert_with(Default::default).left = Emu(n);
2812 }
2813 }
2814 b"tIns" => {
2815 let v = a
2816 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2817 .unwrap_or_default();
2818 if let Ok(n) = v.parse::<i64>() {
2819 bp.insets.get_or_insert_with(Default::default).top = Emu(n);
2820 }
2821 }
2822 b"rIns" => {
2823 let v = a
2824 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2825 .unwrap_or_default();
2826 if let Ok(n) = v.parse::<i64>() {
2827 bp.insets.get_or_insert_with(Default::default).right = Emu(n);
2828 }
2829 }
2830 b"bIns" => {
2831 let v = a
2832 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
2833 .unwrap_or_default();
2834 if let Ok(n) = v.parse::<i64>() {
2835 bp.insets.get_or_insert_with(Default::default).bottom = Emu(n);
2836 }
2837 }
2838 _ => {}
2839 }
2840 }
2841}
2842
2843pub fn parse_paragraph(xml: &str) -> crate::Result<Paragraph> {
2845 let mut p = Paragraph::new();
2846 let mut rd = Reader::from_str(xml);
2847 rd.config_mut().trim_text(true);
2848 let mut buf = Vec::new();
2849 let mut in_ppr = false;
2850 let mut ppr_done = false;
2851 loop {
2852 match rd.read_event_into(&mut buf) {
2853 Ok(Event::Start(e)) => {
2854 let name = e.name();
2855 let local = local_name(name.as_ref());
2856 if local == b"p" {
2857 } else if local == b"pPr" {
2860 let inner = collect_full_element(&mut rd, e.into_owned())?;
2861 p.properties = parse_paragraph_properties(&inner)?;
2862 ppr_done = true;
2863 } else if local == b"r" {
2864 let inner = collect_full_element(&mut rd, e.into_owned())?;
2865 if let Ok(r) = parse_run(&inner) {
2866 p.runs.push(r);
2867 }
2868 } else if local == b"fld" {
2869 let inner = collect_full_element(&mut rd, e.into_owned())?;
2871 if let Ok(f) = parse_field(&inner) {
2872 p.fields.push(f);
2873 }
2874 } else if local == b"br" {
2875 p.runs.push(Run::line_break());
2877 } else if local == b"endParaRPr" {
2878 let inner = collect_full_element(&mut rd, e.into_owned())?;
2881 p.end_properties = Some(parse_run_properties(&inner)?);
2882 } else {
2883 let _ = collect_full_element(&mut rd, e.into_owned());
2884 }
2885 in_ppr = false;
2886 }
2887 Ok(Event::Empty(e)) => {
2888 let name = e.name();
2889 let local = local_name(name.as_ref());
2890 if local == b"pPr" {
2891 p.properties = parse_paragraph_properties_attrs(&e);
2893 ppr_done = true;
2894 } else if local == b"endParaRPr" {
2895 p.end_properties = Some(RunProperties::from_bytes_start(&e));
2897 } else if local == b"fld" {
2898 if let Some(f) = parse_field_attrs(&e) {
2900 p.fields.push(f);
2901 }
2902 }
2903 }
2904 Ok(Event::End(_)) => {
2905 if !ppr_done {
2906 in_ppr = false;
2907 }
2908 }
2909 Ok(Event::Eof) => break,
2910 Err(e) => return Err(crate::Error::Xml(format!("paragraph parse: {e}"))),
2911 _ => {}
2912 }
2913 buf.clear();
2914 }
2915 let _ = in_ppr;
2916 Ok(p)
2917}
2918
2919fn parse_paragraph_properties(xml: &str) -> crate::Result<ParagraphProperties> {
2920 let mut ppr = ParagraphProperties::default();
2921 let mut rd = Reader::from_str(xml);
2922 rd.config_mut().trim_text(true);
2923 let mut buf = Vec::new();
2924 loop {
2925 match rd.read_event_into(&mut buf) {
2926 Ok(Event::Start(e)) => {
2927 let name = e.name();
2928 let local = local_name(name.as_ref());
2929 if local == b"pPr" {
2930 } else if local == b"lnSpc" {
2933 parse_ln_spc_into(&mut rd, &mut ppr, e.into_owned())?;
2934 } else if local == b"spcBef" {
2935 parse_spc_bef_into(&mut rd, &mut ppr, e.into_owned())?;
2936 } else if local == b"spcAft" {
2937 parse_spc_aft_into(&mut rd, &mut ppr, e.into_owned())?;
2938 } else if local == b"buNone" {
2939 ppr.bullet = false;
2941 ppr.bullet_style = Some(BulletStyle::None);
2942 let _ = collect_full_element(&mut rd, e.into_owned());
2943 } else if local == b"buChar" {
2944 let bs = parse_bu_char(&e);
2946 ppr.bullet = true;
2947 ppr.bullet_style = Some(bs);
2948 let _ = collect_full_element(&mut rd, e.into_owned());
2949 } else if local == b"buAutoNum" {
2950 let bs = parse_bu_auto_num(&e);
2952 ppr.bullet = true;
2953 ppr.bullet_style = Some(bs);
2954 let _ = collect_full_element(&mut rd, e.into_owned());
2955 } else if local == b"tabLst" {
2956 let inner = collect_full_element(&mut rd, e.into_owned())?;
2958 ppr.tab_stops = parse_tab_lst(&inner)?;
2959 } else {
2960 let _ = collect_full_element(&mut rd, e.into_owned());
2961 }
2962 }
2963 Ok(Event::Empty(e)) => {
2964 let name = e.name();
2965 let local = local_name(name.as_ref());
2966 if local == b"buNone" {
2967 ppr.bullet = false;
2968 ppr.bullet_style = Some(BulletStyle::None);
2969 } else if local == b"buChar" {
2970 ppr.bullet = true;
2971 ppr.bullet_style = Some(parse_bu_char(&e));
2972 } else if local == b"buAutoNum" {
2973 ppr.bullet = true;
2974 ppr.bullet_style = Some(parse_bu_auto_num(&e));
2975 } else if local == b"tab" {
2976 if let Some(tab) = parse_tab(&e) {
2978 ppr.tab_stops.push(tab);
2979 }
2980 } else {
2981 apply_ppr_attrs(&e, &mut ppr);
2982 }
2983 }
2984 Ok(Event::Eof) => break,
2985 Err(e) => return Err(crate::Error::Xml(format!("pPr parse: {e}"))),
2986 _ => {}
2987 }
2988 buf.clear();
2989 }
2990 Ok(ppr)
2991}
2992
2993fn parse_paragraph_properties_attrs(e: &quick_xml::events::BytesStart<'_>) -> ParagraphProperties {
2994 let mut ppr = ParagraphProperties::default();
2995 apply_ppr_attrs(e, &mut ppr);
2996 ppr
2997}
2998
2999fn parse_bu_char(e: &quick_xml::events::BytesStart<'_>) -> BulletStyle {
3003 let mut ch = String::new();
3004 for a in e.attributes().flatten() {
3005 if a.key.as_ref() == b"char" {
3006 ch = a
3007 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3008 .unwrap_or_default()
3009 .to_string();
3010 }
3011 }
3012 BulletStyle::Char { char: ch }
3013}
3014
3015fn parse_bu_auto_num(e: &quick_xml::events::BytesStart<'_>) -> BulletStyle {
3020 let mut auto_num_type = String::new();
3021 let mut start_at: Option<u32> = None;
3022 for a in e.attributes().flatten() {
3023 match a.key.as_ref() {
3024 b"type" => {
3025 auto_num_type = a
3026 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3027 .unwrap_or_default()
3028 .to_string();
3029 }
3030 b"startAt" => {
3031 let v = a
3032 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3033 .unwrap_or_default();
3034 start_at = v.parse::<u32>().ok();
3035 }
3036 _ => {}
3037 }
3038 }
3039 BulletStyle::AutoNum {
3040 auto_num_type,
3041 start_at,
3042 }
3043}
3044
3045fn parse_tab_lst(xml: &str) -> crate::Result<Vec<TabStop>> {
3049 let mut tabs = Vec::new();
3050 let mut rd = Reader::from_str(xml);
3051 rd.config_mut().trim_text(true);
3052 let mut buf = Vec::new();
3053 loop {
3054 match rd.read_event_into(&mut buf) {
3055 Ok(Event::Start(e)) => {
3056 let name = e.name();
3057 let local = local_name(name.as_ref());
3058 if local == b"tabLst" {
3059 } else if local == b"tab" {
3062 if let Some(tab) = parse_tab(&e) {
3063 tabs.push(tab);
3064 }
3065 let _ = collect_full_element(&mut rd, e.into_owned());
3066 } else {
3067 let _ = collect_full_element(&mut rd, e.into_owned());
3068 }
3069 }
3070 Ok(Event::Empty(e)) => {
3071 let name = e.name();
3072 let local = local_name(name.as_ref());
3073 if local == b"tab" {
3074 if let Some(tab) = parse_tab(&e) {
3075 tabs.push(tab);
3076 }
3077 }
3078 }
3079 Ok(Event::Eof) => break,
3080 Err(e) => return Err(crate::Error::Xml(format!("tabLst parse: {e}"))),
3081 _ => {}
3082 }
3083 buf.clear();
3084 }
3085 Ok(tabs)
3086}
3087
3088fn parse_tab(e: &quick_xml::events::BytesStart<'_>) -> Option<TabStop> {
3092 let mut pos: Option<i64> = None;
3093 let mut alignment = TabAlignment::Left;
3094 for a in e.attributes().flatten() {
3095 match a.key.as_ref() {
3096 b"pos" => {
3097 let v = a
3098 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3099 .unwrap_or_default();
3100 pos = v.parse::<i64>().ok();
3101 }
3102 b"algn" => {
3103 let v = a
3104 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3105 .unwrap_or_default();
3106 if let Ok(al) = v.parse::<TabAlignment>() {
3107 alignment = al;
3108 }
3109 }
3110 _ => {}
3111 }
3112 }
3113 pos.map(|p| TabStop {
3114 pos: Emu(p),
3115 alignment,
3116 })
3117}
3118
3119fn parse_field(xml: &str) -> crate::Result<Field> {
3123 let mut field = Field::default();
3124 let mut rd = Reader::from_str(xml);
3125 rd.config_mut().trim_text(true);
3126 let mut buf = Vec::new();
3127 loop {
3128 match rd.read_event_into(&mut buf) {
3129 Ok(Event::Start(e)) => {
3130 let name = e.name();
3131 let local = local_name(name.as_ref());
3132 if local == b"fld" {
3133 apply_field_attrs(&mut field, &e);
3135 } else if local == b"rPr" {
3136 let inner = collect_full_element(&mut rd, e.into_owned())?;
3137 field.properties = parse_run_properties(&inner)?;
3138 } else if local == b"t" {
3139 let inner = collect_full_element(&mut rd, e.into_owned())?;
3140 field.text = extract_text_content(&inner);
3141 } else {
3142 let _ = collect_full_element(&mut rd, e.into_owned());
3143 }
3144 }
3145 Ok(Event::Empty(e)) => {
3146 let name = e.name();
3147 let local = local_name(name.as_ref());
3148 if local == b"fld" {
3149 apply_field_attrs(&mut field, &e);
3150 } else if local == b"rPr" {
3151 field.properties = RunProperties::from_bytes_start(&e);
3152 }
3153 }
3155 Ok(Event::Eof) => break,
3156 Err(e) => return Err(crate::Error::Xml(format!("fld parse: {e}"))),
3157 _ => {}
3158 }
3159 buf.clear();
3160 }
3161 Ok(field)
3162}
3163
3164fn parse_field_attrs(e: &quick_xml::events::BytesStart<'_>) -> Option<Field> {
3168 let mut field = Field::default();
3169 let mut has_type = false;
3170 for a in e.attributes().flatten() {
3171 match a.key.as_ref() {
3172 b"id" => {
3173 field.id = a
3174 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3175 .unwrap_or_default()
3176 .to_string();
3177 }
3178 b"type" => {
3179 let v = a
3180 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3181 .unwrap_or_default();
3182 field.field_type = FieldType::from_str_value(&v);
3183 has_type = true;
3184 }
3185 _ => {}
3186 }
3187 }
3188 if has_type {
3189 Some(field)
3190 } else {
3191 None
3192 }
3193}
3194
3195fn apply_field_attrs(field: &mut Field, e: &quick_xml::events::BytesStart<'_>) {
3197 for a in e.attributes().flatten() {
3198 match a.key.as_ref() {
3199 b"id" => {
3200 field.id = a
3201 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3202 .unwrap_or_default()
3203 .to_string();
3204 }
3205 b"type" => {
3206 let v = a
3207 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3208 .unwrap_or_default();
3209 field.field_type = FieldType::from_str_value(&v);
3210 }
3211 _ => {}
3212 }
3213 }
3214}
3215
3216fn extract_text_content(xml: &str) -> String {
3218 let mut text = String::new();
3219 let mut rd = Reader::from_str(xml);
3220 rd.config_mut().trim_text(true);
3221 let mut buf = Vec::new();
3222 loop {
3223 match rd.read_event_into(&mut buf) {
3224 Ok(Event::Text(t)) => {
3225 text.push_str(&t.decode().unwrap_or_default());
3226 }
3227 Ok(Event::Eof) => break,
3228 Err(_) => break,
3229 _ => {}
3230 }
3231 buf.clear();
3232 }
3233 text
3234}
3235
3236fn apply_ppr_attrs(e: &quick_xml::events::BytesStart<'_>, ppr: &mut ParagraphProperties) {
3237 for a in e.attributes().flatten() {
3238 match a.key.as_ref() {
3239 b"algn" => {
3240 let v = a
3241 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3242 .unwrap_or_default();
3243 if let Ok(al) = v.parse::<Alignment>() {
3244 ppr.alignment = Some(al);
3245 }
3246 }
3247 b"lvl" => {
3248 if let Ok(v) = a
3249 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3250 .unwrap_or_default()
3251 .parse::<u8>()
3252 {
3253 ppr.level = v;
3254 }
3255 }
3256 b"indent" | b"marL" => {
3257 if let Ok(v) = a
3258 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3259 .unwrap_or_default()
3260 .parse::<i64>()
3261 {
3262 ppr.indent.left = Some(Emu(v));
3263 }
3264 }
3265 #[allow(unreachable_patterns)]
3266 b"indent" | b"r" => {
3267 if let Ok(v) = a
3268 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3269 .unwrap_or_default()
3270 .parse::<i64>()
3271 {
3272 ppr.indent.right = Some(Emu(v));
3273 }
3274 }
3275 #[allow(unreachable_patterns)]
3276 b"indent" | b"firstLine" => {
3277 if let Ok(v) = a
3278 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3279 .unwrap_or_default()
3280 .parse::<i64>()
3281 {
3282 ppr.indent.first_line = Some(Emu(v));
3283 }
3284 }
3285 _ => {}
3286 }
3287 }
3288}
3289
3290fn parse_ln_spc_into<R: std::io::BufRead>(
3291 rd: &mut Reader<R>,
3292 ppr: &mut ParagraphProperties,
3293 _start: quick_xml::events::BytesStart<'static>,
3294) -> crate::Result<()> {
3295 let mut buf = Vec::new();
3296 loop {
3297 match rd.read_event_into(&mut buf) {
3298 Ok(Event::Empty(e)) => {
3299 let name = e.name();
3300 let local = local_name(name.as_ref());
3301 if local == b"spcPct" {
3302 for a in e.attributes().flatten() {
3303 if a.key.as_ref() == b"val" {
3304 if let Ok(v) = a
3305 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3306 .unwrap_or_default()
3307 .parse::<i32>()
3308 {
3309 ppr.line_spacing_pct = Some(v);
3310 }
3311 }
3312 }
3313 } else if local == b"spcPts" {
3314 for a in e.attributes().flatten() {
3315 if a.key.as_ref() == b"val" {
3316 if let Ok(v) = a
3317 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3318 .unwrap_or_default()
3319 .parse::<i32>()
3320 {
3321 ppr.line_spacing = Some(v);
3322 }
3323 }
3324 }
3325 }
3326 }
3327 Ok(Event::End(e)) => {
3328 if local_name(e.name().as_ref()) == b"lnSpc" {
3329 return Ok(());
3330 }
3331 }
3332 Ok(Event::Eof) => return Ok(()),
3333 _ => {}
3334 }
3335 buf.clear();
3336 }
3337}
3338
3339fn parse_spc_bef_into<R: std::io::BufRead>(
3340 rd: &mut Reader<R>,
3341 ppr: &mut ParagraphProperties,
3342 start: quick_xml::events::BytesStart<'static>,
3343) -> crate::Result<()> {
3344 let _ = start;
3345 let mut buf = Vec::new();
3346 loop {
3347 match rd.read_event_into(&mut buf) {
3348 Ok(Event::Empty(e)) => {
3349 let name = e.name();
3350 let local = local_name(name.as_ref());
3351 if local == b"spcPts" {
3352 for a in e.attributes().flatten() {
3353 if a.key.as_ref() == b"val" {
3354 if let Ok(v) = a
3355 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3356 .unwrap_or_default()
3357 .parse::<i32>()
3358 {
3359 ppr.space_before = Some(Emu(v as i64 * 100)); }
3361 }
3362 }
3363 }
3364 }
3365 Ok(Event::End(e)) => {
3366 let name = e.name();
3367 if local_name(name.as_ref()) == b"spcBef" {
3368 return Ok(());
3369 }
3370 }
3371 Ok(Event::Eof) => return Ok(()),
3372 _ => {}
3373 }
3374 buf.clear();
3375 }
3376}
3377
3378fn parse_spc_aft_into<R: std::io::BufRead>(
3379 rd: &mut Reader<R>,
3380 ppr: &mut ParagraphProperties,
3381 start: quick_xml::events::BytesStart<'static>,
3382) -> crate::Result<()> {
3383 let _ = start;
3384 let mut buf = Vec::new();
3385 loop {
3386 match rd.read_event_into(&mut buf) {
3387 Ok(Event::Empty(e)) => {
3388 let name = e.name();
3389 let local = local_name(name.as_ref());
3390 if local == b"spcPts" {
3391 for a in e.attributes().flatten() {
3392 if a.key.as_ref() == b"val" {
3393 if let Ok(v) = a
3394 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3395 .unwrap_or_default()
3396 .parse::<i32>()
3397 {
3398 ppr.space_after = Some(Emu(v as i64 * 100));
3399 }
3400 }
3401 }
3402 }
3403 }
3404 Ok(Event::End(e)) => {
3405 let name = e.name();
3406 if local_name(name.as_ref()) == b"spcAft" {
3407 return Ok(());
3408 }
3409 }
3410 Ok(Event::Eof) => return Ok(()),
3411 _ => {}
3412 }
3413 buf.clear();
3414 }
3415}
3416
3417pub fn parse_run(xml: &str) -> crate::Result<Run> {
3419 let mut run = Run::default();
3420 let mut rd = Reader::from_str(xml);
3421 rd.config_mut().trim_text(true);
3422 let mut buf = Vec::new();
3423 let mut rpr_props: Option<RunProperties> = None;
3424 loop {
3425 match rd.read_event_into(&mut buf) {
3426 Ok(Event::Start(e)) => {
3427 let name = e.name();
3428 let local = local_name(name.as_ref());
3429 if local == b"r" {
3430 } else if local == b"rPr" {
3433 let inner = collect_full_element(&mut rd, e.into_owned())?;
3434 rpr_props = Some(parse_run_properties(&inner)?);
3435 } else if local == b"t" {
3436 let txt = collect_inner_text_only(&mut rd)?;
3438 run.text = txt;
3439 } else {
3440 let _ = collect_full_element(&mut rd, e.into_owned());
3441 }
3442 }
3443 Ok(Event::Empty(e)) => {
3444 let name = e.name();
3445 let local = local_name(name.as_ref());
3446 if local == b"rPr" {
3447 rpr_props = Some(RunProperties::from_bytes_start(&e));
3449 } else if local == b"t" {
3450 }
3452 }
3453 Ok(Event::Eof) => break,
3454 Err(e) => return Err(crate::Error::Xml(format!("run parse: {e}"))),
3455 _ => {}
3456 }
3457 buf.clear();
3458 }
3459 if let Some(p) = rpr_props {
3460 run.properties = p;
3461 }
3462 Ok(run)
3463}
3464
3465pub fn collect_full_element<R: std::io::BufRead>(
3468 rd: &mut Reader<R>,
3469 start: quick_xml::events::BytesStart<'static>,
3470) -> crate::Result<String> {
3471 let mut out = String::new();
3472 out.push('<');
3474 let name = start.name();
3477 out.push_str(&String::from_utf8_lossy(name.as_ref()));
3478 for a in start.attributes().flatten() {
3479 out.push(' ');
3480 out.push_str(&String::from_utf8_lossy(a.key.as_ref()));
3481 out.push_str("=\"");
3482 out.push_str(
3483 &a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3484 .unwrap_or_default(),
3485 );
3486 out.push('"');
3487 }
3488 out.push('>');
3489 let mut depth = 1i32;
3491 let mut buf = Vec::new();
3492 loop {
3493 match rd.read_event_into(&mut buf) {
3494 Ok(Event::Start(e)) => {
3495 depth += 1;
3496 write_event_start(&mut out, &e);
3497 }
3498 Ok(Event::End(e)) => {
3499 depth -= 1;
3500 write_event_end(&mut out, &e);
3501 if depth == 0 {
3502 return Ok(out);
3503 }
3504 }
3505 Ok(Event::Empty(e)) => {
3506 write_event_empty(&mut out, &e);
3507 }
3508 Ok(Event::Text(t)) => {
3509 out.push_str(&t.decode().unwrap_or_default());
3510 }
3511 Ok(Event::CData(c)) => {
3512 out.push_str("<![CDATA[");
3514 let cd = String::from_utf8_lossy(c.as_ref());
3515 out.push_str(&cd);
3516 out.push_str("]]>");
3517 }
3518 Ok(Event::Eof) => return Ok(out),
3519 Err(e) => return Err(crate::Error::Xml(format!("collect: {e}"))),
3520 _ => {}
3521 }
3522 buf.clear();
3523 }
3524}
3525
3526pub fn collect_full_element_skipping<R: std::io::BufRead>(
3529 rd: &mut Reader<R>,
3530 _start: quick_xml::events::BytesStart<'static>,
3531) {
3532 let mut depth = 1i32;
3533 let mut buf = Vec::new();
3534 loop {
3535 match rd.read_event_into(&mut buf) {
3536 Ok(Event::Start(_)) => {
3537 depth += 1;
3538 }
3539 Ok(Event::End(_)) => {
3540 depth -= 1;
3541 if depth == 0 {
3542 return;
3543 }
3544 }
3545 Ok(Event::Eof) => return,
3546 _ => {}
3547 }
3548 buf.clear();
3549 }
3550}
3551
3552fn write_event_start(out: &mut String, e: &quick_xml::events::BytesStart<'_>) {
3553 out.push('<');
3554 let name = e.name();
3556 out.push_str(&String::from_utf8_lossy(name.as_ref()));
3557 for a in e.attributes().flatten() {
3558 out.push(' ');
3559 out.push_str(&String::from_utf8_lossy(a.key.as_ref()));
3560 out.push_str("=\"");
3561 out.push_str(
3562 &a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3563 .unwrap_or_default(),
3564 );
3565 out.push('"');
3566 }
3567 out.push('>');
3568}
3569
3570fn write_event_empty(out: &mut String, e: &quick_xml::events::BytesStart<'_>) {
3571 out.push('<');
3572 let name = e.name();
3573 out.push_str(&String::from_utf8_lossy(name.as_ref()));
3574 for a in e.attributes().flatten() {
3575 out.push(' ');
3576 out.push_str(&String::from_utf8_lossy(a.key.as_ref()));
3577 out.push_str("=\"");
3578 out.push_str(
3579 &a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3580 .unwrap_or_default(),
3581 );
3582 out.push('"');
3583 }
3584 out.push_str("/>");
3585}
3586
3587fn write_event_end(out: &mut String, e: &quick_xml::events::BytesEnd<'_>) {
3588 out.push_str("</");
3589 let name = e.name();
3590 out.push_str(&String::from_utf8_lossy(name.as_ref()));
3591 out.push('>');
3592}
3593
3594pub fn parse_run_properties(xml: &str) -> crate::Result<RunProperties> {
3596 let mut rp = RunProperties::default();
3597 let mut rd = Reader::from_str(xml);
3598 rd.config_mut().trim_text(true);
3599 let mut buf = Vec::new();
3600 loop {
3601 match rd.read_event_into(&mut buf) {
3602 Ok(Event::Start(e)) => {
3603 let name = e.name();
3604 let local = local_name(name.as_ref());
3605 if local == b"rPr" || local == b"endParaRPr" {
3606 rp = RunProperties::from_bytes_start(&e);
3612 } else if local == b"solidFill" {
3613 rp.color = parse_solid_fill(&mut rd, e.into_owned())?;
3614 } else if local == b"highlight" {
3615 let color = parse_solid_fill(&mut rd, e.into_owned())?;
3616 rp.highlight = Some(color);
3617 } else if local == b"latin" {
3618 rp.latin_font = Some(collect_attr_value(&mut rd, "typeface")?);
3619 } else if local == b"ea" {
3620 rp.eastasia_font = Some(collect_attr_value(&mut rd, "typeface")?);
3621 } else if local == b"cs" {
3622 rp.cs_font = Some(collect_attr_value(&mut rd, "typeface")?);
3623 } else if local == b"hlinkClick" {
3624 let hl = parse_hyperlink_attrs(&e);
3626 collect_full_element_skipping(&mut rd, e.into_owned());
3627 rp.hlink_click = Some(hl);
3628 } else if local == b"hlinkHover" {
3629 let hl = parse_hyperlink_attrs(&e);
3631 collect_full_element_skipping(&mut rd, e.into_owned());
3632 rp.hlink_hover = Some(hl);
3633 } else {
3634 let _ = collect_full_element(&mut rd, e.into_owned());
3635 }
3636 }
3637 Ok(Event::Empty(e)) => {
3638 let name = e.name();
3639 let local = local_name(name.as_ref());
3640 if local == b"latin" {
3641 for a in e.attributes().flatten() {
3642 if a.key.as_ref() == b"typeface" {
3643 rp.latin_font = Some(
3644 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3645 .unwrap_or_default()
3646 .to_string(),
3647 );
3648 }
3649 }
3650 } else if local == b"ea" {
3651 for a in e.attributes().flatten() {
3652 if a.key.as_ref() == b"typeface" {
3653 rp.eastasia_font = Some(
3654 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3655 .unwrap_or_default()
3656 .to_string(),
3657 );
3658 }
3659 }
3660 } else if local == b"cs" {
3661 for a in e.attributes().flatten() {
3662 if a.key.as_ref() == b"typeface" {
3663 rp.cs_font = Some(
3664 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3665 .unwrap_or_default()
3666 .to_string(),
3667 );
3668 }
3669 }
3670 } else if local == b"hlinkClick" {
3671 rp.hlink_click = Some(parse_hyperlink_attrs(&e));
3673 } else if local == b"hlinkHover" {
3674 rp.hlink_hover = Some(parse_hyperlink_attrs(&e));
3676 }
3677 }
3678 Ok(Event::Eof) => break,
3679 Err(e) => return Err(crate::Error::Xml(format!("rPr parse: {e}"))),
3680 _ => {}
3681 }
3682 buf.clear();
3683 }
3684 Ok(rp)
3685}
3686
3687fn parse_hyperlink_attrs(e: &quick_xml::events::BytesStart<'_>) -> Hyperlink {
3696 let mut hl = Hyperlink::default();
3697 let mut has_any = false;
3698 for a in e.attributes().flatten() {
3699 let key = a.key.as_ref();
3700 let val = a
3701 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3702 .unwrap_or_default()
3703 .to_string();
3704 if key == b"r:id" || key.ends_with(b":id") {
3706 hl.rid = Some(val);
3707 has_any = true;
3708 } else if key == b"tooltip" {
3709 hl.tooltip = Some(val);
3710 has_any = true;
3711 } else if key == b"action" {
3712 hl.action = Some(val);
3713 has_any = true;
3714 }
3715 }
3716 if !has_any {
3717 hl.invalid = true;
3718 }
3719 hl
3720}
3721
3722fn parse_tile_attrs(e: &quick_xml::events::BytesStart<'_>) -> crate::oxml::sppr::BlipFillMode {
3730 let mut tx = None;
3731 let mut ty = None;
3732 let mut sx = None;
3733 let mut sy = None;
3734 let mut flip = None;
3735 let mut algn = None;
3736 for a in e.attributes().flatten() {
3737 let key = a.key.as_ref();
3738 let val = a
3739 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3740 .unwrap_or_default()
3741 .to_string();
3742 match key {
3743 b"tx" => tx = val.parse().ok(),
3744 b"ty" => ty = val.parse().ok(),
3745 b"sx" => sx = val.parse().ok(),
3746 b"sy" => sy = val.parse().ok(),
3747 b"flip" => flip = Some(val),
3748 b"algn" => algn = Some(val),
3749 _ => {}
3750 }
3751 }
3752 crate::oxml::sppr::BlipFillMode::Tile {
3753 tx,
3754 ty,
3755 sx,
3756 sy,
3757 flip,
3758 algn,
3759 }
3760}
3761
3762fn parse_sp_locks_attrs(e: &quick_xml::events::BytesStart<'_>) -> ShapeLocks {
3766 let mut locks = ShapeLocks::default();
3767 for a in e.attributes().flatten() {
3768 let key = a.key.as_ref();
3769 let v = a
3770 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3771 .unwrap_or_default();
3772 let is_true = v == "1" || v == "true";
3773 match key {
3774 b"noGrp" => locks.no_grp = is_true,
3775 b"noDrilldown" => locks.no_drilldown = is_true,
3776 b"noSelect" => locks.no_select = is_true,
3777 b"noChangeAspect" => locks.no_change_aspect = is_true,
3778 b"noMove" => locks.no_move = is_true,
3779 b"noResize" => locks.no_resize = is_true,
3780 b"noRot" => locks.no_rot = is_true,
3781 b"noEditPoints" => locks.no_edit_points = is_true,
3782 b"noAdjustHandles" => locks.no_adjust_handles = is_true,
3783 b"noChangeArrowheads" => locks.no_change_arrowheads = is_true,
3784 b"noChangeShapeType" => locks.no_change_shape_type = is_true,
3785 b"noCrop" => locks.no_crop = is_true,
3786 _ => {}
3787 }
3788 }
3789 locks
3790}
3791
3792pub fn parse_shape_style(xml: &str) -> crate::Result<ShapeStyle> {
3800 let mut style = ShapeStyle::default();
3801 let mut rd = Reader::from_str(xml);
3802 rd.config_mut().trim_text(true);
3803 let mut buf = Vec::new();
3804 loop {
3805 match rd.read_event_into(&mut buf) {
3806 Ok(Event::Start(e)) => {
3807 let name = e.name();
3808 let local = local_name(name.as_ref());
3809 match local {
3810 b"lnRef" => {
3811 style.line_ref = Some(parse_style_ref(&mut rd, &e)?);
3812 }
3813 b"fillRef" => {
3814 style.fill_ref = Some(parse_style_ref(&mut rd, &e)?);
3815 }
3816 b"effectRef" => {
3817 style.effect_ref = Some(parse_style_ref(&mut rd, &e)?);
3818 }
3819 b"fontRef" => {
3820 style.font_ref = Some(parse_style_ref(&mut rd, &e)?);
3821 }
3822 b"style" => {
3823 }
3825 _ => {
3826 let _ = collect_full_element(&mut rd, e.into_owned());
3827 }
3828 }
3829 }
3830 Ok(Event::Empty(e)) => {
3831 let name = e.name();
3832 let local = local_name(name.as_ref());
3833 match local {
3834 b"lnRef" => {
3835 style.line_ref = Some(parse_style_ref_empty(&e));
3836 }
3837 b"fillRef" => {
3838 style.fill_ref = Some(parse_style_ref_empty(&e));
3839 }
3840 b"effectRef" => {
3841 style.effect_ref = Some(parse_style_ref_empty(&e));
3842 }
3843 b"fontRef" => {
3844 style.font_ref = Some(parse_style_ref_empty(&e));
3845 }
3846 _ => {}
3847 }
3848 }
3849 Ok(Event::Eof) => break,
3850 Err(e) => return Err(crate::Error::Xml(format!("style parse: {e}"))),
3851 _ => {}
3852 }
3853 buf.clear();
3854 }
3855 Ok(style)
3856}
3857
3858fn parse_style_ref<R: std::io::BufRead>(
3862 rd: &mut Reader<R>,
3863 e: &quick_xml::events::BytesStart<'_>,
3864) -> crate::Result<StyleRef> {
3865 let mut sr = parse_style_ref_empty(e);
3866 let mut buf = Vec::new();
3868 loop {
3869 match rd.read_event_into(&mut buf) {
3870 Ok(Event::Empty(e2)) => {
3871 let name2 = e2.name();
3872 let local = local_name(name2.as_ref());
3873 if local == b"schemeClr" {
3874 for a in e2.attributes().flatten() {
3875 if a.key.as_ref() == b"val" {
3876 sr.scheme_color = Some(
3877 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3878 .unwrap_or_default()
3879 .to_string(),
3880 );
3881 }
3882 }
3883 }
3884 }
3885 Ok(Event::End(_)) => return Ok(sr),
3886 Ok(Event::Eof) => return Ok(sr),
3887 _ => {}
3888 }
3889 buf.clear();
3890 }
3891}
3892
3893fn parse_style_ref_empty(e: &quick_xml::events::BytesStart<'_>) -> StyleRef {
3895 let mut sr = StyleRef::default();
3896 for a in e.attributes().flatten() {
3897 if a.key.as_ref() == b"idx" {
3898 sr.idx = Some(
3899 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
3900 .unwrap_or_default()
3901 .to_string(),
3902 );
3903 }
3904 }
3905 sr
3906}
3907
3908fn collect_attr_value<R: std::io::BufRead>(rd: &mut Reader<R>, key: &str) -> crate::Result<String> {
3909 let mut buf = Vec::new();
3915 let mut val = String::new();
3916 let key_b = key.as_bytes();
3917 loop {
3918 match rd.read_event_into(&mut buf) {
3919 Ok(Event::Empty(e)) => {
3920 for a in e.attributes().flatten() {
3921 if a.key.as_ref() == key_b {
3922 val = a
3923 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3924 .unwrap_or_default()
3925 .to_string();
3926 }
3927 }
3928 }
3929 Ok(Event::End(_)) => return Ok(val),
3930 Ok(Event::Eof) => return Ok(val),
3931 _ => {}
3932 }
3933 buf.clear();
3934 }
3935}
3936
3937fn local_name(name: &[u8]) -> &[u8] {
3942 match name.iter().position(|&c| c == b':') {
3943 Some(i) => &name[i + 1..],
3944 None => name,
3945 }
3946}
3947
3948fn collect_inner_text_only<R: std::io::BufRead>(rd: &mut Reader<R>) -> crate::Result<String> {
3950 let mut out = String::new();
3951 let mut buf = Vec::new();
3952 loop {
3953 match rd.read_event_into(&mut buf) {
3954 Ok(Event::Text(t)) => {
3955 out.push_str(&t.decode().unwrap_or_default());
3956 }
3957 Ok(Event::CData(c)) => {
3958 out.push_str(&String::from_utf8_lossy(c.as_ref()));
3959 }
3960 Ok(Event::End(_)) => return Ok(out),
3961 Ok(Event::Eof) => return Ok(out),
3962 _ => {}
3963 }
3964 buf.clear();
3965 }
3966}
3967
3968impl RunProperties {
3969 pub fn from_bytes_start(e: &quick_xml::events::BytesStart<'_>) -> Self {
3971 let mut rp = RunProperties::default();
3972 for a in e.attributes().flatten() {
3973 let v = a
3974 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3975 .unwrap_or_default();
3976 match a.key.as_ref() {
3977 b"sz" => {
3978 if let Ok(p) = v.parse::<i32>() {
3979 rp.size = Some(Pt(p as f64 / 100.0));
3980 }
3981 }
3982 b"b" => {
3983 if v == "1" || v == "true" {
3984 rp.bold = true;
3985 }
3986 }
3987 b"i" => {
3988 if v == "1" || v == "true" {
3989 rp.italic = true;
3990 }
3991 }
3992 b"u" => {
3993 if let Ok(u) = v.parse::<Underline>() {
3994 rp.underline = Some(u);
3995 }
3996 }
3997 b"strike" => {
3998 if v == "sngStrike" {
3999 rp.strike = true;
4000 }
4001 if v == "dblStrike" {
4002 rp.strike_dbl = true;
4003 }
4004 }
4005 b"baseline" => {
4006 if let Ok(v) = v.parse::<i32>() {
4007 rp.baseline = Some(v);
4008 }
4009 }
4010 b"kern" => {
4011 if let Ok(v) = v.parse::<i32>() {
4012 rp.kerning = Some(v);
4013 }
4014 }
4015 b"spc" => {
4016 if let Ok(v) = v.parse::<i32>() {
4017 rp.spc = Some(v);
4018 }
4019 }
4020 b"lang" => {
4021 rp.lang = Some(v.to_string());
4022 }
4023 _ => {}
4024 }
4025 }
4026 rp
4027 }
4028}
4029
4030pub fn parse_pic(xml: &str) -> crate::Result<Pic> {
4035 let mut pic = Pic::default();
4036 let mut rd = Reader::from_str(xml);
4037 rd.config_mut().trim_text(true);
4038 let mut buf = Vec::new();
4039 let mut in_blipfill = false;
4040 let mut blipfill_depth: i32 = 0;
4041 loop {
4042 match rd.read_event_into(&mut buf) {
4043 Ok(Event::Start(e)) => {
4044 let name = e.name();
4045 let local = local_name(name.as_ref());
4046 if local == b"pic" {
4047 } else if local == b"nvPicPr" {
4050 let mut nv_depth = 1i32;
4053 loop {
4054 match rd.read_event_into(&mut buf) {
4055 Ok(Event::Start(_)) => nv_depth += 1,
4056 Ok(Event::End(_)) => {
4057 nv_depth -= 1;
4058 if nv_depth == 0 {
4059 break;
4060 }
4061 }
4062 Ok(Event::Empty(e2)) => {
4063 let name2 = e2.name();
4064 let local2 = local_name(name2.as_ref());
4065 if local2 == b"cNvPr" {
4066 for a in e2.attributes().flatten() {
4067 match a.key.as_ref() {
4068 b"id" => {
4069 if let Ok(v) = a
4070 .normalized_value(
4071 quick_xml::XmlVersion::Implicit1_0,
4072 )
4073 .unwrap_or_default()
4074 .parse::<u32>()
4075 {
4076 pic.id = v;
4077 }
4078 }
4079 b"name" => {
4080 pic.name = a
4081 .normalized_value(
4082 quick_xml::XmlVersion::Implicit1_0,
4083 )
4084 .unwrap_or_default()
4085 .to_string();
4086 }
4087 _ => {}
4088 }
4089 }
4090 }
4091 }
4092 Ok(Event::Eof) => break,
4093 Err(_) => break,
4094 _ => {}
4095 }
4096 buf.clear();
4097 }
4098 } else if local == b"blipFill" {
4099 in_blipfill = true;
4100 blipfill_depth = 1;
4101 } else if local == b"spPr" {
4104 let inner = collect_full_element(&mut rd, e.into_owned())?;
4105 pic.properties = parse_sppr(&inner)?;
4106 } else if in_blipfill && local == b"blip" {
4107 for a in e.attributes().flatten() {
4110 let key = a.key.as_ref();
4111 if key == b"r:embed" || key.ends_with(b":embed") {
4113 pic.rid = a
4114 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4115 .unwrap_or_default()
4116 .to_string();
4117 }
4118 }
4119 } else if in_blipfill && local == b"srcRect" {
4120 let (mut l, mut t, mut r, mut b) = (None, None, None, None);
4122 for a in e.attributes().flatten() {
4123 let v = a
4124 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4125 .unwrap_or_default();
4126 match a.key.as_ref() {
4127 b"l" => l = v.parse().ok(),
4128 b"t" => t = v.parse().ok(),
4129 b"r" => r = v.parse().ok(),
4130 b"b" => b = v.parse().ok(),
4131 _ => {}
4132 }
4133 }
4134 if let (Some(ll), Some(tt), Some(rr), Some(bb)) = (l, t, r, b) {
4135 pic.src_rect = Some((ll, tt, rr, bb));
4136 }
4137 } else if in_blipfill && local == b"stretch" {
4138 let _ = collect_full_element(&mut rd, e.into_owned())?;
4140 pic.fill_mode = crate::oxml::sppr::BlipFillMode::Stretch;
4141 } else if in_blipfill && local == b"tile" {
4142 let mode = parse_tile_attrs(&e);
4145 let _ = collect_full_element(&mut rd, e.into_owned())?;
4146 pic.fill_mode = mode;
4147 } else {
4148 let _ = collect_full_element(&mut rd, e.into_owned());
4149 }
4150 }
4151 Ok(Event::Empty(e)) => {
4152 let name = e.name();
4153 let local = local_name(name.as_ref());
4154 if in_blipfill && local == b"blip" {
4155 for a in e.attributes().flatten() {
4157 let key = a.key.as_ref();
4158 if key == b"r:embed" || key.ends_with(b":embed") {
4159 pic.rid = a
4160 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4161 .unwrap_or_default()
4162 .to_string();
4163 }
4164 }
4165 } else if in_blipfill && local == b"srcRect" {
4166 let (mut l, mut t, mut r, mut b) = (None, None, None, None);
4167 for a in e.attributes().flatten() {
4168 let v = a
4169 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4170 .unwrap_or_default();
4171 match a.key.as_ref() {
4172 b"l" => l = v.parse().ok(),
4173 b"t" => t = v.parse().ok(),
4174 b"r" => r = v.parse().ok(),
4175 b"b" => b = v.parse().ok(),
4176 _ => {}
4177 }
4178 }
4179 if let (Some(ll), Some(tt), Some(rr), Some(bb)) = (l, t, r, b) {
4180 pic.src_rect = Some((ll, tt, rr, bb));
4181 }
4182 } else if in_blipfill && local == b"stretch" {
4183 pic.fill_mode = crate::oxml::sppr::BlipFillMode::Stretch;
4185 } else if in_blipfill && local == b"tile" {
4186 pic.fill_mode = parse_tile_attrs(&e);
4188 }
4189 }
4190 Ok(Event::End(e)) => {
4191 let name = e.name();
4192 let local = local_name(name.as_ref());
4193 if in_blipfill && local == b"blipFill" {
4197 blipfill_depth -= 1;
4198 if blipfill_depth == 0 {
4199 in_blipfill = false;
4200 }
4201 }
4202 }
4203 Ok(Event::Eof) => break,
4204 Err(e) => return Err(crate::Error::Xml(format!("pic parse: {e}"))),
4205 _ => {}
4206 }
4207 buf.clear();
4208 }
4209 Ok(pic)
4210}
4211
4212pub fn parse_cxn_sp(xml: &str) -> crate::Result<Connector> {
4237 let mut cxn = Connector::default();
4238 let mut rd = Reader::from_str(xml);
4239 rd.config_mut().trim_text(true);
4240 let mut buf = Vec::new();
4241 loop {
4242 match rd.read_event_into(&mut buf) {
4243 Ok(Event::Start(e)) => {
4244 let name = e.name();
4245 let local = local_name(name.as_ref());
4246 if local == b"cxnSp" {
4247 } else if local == b"nvCxnSpPr" {
4249 let mut nv_depth = 1i32;
4251 loop {
4252 match rd.read_event_into(&mut buf) {
4253 Ok(Event::Start(_)) => nv_depth += 1,
4254 Ok(Event::End(_)) => {
4255 nv_depth -= 1;
4256 if nv_depth == 0 {
4257 break;
4258 }
4259 }
4260 Ok(Event::Empty(e2)) => {
4261 let name2 = e2.name();
4262 let local2 = local_name(name2.as_ref());
4263 if local2 == b"cNvPr" {
4264 for a in e2.attributes().flatten() {
4265 match a.key.as_ref() {
4266 b"id" => {
4267 if let Ok(v) = a
4268 .normalized_value(
4269 quick_xml::XmlVersion::Implicit1_0,
4270 )
4271 .unwrap_or_default()
4272 .parse::<u32>()
4273 {
4274 cxn.id = v;
4275 }
4276 }
4277 b"name" => {
4278 cxn.name = a
4279 .normalized_value(
4280 quick_xml::XmlVersion::Implicit1_0,
4281 )
4282 .unwrap_or_default()
4283 .to_string();
4284 }
4285 _ => {}
4286 }
4287 }
4288 } else if local2 == b"stCxn" {
4289 let mut sid: Option<u32> = None;
4291 let mut idx: Option<u32> = None;
4292 for a in e2.attributes().flatten() {
4293 match a.key.as_ref() {
4294 b"id" => {
4295 sid = a
4296 .normalized_value(
4297 quick_xml::XmlVersion::Implicit1_0,
4298 )
4299 .unwrap_or_default()
4300 .parse()
4301 .ok();
4302 }
4303 b"idx" => {
4304 idx = a
4305 .normalized_value(
4306 quick_xml::XmlVersion::Implicit1_0,
4307 )
4308 .unwrap_or_default()
4309 .parse()
4310 .ok();
4311 }
4312 _ => {}
4313 }
4314 }
4315 if let (Some(s), Some(i)) = (sid, idx) {
4316 cxn.st_cxn = Some((s, i));
4317 }
4318 } else if local2 == b"endCxn" {
4319 let mut sid: Option<u32> = None;
4321 let mut idx: Option<u32> = None;
4322 for a in e2.attributes().flatten() {
4323 match a.key.as_ref() {
4324 b"id" => {
4325 sid = a
4326 .normalized_value(
4327 quick_xml::XmlVersion::Implicit1_0,
4328 )
4329 .unwrap_or_default()
4330 .parse()
4331 .ok();
4332 }
4333 b"idx" => {
4334 idx = a
4335 .normalized_value(
4336 quick_xml::XmlVersion::Implicit1_0,
4337 )
4338 .unwrap_or_default()
4339 .parse()
4340 .ok();
4341 }
4342 _ => {}
4343 }
4344 }
4345 if let (Some(s), Some(i)) = (sid, idx) {
4346 cxn.end_cxn = Some((s, i));
4347 }
4348 }
4349 }
4350 Ok(Event::Eof) => break,
4351 Err(_) => break,
4352 _ => {}
4353 }
4354 buf.clear();
4355 }
4356 } else if local == b"spPr" {
4357 let inner = collect_full_element(&mut rd, e.into_owned())?;
4358 cxn.properties = parse_sppr(&inner)?;
4359 } else if local == b"style" {
4360 let inner = collect_full_element(&mut rd, e.into_owned())?;
4362 cxn.style = Some(parse_shape_style(&inner)?);
4363 } else {
4364 let _ = collect_full_element(&mut rd, e.into_owned());
4366 }
4367 }
4368 Ok(Event::End(e)) => {
4369 if local_name(e.name().as_ref()) == b"cxnSp" {
4370 break;
4371 }
4372 }
4373 Ok(Event::Eof) => break,
4374 Err(e) => return Err(crate::Error::Xml(format!("cxnSp parse: {e}"))),
4375 _ => {}
4376 }
4377 buf.clear();
4378 }
4379 Ok(cxn)
4380}
4381
4382pub fn parse_grp_sp(xml: &str) -> crate::Result<Group> {
4411 let mut grp = Group::default();
4412 let mut rd = Reader::from_str(xml);
4413 rd.config_mut().trim_text(true);
4414 let mut buf = Vec::new();
4415 let mut child_sp_bufs: Vec<String> = Vec::new();
4417 let mut child_pic_bufs: Vec<String> = Vec::new();
4418 let mut child_cxn_bufs: Vec<String> = Vec::new();
4419 let mut child_grp_bufs: Vec<String> = Vec::new();
4420 let mut child_gfx_bufs: Vec<String> = Vec::new();
4421 let mut entered_root = false;
4423
4424 loop {
4425 match rd.read_event_into(&mut buf) {
4426 Ok(Event::Start(e)) => {
4427 let name = e.name();
4428 let local = local_name(name.as_ref());
4429 if local == b"grpSp" {
4430 if !entered_root {
4431 entered_root = true;
4433 } else {
4434 let inner = collect_full_element(&mut rd, e.into_owned())?;
4436 child_grp_bufs.push(inner);
4437 }
4438 } else if local == b"nvGrpSpPr" {
4439 let mut nv_depth = 1i32;
4441 loop {
4442 match rd.read_event_into(&mut buf) {
4443 Ok(Event::Start(_)) => nv_depth += 1,
4444 Ok(Event::End(_)) => {
4445 nv_depth -= 1;
4446 if nv_depth == 0 {
4447 break;
4448 }
4449 }
4450 Ok(Event::Empty(e2)) => {
4451 let name2 = e2.name();
4452 let local2 = local_name(name2.as_ref());
4453 if local2 == b"cNvPr" {
4454 for a in e2.attributes().flatten() {
4455 match a.key.as_ref() {
4456 b"id" => {
4457 if let Ok(v) = a
4458 .normalized_value(
4459 quick_xml::XmlVersion::Implicit1_0,
4460 )
4461 .unwrap_or_default()
4462 .parse::<u32>()
4463 {
4464 grp.id = v;
4465 }
4466 }
4467 b"name" => {
4468 grp.name = a
4469 .normalized_value(
4470 quick_xml::XmlVersion::Implicit1_0,
4471 )
4472 .unwrap_or_default()
4473 .to_string();
4474 }
4475 _ => {}
4476 }
4477 }
4478 }
4479 }
4480 Ok(Event::Eof) => break,
4481 Err(_) => break,
4482 _ => {}
4483 }
4484 buf.clear();
4485 }
4486 } else if local == b"grpSpPr" {
4487 parse_grp_sppr_into(&mut rd, &mut grp, e.into_owned())?;
4489 } else if local == b"sp" {
4490 let inner = collect_full_element(&mut rd, e.into_owned())?;
4491 child_sp_bufs.push(inner);
4492 } else if local == b"pic" {
4493 let inner = collect_full_element(&mut rd, e.into_owned())?;
4494 child_pic_bufs.push(inner);
4495 } else if local == b"cxnSp" {
4496 let inner = collect_full_element(&mut rd, e.into_owned())?;
4497 child_cxn_bufs.push(inner);
4498 } else if local == b"graphicFrame" {
4499 let inner = collect_full_element(&mut rd, e.into_owned())?;
4500 child_gfx_bufs.push(inner);
4501 } else if local == b"style" {
4502 let inner = collect_full_element(&mut rd, e.into_owned())?;
4504 grp.style = Some(parse_shape_style(&inner)?);
4505 } else {
4506 let _ = collect_full_element(&mut rd, e.into_owned());
4508 }
4509 }
4510 Ok(Event::End(e)) => {
4511 if local_name(e.name().as_ref()) == b"grpSp" {
4512 break;
4513 }
4514 }
4515 Ok(Event::Eof) => break,
4516 Err(e) => return Err(crate::Error::Xml(format!("grpSp parse: {e}"))),
4517 _ => {}
4518 }
4519 buf.clear();
4520 }
4521 for s in child_sp_bufs {
4523 if let Ok(sp) = parse_sp(&s) {
4524 grp.children.push(GroupChild::Sp(sp));
4525 }
4526 }
4527 for p in child_pic_bufs {
4528 if let Ok(pic) = parse_pic(&p) {
4529 grp.children.push(GroupChild::Pic(pic));
4530 }
4531 }
4532 for c in child_cxn_bufs {
4533 if let Ok(cxn) = parse_cxn_sp(&c) {
4534 grp.children.push(GroupChild::CxnSp(cxn));
4535 }
4536 }
4537 for g in child_grp_bufs {
4538 if let Ok(sub) = parse_grp_sp(&g) {
4539 grp.children.push(GroupChild::Group(Box::new(sub)));
4540 }
4541 }
4542 for f in child_gfx_bufs {
4543 if let Ok(frame) = parse_graphic_frame(&f) {
4544 grp.children.push(GroupChild::GraphicFrame(frame));
4545 }
4546 }
4547 Ok(grp)
4548}
4549
4550fn parse_grp_sppr_into<R: std::io::BufRead>(
4552 rd: &mut Reader<R>,
4553 grp: &mut Group,
4554 _start: quick_xml::events::BytesStart<'static>,
4555) -> crate::Result<()> {
4556 let mut buf = Vec::new();
4557 loop {
4558 match rd.read_event_into(&mut buf) {
4559 Ok(Event::Empty(e)) => {
4560 let name = e.name();
4561 let local = local_name(name.as_ref());
4562 if local == b"off" {
4563 let (mut x, mut y) = (None, None);
4564 for a in e.attributes().flatten() {
4565 match a.key.as_ref() {
4566 b"x" => {
4567 x = a
4568 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4569 .unwrap_or_default()
4570 .parse()
4571 .ok()
4572 }
4573 b"y" => {
4574 y = a
4575 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4576 .unwrap_or_default()
4577 .parse()
4578 .ok()
4579 }
4580 _ => {}
4581 }
4582 }
4583 if let (Some(x), Some(y)) = (x, y) {
4584 grp.off = (Emu(x), Emu(y));
4585 }
4586 } else if local == b"ext" {
4587 let (mut cx, mut cy) = (None, None);
4588 for a in e.attributes().flatten() {
4589 match a.key.as_ref() {
4590 b"cx" => {
4591 cx = a
4592 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4593 .unwrap_or_default()
4594 .parse()
4595 .ok()
4596 }
4597 b"cy" => {
4598 cy = a
4599 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4600 .unwrap_or_default()
4601 .parse()
4602 .ok()
4603 }
4604 _ => {}
4605 }
4606 }
4607 if let (Some(cx), Some(cy)) = (cx, cy) {
4608 grp.ext = (Emu(cx), Emu(cy));
4609 }
4610 }
4611 }
4613 Ok(Event::End(e)) => {
4614 if local_name(e.name().as_ref()) == b"grpSpPr" {
4615 return Ok(());
4616 }
4617 }
4618 Ok(Event::Eof) => return Ok(()),
4619 _ => {}
4620 }
4621 buf.clear();
4622 }
4623}
4624
4625pub fn parse_graphic_frame(xml: &str) -> crate::Result<GraphicFrame> {
4654 let mut frame = GraphicFrame::default();
4655 let mut rd = Reader::from_str(xml);
4656 rd.config_mut().trim_text(true);
4657 let mut buf = Vec::new();
4658 loop {
4659 match rd.read_event_into(&mut buf) {
4660 Ok(Event::Start(e)) => {
4661 let name = e.name();
4662 let local = local_name(name.as_ref());
4663 if local == b"graphicFrame" {
4664 } else if local == b"nvGraphicFramePr" {
4666 let mut nv_depth = 1i32;
4668 loop {
4669 match rd.read_event_into(&mut buf) {
4670 Ok(Event::Start(_)) => nv_depth += 1,
4671 Ok(Event::End(_)) => {
4672 nv_depth -= 1;
4673 if nv_depth == 0 {
4674 break;
4675 }
4676 }
4677 Ok(Event::Empty(e2)) => {
4678 let name2 = e2.name();
4679 let local2 = local_name(name2.as_ref());
4680 if local2 == b"cNvPr" {
4681 for a in e2.attributes().flatten() {
4682 match a.key.as_ref() {
4683 b"id" => {
4684 if let Ok(v) = a
4685 .normalized_value(
4686 quick_xml::XmlVersion::Implicit1_0,
4687 )
4688 .unwrap_or_default()
4689 .parse::<u32>()
4690 {
4691 frame.id = v;
4692 }
4693 }
4694 b"name" => {
4695 frame.name = a
4696 .normalized_value(
4697 quick_xml::XmlVersion::Implicit1_0,
4698 )
4699 .unwrap_or_default()
4700 .to_string();
4701 }
4702 _ => {}
4703 }
4704 }
4705 }
4706 }
4707 Ok(Event::Eof) => break,
4708 Err(_) => break,
4709 _ => {}
4710 }
4711 buf.clear();
4712 }
4713 } else if local == b"xfrm" {
4714 for a in e.attributes().flatten() {
4716 match a.key.as_ref() {
4717 b"rot" => {
4718 if let Ok(v) = a
4719 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4720 .unwrap_or_default()
4721 .parse::<i32>()
4722 {
4723 frame.properties.xfrm.rot = Some(v);
4724 }
4725 }
4726 b"flipH" => {
4727 let v = a
4728 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4729 .unwrap_or_default();
4730 if v == "1" {
4731 frame.properties.xfrm.flip_h = true;
4732 }
4733 }
4734 b"flipV" => {
4735 let v = a
4736 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4737 .unwrap_or_default();
4738 if v == "1" {
4739 frame.properties.xfrm.flip_v = true;
4740 }
4741 }
4742 _ => {}
4743 }
4744 }
4745 parse_xfrm_into(&mut rd, &mut frame.properties);
4747 } else if local == b"graphic" {
4748 parse_graphic_into(&mut rd, &mut frame)?;
4750 } else {
4751 let _ = collect_full_element(&mut rd, e.into_owned());
4753 }
4754 }
4755 Ok(Event::End(e)) => {
4756 if local_name(e.name().as_ref()) == b"graphicFrame" {
4757 break;
4758 }
4759 }
4760 Ok(Event::Eof) => break,
4761 Err(e) => return Err(crate::Error::Xml(format!("graphicFrame parse: {e}"))),
4762 _ => {}
4763 }
4764 buf.clear();
4765 }
4766 Ok(frame)
4767}
4768
4769fn parse_graphic_into<R: std::io::BufRead>(
4771 rd: &mut Reader<R>,
4772 frame: &mut GraphicFrame,
4773) -> crate::Result<()> {
4774 let mut buf = Vec::new();
4775 loop {
4776 match rd.read_event_into(&mut buf) {
4777 Ok(Event::Start(e)) => {
4778 let name = e.name();
4779 let local = local_name(name.as_ref());
4780 if local == b"graphicData" {
4781 let mut uri = String::new();
4783 for a in e.attributes().flatten() {
4784 if a.key.as_ref() == b"uri" {
4785 uri = a
4786 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4787 .unwrap_or_default()
4788 .to_string();
4789 }
4790 }
4791 if uri == "http://schemas.openxmlformats.org/drawingml/2006/table" {
4793 loop {
4795 match rd.read_event_into(&mut buf) {
4796 Ok(Event::Start(e2)) => {
4797 if local_name(e2.name().as_ref()) == b"tbl" {
4798 let tbl_xml = collect_full_element(rd, e2.into_owned())?;
4799 if let Ok(tbl) = parse_table(&tbl_xml) {
4800 frame.graphic = crate::oxml::shape::Graphic::Table(tbl);
4801 }
4802 } else {
4803 let _ = collect_full_element(rd, e2.into_owned());
4804 }
4805 }
4806 Ok(Event::End(e2)) => {
4807 if local_name(e2.name().as_ref()) == b"graphicData" {
4808 break;
4809 }
4810 }
4811 Ok(Event::Eof) => break,
4812 _ => {}
4813 }
4814 buf.clear();
4815 }
4816 } else if uri == "http://schemas.openxmlformats.org/drawingml/2006/diagram" {
4817 let raw_xml = collect_full_element(rd, e.into_owned())?;
4821 let (dm_rid, lo_rid, qs_rid, cs_rid) = parse_smartart_rel_ids(&raw_xml);
4824 frame.graphic = crate::oxml::shape::Graphic::SmartArt(
4825 crate::oxml::shape::SmartArtRef {
4826 raw_xml,
4827 dm_rid,
4828 lo_rid,
4829 qs_rid,
4830 cs_rid,
4831 },
4832 );
4833 } else if uri == "http://schemas.openxmlformats.org/drawingml/2006/chart" {
4834 let raw_xml = collect_full_element(rd, e.into_owned())?;
4839 let rid = parse_chart_rid(&raw_xml);
4840 frame.graphic =
4841 crate::oxml::shape::Graphic::Chart(crate::oxml::chart::Chart::new(
4842 crate::oxml::chart::ChartType::Column,
4843 crate::oxml::chart::ChartData::default(),
4844 ));
4845 if let crate::oxml::shape::Graphic::Chart(c) = &mut frame.graphic {
4847 c.rid = rid;
4848 }
4849 } else {
4850 let _ = collect_full_element(rd, e.into_owned());
4852 }
4853 } else {
4854 let _ = collect_full_element(rd, e.into_owned());
4855 }
4856 }
4857 Ok(Event::End(e)) => {
4858 if local_name(e.name().as_ref()) == b"graphic" {
4859 return Ok(());
4860 }
4861 }
4862 Ok(Event::Eof) => return Ok(()),
4863 _ => {}
4864 }
4865 buf.clear();
4866 }
4867}
4868
4869fn parse_smartart_rel_ids(
4889 raw_xml: &str,
4890) -> (
4891 Option<String>,
4892 Option<String>,
4893 Option<String>,
4894 Option<String>,
4895) {
4896 fn find_attr(xml: &str, attr_name: &str) -> Option<String> {
4900 let pattern = format!("{}=\"", attr_name);
4902 let pos = xml.find(&pattern)?;
4903 let value_start = pos + pattern.len();
4904 let value_end = xml[value_start..].find('"')?;
4905 Some(xml[value_start..value_start + value_end].to_string())
4906 }
4907
4908 let dm = find_attr(raw_xml, "r:dm");
4909 let lo = find_attr(raw_xml, "r:lo");
4910 let qs = find_attr(raw_xml, "r:qs");
4911 let cs = find_attr(raw_xml, "r:cs");
4912 (dm, lo, qs, cs)
4913}
4914
4915fn parse_chart_rid(raw_xml: &str) -> String {
4935 let pattern = "r:id=\"";
4937 if let Some(pos) = raw_xml.find(pattern) {
4938 let value_start = pos + pattern.len();
4939 if let Some(end) = raw_xml[value_start..].find('"') {
4940 return raw_xml[value_start..value_start + end].to_string();
4941 }
4942 }
4943 String::new()
4944}
4945
4946fn parse_table(xml: &str) -> crate::Result<crate::oxml::table::Table> {
4968 use crate::oxml::table::{Col, Table};
4969
4970 let mut tbl = Table::default();
4971 let mut rd = Reader::from_str(xml);
4972 rd.config_mut().trim_text(true);
4973 let mut buf = Vec::new();
4974 let mut tr_bufs: Vec<String> = Vec::new();
4976 let mut grid_col_widths: Vec<Emu> = Vec::new();
4977
4978 loop {
4979 match rd.read_event_into(&mut buf) {
4980 Ok(Event::Start(e)) => {
4981 let name = e.name();
4982 let local = local_name(name.as_ref());
4983 if local == b"tbl" {
4984 } else if local == b"tblPr" {
4986 let inner = collect_full_element(&mut rd, e.into_owned())?;
4988 parse_tbl_pr_into(&inner, &mut tbl);
4989 } else if local == b"tblGrid" {
4990 loop {
4992 match rd.read_event_into(&mut buf) {
4993 Ok(Event::Empty(e2)) => {
4994 if local_name(e2.name().as_ref()) == b"gridCol" {
4995 let mut w: Option<i64> = None;
4996 for a in e2.attributes().flatten() {
4997 if a.key.as_ref() == b"w" {
4998 w = a
4999 .normalized_value(
5000 quick_xml::XmlVersion::Implicit1_0,
5001 )
5002 .unwrap_or_default()
5003 .parse()
5004 .ok();
5005 }
5006 }
5007 grid_col_widths.push(Emu(w.unwrap_or(0)));
5008 }
5009 }
5010 Ok(Event::End(e2)) => {
5011 if local_name(e2.name().as_ref()) == b"tblGrid" {
5012 break;
5013 }
5014 }
5015 Ok(Event::Eof) => break,
5016 _ => {}
5017 }
5018 buf.clear();
5019 }
5020 } else if local == b"tr" {
5021 let inner = collect_full_element(&mut rd, e.into_owned())?;
5022 tr_bufs.push(inner);
5023 } else {
5024 let _ = collect_full_element(&mut rd, e.into_owned());
5025 }
5026 }
5027 Ok(Event::Eof) => break,
5028 Err(e) => return Err(crate::Error::Xml(format!("tbl parse: {e}"))),
5029 _ => {}
5030 }
5031 buf.clear();
5032 }
5033
5034 tbl.cols = grid_col_widths.iter().map(|w| Col { width: *w }).collect();
5036
5037 for tr_xml in tr_bufs {
5039 if let Ok(row) = parse_table_row(&tr_xml) {
5040 tbl.rows.push(row);
5041 }
5042 }
5043
5044 Ok(tbl)
5045}
5046
5047fn parse_tbl_pr_into(xml: &str, tbl: &mut crate::oxml::table::Table) {
5051 use crate::oxml::table::TableStyle;
5052
5053 let look = &mut tbl.tbl_look;
5054 let mut rd = Reader::from_str(xml);
5055 let mut buf = Vec::new();
5056 loop {
5057 match rd.read_event_into(&mut buf) {
5058 Ok(Event::Empty(e)) => {
5059 if local_name(e.name().as_ref()) == b"tblLook" {
5060 for a in e.attributes().flatten() {
5061 let v = a
5062 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5063 .unwrap_or_default();
5064 match a.key.as_ref() {
5065 b"val" => look.val = v.to_string(),
5066 b"firstRow" => look.first_row = v == "1",
5067 b"lastRow" => look.last_row = v == "1",
5068 b"firstColumn" => look.first_column = v == "1",
5069 b"lastColumn" => look.last_column = v == "1",
5070 b"noHBand" => look.no_h_band = v == "1",
5071 b"noVBand" => look.no_v_band = v == "1",
5072 _ => {}
5073 }
5074 }
5075 }
5076 if local_name(e.name().as_ref()) == b"tableStyleId" {
5078 }
5080 }
5081 Ok(Event::Start(e)) => {
5082 if local_name(e.name().as_ref()) == b"tableStyleId" {
5083 let mut guid = String::new();
5085 loop {
5086 match rd.read_event_into(&mut buf) {
5087 Ok(Event::Text(t)) => {
5088 guid.push_str(&t.decode().unwrap_or_default());
5089 }
5090 Ok(Event::End(e2)) => {
5091 if local_name(e2.name().as_ref()) == b"tableStyleId" {
5092 break;
5093 }
5094 }
5095 Ok(Event::Eof) => break,
5096 _ => {}
5097 }
5098 buf.clear();
5099 }
5100 let trimmed = guid.trim();
5101 if !trimmed.is_empty() {
5102 tbl.table_style = Some(TableStyle::new(trimmed));
5103 }
5104 }
5105 }
5106 Ok(Event::Eof) => break,
5107 _ => {}
5108 }
5109 buf.clear();
5110 }
5111}
5112
5113fn parse_table_row(xml: &str) -> crate::Result<crate::oxml::table::Row> {
5115 use crate::oxml::table::Row;
5116
5117 let mut row = Row::default();
5118 let mut rd = Reader::from_str(xml);
5119 rd.config_mut().trim_text(true);
5120 let mut buf = Vec::new();
5121 let mut tc_bufs: Vec<String> = Vec::new();
5124
5125 loop {
5126 match rd.read_event_into(&mut buf) {
5127 Ok(Event::Start(e)) => {
5128 let name = e.name();
5129 let local = local_name(name.as_ref());
5130 if local == b"tr" {
5131 for a in e.attributes().flatten() {
5133 if a.key.as_ref() == b"h" {
5134 if let Ok(v) = a
5135 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5136 .unwrap_or_default()
5137 .parse::<i64>()
5138 {
5139 row.height = Emu(v);
5140 }
5141 }
5142 }
5143 } else if local == b"tc" {
5144 let inner = collect_full_element(&mut rd, e.into_owned())?;
5145 tc_bufs.push(inner);
5146 } else {
5147 let _ = collect_full_element(&mut rd, e.into_owned());
5148 }
5149 }
5150 Ok(Event::Empty(_e)) => {
5151 }
5153 Ok(Event::End(e)) => {
5154 if local_name(e.name().as_ref()) == b"tr" {
5155 break;
5156 }
5157 }
5158 Ok(Event::Eof) => break,
5159 Err(e) => return Err(crate::Error::Xml(format!("tr parse: {e}"))),
5160 _ => {}
5161 }
5162 buf.clear();
5163 }
5164
5165 for tc_xml in tc_bufs {
5167 if let Ok(cell) = parse_table_cell(&tc_xml) {
5168 row.cells.push(cell);
5169 }
5170 }
5171
5172 Ok(row)
5173}
5174
5175fn parse_table_cell(xml: &str) -> crate::Result<crate::oxml::table::Cell> {
5177 let mut cell = crate::oxml::table::Cell::default();
5178 let mut rd = Reader::from_str(xml);
5179 rd.config_mut().trim_text(true);
5180 let mut buf = Vec::new();
5181
5182 loop {
5183 match rd.read_event_into(&mut buf) {
5184 Ok(Event::Start(e)) => {
5185 let name = e.name();
5186 let local = local_name(name.as_ref());
5187 if local == b"tc" {
5188 for a in e.attributes().flatten() {
5190 let v = a
5191 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5192 .unwrap_or_default();
5193 match a.key.as_ref() {
5194 b"gridSpan" => {
5195 if let Ok(g) = v.parse::<u32>() {
5196 cell.grid_span = g;
5197 }
5198 }
5199 b"rowSpan" => {
5200 if let Ok(r) = v.parse::<u32>() {
5201 cell.row_span = r;
5202 }
5203 }
5204 b"hMerge" if v == "1" => {
5205 cell.h_merge = true;
5206 }
5207 b"vMerge" if v == "1" => {
5208 cell.v_merge = true;
5209 }
5210 _ => {}
5211 }
5212 }
5213 } else if local == b"txBody" {
5214 let inner = collect_full_element(&mut rd, e.into_owned())?;
5215 cell.text = parse_txbody(&inner)?;
5216 } else if local == b"tcPr" {
5217 let inner = collect_full_element(&mut rd, e.into_owned())?;
5219 parse_tc_pr_into(&inner, &mut cell);
5220 } else {
5221 let _ = collect_full_element(&mut rd, e.into_owned());
5222 }
5223 }
5224 Ok(Event::End(e)) => {
5225 if local_name(e.name().as_ref()) == b"tc" {
5226 break;
5227 }
5228 }
5229 Ok(Event::Eof) => break,
5230 Err(e) => return Err(crate::Error::Xml(format!("tc parse: {e}"))),
5231 _ => {}
5232 }
5233 buf.clear();
5234 }
5235
5236 Ok(cell)
5237}
5238
5239fn parse_tc_pr_into(xml: &str, cell: &mut crate::oxml::table::Cell) {
5241 use crate::oxml::table::VerticalAnchor;
5242
5243 let mut rd = Reader::from_str(xml);
5244 let mut buf = Vec::new();
5245
5246 loop {
5247 match rd.read_event_into(&mut buf) {
5248 Ok(Event::Start(e)) => {
5249 let name = e.name();
5250 let local = local_name(name.as_ref());
5251 if local == b"tcPr" {
5254 for a in e.attributes().flatten() {
5255 let v = a
5256 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5257 .unwrap_or_default();
5258 match a.key.as_ref() {
5259 b"marT" => {
5260 if let Ok(m) = v.parse::<i64>() {
5261 cell.margin.0 = Some(Emu(m));
5262 }
5263 }
5264 b"marL" => {
5265 if let Ok(m) = v.parse::<i64>() {
5266 cell.margin.1 = Some(Emu(m));
5267 }
5268 }
5269 b"marB" => {
5270 if let Ok(m) = v.parse::<i64>() {
5271 cell.margin.2 = Some(Emu(m));
5272 }
5273 }
5274 b"marR" => {
5275 if let Ok(m) = v.parse::<i64>() {
5276 cell.margin.3 = Some(Emu(m));
5277 }
5278 }
5279 b"anchor" => {
5280 cell.anchor = match &*v {
5282 "t" => VerticalAnchor::Top,
5283 "ctr" => VerticalAnchor::Middle,
5284 "b" => VerticalAnchor::Bottom,
5285 _ => VerticalAnchor::Middle,
5286 };
5287 }
5288 _ => {}
5289 }
5290 }
5291 } else if local == b"solidFill" {
5292 if let Ok(color) = parse_solid_fill(&mut rd, e.into_owned()) {
5294 cell.fill = color;
5295 }
5296 } else if local == b"lnL" {
5297 let border = parse_cell_border(&mut rd, e.into_owned());
5299 cell.border_left = Some(border);
5300 } else if local == b"lnR" {
5301 let border = parse_cell_border(&mut rd, e.into_owned());
5302 cell.border_right = Some(border);
5303 } else if local == b"lnT" {
5304 let border = parse_cell_border(&mut rd, e.into_owned());
5305 cell.border_top = Some(border);
5306 } else if local == b"lnB" {
5307 let border = parse_cell_border(&mut rd, e.into_owned());
5308 cell.border_bottom = Some(border);
5309 } else {
5310 let _ = collect_full_element(&mut rd, e.into_owned());
5311 }
5312 }
5313 Ok(Event::Empty(e)) => {
5314 let name = e.name();
5315 let local = local_name(name.as_ref());
5316 if local == b"tcPr" {
5317 for a in e.attributes().flatten() {
5319 let v = a
5320 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5321 .unwrap_or_default();
5322 match a.key.as_ref() {
5323 b"marT" => {
5324 if let Ok(m) = v.parse::<i64>() {
5325 cell.margin.0 = Some(Emu(m));
5326 }
5327 }
5328 b"marL" => {
5329 if let Ok(m) = v.parse::<i64>() {
5330 cell.margin.1 = Some(Emu(m));
5331 }
5332 }
5333 b"marB" => {
5334 if let Ok(m) = v.parse::<i64>() {
5335 cell.margin.2 = Some(Emu(m));
5336 }
5337 }
5338 b"marR" => {
5339 if let Ok(m) = v.parse::<i64>() {
5340 cell.margin.3 = Some(Emu(m));
5341 }
5342 }
5343 b"anchor" => {
5344 cell.anchor = match &*v {
5346 "t" => VerticalAnchor::Top,
5347 "ctr" => VerticalAnchor::Middle,
5348 "b" => VerticalAnchor::Bottom,
5349 _ => VerticalAnchor::Middle,
5350 };
5351 }
5352 _ => {}
5353 }
5354 }
5355 }
5356 }
5357 Ok(Event::End(e)) => {
5358 if local_name(e.name().as_ref()) == b"tcPr" {
5359 return;
5360 }
5361 }
5362 Ok(Event::Eof) => return,
5363 _ => {}
5364 }
5365 buf.clear();
5366 }
5367}
5368
5369fn parse_cell_border<R: std::io::BufRead>(
5371 rd: &mut Reader<R>,
5372 start: quick_xml::events::BytesStart<'static>,
5373) -> crate::oxml::table::CellBorder {
5374 use crate::oxml::table::CellBorder;
5375 let mut border = CellBorder::default();
5376 for a in start.attributes().flatten() {
5378 if a.key.as_ref() == b"w" {
5379 if let Ok(v) = a
5380 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5381 .unwrap_or_default()
5382 .parse::<i64>()
5383 {
5384 border.width = Emu(v);
5385 }
5386 }
5387 }
5388 let mut buf = Vec::new();
5389 loop {
5390 match rd.read_event_into(&mut buf) {
5391 Ok(Event::Empty(e)) => {
5392 let name = e.name();
5393 let local = local_name(name.as_ref());
5394 if local == b"noFill" {
5395 border.no_fill = true;
5396 }
5397 }
5399 Ok(Event::Start(e)) => {
5400 let name = e.name();
5401 let local = local_name(name.as_ref());
5402 if local == b"solidFill" {
5403 if let Ok(color) = parse_solid_fill(rd, e.into_owned()) {
5404 border.color = color;
5405 }
5406 } else {
5407 let _ = collect_full_element(rd, e.into_owned());
5408 }
5409 }
5410 Ok(Event::End(e)) => {
5411 let name = e.name();
5413 let local = local_name(name.as_ref());
5414 if local == b"lnL" || local == b"lnR" || local == b"lnT" || local == b"lnB" {
5415 return border;
5416 }
5417 }
5418 Ok(Event::Eof) => return border,
5419 _ => {}
5420 }
5421 buf.clear();
5422 }
5423}
5424
5425pub fn parse_notes(xml: &str) -> crate::Result<TextBody> {
5452 let mut rd = Reader::from_str(xml);
5453 rd.config_mut().trim_text(true);
5454 let mut buf = Vec::new();
5455 let mut tx_buf: Option<String> = None;
5456 loop {
5457 match rd.read_event_into(&mut buf) {
5458 Ok(Event::Start(e)) => {
5459 let name = e.name();
5460 let local = local_name(name.as_ref());
5461 if local == b"txBody" && tx_buf.is_none() {
5462 let inner = collect_full_element(&mut rd, e.into_owned())?;
5463 tx_buf = Some(inner);
5464 }
5465 }
5469 Ok(Event::Eof) => break,
5470 Err(e) => return Err(crate::Error::Xml(format!("notes parse: {e}"))),
5471 _ => {}
5472 }
5473 buf.clear();
5474 }
5475 if let Some(buf) = tx_buf {
5476 parse_txbody(&buf)
5477 } else {
5478 Ok(TextBody::new())
5479 }
5480}
5481
5482pub fn parse_comments(xml: &str) -> crate::Result<crate::oxml::comments::CommentList> {
5492 use crate::oxml::comments::{Comment, CommentList};
5493
5494 let mut lst = CommentList::new();
5495 let mut rd = Reader::from_str(xml);
5496 rd.config_mut().trim_text(true);
5497 let mut buf = Vec::new();
5498 let mut cur_author_id: Option<u32> = None;
5500 let mut cur_dt: String = String::new();
5501 let mut cur_idx: Option<u32> = None;
5502 let mut in_cm = false;
5504 let mut in_text = false;
5506 let mut cur_pos_x: i64 = 0;
5508 let mut cur_pos_y: i64 = 0;
5509 let mut cur_text = String::new();
5510
5511 loop {
5512 match rd.read_event_into(&mut buf) {
5513 Ok(Event::Start(e)) => {
5514 let name = e.name();
5515 let local = local_name(name.as_ref());
5516 if local == b"cm" {
5517 in_cm = true;
5519 cur_author_id = None;
5520 cur_dt.clear();
5521 cur_idx = None;
5522 cur_pos_x = 0;
5523 cur_pos_y = 0;
5524 cur_text.clear();
5525 for a in e.attributes().flatten() {
5527 let key = a.key.as_ref();
5528 let val = a
5529 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5530 .unwrap_or_default()
5531 .to_string();
5532 match key {
5533 b"authorId" => {
5534 cur_author_id = val.parse::<u32>().ok();
5535 }
5536 b"dt" => {
5537 cur_dt = val;
5538 }
5539 b"idx" => {
5540 cur_idx = val.parse::<u32>().ok();
5541 }
5542 _ => {}
5543 }
5544 }
5545 } else if in_cm && local == b"text" {
5546 in_text = true;
5547 cur_text.clear();
5548 }
5549 }
5550 Ok(Event::Empty(e)) => {
5551 let name = e.name();
5552 let local = local_name(name.as_ref());
5553 if in_cm && local == b"pos" {
5554 for a in e.attributes().flatten() {
5556 let key = a.key.as_ref();
5557 let val = a
5558 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5559 .unwrap_or_default();
5560 match key {
5561 b"x" => {
5562 cur_pos_x = val.parse::<i64>().unwrap_or(0);
5563 }
5564 b"y" => {
5565 cur_pos_y = val.parse::<i64>().unwrap_or(0);
5566 }
5567 _ => {}
5568 }
5569 }
5570 }
5571 }
5572 Ok(Event::Text(t)) => {
5573 if in_text {
5574 cur_text.push_str(&t.decode().unwrap_or_default());
5575 }
5576 }
5577 Ok(Event::End(e)) => {
5578 let name = e.name();
5579 let local = local_name(name.as_ref());
5580 if local == b"cm" && in_cm {
5581 let c = Comment {
5583 author_id: cur_author_id.unwrap_or(0),
5584 date_time: std::mem::take(&mut cur_dt),
5585 idx: cur_idx.unwrap_or(0),
5586 pos_x: cur_pos_x,
5587 pos_y: cur_pos_y,
5588 text: std::mem::take(&mut cur_text),
5589 };
5590 lst.push(c);
5591 in_cm = false;
5592 in_text = false;
5593 } else if local == b"text" && in_text {
5594 in_text = false;
5595 }
5596 }
5597 Ok(Event::Eof) => break,
5598 Err(e) => return Err(crate::Error::Xml(format!("comments parse: {e}"))),
5599 _ => {}
5600 }
5601 buf.clear();
5602 }
5603 Ok(lst)
5604}
5605
5606pub fn parse_comment_authors(xml: &str) -> crate::Result<crate::oxml::comments::CommentAuthorList> {
5611 use crate::oxml::comments::{CommentAuthor, CommentAuthorList};
5612
5613 let mut lst = CommentAuthorList::new();
5614 let mut rd = Reader::from_str(xml);
5615 rd.config_mut().trim_text(true);
5616 let mut buf = Vec::new();
5617
5618 loop {
5619 match rd.read_event_into(&mut buf) {
5620 Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
5621 let name = e.name();
5622 let local = local_name(name.as_ref());
5623 if local == b"cmAuthor" {
5624 let mut id: u32 = 0;
5625 let mut name_val = String::new();
5626 let mut initials = String::new();
5627 for a in e.attributes().flatten() {
5628 let key = a.key.as_ref();
5629 let val = a
5630 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5631 .unwrap_or_default()
5632 .to_string();
5633 match key {
5634 b"id" => {
5635 id = val.parse::<u32>().unwrap_or(0);
5636 }
5637 b"name" => {
5638 name_val = val;
5639 }
5640 b"initials" => {
5641 initials = val;
5642 }
5643 _ => {}
5644 }
5645 }
5646 lst.push(CommentAuthor::new(id, name_val, initials));
5647 }
5648 }
5649 Ok(Event::Eof) => break,
5650 Err(e) => return Err(crate::Error::Xml(format!("commentAuthors parse: {e}"))),
5651 _ => {}
5652 }
5653 buf.clear();
5654 }
5655 Ok(lst)
5656}
5657
5658#[allow(clippy::type_complexity)]
5670pub fn parse_pres_root(
5671 xml: &str,
5672) -> crate::Result<(
5673 Vec<(u32, String)>,
5674 Option<Emu>,
5675 Option<Emu>,
5676 Vec<(u32, String)>,
5677 crate::oxml::section::SectionList,
5678)> {
5679 let mut rd = Reader::from_str(xml);
5680 rd.config_mut().trim_text(true);
5681 let mut buf = Vec::new();
5682 let mut sld_ids: Vec<(u32, String)> = Vec::new();
5683 let mut sld_sz: (Option<Emu>, Option<Emu>) = (None, None);
5684 let mut sld_master_ids: Vec<(u32, String)> = Vec::new();
5686 let mut sections = crate::oxml::section::SectionList::default();
5688 loop {
5689 match rd.read_event_into(&mut buf) {
5690 Ok(Event::Empty(e)) => {
5691 let name = e.name();
5692 let local = local_name(name.as_ref());
5693 if local == b"sldId" {
5694 let mut id: Option<u32> = None;
5695 let mut rid: Option<String> = None;
5696 for a in e.attributes().flatten() {
5697 let k = a.key.as_ref();
5698 if k == b"id" {
5700 if let Ok(v) = a
5701 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5702 .unwrap_or_default()
5703 .parse::<u32>()
5704 {
5705 id = Some(v);
5706 }
5707 } else if k == b"r:id" || k.ends_with(b":id") {
5708 rid = Some(
5709 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5710 .unwrap_or_default()
5711 .to_string(),
5712 );
5713 }
5714 }
5715 if let (Some(i), Some(r)) = (id, rid) {
5716 sld_ids.push((i, r));
5717 }
5718 } else if local == b"sldMasterId" {
5719 let mut id: Option<u32> = None;
5721 let mut rid: Option<String> = None;
5722 for a in e.attributes().flatten() {
5723 let k = a.key.as_ref();
5724 if k == b"id" {
5725 if let Ok(v) = a
5726 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5727 .unwrap_or_default()
5728 .parse::<u32>()
5729 {
5730 id = Some(v);
5731 }
5732 } else if k == b"r:id" || k.ends_with(b":id") {
5733 rid = Some(
5734 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5735 .unwrap_or_default()
5736 .to_string(),
5737 );
5738 }
5739 }
5740 if let (Some(i), Some(r)) = (id, rid) {
5741 sld_master_ids.push((i, r));
5742 }
5743 } else if local == b"sldSz" {
5744 let mut cx: Option<i64> = None;
5745 let mut cy: Option<i64> = None;
5746 for a in e.attributes().flatten() {
5747 match a.key.as_ref() {
5748 b"cx" => {
5749 cx = a
5750 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5751 .unwrap_or_default()
5752 .parse()
5753 .ok()
5754 }
5755 b"cy" => {
5756 cy = a
5757 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5758 .unwrap_or_default()
5759 .parse()
5760 .ok()
5761 }
5762 _ => {}
5763 }
5764 }
5765 sld_sz = (cx.map(Emu), cy.map(Emu));
5766 }
5767 }
5768 Ok(Event::Start(e)) => {
5769 let name = e.name();
5770 let local = local_name(name.as_ref());
5771 if local == b"sldMasterId" {
5772 let mut id: Option<u32> = None;
5774 let mut rid: Option<String> = None;
5775 for a in e.attributes().flatten() {
5776 let k = a.key.as_ref();
5777 if k == b"id" {
5778 if let Ok(v) = a
5779 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5780 .unwrap_or_default()
5781 .parse::<u32>()
5782 {
5783 id = Some(v);
5784 }
5785 } else if k == b"r:id" || k.ends_with(b":id") {
5786 rid = Some(
5787 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5788 .unwrap_or_default()
5789 .to_string(),
5790 );
5791 }
5792 }
5793 if let (Some(i), Some(r)) = (id, rid) {
5794 sld_master_ids.push((i, r));
5795 }
5796 let _ = collect_full_element(&mut rd, e.into_owned());
5798 } else if local == b"extLst" {
5799 let ext_lst_xml = collect_full_element(&mut rd, e.into_owned())?;
5802 let parsed = parse_sections_from_ext_lst(&ext_lst_xml)?;
5803 if !parsed.is_empty() {
5804 sections = parsed;
5805 }
5806 }
5807 }
5808 Ok(Event::Eof) => break,
5809 Err(e) => return Err(crate::Error::Xml(format!("pres root parse: {e}"))),
5810 _ => {}
5811 }
5812 buf.clear();
5813 }
5814 Ok((sld_ids, sld_sz.0, sld_sz.1, sld_master_ids, sections))
5815}
5816
5817fn parse_sections_from_ext_lst(xml: &str) -> crate::Result<crate::oxml::section::SectionList> {
5844 let mut rd = Reader::from_str(xml);
5845 rd.config_mut().trim_text(true);
5846 let mut buf = Vec::new();
5847 let mut result = crate::oxml::section::SectionList::default();
5848 let mut state: u8 = 0;
5854 let mut cur_name = String::new();
5856 let mut cur_ids: Vec<u32> = Vec::new();
5857
5858 loop {
5859 match rd.read_event_into(&mut buf) {
5860 Ok(Event::Start(e)) => {
5861 let name = e.name();
5863 let local = local_name(name.as_ref());
5864 match state {
5865 0 if local == b"ext" => {
5866 let mut uri_val = String::new();
5868 for a in e.attributes().flatten() {
5869 if a.key.as_ref() == b"uri" {
5870 uri_val = a
5871 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5872 .unwrap_or_default()
5873 .to_string();
5874 }
5875 }
5876 if uri_val == crate::oxml::section::SECTION_EXT_URI {
5877 state = 1;
5878 } else {
5879 let _ = collect_full_element(&mut rd, e.into_owned());
5881 }
5882 }
5883 1 if local == b"sectionLst" => {
5884 state = 2;
5885 }
5886 2 if local == b"section" => {
5887 cur_name.clear();
5889 cur_ids.clear();
5890 for a in e.attributes().flatten() {
5891 if a.key.as_ref() == b"name" {
5892 cur_name = a
5893 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5894 .unwrap_or_default()
5895 .to_string();
5896 }
5897 }
5898 state = 3;
5899 }
5900 3 if local == b"sldIdLst" => {
5901 }
5903 _ => {
5904 let _ = collect_full_element(&mut rd, e.into_owned());
5906 }
5907 }
5908 }
5909 Ok(Event::Empty(e)) => {
5910 let name = e.name();
5912 let local = local_name(name.as_ref());
5913 if state == 3 && local == b"sldId" {
5915 for a in e.attributes().flatten() {
5916 if a.key.as_ref() == b"id" {
5917 if let Ok(v) = a
5918 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5919 .unwrap_or_default()
5920 .parse::<u32>()
5921 {
5922 cur_ids.push(v);
5923 }
5924 }
5925 }
5926 }
5927 }
5928 Ok(Event::End(e)) => {
5929 let name = e.name();
5931 let local = local_name(name.as_ref());
5932 match state {
5933 3 if local == b"section" => {
5934 result.push(crate::oxml::section::Section {
5936 name: std::mem::take(&mut cur_name),
5937 slide_ids: std::mem::take(&mut cur_ids),
5938 });
5939 state = 2;
5940 }
5941 2 if local == b"sectionLst" => {
5942 state = 1;
5943 }
5944 1 if local == b"ext" => {
5945 state = 0;
5946 }
5947 0 if local == b"extLst" => {
5948 break;
5949 }
5950 _ => {}
5951 }
5952 }
5953 Ok(Event::Eof) => break,
5954 Err(e) => return Err(crate::Error::Xml(format!("sectionLst parse: {e}"))),
5955 _ => {}
5956 }
5957 buf.clear();
5958 }
5959 Ok(result)
5960}
5961
5962pub fn parse_sld_master(xml: &str) -> crate::Result<crate::oxml::slidemaster::SldMaster> {
5976 let mut rd = Reader::from_str(xml);
5977 rd.config_mut().trim_text(true);
5978 let mut buf = Vec::new();
5979 let mut master = crate::oxml::slidemaster::SldMaster::default();
5980 let mut state: u8 = 0;
5982 let mut sp_bufs: Vec<String> = Vec::new();
5983
5984 loop {
5985 match rd.read_event_into(&mut buf) {
5986 Ok(Event::Start(e)) => {
5987 let name = e.name();
5988 let local = local_name(name.as_ref());
5989 match state {
5990 0 if local == b"sldMaster" => {
5991 state = 1;
5992 }
5993 1 if local == b"cSld" => {}
5995 1 if local == b"spTree" => {
5996 state = 2;
5997 }
5998 1 if local == b"sldLayoutIdLst" => {
5999 state = 3;
6000 }
6001 2 if local == b"sp" => {
6002 let inner = collect_full_element(&mut rd, e.into_owned())?;
6004 sp_bufs.push(inner);
6005 }
6006 _ => {
6007 let _ = collect_full_element(&mut rd, e.into_owned());
6009 }
6010 }
6011 }
6012 Ok(Event::Empty(e)) => {
6013 let name = e.name();
6014 let local = local_name(name.as_ref());
6015 if state == 3 && local == b"sldLayoutId" {
6016 for a in e.attributes().flatten() {
6018 let k = a.key.as_ref();
6019 if k == b"r:id" || k.ends_with(b":id") {
6020 let rid = a
6021 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6022 .unwrap_or_default()
6023 .to_string();
6024 master.layout_rids.push(rid);
6025 }
6026 }
6027 }
6028 }
6029 Ok(Event::End(e)) => {
6030 let name = e.name();
6031 let local = local_name(name.as_ref());
6032 if (state == 2 && local == b"spTree") || (state == 3 && local == b"sldLayoutIdLst")
6034 {
6035 state = 1;
6036 } else if state == 1 && local == b"sldMaster" {
6037 break;
6038 }
6039 }
6040 Ok(Event::Eof) => break,
6041 Err(e) => return Err(crate::Error::Xml(format!("sldMaster parse: {e}"))),
6042 _ => {}
6043 }
6044 buf.clear();
6045 }
6046 for s in sp_bufs {
6048 if let Ok(sp) = parse_sp(&s) {
6049 master.shapes.push(sp);
6050 }
6051 }
6052 Ok(master)
6053}
6054
6055pub fn parse_notes_master(xml: &str) -> crate::Result<crate::oxml::notesmaster::NotesMaster> {
6071 let mut rd = Reader::from_str(xml);
6072 rd.config_mut().trim_text(true);
6073 let mut buf = Vec::new();
6074 let mut master = crate::oxml::notesmaster::NotesMaster::default();
6075 let mut state: u8 = 0;
6077 let mut sp_bufs: Vec<String> = Vec::new();
6078
6079 loop {
6080 match rd.read_event_into(&mut buf) {
6081 Ok(Event::Start(e)) => {
6082 let name = e.name();
6083 let local = local_name(name.as_ref());
6084 match state {
6085 0 if local == b"notesMaster" => {
6086 state = 1;
6087 }
6088 1 if local == b"cSld" => {}
6090 1 if local == b"spTree" => {
6091 state = 2;
6092 }
6093 2 if local == b"sp" => {
6094 let inner = collect_full_element(&mut rd, e.into_owned())?;
6096 sp_bufs.push(inner);
6097 }
6098 _ => {
6099 let _ = collect_full_element(&mut rd, e.into_owned());
6101 }
6102 }
6103 }
6104 Ok(Event::End(e)) => {
6105 let name = e.name();
6106 let local = local_name(name.as_ref());
6107 if state == 2 && local == b"spTree" {
6108 state = 1;
6109 } else if state == 1 && local == b"notesMaster" {
6110 break;
6111 }
6112 }
6113 Ok(Event::Eof) => break,
6114 Err(e) => return Err(crate::Error::Xml(format!("notesMaster parse: {e}"))),
6115 _ => {}
6116 }
6117 buf.clear();
6118 }
6119 for s in sp_bufs {
6121 if let Ok(sp) = parse_sp(&s) {
6122 master.shapes.push(sp);
6123 }
6124 }
6125 Ok(master)
6126}
6127
6128pub fn parse_sld_layout(xml: &str) -> crate::Result<crate::oxml::slidelayout::SldLayout> {
6142 let mut rd = Reader::from_str(xml);
6143 rd.config_mut().trim_text(true);
6144 let mut buf = Vec::new();
6145 let mut layout = crate::oxml::slidelayout::SldLayout::default();
6146 let mut state: u8 = 0;
6148 let mut sp_bufs: Vec<String> = Vec::new();
6149
6150 loop {
6151 match rd.read_event_into(&mut buf) {
6152 Ok(Event::Start(e)) => {
6153 let name = e.name();
6154 let local = local_name(name.as_ref());
6155 match state {
6156 0 if local == b"sldLayout" => {
6157 for a in e.attributes().flatten() {
6159 if a.key.as_ref() == b"type" {
6160 layout.type_ = a
6161 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6162 .unwrap_or_default()
6163 .to_string();
6164 }
6165 }
6166 state = 1;
6167 }
6168 1 if local == b"cSld" => {
6169 for a in e.attributes().flatten() {
6171 if a.key.as_ref() == b"name" {
6172 layout.name = a
6173 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6174 .unwrap_or_default()
6175 .to_string();
6176 }
6177 }
6178 }
6179 1 if local == b"spTree" => {
6180 state = 2;
6181 }
6182 2 if local == b"sp" => {
6183 let inner = collect_full_element(&mut rd, e.into_owned())?;
6184 sp_bufs.push(inner);
6185 }
6186 _ => {
6187 let _ = collect_full_element(&mut rd, e.into_owned());
6188 }
6189 }
6190 }
6191 Ok(Event::End(e)) => {
6192 let name = e.name();
6193 let local = local_name(name.as_ref());
6194 if state == 2 && local == b"spTree" {
6195 state = 1;
6196 } else if state == 1 && local == b"sldLayout" {
6197 break;
6198 }
6199 }
6200 Ok(Event::Eof) => break,
6201 Err(e) => return Err(crate::Error::Xml(format!("sldLayout parse: {e}"))),
6202 _ => {}
6203 }
6204 buf.clear();
6205 }
6206 for s in sp_bufs {
6208 if let Ok(sp) = parse_sp(&s) {
6209 layout.shapes.push(sp);
6210 }
6211 }
6212 Ok(layout)
6213}
6214
6215pub fn parse_theme(xml: &str) -> crate::Result<crate::oxml::theme::Theme> {
6230 let mut rd = Reader::from_str(xml);
6231 rd.config_mut().trim_text(true);
6232 let mut buf = Vec::new();
6233 let mut theme = crate::oxml::theme::Theme::default();
6234
6235 loop {
6236 match rd.read_event_into(&mut buf) {
6237 Ok(Event::Start(e)) => {
6238 let name = e.name();
6239 let local = local_name(name.as_ref());
6240 if local == b"theme" {
6241 for a in e.attributes().flatten() {
6243 if a.key.as_ref() == b"name" {
6244 theme.name = a
6245 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6246 .unwrap_or_default()
6247 .to_string();
6248 }
6249 }
6250 } else if local == b"clrScheme" {
6251 let inner = collect_full_element(&mut rd, e.into_owned())?;
6253 theme.color_scheme = parse_clr_scheme(&inner);
6254 } else if local == b"fontScheme" {
6255 let inner = collect_full_element(&mut rd, e.into_owned())?;
6257 theme.font_scheme = parse_font_scheme(&inner);
6258 } else if local == b"fmtScheme" {
6259 let inner = collect_full_element(&mut rd, e.into_owned())?;
6262 let (fmt_name, raw) = parse_fmt_scheme(&inner);
6263 theme.format_scheme.name = fmt_name;
6264 theme.format_scheme.raw_xml = raw;
6265 theme.format_scheme.parse_from_raw_xml();
6267 } else if local == b"themeElements" {
6268 } else {
6270 let _ = collect_full_element(&mut rd, e.into_owned());
6271 }
6272 }
6273 Ok(Event::Eof) => break,
6274 Err(e) => return Err(crate::Error::Xml(format!("theme parse: {e}"))),
6275 _ => {}
6276 }
6277 buf.clear();
6278 }
6279 Ok(theme)
6280}
6281
6282fn parse_fmt_scheme(xml: &str) -> (String, String) {
6288 let mut fmt_name = String::new();
6289 let mut raw_xml = String::new();
6290 let mut rd = Reader::from_str(xml);
6291 let mut buf = Vec::new();
6292 let mut in_fmt_scheme = false;
6293
6294 loop {
6295 match rd.read_event_into(&mut buf) {
6296 Ok(Event::Start(e)) => {
6297 let ev_name = e.name();
6298 let local = local_name(ev_name.as_ref());
6299 if !in_fmt_scheme && local == b"fmtScheme" {
6300 in_fmt_scheme = true;
6301 for a in e.attributes().flatten() {
6303 if a.key.as_ref() == b"name" {
6304 fmt_name = a
6305 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6306 .unwrap_or_default()
6307 .to_string();
6308 }
6309 }
6310 } else if in_fmt_scheme {
6311 let inner = collect_full_element(&mut rd, e.into_owned()).unwrap_or_default();
6313 raw_xml.push_str(&inner);
6314 }
6315 }
6316 Ok(Event::Empty(e)) => {
6317 let ev_name = e.name();
6318 let local = local_name(ev_name.as_ref());
6319 if in_fmt_scheme {
6320 let mut s = String::new();
6322 s.push('<');
6323 s.push_str(std::str::from_utf8(local).unwrap_or(""));
6324 for a in e.attributes().flatten() {
6325 s.push(' ');
6326 s.push_str(std::str::from_utf8(a.key.as_ref()).unwrap_or(""));
6327 s.push_str("=\"");
6328 s.push_str(
6329 &a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
6330 .unwrap_or_default(),
6331 );
6332 s.push('"');
6333 }
6334 s.push_str("/>");
6335 raw_xml.push_str(&s);
6336 }
6337 }
6338 Ok(Event::End(e)) => {
6339 let ev_name = e.name();
6340 let local = local_name(ev_name.as_ref());
6341 if in_fmt_scheme && local == b"fmtScheme" {
6342 break;
6343 }
6344 }
6345 Ok(Event::Eof) => break,
6346 _ => {}
6347 }
6348 buf.clear();
6349 }
6350 (fmt_name, raw_xml)
6351}
6352
6353fn parse_clr_scheme(xml: &str) -> crate::oxml::theme::ColorScheme {
6355 use crate::oxml::theme::{ColorScheme, ThemeColor};
6356
6357 let mut scheme = ColorScheme::default();
6358 let mut rd = Reader::from_str(xml);
6359 let mut buf = Vec::new();
6360
6361 loop {
6362 match rd.read_event_into(&mut buf) {
6363 Ok(Event::Start(e)) => {
6364 let name = e.name();
6365 let local = local_name(name.as_ref());
6366 let slot: &mut Option<ThemeColor> = match local {
6368 b"dk1" => &mut scheme.dk1,
6369 b"lt1" => &mut scheme.lt1,
6370 b"dk2" => &mut scheme.dk2,
6371 b"lt2" => &mut scheme.lt2,
6372 b"accent1" => &mut scheme.accent1,
6373 b"accent2" => &mut scheme.accent2,
6374 b"accent3" => &mut scheme.accent3,
6375 b"accent4" => &mut scheme.accent4,
6376 b"accent5" => &mut scheme.accent5,
6377 b"accent6" => &mut scheme.accent6,
6378 b"hlink" => &mut scheme.hlink,
6379 b"folHlink" => &mut scheme.fol_hlink,
6380 b"clrScheme" => {
6382 for a in e.attributes().flatten() {
6384 if a.key.as_ref() == b"name" {
6385 scheme.name = a
6386 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6387 .unwrap_or_default()
6388 .to_string();
6389 }
6390 }
6391 continue;
6392 }
6393 _ => {
6394 let _ = collect_full_element(&mut rd, e.into_owned());
6396 continue;
6397 }
6398 };
6399 *slot = Some(read_color_child(&mut rd, local));
6400 }
6401 Ok(Event::End(e)) => {
6402 if local_name(e.name().as_ref()) == b"clrScheme" {
6403 break;
6404 }
6405 }
6406 Ok(Event::Eof) => break,
6407 _ => {}
6408 }
6409 buf.clear();
6410 }
6411 scheme
6412}
6413
6414fn read_color_child<R: std::io::BufRead>(
6418 rd: &mut Reader<R>,
6419 parent_local: &[u8],
6420) -> crate::oxml::theme::ThemeColor {
6421 use crate::oxml::theme::ThemeColor;
6422 let mut buf = Vec::new();
6423 loop {
6424 match rd.read_event_into(&mut buf) {
6425 Ok(Event::Start(e)) => {
6426 let name = e.name();
6427 let local = local_name(name.as_ref());
6428 if local == b"srgbClr" {
6429 let mut val = String::new();
6430 for a in e.attributes().flatten() {
6431 if a.key.as_ref() == b"val" {
6432 val = a
6433 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6434 .unwrap_or_default()
6435 .to_string();
6436 }
6437 }
6438 let _ = collect_full_element(rd, e.into_owned());
6440 return ThemeColor::Srgb(val);
6441 } else if local == b"sysClr" {
6442 let mut val = String::new();
6443 let mut last_clr = String::new();
6444 for a in e.attributes().flatten() {
6445 match a.key.as_ref() {
6446 b"val" => {
6447 val = a
6448 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6449 .unwrap_or_default()
6450 .to_string();
6451 }
6452 b"lastClr" => {
6453 last_clr = a
6454 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6455 .unwrap_or_default()
6456 .to_string();
6457 }
6458 _ => {}
6459 }
6460 }
6461 let _ = collect_full_element(rd, e.into_owned());
6462 return ThemeColor::Sys(val, last_clr);
6463 } else {
6464 let _ = collect_full_element(rd, e.into_owned());
6465 }
6466 }
6467 Ok(Event::Empty(e)) => {
6468 let name = e.name();
6469 let local = local_name(name.as_ref());
6470 if local == b"srgbClr" {
6471 for a in e.attributes().flatten() {
6472 if a.key.as_ref() == b"val" {
6473 return ThemeColor::Srgb(
6474 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
6475 .unwrap_or_default()
6476 .to_string(),
6477 );
6478 }
6479 }
6480 } else if local == b"sysClr" {
6481 let mut val = String::new();
6482 let mut last_clr = String::new();
6483 for a in e.attributes().flatten() {
6484 match a.key.as_ref() {
6485 b"val" => {
6486 val = a
6487 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6488 .unwrap_or_default()
6489 .to_string();
6490 }
6491 b"lastClr" => {
6492 last_clr = a
6493 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6494 .unwrap_or_default()
6495 .to_string();
6496 }
6497 _ => {}
6498 }
6499 }
6500 return ThemeColor::Sys(val, last_clr);
6501 }
6502 }
6503 Ok(Event::End(e)) => {
6504 if local_name(e.name().as_ref()) == parent_local {
6506 return ThemeColor::None;
6507 }
6508 }
6509 Ok(Event::Eof) => return ThemeColor::None,
6510 _ => {}
6511 }
6512 buf.clear();
6513 }
6514}
6515
6516fn parse_font_scheme(xml: &str) -> crate::oxml::theme::FontScheme {
6518 let mut scheme = crate::oxml::theme::FontScheme::default();
6519 let mut rd = Reader::from_str(xml);
6520 let mut buf = Vec::new();
6521 let mut state: u8 = 0;
6523
6524 loop {
6525 match rd.read_event_into(&mut buf) {
6526 Ok(Event::Start(e)) => {
6527 let name = e.name();
6528 let local = local_name(name.as_ref());
6529 if local == b"fontScheme" {
6530 state = 1;
6531 for a in e.attributes().flatten() {
6533 if a.key.as_ref() == b"name" {
6534 scheme.name = a
6535 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6536 .unwrap_or_default()
6537 .to_string();
6538 }
6539 }
6540 } else if state == 1 && local == b"majorFont" {
6541 let (latin, ea, cs) = read_font_typefaces(&mut rd);
6543 scheme.major_latin = latin;
6544 scheme.major_ea = ea;
6545 scheme.major_cs = cs;
6546 } else if state == 1 && local == b"minorFont" {
6547 let (latin, ea, cs) = read_font_typefaces(&mut rd);
6548 scheme.minor_latin = latin;
6549 scheme.minor_ea = ea;
6550 scheme.minor_cs = cs;
6551 }
6552 }
6553 Ok(Event::End(e)) => {
6554 if local_name(e.name().as_ref()) == b"fontScheme" {
6555 break;
6556 }
6557 }
6558 Ok(Event::Eof) => break,
6559 _ => {}
6560 }
6561 buf.clear();
6562 }
6563 scheme
6564}
6565
6566fn read_font_typefaces<R: std::io::BufRead>(rd: &mut Reader<R>) -> (String, String, String) {
6571 let mut buf = Vec::new();
6572 let mut latin = String::new();
6573 let mut ea = String::new();
6574 let mut cs = String::new();
6575 loop {
6576 match rd.read_event_into(&mut buf) {
6577 Ok(Event::Empty(e)) => {
6578 let name = e.name();
6579 let local = local_name(name.as_ref());
6580 let typeface = extract_typeface(&e);
6582 match local {
6583 b"latin" => latin = typeface,
6584 b"ea" => ea = typeface,
6585 b"cs" => cs = typeface,
6586 _ => {}
6587 }
6588 }
6589 Ok(Event::Start(e)) => {
6590 let name = e.name();
6591 let local = local_name(name.as_ref());
6592 let typeface = extract_typeface(&e);
6593 match local {
6594 b"latin" => latin = typeface,
6595 b"ea" => ea = typeface,
6596 b"cs" => cs = typeface,
6597 _ => {}
6598 }
6599 let _ = collect_full_element(rd, e.into_owned());
6601 }
6602 Ok(Event::End(e)) => {
6603 let name = e.name();
6604 let local = local_name(name.as_ref());
6605 if local == b"majorFont" || local == b"minorFont" {
6606 return (latin, ea, cs);
6607 }
6608 }
6609 Ok(Event::Eof) => return (latin, ea, cs),
6610 _ => {}
6611 }
6612 buf.clear();
6613 }
6614}
6615
6616fn extract_typeface(e: &quick_xml::events::BytesStart<'_>) -> String {
6618 for a in e.attributes().flatten() {
6619 if a.key.as_ref() == b"typeface" {
6620 return a
6621 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6622 .unwrap_or_default()
6623 .to_string();
6624 }
6625 }
6626 String::new()
6627}
6628
6629#[cfg(test)]
6630mod tests {
6631 use super::*;
6632 use crate::oxml::txbody::TextBody;
6633
6634 #[test]
6638 fn parse_backdrop_full() {
6639 let xml = r#"<?xml version="1.0"?>
6640<a:backdrop xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6641 <a:anchor x="100000" y="200000" z="300000"/>
6642 <a:floor/>
6643 <a:wall/>
6644 <a:l/>
6645 <a:r/>
6646 <a:t/>
6647 <a:b/>
6648</a:backdrop>"#;
6649 let mut rd = Reader::from_str(xml);
6650 rd.config_mut().trim_text(true);
6651 let mut buf = Vec::new();
6652 let start = loop {
6653 match rd.read_event_into(&mut buf) {
6654 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"backdrop" => {
6655 break e.into_owned();
6656 }
6657 Ok(Event::Eof) => panic!("backdrop not found"),
6658 _ => {}
6659 }
6660 buf.clear();
6661 };
6662 let bd = parse_backdrop(&mut rd, start).expect("parse backdrop");
6663
6664 assert_eq!(
6665 bd.anchor,
6666 Some(Point3d {
6667 x: 100000,
6668 y: 200000,
6669 z: 300000
6670 })
6671 );
6672 assert!(bd.floor);
6673 assert!(bd.wall);
6674 assert!(bd.left);
6675 assert!(bd.right);
6676 assert!(bd.top);
6677 assert!(bd.bottom);
6678 }
6679
6680 #[test]
6682 fn parse_backdrop_partial() {
6683 let xml = r#"<?xml version="1.0"?>
6684<a:backdrop xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6685 <a:floor/>
6686 <a:wall/>
6687</a:backdrop>"#;
6688 let mut rd = Reader::from_str(xml);
6689 rd.config_mut().trim_text(true);
6690 let mut buf = Vec::new();
6691 let start = loop {
6692 match rd.read_event_into(&mut buf) {
6693 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"backdrop" => {
6694 break e.into_owned();
6695 }
6696 Ok(Event::Eof) => panic!("backdrop not found"),
6697 _ => {}
6698 }
6699 buf.clear();
6700 };
6701 let bd = parse_backdrop(&mut rd, start).expect("parse backdrop");
6702
6703 assert!(bd.anchor.is_none());
6704 assert!(bd.floor);
6705 assert!(bd.wall);
6706 assert!(!bd.left);
6707 assert!(!bd.right);
6708 assert!(!bd.top);
6709 assert!(!bd.bottom);
6710 }
6711
6712 #[test]
6714 fn parse_scene_3d_with_backdrop() {
6715 let xml = r#"<?xml version="1.0"?>
6716<a:scene3d xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6717 <a:camera prst="orthographicFront"/>
6718 <a:lightRig rig="balanced" dir="t"/>
6719 <a:backdrop>
6720 <a:anchor x="0" y="0" z="0"/>
6721 <a:floor/>
6722 <a:wall/>
6723 </a:backdrop>
6724</a:scene3d>"#;
6725 let mut rd = Reader::from_str(xml);
6726 rd.config_mut().trim_text(true);
6727 let mut buf = Vec::new();
6728 let start = loop {
6729 match rd.read_event_into(&mut buf) {
6730 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"scene3d" => {
6731 break e.into_owned();
6732 }
6733 Ok(Event::Eof) => panic!("scene3d not found"),
6734 _ => {}
6735 }
6736 buf.clear();
6737 };
6738 let scene = parse_scene_3d(&mut rd, start).expect("parse scene3d");
6739
6740 let bd = scene.backdrop.expect("backdrop should be parsed");
6742 assert!(bd.floor);
6743 assert!(bd.wall);
6744 assert!(!bd.left);
6745 assert_eq!(bd.anchor, Some(Point3d { x: 0, y: 0, z: 0 }));
6746 }
6747
6748 #[test]
6750 fn parse_scene_3d_without_backdrop() {
6751 let xml = r#"<?xml version="1.0"?>
6752<a:scene3d xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6753 <a:camera prst="orthographicFront"/>
6754 <a:lightRig rig="balanced" dir="t"/>
6755</a:scene3d>"#;
6756 let mut rd = Reader::from_str(xml);
6757 rd.config_mut().trim_text(true);
6758 let mut buf = Vec::new();
6759 let start = loop {
6760 match rd.read_event_into(&mut buf) {
6761 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"scene3d" => {
6762 break e.into_owned();
6763 }
6764 Ok(Event::Eof) => panic!("scene3d not found"),
6765 _ => {}
6766 }
6767 buf.clear();
6768 };
6769 let scene = parse_scene_3d(&mut rd, start).expect("parse scene3d");
6770 assert!(scene.backdrop.is_none());
6771 }
6772
6773 #[test]
6774 fn parse_minimal_sld() {
6775 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6776<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6777 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6778 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6779 <p:cSld>
6780 <p:spTree>
6781 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
6782 <p:grpSpPr><a:xfrm/></p:grpSpPr>
6783 <p:sp>
6784 <p:nvSpPr>
6785 <p:cNvPr id="2" name="TextBox 1"/>
6786 <p:cNvSpPr txBox="1"/>
6787 <p:nvPr/>
6788 </p:nvSpPr>
6789 <p:spPr>
6790 <a:xfrm>
6791 <a:off x="914400" y="914400"/>
6792 <a:ext cx="3657600" cy="457200"/>
6793 </a:xfrm>
6794 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
6795 <a:noFill/>
6796 </p:spPr>
6797 <p:txBody>
6798 <a:bodyPr wrap="square" rtlCol="0"><a:noAutofit/></a:bodyPr>
6799 <a:p>
6800 <a:r><a:rPr lang="en-US"/><a:t>Hello</a:t></a:r>
6801 </a:p>
6802 </p:txBody>
6803 </p:sp>
6804 </p:spTree>
6805 </p:cSld>
6806 <p:clrMapOvr/>
6807</p:sld>"#;
6808 let sld = parse_sld(xml).expect("parse ok");
6809 assert_eq!(sld.shapes.len(), 1);
6810 if let OxmlSlideShape::Sp(sp) = &sld.shapes[0] {
6811 assert_eq!(sp.id, 2);
6812 assert_eq!(sp.name, "TextBox 1");
6813 assert!(sp.c_nv_sp_pr_tx_box);
6814 assert_eq!(sp.text.paragraphs.len(), 1);
6815 assert_eq!(sp.text.paragraphs[0].runs.len(), 1);
6816 assert_eq!(sp.text.paragraphs[0].runs[0].text, "Hello");
6817 } else {
6818 panic!("expected Sp");
6819 }
6820 }
6821
6822 #[test]
6823 fn parse_empty_txbody_keeps_one_paragraph() {
6824 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6825 <a:bodyPr/>
6826 </p:txBody>"#;
6827 let tb = parse_txbody(xml).unwrap();
6828 assert!(tb.paragraphs.is_empty());
6829 let tb2 = TextBody::new();
6832 assert!(tb2.paragraphs.is_empty());
6833 }
6834
6835 #[test]
6836 fn parse_pres_root_extracts_sld_id_lst_and_size() {
6837 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6838<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6839 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6840 <p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
6841 <p:sldIdLst>
6842 <p:sldId id="256" r:id="rId2"/>
6843 <p:sldId id="257" r:id="rId3"/>
6844 </p:sldIdLst>
6845 <p:sldSz cx="9144000" cy="6858000"/>
6846</p:presentation>"#;
6847 let (ids, w, h, master_ids, _sections) = parse_pres_root(xml).expect("parse ok");
6848 assert_eq!(ids.len(), 2);
6849 assert_eq!(ids[0], (256, "rId2".to_string()));
6850 assert_eq!(ids[1], (257, "rId3".to_string()));
6851 assert_eq!(w.map(|v| v.0), Some(9_144_000));
6852 assert_eq!(h.map(|v| v.0), Some(6_858_000));
6853 assert_eq!(master_ids.len(), 1);
6855 assert_eq!(master_ids[0], (2147483648, "rId1".to_string()));
6856 }
6857
6858 #[test]
6859 fn parse_notes_returns_text_body() {
6860 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6861<p:notes xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6862 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
6863 <p:cSld>
6864 <p:spTree>
6865 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
6866 <p:grpSpPr/>
6867 <p:sp>
6868 <p:nvSpPr>
6869 <p:cNvPr id="2" name="Notes Placeholder"/>
6870 <p:cNvSpPr txBox="1"/>
6871 <p:nvPr><p:ph type="body" idx="1"/></p:nvPr>
6872 </p:nvSpPr>
6873 <p:spPr/>
6874 <p:txBody>
6875 <a:bodyPr/>
6876 <a:p><a:r><a:t>hello notes</a:t></a:r></a:p>
6877 </p:txBody>
6878 </p:sp>
6879 </p:spTree>
6880 </p:cSld>
6881</p:notes>"#;
6882 let tb = parse_notes(xml).expect("parse notes ok");
6883 assert_eq!(tb.paragraphs.len(), 1);
6884 assert_eq!(tb.paragraphs[0].runs.len(), 1);
6885 assert_eq!(tb.paragraphs[0].runs[0].text, "hello notes");
6886 }
6887
6888 #[test]
6893 fn parse_pic_extracts_rid_and_src_rect() {
6894 let xml1 = r#"<p:pic xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6896 xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6897 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6898 <p:nvPicPr><p:cNvPr id="42" name="MyPic"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>
6899 <p:blipFill>
6900 <a:blip r:embed="rIdImg5"/>
6901 <a:srcRect l="1000" t="2000" r="3000" b="4000"/>
6902 </p:blipFill>
6903 <p:spPr/>
6904</p:pic>"#;
6905 let p1 = parse_pic(xml1).expect("parse pic 1");
6906 assert_eq!(p1.id, 42);
6907 assert_eq!(p1.name, "MyPic");
6908 assert_eq!(p1.rid, "rIdImg5");
6909 assert_eq!(p1.src_rect, Some((1000, 2000, 3000, 4000)));
6910
6911 let xml2 = r#"<p:pic xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6913 xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6914 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6915<p:nvPicPr><p:cNvPr id="7" name="p2"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>
6916<p:blipFill><a:blip r:embed="rIdImg99"></a:blip></p:blipFill>
6917<p:spPr/>
6918</p:pic>"#;
6919 let p2 = parse_pic(xml2).expect("parse pic 2");
6920 assert_eq!(p2.id, 7);
6921 assert_eq!(p2.rid, "rIdImg99");
6922 }
6923
6924 #[test]
6927 fn parse_run_with_typeface_works() {
6928 let xml = r#"<a:r xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6929 <a:rPr>
6930 <a:latin typeface="Calibri"/>
6931 <a:ea typeface="宋体"/>
6932 <a:cs typeface="Times New Roman"/>
6933 </a:rPr>
6934 <a:t>hello</a:t>
6935</a:r>"#;
6936 let r = parse_run(xml).expect("parse run");
6937 assert_eq!(r.text, "hello");
6938 assert_eq!(r.properties.latin_font.as_deref(), Some("Calibri"));
6939 assert_eq!(r.properties.eastasia_font.as_deref(), Some("宋体"));
6940 assert_eq!(r.properties.cs_font.as_deref(), Some("Times New Roman"));
6941 }
6942
6943 #[test]
6946 fn write_sp_placeholder_defaults_to_body() {
6947 use crate::oxml::shape::Sp;
6948 use crate::oxml::writer::XmlWriter;
6949 let sp = Sp {
6950 id: 3,
6951 name: "Ph".into(),
6952 is_placeholder: true,
6953 ph_idx: Some(0),
6954 ph_type: None, ..Default::default()
6956 };
6957 let mut w = XmlWriter::new();
6958 sp.write_xml(&mut w);
6959 let s = w.into_string();
6960 assert!(
6961 s.contains(r#"<p:ph type="body" idx="0"/>"#),
6962 "应写出 type=\"body\",实际:{s}"
6963 );
6964 }
6965
6966 #[test]
6969 fn connector_set_begin_end_recomputes_xfrm() {
6970 use crate::oxml::simpletypes::MsoConnectorType;
6971 use crate::shape::connector::Connector;
6972 use crate::units::EmuPoint;
6973 let mut c = Connector::new_with_type("c1", MsoConnectorType::Straight);
6974 c.set_begin(EmuPoint::new(1000, 2000));
6975 c.set_end(EmuPoint::new(5000, 6000));
6976 assert_eq!(c.properties().xfrm.off_x.unwrap().value(), 1000);
6978 assert_eq!(c.properties().xfrm.off_y.unwrap().value(), 2000);
6979 assert_eq!(c.properties().xfrm.ext_cx.unwrap().value(), 4000);
6980 assert_eq!(c.properties().xfrm.ext_cy.unwrap().value(), 4000);
6981 }
6982
6983 #[test]
6985 fn parse_minimal_sld_master() {
6986 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6987<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6988 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6989 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6990 <p:cSld>
6991 <p:spTree>
6992 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
6993 <p:grpSpPr><a:xfrm/></p:grpSpPr>
6994 <p:sp>
6995 <p:nvSpPr>
6996 <p:cNvPr id="2" name="Title Placeholder 1"/>
6997 <p:cNvSpPr txBox="1"/>
6998 <p:nvPr><p:ph type="title" idx="0"/></p:nvPr>
6999 </p:nvSpPr>
7000 <p:spPr><a:xfrm><a:off x="457200" y="274638"/><a:ext cx="8229600" cy="1143000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7001 <p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody>
7002 </p:sp>
7003 </p:spTree>
7004 </p:cSld>
7005 <p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
7006 <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
7007</p:sldMaster>"#;
7008 let master = parse_sld_master(xml).unwrap();
7009 assert_eq!(master.shapes.len(), 1, "应解析出 1 个 shape");
7010 assert_eq!(master.shapes[0].id, 2);
7011 assert_eq!(master.shapes[0].name, "Title Placeholder 1");
7012 assert!(master.shapes[0].is_placeholder);
7013 assert_eq!(master.shapes[0].ph_type.as_deref(), Some("title"));
7014 assert_eq!(master.layout_rids.len(), 1, "应解析出 1 个 layout rid");
7015 assert_eq!(master.layout_rids[0], "rId1");
7016 }
7017
7018 #[test]
7020 fn parse_minimal_sld_layout() {
7021 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7022<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7023 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7024 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7025 type="title" preserve="1">
7026 <p:cSld name="Title Slide">
7027 <p:spTree>
7028 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7029 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7030 <p:sp>
7031 <p:nvSpPr>
7032 <p:cNvPr id="2" name="Title 1"/>
7033 <p:cNvSpPr txBox="1"/>
7034 <p:nvPr><p:ph type="title" idx="0"/></p:nvPr>
7035 </p:nvSpPr>
7036 <p:spPr><a:xfrm><a:off x="685800" y="2130425"/><a:ext cx="7772400" cy="1470025"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7037 <p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody>
7038 </p:sp>
7039 </p:spTree>
7040 </p:cSld>
7041 <p:clrMapOvr bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2"/>
7042</p:sldLayout>"#;
7043 let layout = parse_sld_layout(xml).unwrap();
7044 assert_eq!(layout.type_, "title");
7045 assert_eq!(layout.name, "Title Slide");
7046 assert_eq!(layout.shapes.len(), 1, "应解析出 1 个 shape");
7047 assert_eq!(layout.shapes[0].name, "Title 1");
7048 assert!(layout.shapes[0].is_placeholder);
7049 }
7050
7051 #[test]
7053 fn parse_theme_extracts_name_and_colors() {
7054 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7055<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
7056 <a:themeElements>
7057 <a:clrScheme name="Office">
7058 <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
7059 <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
7060 <a:dk2><a:srgbClr val="1F497D"/></a:dk2>
7061 <a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
7062 <a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
7063 <a:accent2><a:srgbClr val="C0504D"/></a:accent2>
7064 <a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
7065 <a:accent4><a:srgbClr val="8064A2"/></a:accent4>
7066 <a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
7067 <a:accent6><a:srgbClr val="F79646"/></a:accent6>
7068 <a:hlink><a:srgbClr val="0000FF"/></a:hlink>
7069 <a:folHlink><a:srgbClr val="800080"/></a:folHlink>
7070 </a:clrScheme>
7071 <a:fontScheme name="Office">
7072 <a:majorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
7073 <a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
7074 </a:fontScheme>
7075 <a:fmtScheme name="Office">
7076 <a:fillStyleLst>
7077 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
7078 </a:fillStyleLst>
7079 <a:lnStyleLst/>
7080 <a:effectStyleLst/>
7081 <a:bgFillStyleLst/>
7082 </a:fmtScheme>
7083 </a:themeElements>
7084</a:theme>"#;
7085 let theme = parse_theme(xml).unwrap();
7086 assert_eq!(theme.name, "Office Theme");
7087 assert_eq!(theme.color_scheme.name, "Office");
7089 assert_eq!(
7091 format!("{:?}", theme.color_scheme.dk1),
7092 r#"Some(Sys("windowText", "000000"))"#,
7093 "dk1 mismatch"
7094 );
7095 assert_eq!(
7096 format!("{:?}", theme.color_scheme.lt1),
7097 r#"Some(Sys("window", "FFFFFF"))"#,
7098 "lt1 mismatch"
7099 );
7100 assert_eq!(
7101 format!("{:?}", theme.color_scheme.accent1),
7102 r#"Some(Srgb("4F81BD"))"#,
7103 "accent1 mismatch"
7104 );
7105 assert_eq!(
7106 format!("{:?}", theme.color_scheme.hlink),
7107 r#"Some(Srgb("0000FF"))"#,
7108 "hlink mismatch"
7109 );
7110 assert_eq!(theme.font_scheme.name, "Office");
7112 assert_eq!(theme.font_scheme.major_latin, "Calibri");
7114 assert_eq!(theme.font_scheme.minor_latin, "Calibri");
7115 assert_eq!(theme.format_scheme.name, "Office");
7117 assert!(
7118 !theme.format_scheme.raw_xml.is_empty(),
7119 "fmtScheme raw_xml should not be empty"
7120 );
7121 assert!(
7122 theme.format_scheme.raw_xml.contains("<a:fillStyleLst>"),
7123 "fmtScheme raw_xml should contain fillStyleLst"
7124 );
7125 }
7126
7127 #[test]
7129 fn parse_theme_font_scheme_ea_cs() {
7130 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7131<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Test">
7132 <a:themeElements>
7133 <a:clrScheme name="Test"/>
7134 <a:fontScheme name="Test">
7135 <a:majorFont>
7136 <a:latin typeface="Calibri Light"/>
7137 <a:ea typeface="宋体"/>
7138 <a:cs typeface="Times New Roman"/>
7139 </a:majorFont>
7140 <a:minorFont>
7141 <a:latin typeface="Calibri"/>
7142 <a:ea typeface="黑体"/>
7143 <a:cs typeface="Arial"/>
7144 </a:minorFont>
7145 </a:fontScheme>
7146 <a:fmtScheme name="Test"/>
7147 </a:themeElements>
7148</a:theme>"#;
7149 let theme = parse_theme(xml).unwrap();
7150 assert_eq!(theme.font_scheme.major_latin, "Calibri Light");
7151 assert_eq!(theme.font_scheme.major_ea, "宋体");
7152 assert_eq!(theme.font_scheme.major_cs, "Times New Roman");
7153 assert_eq!(theme.font_scheme.minor_latin, "Calibri");
7154 assert_eq!(theme.font_scheme.minor_ea, "黑体");
7155 assert_eq!(theme.font_scheme.minor_cs, "Arial");
7156 }
7157
7158 #[test]
7160 fn theme_round_trip() {
7161 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7162<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="RoundTrip">
7163 <a:themeElements>
7164 <a:clrScheme name="RT">
7165 <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
7166 <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
7167 <a:dk2><a:srgbClr val="1F497D"/></a:dk2>
7168 <a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
7169 <a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
7170 <a:accent2><a:srgbClr val="C0504D"/></a:accent2>
7171 <a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
7172 <a:accent4><a:srgbClr val="8064A2"/></a:accent4>
7173 <a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
7174 <a:accent6><a:srgbClr val="F79646"/></a:accent6>
7175 <a:hlink><a:srgbClr val="0000FF"/></a:hlink>
7176 <a:folHlink><a:srgbClr val="800080"/></a:folHlink>
7177 </a:clrScheme>
7178 <a:fontScheme name="RT">
7179 <a:majorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
7180 <a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
7181 </a:fontScheme>
7182 <a:fmtScheme name="Office"/>
7183 </a:themeElements>
7184</a:theme>"#;
7185 let theme1 = parse_theme(xml).unwrap();
7187 assert_eq!(theme1.name, "RoundTrip");
7188 assert_eq!(theme1.color_scheme.name, "RT");
7189 assert_eq!(theme1.font_scheme.name, "RT");
7190 let serialized = theme1.to_xml();
7192 let theme2 = parse_theme(&serialized).unwrap();
7194 assert_eq!(theme2.name, theme1.name);
7196 assert_eq!(theme2.color_scheme.name, theme1.color_scheme.name);
7197 assert_eq!(theme2.color_scheme.dk1, theme1.color_scheme.dk1);
7198 assert_eq!(theme2.color_scheme.accent1, theme1.color_scheme.accent1);
7199 assert_eq!(theme2.font_scheme.name, theme1.font_scheme.name);
7200 assert_eq!(
7201 theme2.font_scheme.major_latin,
7202 theme1.font_scheme.major_latin
7203 );
7204 assert_eq!(
7205 theme2.font_scheme.minor_latin,
7206 theme1.font_scheme.minor_latin
7207 );
7208 }
7209
7210 #[test]
7215 fn parse_cxn_sp_extracts_geometry_and_connections() {
7216 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7217<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7218 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7219 <p:nvCxnSpPr>
7220 <p:cNvPr id="10" name="Connector1"/>
7221 <p:cNvCxnSpPr/>
7222 <p:nvPr>
7223 <p:stCxn id="2" idx="3"/>
7224 <p:endCxn id="5" idx="1"/>
7225 </p:nvPr>
7226 </p:nvCxnSpPr>
7227 <p:spPr>
7228 <a:xfrm>
7229 <a:off x="1000" y="2000"/>
7230 <a:ext cx="5000" cy="4000"/>
7231 </a:xfrm>
7232 <a:prstGeom prst="line"><a:avLst/></a:prstGeom>
7233 </p:spPr>
7234</p:cxnSp>"#;
7235 let cxn = parse_cxn_sp(xml).expect("parse cxnSp ok");
7236 assert_eq!(cxn.id, 10);
7237 assert_eq!(cxn.name, "Connector1");
7238 assert_eq!(cxn.st_cxn, Some((2, 3)), "stCxn 应解析出 (id=2, idx=3)");
7240 assert_eq!(cxn.end_cxn, Some((5, 1)), "endCxn 应解析出 (id=5, idx=1)");
7241 assert_eq!(cxn.properties.xfrm.off_x.unwrap().value(), 1000);
7243 assert_eq!(cxn.properties.xfrm.off_y.unwrap().value(), 2000);
7244 assert_eq!(cxn.properties.xfrm.ext_cx.unwrap().value(), 5000);
7245 assert_eq!(cxn.properties.xfrm.ext_cy.unwrap().value(), 4000);
7246 }
7247
7248 #[test]
7250 fn parse_cxn_sp_without_connections() {
7251 let xml = r#"<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7252 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7253 <p:nvCxnSpPr><p:cNvPr id="20" name="FreeLine"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7254 <p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="100" cy="200"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom></p:spPr>
7255</p:cxnSp>"#;
7256 let cxn = parse_cxn_sp(xml).unwrap();
7257 assert_eq!(cxn.id, 20);
7258 assert_eq!(cxn.name, "FreeLine");
7259 assert!(cxn.st_cxn.is_none(), "无 stCxn 时应为 None");
7260 assert!(cxn.end_cxn.is_none(), "无 endCxn 时应为 None");
7261 }
7262
7263 #[test]
7265 fn parse_cxn_sp_with_style() {
7266 let xml = r#"<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7267 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7268 <p:nvCxnSpPr><p:cNvPr id="30" name="StyledConn"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7269 <p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="100" cy="200"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom></p:spPr>
7270 <p:style>
7271 <a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef>
7272 <a:fillRef idx="0"><a:schemeClr val="accent2"/></a:fillRef>
7273 </p:style>
7274</p:cxnSp>"#;
7275 let cxn = parse_cxn_sp(xml).expect("parse cxnSp ok");
7276 let style = cxn.style.as_ref().expect("style 应存在");
7277 let ln = style.line_ref.as_ref().expect("line_ref 应存在");
7278 assert_eq!(ln.idx.as_deref(), Some("2"));
7279 assert_eq!(ln.scheme_color.as_deref(), Some("accent1"));
7280 let fill = style.fill_ref.as_ref().expect("fill_ref 应存在");
7281 assert_eq!(fill.idx.as_deref(), Some("0"));
7282 assert_eq!(fill.scheme_color.as_deref(), Some("accent2"));
7283 }
7284
7285 #[test]
7287 fn parse_grp_sp_with_style() {
7288 let xml = r#"<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7289 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7290 <p:nvGrpSpPr><p:cNvPr id="40" name="StyledGroup"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7291 <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="1000" cy="1000"/><a:chOff x="0" y="0"/><a:chExt cx="1000" cy="1000"/></a:xfrm></p:grpSpPr>
7292 <p:style>
7293 <a:lnRef idx="1"><a:schemeClr val="tx1"/></a:lnRef>
7294 <a:fontRef idx="minor"><a:schemeClr val="dk1"/></a:fontRef>
7295 </p:style>
7296</p:grpSp>"#;
7297 let grp = parse_grp_sp(xml).expect("parse grpSp ok");
7298 assert_eq!(grp.id, 40);
7299 let style = grp.style.as_ref().expect("style 应存在");
7300 let ln = style.line_ref.as_ref().expect("line_ref 应存在");
7301 assert_eq!(ln.idx.as_deref(), Some("1"));
7302 assert_eq!(ln.scheme_color.as_deref(), Some("tx1"));
7303 let font = style.font_ref.as_ref().expect("font_ref 应存在");
7304 assert_eq!(font.idx.as_deref(), Some("minor"));
7305 assert_eq!(font.scheme_color.as_deref(), Some("dk1"));
7306 }
7307
7308 #[test]
7313 fn parse_grp_sp_extracts_children() {
7314 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7315<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7316 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7317 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7318 <p:nvGrpSpPr>
7319 <p:cNvPr id="100" name="Group1"/>
7320 <p:cNvGrpSpPr/>
7321 <p:nvPr/>
7322 </p:nvGrpSpPr>
7323 <p:grpSpPr>
7324 <a:xfrm>
7325 <a:off x="0" y="0"/>
7326 <a:ext cx="8000" cy="6000"/>
7327 <a:chOff x="0" y="0"/>
7328 <a:chExt cx="8000" cy="6000"/>
7329 </a:xfrm>
7330 </p:grpSpPr>
7331 <p:sp>
7332 <p:nvSpPr><p:cNvPr id="101" name="Child1"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7333 <p:spPr><a:xfrm><a:off x="100" y="100"/><a:ext cx="2000" cy="1000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7334 <p:txBody><a:bodyPr/><a:p><a:r><a:t>child text</a:t></a:r></a:p></p:txBody>
7335 </p:sp>
7336 <p:cxnSp>
7337 <p:nvCxnSpPr><p:cNvPr id="102" name="Link"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7338 <p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="3000" cy="3000"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom></p:spPr>
7339 </p:cxnSp>
7340</p:grpSp>"#;
7341 let grp = parse_grp_sp(xml).expect("parse grpSp ok");
7342 assert_eq!(grp.id, 100);
7343 assert_eq!(grp.name, "Group1");
7344 assert_eq!(grp.children.len(), 2, "应解析出 2 个子形状");
7345
7346 match &grp.children[0] {
7348 GroupChild::Sp(sp) => {
7349 assert_eq!(sp.id, 101);
7350 assert_eq!(sp.name, "Child1");
7351 assert_eq!(sp.text.paragraphs[0].runs[0].text, "child text");
7352 }
7353 other => panic!("第一个子形状应为 Sp,实际:{other:?}"),
7354 }
7355 match &grp.children[1] {
7357 GroupChild::CxnSp(cxn) => {
7358 assert_eq!(cxn.id, 102);
7359 assert_eq!(cxn.name, "Link");
7360 }
7361 other => panic!("第二个子形状应为 CxnSp,实际:{other:?}"),
7362 }
7363 }
7364
7365 #[test]
7371 fn parse_graphic_frame_with_table() {
7372 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7373<p:graphicFrame xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7374 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7375 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7376 <p:nvGraphicFramePr>
7377 <p:cNvPr id="200" name="TableFrame"/>
7378 <p:cNvGraphicFramePr/>
7379 <p:nvPr/>
7380 </p:nvGraphicFramePr>
7381 <p:xfrm>
7382 <a:off x="457200" y="1828800"/>
7383 <a:ext cx="8229600" cy="1143000"/>
7384 </p:xfrm>
7385 <a:graphic>
7386 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
7387 <a:tbl>
7388 <a:tblPr firstRow="1" bandRow="1"/>
7389 <a:tblGrid>
7390 <a:gridCol w="4114800"/>
7391 <a:gridCol w="4114800"/>
7392 </a:tblGrid>
7393 <a:tr h="370840" h="0">
7394 <a:tc>
7395 <a:txBody><a:bodyPr/><a:p><a:r><a:t>A1</a:t></a:r></a:p></a:txBody>
7396 <a:tcPr/>
7397 </a:tc>
7398 <a:tc>
7399 <a:txBody><a:bodyPr/><a:p><a:r><a:t>B1</a:t></a:r></a:p></a:txBody>
7400 <a:tcPr/>
7401 </a:tc>
7402 </a:tr>
7403 <a:tr h="370840" h="0">
7404 <a:tc>
7405 <a:txBody><a:bodyPr/><a:p><a:r><a:t>A2</a:t></a:r></a:p></a:txBody>
7406 <a:tcPr/>
7407 </a:tc>
7408 <a:tc>
7409 <a:txBody><a:bodyPr/><a:p><a:r><a:t>B2</a:t></a:r></a:p></a:txBody>
7410 <a:tcPr/>
7411 </a:tc>
7412 </a:tr>
7413 </a:tbl>
7414 </a:graphicData>
7415 </a:graphic>
7416</p:graphicFrame>"#;
7417 let frame = parse_graphic_frame(xml).expect("parse graphicFrame ok");
7418 assert_eq!(frame.id, 200);
7419 assert_eq!(frame.name, "TableFrame");
7420 assert_eq!(frame.properties.xfrm.off_x.unwrap().value(), 457200);
7422 assert_eq!(frame.properties.xfrm.off_y.unwrap().value(), 1828800);
7423 assert_eq!(frame.properties.xfrm.ext_cx.unwrap().value(), 8229600);
7424 assert_eq!(frame.properties.xfrm.ext_cy.unwrap().value(), 1143000);
7425 match &frame.graphic {
7427 crate::oxml::shape::Graphic::Table(tbl) => {
7428 assert_eq!(tbl.cols.len(), 2, "应有 2 列");
7429 assert_eq!(tbl.rows.len(), 2, "应有 2 行");
7430 assert_eq!(tbl.cols[0].width.value(), 4114800);
7431 let cell_a1 = &tbl.rows[0].cells[0];
7433 let text_a1: String = cell_a1
7434 .text
7435 .paragraphs
7436 .iter()
7437 .flat_map(|p| p.runs.iter().map(|r| r.text.as_str()))
7438 .collect();
7439 assert_eq!(text_a1, "A1");
7440 let cell_b2 = &tbl.rows[1].cells[1];
7441 let text_b2: String = cell_b2
7442 .text
7443 .paragraphs
7444 .iter()
7445 .flat_map(|p| p.runs.iter().map(|r| r.text.as_str()))
7446 .collect();
7447 assert_eq!(text_b2, "B2");
7448 }
7449 _ => panic!("期望 Table 变体"),
7450 }
7451 }
7452
7453 #[test]
7457 fn parse_sld_with_mixed_shape_types() {
7458 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7459<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7460 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7461 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7462 <p:cSld>
7463 <p:spTree>
7464 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7465 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7466 <p:sp>
7467 <p:nvSpPr><p:cNvPr id="2" name="TextBox"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7468 <p:spPr><a:xfrm><a:off x="100" y="100"/><a:ext cx="2000" cy="1000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7469 <p:txBody><a:bodyPr/><a:p><a:r><a:t>text</a:t></a:r></a:p></p:txBody>
7470 </p:sp>
7471 <p:pic>
7472 <p:nvPicPr><p:cNvPr id="3" name="Pic1"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>
7473 <p:blipFill><a:blip r:embed="rIdImg1"/></p:blipFill>
7474 <p:spPr><a:xfrm><a:off x="5000" y="5000"/><a:ext cx="3000" cy="2000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7475 </p:pic>
7476 <p:cxnSp>
7477 <p:nvCxnSpPr><p:cNvPr id="4" name="Conn1"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7478 <p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="8000" cy="6000"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom></p:spPr>
7479 </p:cxnSp>
7480 <p:grpSp>
7481 <p:nvGrpSpPr><p:cNvPr id="5" name="Group1"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7482 <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="1000" cy="1000"/><a:chOff x="0" y="0"/><a:chExt cx="1000" cy="1000"/></a:xfrm></p:grpSpPr>
7483 <p:sp>
7484 <p:nvSpPr><p:cNvPr id="6" name="Inner"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7485 <p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="500" cy="500"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7486 <p:txBody><a:bodyPr/><a:p><a:r><a:t>inner</a:t></a:r></a:p></p:txBody>
7487 </p:sp>
7488 </p:grpSp>
7489 </p:spTree>
7490 </p:cSld>
7491 <p:clrMapOvr/>
7492</p:sld>"#;
7493 let sld = parse_sld(xml).expect("parse sld ok");
7494 assert_eq!(sld.shapes.len(), 4, "应解析出 4 个形状");
7495
7496 assert!(
7498 matches!(&sld.shapes[0], OxmlSlideShape::Sp(s) if s.id == 2 && s.name == "TextBox")
7499 );
7500 assert!(
7501 matches!(&sld.shapes[1], OxmlSlideShape::Pic(p) if p.id == 3 && p.rid == "rIdImg1")
7502 );
7503 assert!(
7504 matches!(&sld.shapes[2], OxmlSlideShape::CxnSp(c) if c.id == 4 && c.name == "Conn1")
7505 );
7506 match &sld.shapes[3] {
7507 OxmlSlideShape::Group(grp) => {
7508 assert_eq!(grp.id, 5);
7509 assert_eq!(grp.name, "Group1");
7510 assert_eq!(grp.children.len(), 1, "组合内应有 1 个子形状");
7511 assert!(
7512 matches!(&grp.children[0], GroupChild::Sp(s) if s.id == 6 && s.name == "Inner")
7513 );
7514 }
7515 other => panic!("第 4 个形状应为 Group,实际:{other:?}"),
7516 }
7517 }
7518
7519 #[test]
7524 fn round_trip_sld_preserves_shapes() {
7525 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7526<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7527 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7528 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7529 <p:cSld>
7530 <p:spTree>
7531 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7532 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7533 <p:sp>
7534 <p:nvSpPr><p:cNvPr id="2" name="TB1"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7535 <p:spPr><a:xfrm><a:off x="914400" y="457200"/><a:ext cx="3657600" cy="457200"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7536 <p:txBody><a:bodyPr/><a:p><a:r><a:t>round-trip</a:t></a:r></a:p></p:txBody>
7537 </p:sp>
7538 <p:cxnSp>
7539 <p:nvCxnSpPr><p:cNvPr id="3" name="C1"/><p:cNvCxnSpPr/><p:nvPr><p:stCxn id="2" idx="0"/><p:endCxn id="5" idx="1"/></p:nvPr></p:nvCxnSpPr>
7540 <p:spPr><a:xfrm><a:off x="100" y="200"/><a:ext cx="3000" cy="4000"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom></p:spPr>
7541 </p:cxnSp>
7542 </p:spTree>
7543 </p:cSld>
7544 <p:clrMapOvr/>
7545</p:sld>"#;
7546
7547 let sld1 = parse_sld(xml).expect("first parse ok");
7549 assert_eq!(sld1.shapes.len(), 2);
7550
7551 let rewritten = sld1.to_xml();
7553
7554 let sld2 = parse_sld(&rewritten).expect("second parse ok");
7556 assert_eq!(sld2.shapes.len(), 2, "round-trip 后形状数应不变");
7557
7558 match (&sld1.shapes[0], &sld2.shapes[0]) {
7560 (OxmlSlideShape::Sp(a), OxmlSlideShape::Sp(b)) => {
7561 assert_eq!(a.id, b.id, "Sp id 应保留");
7562 assert_eq!(a.name, b.name, "Sp name 应保留");
7563 assert_eq!(
7564 a.text.paragraphs[0].runs[0].text, b.text.paragraphs[0].runs[0].text,
7565 "Sp 文本应保留"
7566 );
7567 assert_eq!(
7569 a.properties.xfrm.off_x, b.properties.xfrm.off_x,
7570 "Sp off_x 应保留"
7571 );
7572 assert_eq!(
7573 a.properties.xfrm.ext_cx, b.properties.xfrm.ext_cx,
7574 "Sp ext_cx 应保留"
7575 );
7576 }
7577 _ => panic!("形状类型不匹配"),
7578 }
7579
7580 match (&sld1.shapes[1], &sld2.shapes[1]) {
7582 (OxmlSlideShape::CxnSp(a), OxmlSlideShape::CxnSp(b)) => {
7583 assert_eq!(a.id, b.id, "CxnSp id 应保留");
7584 assert_eq!(a.name, b.name, "CxnSp name 应保留");
7585 assert_eq!(a.st_cxn, b.st_cxn, "stCxn 挂接应保留");
7586 assert_eq!(a.end_cxn, b.end_cxn, "endCxn 挂接应保留");
7587 }
7588 _ => panic!("形状类型不匹配"),
7589 }
7590 }
7591
7592 #[test]
7594 fn round_trip_table_preserves_cells() {
7595 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7596<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7597 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7598 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7599 <p:cSld>
7600 <p:spTree>
7601 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7602 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7603 <p:graphicFrame>
7604 <p:nvGraphicFramePr><p:cNvPr id="10" name="Tbl1"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
7605 <p:xfrm><a:off x="0" y="0"/><a:ext cx="8000" cy="4000"/></p:xfrm>
7606 <a:graphic>
7607 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
7608 <a:tbl>
7609 <a:tblPr/>
7610 <a:tblGrid><a:gridCol w="4000"/><a:gridCol w="4000"/></a:tblGrid>
7611 <a:tr h="2000">
7612 <a:tc><a:txBody><a:bodyPr/><a:p><a:r><a:t>R1C1</a:t></a:r></a:p></a:txBody><a:tcPr/></a:tc>
7613 <a:tc><a:txBody><a:bodyPr/><a:p><a:r><a:t>R1C2</a:t></a:r></a:p></a:txBody><a:tcPr/></a:tc>
7614 </a:tr>
7615 </a:tbl>
7616 </a:graphicData>
7617 </a:graphic>
7618 </p:graphicFrame>
7619 </p:spTree>
7620 </p:cSld>
7621 <p:clrMapOvr/>
7622</p:sld>"#;
7623
7624 let sld1 = parse_sld(xml).expect("first parse ok");
7625 let rewritten = sld1.to_xml();
7626 let sld2 = parse_sld(&rewritten).expect("second parse ok");
7627
7628 assert_eq!(sld1.shapes.len(), sld2.shapes.len(), "形状数应一致");
7629 match (&sld1.shapes[0], &sld2.shapes[0]) {
7630 (OxmlSlideShape::GraphicFrame(f1), OxmlSlideShape::GraphicFrame(f2)) => {
7631 assert_eq!(f1.id, f2.id, "GraphicFrame id 应保留");
7632 assert_eq!(f1.name, f2.name, "GraphicFrame name 应保留");
7633 match (&f1.graphic, &f2.graphic) {
7634 (
7635 crate::oxml::shape::Graphic::Table(t1),
7636 crate::oxml::shape::Graphic::Table(t2),
7637 ) => {
7638 assert_eq!(t1.cols.len(), t2.cols.len(), "列数应保留");
7639 assert_eq!(t1.rows.len(), t2.rows.len(), "行数应保留");
7640 let get_text = |t: &crate::oxml::table::Table| -> String {
7642 t.rows[0].cells[0]
7643 .text
7644 .paragraphs
7645 .iter()
7646 .flat_map(|p| p.runs.iter().map(|r| r.text.as_str()))
7647 .collect()
7648 };
7649 assert_eq!(get_text(t1), get_text(t2), "单元格文本应保留");
7650 assert_eq!(get_text(t1), "R1C1");
7651 }
7652 _ => panic!("期望两侧均为 Table 变体"),
7653 }
7654 }
7655 _ => panic!("形状类型不匹配"),
7656 }
7657 }
7658
7659 #[test]
7661 fn parse_sld_empty_sp_tree() {
7662 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7663<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7664 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7665 <p:cSld>
7666 <p:spTree>
7667 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7668 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7669 </p:spTree>
7670 </p:cSld>
7671 <p:clrMapOvr/>
7672</p:sld>"#;
7673 let sld = parse_sld(xml).expect("parse ok");
7674 assert_eq!(sld.shapes.len(), 0, "空 spTree 应解析出 0 个形状");
7675 }
7676
7677 #[test]
7682 fn parse_grp_sp_nested_group() {
7683 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7684<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7685 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7686 <p:nvGrpSpPr><p:cNvPr id="1" name="Outer"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7687 <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="10000" cy="10000"/><a:chOff x="0" y="0"/><a:chExt cx="10000" cy="10000"/></a:xfrm></p:grpSpPr>
7688 <p:sp>
7689 <p:nvSpPr><p:cNvPr id="2" name="OuterChild"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7690 <p:spPr><a:xfrm><a:off x="100" y="100"/><a:ext cx="1000" cy="1000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7691 <p:txBody><a:bodyPr/><a:p><a:r><a:t>outer</a:t></a:r></a:p></p:txBody>
7692 </p:sp>
7693 <p:grpSp>
7694 <p:nvGrpSpPr><p:cNvPr id="3" name="Inner"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7695 <p:grpSpPr><a:xfrm><a:off x="2000" y="2000"/><a:ext cx="5000" cy="5000"/><a:chOff x="0" y="0"/><a:chExt cx="5000" cy="5000"/></a:xfrm></p:grpSpPr>
7696 <p:sp>
7697 <p:nvSpPr><p:cNvPr id="4" name="InnerChild"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7698 <p:spPr><a:xfrm><a:off x="50" y="50"/><a:ext cx="500" cy="500"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
7699 <p:txBody><a:bodyPr/><a:p><a:r><a:t>inner</a:t></a:r></a:p></p:txBody>
7700 </p:sp>
7701 </p:grpSp>
7702</p:grpSp>"#;
7703 let grp = parse_grp_sp(xml).expect("parse ok");
7704 assert_eq!(grp.id, 1);
7705 assert_eq!(grp.name, "Outer");
7706 assert_eq!(
7707 grp.children.len(),
7708 2,
7709 "外层组合应有 2 个子形状(sp + 嵌套 grpSp)"
7710 );
7711
7712 assert!(
7714 matches!(&grp.children[0], GroupChild::Sp(s) if s.id == 2 && s.name == "OuterChild")
7715 );
7716
7717 match &grp.children[1] {
7719 GroupChild::Group(inner) => {
7720 assert_eq!(inner.id, 3);
7721 assert_eq!(inner.name, "Inner");
7722 assert_eq!(inner.children.len(), 1, "内层组合应有 1 个子形状");
7723 assert!(
7724 matches!(&inner.children[0], GroupChild::Sp(s) if s.id == 4 && s.name == "InnerChild")
7725 );
7726 }
7727 other => panic!("第二个子形状应为 Group,实际:{other:?}"),
7728 }
7729 }
7730
7731 #[test]
7735 fn parse_sppr_grad_fill_linear() {
7736 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7737 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7738 <a:xfrm><a:off x="100" y="200"/><a:ext cx="300" cy="400"/></a:xfrm>
7739 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7740 <a:gradFill flip="none" rotWithShape="1">
7741 <a:gsLst>
7742 <a:gs pos="0"><a:srgbClr val="FF0000"/></a:gs>
7743 <a:gs pos="100000"><a:srgbClr val="0000FF"/></a:gs>
7744 </a:gsLst>
7745 <a:lin ang="5400000" scaled="1"/>
7746 </a:gradFill>
7747</p:spPr>"#;
7748 let sp = parse_sppr(xml).expect("parse ok");
7749 match &sp.fill {
7750 Fill::Gradient(grad) => {
7751 assert_eq!(grad.stops.len(), 2, "应有 2 个渐变光轨");
7752 assert_eq!(grad.stops[0].pos, 0);
7753 assert_eq!(grad.stops[1].pos, 100000);
7754 assert!(
7756 matches!(grad.stops[0].color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0x00)
7757 );
7758 assert!(
7759 matches!(grad.stops[1].color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0x00 && c.2 == 0xFF)
7760 );
7761 assert_eq!(grad.gradient_type, GradientType::Linear(5400000));
7763 assert_eq!(grad.flip.as_deref(), Some("none"));
7765 assert_eq!(grad.rot_with_shape, Some(true));
7766 }
7767 other => panic!("fill 应为 Gradient,实际:{other:?}"),
7768 }
7769 }
7770
7771 #[test]
7773 fn parse_sppr_grad_fill_path() {
7774 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7775 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7776 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7777 <a:gradFill>
7778 <a:gsLst>
7779 <a:gs pos="0"><a:srgbClr val="00FF00"/></a:gs>
7780 <a:gs pos="50000"><a:srgbClr val="FFFF00"/></a:gs>
7781 <a:gs pos="100000"><a:srgbClr val="FF00FF"/></a:gs>
7782 </a:gsLst>
7783 <a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>
7784 </a:gradFill>
7785</p:spPr>"#;
7786 let sp = parse_sppr(xml).expect("parse ok");
7787 match &sp.fill {
7788 Fill::Gradient(grad) => {
7789 assert_eq!(grad.stops.len(), 3, "应有 3 个渐变光轨");
7790 assert_eq!(grad.stops[1].pos, 50000);
7791 assert_eq!(grad.gradient_type, GradientType::Path(GradientPath::Circle));
7793 }
7794 other => panic!("fill 应为 Gradient,实际:{other:?}"),
7795 }
7796 }
7797
7798 #[test]
7800 fn parse_sppr_patt_fill() {
7801 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7802 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7803 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7804 <a:pattFill prst="pct5">
7805 <a:fgClr><a:srgbClr val="FF0000"/></a:fgClr>
7806 <a:bgClr><a:srgbClr val="FFFFFF"/></a:bgClr>
7807 </a:pattFill>
7808</p:spPr>"#;
7809 let sp = parse_sppr(xml).expect("parse ok");
7810 match &sp.fill {
7811 Fill::Pattern(patt) => {
7812 assert_eq!(patt.prst, "pct5", "预置图案应为 pct5");
7813 assert!(
7815 matches!(patt.fg_color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0x00)
7816 );
7817 assert!(
7819 matches!(patt.bg_color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0xFF && c.2 == 0xFF)
7820 );
7821 }
7822 other => panic!("fill 应为 Pattern,实际:{other:?}"),
7823 }
7824 }
7825
7826 #[test]
7831 fn parse_sppr_blip_fill_stretch() {
7832 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7833 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7834 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7835 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7836 <a:blipFill>
7837 <a:blip r:embed="rId1"/>
7838 <a:stretch><a:fillRect/></a:stretch>
7839 </a:blipFill>
7840</p:spPr>"#;
7841 let sp = parse_sppr(xml).expect("parse ok");
7842 match &sp.fill {
7843 Fill::Blip { rid, mode } => {
7844 assert_eq!(rid, "rId1", "rid 应为 rId1");
7845 assert!(
7846 matches!(mode, crate::oxml::sppr::BlipFillMode::Stretch),
7847 "mode 应为 Stretch,实际:{:?}",
7848 mode
7849 );
7850 }
7851 other => panic!("fill 应为 Blip,实际:{other:?}"),
7852 }
7853 }
7854
7855 #[test]
7857 fn parse_sppr_blip_fill_tile() {
7858 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7859 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7860 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7861 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7862 <a:blipFill>
7863 <a:blip r:embed="rIdImg7"/>
7864 <a:tile tx="100" ty="200" sx="50000" sy="50000" flip="x" algn="ctr"/>
7865 </a:blipFill>
7866</p:spPr>"#;
7867 let sp = parse_sppr(xml).expect("parse ok");
7868 match &sp.fill {
7869 Fill::Blip { rid, mode } => {
7870 assert_eq!(rid, "rIdImg7", "rid 应为 rIdImg7");
7871 match mode {
7872 crate::oxml::sppr::BlipFillMode::Tile {
7873 tx,
7874 ty,
7875 sx,
7876 sy,
7877 flip,
7878 algn,
7879 } => {
7880 assert_eq!(*tx, Some(100), "tx 应为 100");
7881 assert_eq!(*ty, Some(200), "ty 应为 200");
7882 assert_eq!(*sx, Some(50000), "sx 应为 50000");
7883 assert_eq!(*sy, Some(50000), "sy 应为 50000");
7884 assert_eq!(flip.as_deref(), Some("x"), "flip 应为 x");
7885 assert_eq!(algn.as_deref(), Some("ctr"), "algn 应为 ctr");
7886 }
7887 other => panic!("mode 应为 Tile,实际:{other:?}"),
7888 }
7889 }
7890 other => panic!("fill 应为 Blip,实际:{other:?}"),
7891 }
7892 }
7893
7894 #[test]
7896 fn parse_sppr_blip_fill_self_closing() {
7897 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7898 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7899 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7900 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7901 <a:blipFill>
7902 <a:blip r:embed="rId2"/>
7903 <a:stretch/>
7904 </a:blipFill>
7905</p:spPr>"#;
7906 let sp = parse_sppr(xml).expect("parse ok");
7907 match &sp.fill {
7908 Fill::Blip { rid, mode } => {
7909 assert_eq!(rid, "rId2");
7910 assert!(matches!(mode, crate::oxml::sppr::BlipFillMode::Stretch));
7911 }
7912 other => panic!("fill 应为 Blip,实际:{other:?}"),
7913 }
7914 }
7915
7916 #[test]
7921 fn parse_sppr_grad_fill_self_closing_lin() {
7922 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7923 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7924 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7925 <a:gradFill>
7926 <a:gsLst>
7927 <a:gs pos="0"><a:srgbClr val="FF0000"/></a:gs>
7928 <a:gs pos="100000"><a:srgbClr val="0000FF"/></a:gs>
7929 </a:gsLst>
7930 <a:lin ang="2700000" scaled="0"/>
7931 </a:gradFill>
7932</p:spPr>"#;
7933 let sp = parse_sppr(xml).expect("parse ok");
7934 match &sp.fill {
7935 Fill::Gradient(grad) => {
7936 assert_eq!(grad.gradient_type, GradientType::Linear(2700000));
7937 assert_eq!(grad.stops.len(), 2);
7938 }
7939 other => panic!("fill 应为 Gradient,实际:{other:?}"),
7940 }
7941 }
7942
7943 #[test]
7947 fn parse_sppr_ln_with_arrow_heads() {
7948 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7949 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7950 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7951 <a:ln w="12700">
7952 <a:solidFill><a:srgbClr val="000000"/></a:solidFill>
7953 <a:headEnd type="triangle" w="lg" len="med"/>
7954 <a:tailEnd type="stealth" w="sm" len="lg"/>
7955 </a:ln>
7956</p:spPr>"#;
7957 let sp = parse_sppr(xml).expect("parse ok");
7958 let ln = sp.line.expect("应有 line");
7959 let head = ln.head_end.expect("应有 headEnd");
7961 assert_eq!(head.arrow_type, ArrowType::Triangle);
7962 assert_eq!(head.width, ArrowSize::Large);
7963 assert_eq!(head.length, ArrowSize::Medium);
7964 let tail = ln.tail_end.expect("应有 tailEnd");
7966 assert_eq!(tail.arrow_type, ArrowType::Stealth);
7967 assert_eq!(tail.width, ArrowSize::Small);
7968 assert_eq!(tail.length, ArrowSize::Large);
7969 }
7970
7971 #[test]
7973 fn parse_sppr_ln_with_join() {
7974 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7976 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7977 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7978 <a:ln><a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:round/></a:ln>
7979</p:spPr>"#;
7980 let sp = parse_sppr(xml).expect("parse ok");
7981 let ln = sp.line.expect("应有 line");
7982 assert_eq!(ln.join, Some(LineJoin::Round));
7983
7984 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7986 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7987 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7988 <a:ln><a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:miter lim="600000"/></a:ln>
7989</p:spPr>"#;
7990 let sp = parse_sppr(xml).expect("parse ok");
7991 let ln = sp.line.expect("应有 line");
7992 assert_eq!(ln.join, Some(LineJoin::Miter(600000)));
7993
7994 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7996 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7997 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7998 <a:ln><a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:bevel/></a:ln>
7999</p:spPr>"#;
8000 let sp = parse_sppr(xml).expect("parse ok");
8001 let ln = sp.line.expect("应有 line");
8002 assert_eq!(ln.join, Some(LineJoin::Bevel));
8003 }
8004
8005 #[test]
8007 fn parse_sppr_ln_with_grad_fill() {
8008 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8009 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8010 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8011 <a:ln w="12700">
8012 <a:gradFill>
8013 <a:gsLst>
8014 <a:gs pos="0"><a:srgbClr val="FF0000"/></a:gs>
8015 <a:gs pos="100000"><a:srgbClr val="00FF00"/></a:gs>
8016 </a:gsLst>
8017 <a:lin ang="0" scaled="1"/>
8018 </a:gradFill>
8019 </a:ln>
8020</p:spPr>"#;
8021 let sp = parse_sppr(xml).expect("parse ok");
8022 let ln = sp.line.expect("应有 line");
8023 match &ln.fill {
8024 Fill::Gradient(grad) => {
8025 assert_eq!(grad.stops.len(), 2);
8026 assert_eq!(grad.gradient_type, GradientType::Linear(0));
8027 }
8028 other => panic!("line fill 应为 Gradient,实际:{other:?}"),
8029 }
8030 }
8031
8032 #[test]
8034 fn parse_sppr_ln_with_patt_fill() {
8035 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8036 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8037 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8038 <a:ln w="12700">
8039 <a:pattFill prst="cross">
8040 <a:fgClr><a:srgbClr val="FF0000"/></a:fgClr>
8041 <a:bgClr><a:srgbClr val="FFFFFF"/></a:bgClr>
8042 </a:pattFill>
8043 </a:ln>
8044</p:spPr>"#;
8045 let sp = parse_sppr(xml).expect("parse ok");
8046 let ln = sp.line.expect("应有 line");
8047 match &ln.fill {
8048 Fill::Pattern(patt) => {
8049 assert_eq!(patt.prst, "cross");
8050 assert!(matches!(patt.fg_color, Color::RGB(c) if c.0 == 0xFF));
8051 assert!(
8052 matches!(patt.bg_color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0xFF && c.2 == 0xFF)
8053 );
8054 }
8055 other => panic!("line fill 应为 Pattern,实际:{other:?}"),
8056 }
8057 }
8058
8059 #[test]
8061 fn parse_sppr_effect_lst_outer_shadow() {
8062 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8063 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8064 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8065 <a:effectLst>
8066 <a:outerShdw blurRad="40000" dist="38100" dir="2700000" rotWithShape="0">
8067 <a:srgbClr val="000000"/>
8068 </a:outerShdw>
8069 </a:effectLst>
8070</p:spPr>"#;
8071 let sp = parse_sppr(xml).expect("parse ok");
8072 let effects = sp.effects.expect("应有 effects");
8073 let shdw = effects.outer_shadow.expect("应有 outer_shadow");
8074 assert_eq!(shdw.dir, 2_700_000);
8075 assert_eq!(shdw.dist, 38100);
8076 assert_eq!(shdw.blur_rad, 40000);
8077 assert_eq!(shdw.rot_with_shape, Some(false));
8078 assert!(matches!(shdw.color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0x00 && c.2 == 0x00));
8079 }
8080
8081 #[test]
8083 fn parse_sppr_effect_lst_glow_and_soft_edge() {
8084 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8085 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8086 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8087 <a:effectLst>
8088 <a:glow rad="50000">
8089 <a:srgbClr val="FF00FF"/>
8090 </a:glow>
8091 <a:softEdge rad="25000"/>
8092 </a:effectLst>
8093</p:spPr>"#;
8094 let sp = parse_sppr(xml).expect("parse ok");
8095 let effects = sp.effects.expect("应有 effects");
8096 let glow = effects.glow.expect("应有 glow");
8097 assert_eq!(glow.rad, 50000);
8098 assert!(matches!(glow.color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0xFF));
8099 let se = effects.soft_edge.expect("应有 soft_edge");
8100 assert_eq!(se.rad, 25000);
8101 }
8102
8103 #[test]
8105 fn parse_sppr_effect_lst_reflection() {
8106 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8107 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8108 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8109 <a:effectLst>
8110 <a:reflection blurRad="50000" stA="52000" stPos="0" endA="30000" endPos="50000" dist="38100" dir="5400000"/>
8111 </a:effectLst>
8112</p:spPr>"#;
8113 let sp = parse_sppr(xml).expect("parse ok");
8114 let effects = sp.effects.expect("应有 effects");
8115 let refl = effects.reflection.expect("应有 reflection");
8116 assert_eq!(refl.blur_rad, Some(50000));
8117 assert_eq!(refl.st_a, Some(52000));
8118 assert_eq!(refl.st_pos, Some(0));
8119 assert_eq!(refl.end_a, Some(30000));
8120 assert_eq!(refl.end_pos, Some(50000));
8121 assert_eq!(refl.dist, Some(38100));
8122 assert_eq!(refl.dir, Some(5_400_000));
8123 }
8124
8125 #[test]
8129 fn round_trip_cell_merge_attributes() {
8130 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8131<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8132 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8133 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8134 <p:cSld><p:spTree>
8135 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8136 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8137 <p:graphicFrame>
8138 <p:nvGraphicFramePr><p:cNvPr id="2" name="MergedTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8139 <p:xfrm><a:off x="0" y="0"/><a:ext cx="6000000" cy="4000000"/></p:xfrm>
8140 <a:graphic>
8141 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8142 <a:tbl>
8143 <a:tblPr><a:tableStyleId>{5940675A-B579-460E-94D1-54222C63F5DA}</a:tableStyleId></a:tblPr>
8144 <a:tblGrid>
8145 <a:gridCol w="2000000"/>
8146 <a:gridCol w="2000000"/>
8147 <a:gridCol w="2000000"/>
8148 </a:tblGrid>
8149 <a:tr h="2000000">
8150 <a:tc gridSpan="3" rowSpan="2">
8151 <a:txBody><a:bodyPr/><a:p><a:r><a:t>Merged</a:t></a:r></a:p></a:txBody>
8152 <a:tcPr/>
8153 </a:tc>
8154 </a:tr>
8155 <a:tr h="2000000">
8156 <a:tc hMerge="1"><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8157 <a:tc hMerge="1"><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8158 <a:tc vMerge="1"><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8159 </a:tr>
8160 </a:tbl>
8161 </a:graphicData>
8162 </a:graphic>
8163 </p:graphicFrame>
8164 </p:spTree></p:cSld>
8165</p:sld>"#;
8166 let sld = parse_sld(xml).expect("parse ok");
8167 let frame = sld
8169 .shapes
8170 .iter()
8171 .find_map(|s| {
8172 if let OxmlSlideShape::GraphicFrame(g) = s {
8173 Some(g)
8174 } else {
8175 None
8176 }
8177 })
8178 .expect("应有 graphicFrame");
8179 match &frame.graphic {
8180 crate::oxml::shape::Graphic::Table(tbl) => {
8181 assert_eq!(tbl.rows.len(), 2, "应有 2 行");
8182 assert_eq!(
8184 tbl.table_style
8185 .as_ref()
8186 .expect("应有 table_style")
8187 .style_id(),
8188 "{5940675A-B579-460E-94D1-54222C63F5DA}"
8189 );
8190 let origin = &tbl.rows[0].cells[0];
8192 assert_eq!(origin.grid_span, 3, "gridSpan 应为 3");
8193 assert_eq!(origin.row_span, 2, "rowSpan 应为 2");
8194 assert!(!origin.h_merge, "合并源不应有 hMerge");
8195 assert!(!origin.v_merge, "合并源不应有 vMerge");
8196 let h1 = &tbl.rows[1].cells[0];
8198 assert!(h1.h_merge, "应有 hMerge");
8199 let v1 = &tbl.rows[1].cells[2];
8200 assert!(v1.v_merge, "应有 vMerge");
8201 }
8202 _ => panic!("期望 Table 变体"),
8203 }
8204 }
8205
8206 #[test]
8210 fn round_trip_table_style_id() {
8211 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8212<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8213 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8214 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8215 <p:cSld><p:spTree>
8216 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8217 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8218 <p:graphicFrame>
8219 <p:nvGraphicFramePr><p:cNvPr id="2" name="StyledTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8220 <p:xfrm><a:off x="0" y="0"/><a:ext cx="4000000" cy="2000000"/></p:xfrm>
8221 <a:graphic>
8222 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8223 <a:tbl>
8224 <a:tblPr><a:tableStyleId>{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}</a:tableStyleId></a:tblPr>
8225 <a:tblGrid><a:gridCol w="2000000"/><a:gridCol w="2000000"/></a:tblGrid>
8226 <a:tr h="2000000">
8227 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8228 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8229 </a:tr>
8230 </a:tbl>
8231 </a:graphicData>
8232 </a:graphic>
8233 </p:graphicFrame>
8234 </p:spTree></p:cSld>
8235</p:sld>"#;
8236 let sld = parse_sld(xml).expect("parse ok");
8237 let frame = sld
8238 .shapes
8239 .iter()
8240 .find_map(|s| {
8241 if let OxmlSlideShape::GraphicFrame(g) = s {
8242 Some(g)
8243 } else {
8244 None
8245 }
8246 })
8247 .expect("应有 graphicFrame");
8248 match &frame.graphic {
8249 crate::oxml::shape::Graphic::Table(tbl) => {
8250 let style = tbl.table_style.as_ref().expect("应有 table_style");
8252 assert_eq!(style.style_id(), "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}");
8253 let mut w = crate::oxml::writer::XmlWriter::new();
8255 tbl.write_xml(&mut w);
8256 let out = &w.buf;
8257 assert!(out.contains(
8258 "<a:tableStyleId>{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}</a:tableStyleId>"
8259 ));
8260 }
8261 _ => panic!("期望 Table 变体"),
8262 }
8263 }
8264
8265 #[test]
8267 fn parse_table_without_style_id() {
8268 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8269<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8270 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8271 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8272 <p:cSld><p:spTree>
8273 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8274 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8275 <p:graphicFrame>
8276 <p:nvGraphicFramePr><p:cNvPr id="2" name="PlainTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8277 <p:xfrm><a:off x="0" y="0"/><a:ext cx="4000000" cy="2000000"/></p:xfrm>
8278 <a:graphic>
8279 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8280 <a:tbl>
8281 <a:tblPr/>
8282 <a:tblGrid><a:gridCol w="2000000"/><a:gridCol w="2000000"/></a:tblGrid>
8283 <a:tr h="2000000">
8284 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8285 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8286 </a:tr>
8287 </a:tbl>
8288 </a:graphicData>
8289 </a:graphic>
8290 </p:graphicFrame>
8291 </p:spTree></p:cSld>
8292</p:sld>"#;
8293 let sld = parse_sld(xml).expect("parse ok");
8294 let frame = sld
8295 .shapes
8296 .iter()
8297 .find_map(|s| {
8298 if let OxmlSlideShape::GraphicFrame(g) = s {
8299 Some(g)
8300 } else {
8301 None
8302 }
8303 })
8304 .expect("应有 graphicFrame");
8305 match &frame.graphic {
8306 crate::oxml::shape::Graphic::Table(tbl) => {
8307 assert!(
8308 tbl.table_style.is_none(),
8309 "无 tableStyleId 时 table_style 应为 None"
8310 );
8311 }
8312 _ => panic!("期望 Table 变体"),
8313 }
8314 }
8315
8316 #[test]
8321 fn round_trip_cell_borders_margins_anchor() {
8322 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8323<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8324 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8325 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8326 <p:cSld><p:spTree>
8327 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8328 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8329 <p:graphicFrame>
8330 <p:nvGraphicFramePr><p:cNvPr id="2" name="FormattedTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8331 <p:xfrm><a:off x="0" y="0"/><a:ext cx="4000000" cy="2000000"/></p:xfrm>
8332 <a:graphic>
8333 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8334 <a:tbl>
8335 <a:tblPr><a:tableStyleId>{5940675A-B579-460E-94D1-54222C63F5DA}</a:tableStyleId></a:tblPr>
8336 <a:tblGrid><a:gridCol w="2000000"/><a:gridCol w="2000000"/></a:tblGrid>
8337 <a:tr h="2000000">
8338 <a:tc>
8339 <a:txBody><a:bodyPr/><a:p><a:r><a:t>A1</a:t></a:r></a:p></a:txBody>
8340 <a:tcPr marT="50000" marL="60000" marB="70000" marR="80000" anchor="ctr">
8341 <a:lnL w="9525"><a:solidFill><a:srgbClr val="FF0000"/></a:solidFill></a:lnL>
8342 <a:lnR w="9525"><a:solidFill><a:srgbClr val="00FF00"/></a:solidFill></a:lnR>
8343 <a:lnT w="9525"><a:solidFill><a:srgbClr val="0000FF"/></a:solidFill></a:lnT>
8344 <a:lnB w="9525"><a:noFill/></a:lnB>
8345 </a:tcPr>
8346 </a:tc>
8347 <a:tc>
8348 <a:txBody><a:bodyPr/><a:p><a:r><a:t>B1</a:t></a:r></a:p></a:txBody>
8349 <a:tcPr/>
8350 </a:tc>
8351 </a:tr>
8352 </a:tbl>
8353 </a:graphicData>
8354 </a:graphic>
8355 </p:graphicFrame>
8356 </p:spTree></p:cSld>
8357</p:sld>"#;
8358 let sld = parse_sld(xml).expect("parse ok");
8359 let frame = sld
8360 .shapes
8361 .iter()
8362 .find_map(|s| {
8363 if let OxmlSlideShape::GraphicFrame(g) = s {
8364 Some(g)
8365 } else {
8366 None
8367 }
8368 })
8369 .expect("应有 graphicFrame");
8370 let tbl = match &frame.graphic {
8371 crate::oxml::shape::Graphic::Table(t) => t,
8372 _ => panic!("期望 Table 变体"),
8373 };
8374 let cell = &tbl.rows[0].cells[0];
8375 assert_eq!(cell.margin.0.map(|m| m.value()), Some(50000), "marT");
8377 assert_eq!(cell.margin.1.map(|m| m.value()), Some(60000), "marL");
8378 assert_eq!(cell.margin.2.map(|m| m.value()), Some(70000), "marB");
8379 assert_eq!(cell.margin.3.map(|m| m.value()), Some(80000), "marR");
8380 assert_eq!(
8382 cell.anchor,
8383 crate::oxml::table::VerticalAnchor::Middle,
8384 "anchor=ctr"
8385 );
8386 let bl = cell.border_left.as_ref().expect("应有 lnL");
8388 assert_eq!(bl.width.value(), 9525);
8389 assert!(matches!(bl.color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0x00));
8390 let br = cell.border_right.as_ref().expect("应有 lnR");
8391 assert!(matches!(br.color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0xFF && c.2 == 0x00));
8392 let bt = cell.border_top.as_ref().expect("应有 lnT");
8393 assert!(matches!(bt.color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0x00 && c.2 == 0xFF));
8394 let bb = cell.border_bottom.as_ref().expect("应有 lnB");
8395 assert!(bb.no_fill, "lnB 应为 noFill");
8396 }
8397
8398 #[test]
8402 fn parse_txbody_multi_column() {
8403 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8404 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8405 <a:bodyPr numCol="3" spcCol="91440" anchor="ctr" wrap="square" lIns="91440" tIns="45720" rIns="91440" bIns="45720"/>
8406 <a:p><a:r><a:t>multi-col text</a:t></a:r></a:p>
8407</p:txBody>"#;
8408 let tb = parse_txbody(xml).expect("parse ok");
8409 let bp = tb.body_properties.as_ref().expect("应有 body_properties");
8410 assert_eq!(bp.num_cols, Some(3), "numCol");
8411 assert_eq!(bp.col_spacing.map(|v| v.value()), Some(91440), "spcCol");
8412 assert_eq!(bp.anchor, Some(MsoAnchor::Middle), "anchor=ctr");
8413 assert_eq!(bp.wrap, Some(TextWrapping::Square), "wrap=square");
8414 let insets = bp.insets.as_ref().expect("应有 insets");
8415 assert_eq!(insets.left.value(), 91440, "lIns");
8416 assert_eq!(insets.top.value(), 45720, "tIns");
8417 assert_eq!(insets.right.value(), 91440, "rIns");
8418 assert_eq!(insets.bottom.value(), 45720, "bIns");
8419 assert_eq!(tb.paragraphs.len(), 1, "应有一个段落");
8421 assert_eq!(tb.paragraphs[0].runs.len(), 1, "应有一个 run");
8422 assert_eq!(tb.paragraphs[0].runs[0].text, "multi-col text");
8423 }
8424
8425 #[test]
8429 fn parse_txbody_self_closing_body_pr() {
8430 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8431 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8432 <a:bodyPr numCol="2" spcCol="45720"/>
8433 <a:p/>
8434</p:txBody>"#;
8435 let tb = parse_txbody(xml).expect("parse ok");
8436 let bp = tb.body_properties.as_ref().expect("应有 body_properties");
8437 assert_eq!(bp.num_cols, Some(2), "numCol");
8438 assert_eq!(bp.col_spacing.map(|v| v.value()), Some(45720), "spcCol");
8439 }
8440
8441 #[test]
8445 fn parse_txbody_with_sp_auto_fit() {
8446 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8447 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8448 <a:bodyPr numCol="1"><a:spAutoFit/></a:bodyPr>
8449 <a:p/>
8450</p:txBody>"#;
8451 let tb = parse_txbody(xml).expect("parse ok");
8452 let bp = tb.body_properties.as_ref().expect("应有 body_properties");
8453 assert_eq!(bp.num_cols, Some(1), "numCol=1");
8454 assert!(bp.sp_auto_fit, "应启用 spAutoFit");
8455 assert!(!bp.norm_autofit, "不应启用 normAutofit");
8456 }
8457
8458 #[test]
8462 fn parse_paragraph_bullet_styles() {
8463 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8465 <a:buChar char="•"/>
8466</a:pPr>"#;
8467 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8468 assert!(ppr.bullet, "bullet 应为 true");
8469 match &ppr.bullet_style {
8470 Some(BulletStyle::Char { char }) => assert_eq!(char, "•"),
8471 other => panic!("期望 Char,实际: {other:?}"),
8472 }
8473
8474 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8476 <a:buAutoNum type="arabicPeriod" startAt="3"/>
8477</a:pPr>"#;
8478 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8479 assert!(ppr.bullet);
8480 match &ppr.bullet_style {
8481 Some(BulletStyle::AutoNum {
8482 auto_num_type,
8483 start_at,
8484 }) => {
8485 assert_eq!(auto_num_type, "arabicPeriod");
8486 assert_eq!(*start_at, Some(3));
8487 }
8488 other => panic!("期望 AutoNum,实际: {other:?}"),
8489 }
8490
8491 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8493 <a:buAutoNum type="alphaLcParenR"/>
8494</a:pPr>"#;
8495 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8496 match &ppr.bullet_style {
8497 Some(BulletStyle::AutoNum {
8498 auto_num_type,
8499 start_at,
8500 }) => {
8501 assert_eq!(auto_num_type, "alphaLcParenR");
8502 assert_eq!(*start_at, None);
8503 }
8504 other => panic!("期望 AutoNum,实际: {other:?}"),
8505 }
8506
8507 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8509 <a:buNone/>
8510</a:pPr>"#;
8511 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8512 assert!(!ppr.bullet, "bullet 应为 false");
8513 assert!(matches!(ppr.bullet_style, Some(BulletStyle::None)));
8514 }
8515
8516 #[test]
8520 fn parse_paragraph_bullet_styles_self_closing() {
8521 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8523 <a:buChar char="▪"/>
8524</a:pPr>"#;
8525 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8526 match &ppr.bullet_style {
8527 Some(BulletStyle::Char { char }) => assert_eq!(char, "▪"),
8528 other => panic!("期望 Char,实际: {other:?}"),
8529 }
8530
8531 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8533 <a:buAutoNum type="romanLcParenBoth" startAt="5"/>
8534</a:pPr>"#;
8535 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8536 match &ppr.bullet_style {
8537 Some(BulletStyle::AutoNum {
8538 auto_num_type,
8539 start_at,
8540 }) => {
8541 assert_eq!(auto_num_type, "romanLcParenBoth");
8542 assert_eq!(*start_at, Some(5));
8543 }
8544 other => panic!("期望 AutoNum,实际: {other:?}"),
8545 }
8546 }
8547
8548 #[test]
8552 fn parse_paragraph_tab_stops() {
8553 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8554 <a:tabLst>
8555 <a:tab pos="914400" algn="l"/>
8556 <a:tab pos="1828800" algn="r"/>
8557 <a:tab pos="2743200" algn="ctr"/>
8558 <a:tab pos="3657600" algn="dec"/>
8559 </a:tabLst>
8560</a:pPr>"#;
8561 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8562 assert_eq!(ppr.tab_stops.len(), 4, "应有 4 个制表位");
8563 assert_eq!(ppr.tab_stops[0].pos.value(), 914400);
8564 assert_eq!(ppr.tab_stops[0].alignment, TabAlignment::Left);
8565 assert_eq!(ppr.tab_stops[1].pos.value(), 1828800);
8566 assert_eq!(ppr.tab_stops[1].alignment, TabAlignment::Right);
8567 assert_eq!(ppr.tab_stops[2].pos.value(), 2743200);
8568 assert_eq!(ppr.tab_stops[2].alignment, TabAlignment::Center);
8569 assert_eq!(ppr.tab_stops[3].pos.value(), 3657600);
8570 assert_eq!(ppr.tab_stops[3].alignment, TabAlignment::Decimal);
8571 }
8572
8573 #[test]
8577 fn parse_paragraph_empty_tab_lst() {
8578 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8579 <a:tabLst/>
8580</a:pPr>"#;
8581 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8582 assert!(ppr.tab_stops.is_empty(), "空 tabLst 应无制表位");
8583 }
8584
8585 #[test]
8589 fn parse_paragraph_field() {
8590 let xml = r#"<a:p xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8591 <a:fld id="{12345678-ABCD-EF01-2345-678901234567}" type="slidenum">
8592 <a:rPr lang="en-US"/>
8593 <a:t>1</a:t>
8594 </a:fld>
8595</a:p>"#;
8596 let p = parse_paragraph(xml).expect("parse ok");
8597 assert_eq!(p.fields.len(), 1, "应有 1 个字段");
8598 let f = &p.fields[0];
8599 assert_eq!(f.id, "{12345678-ABCD-EF01-2345-678901234567}");
8600 assert_eq!(f.field_type, FieldType::SlideNumber);
8601 assert_eq!(f.text, "1");
8602 }
8603
8604 #[test]
8608 fn parse_paragraph_mixed_runs_and_fields() {
8609 let xml = r#"<a:p xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8610 <a:r><a:t>Page </a:t></a:r>
8611 <a:fld id="{AAA}" type="slidenum"><a:t>1</a:t></a:fld>
8612 <a:fld id="{BBB}" type="datetime"><a:t>1/1/2024</a:t></a:fld>
8613</a:p>"#;
8614 let p = parse_paragraph(xml).expect("parse ok");
8615 assert_eq!(p.runs.len(), 1, "应有 1 个 run");
8616 assert_eq!(p.runs[0].text, "Page");
8618 assert_eq!(p.fields.len(), 2, "应有 2 个字段");
8619 assert_eq!(p.fields[0].field_type, FieldType::SlideNumber);
8620 assert_eq!(p.fields[0].text, "1");
8621 assert_eq!(p.fields[1].field_type, FieldType::DateTime);
8622 assert_eq!(p.fields[1].text, "1/1/2024");
8623 }
8624
8625 #[test]
8629 fn parse_paragraph_custom_field_type() {
8630 let xml = r#"<a:p xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8631 <a:fld id="{CCC}" type="customField"><a:t>custom</a:t></a:fld>
8632</a:p>"#;
8633 let p = parse_paragraph(xml).expect("parse ok");
8634 assert_eq!(p.fields.len(), 1);
8635 assert_eq!(
8636 p.fields[0].field_type,
8637 FieldType::Custom("customField".to_string())
8638 );
8639 assert_eq!(p.fields[0].text, "custom");
8640 }
8641
8642 #[test]
8646 fn parse_run_properties_hlink_click_self_closing() {
8647 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" lang="en-US">
8648 <a:hlinkClick r:id="rId3" tooltip="点击访问"/>
8649</a:rPr>"#;
8650 let rp = parse_run_properties(xml).expect("parse ok");
8651 let hl = rp.hlink_click.expect("hlink_click 应存在");
8652 assert_eq!(hl.rid.as_deref(), Some("rId3"));
8653 assert_eq!(hl.tooltip.as_deref(), Some("点击访问"));
8654 assert!(hl.action.is_none());
8655 assert!(!hl.invalid);
8656 }
8657
8658 #[test]
8662 fn parse_run_properties_hlink_click_action() {
8663 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8664 <a:hlinkClick action="ppaction://hlinksldjump"/>
8665</a:rPr>"#;
8666 let rp = parse_run_properties(xml).expect("parse ok");
8667 let hl = rp.hlink_click.expect("hlink_click 应存在");
8668 assert_eq!(hl.action.as_deref(), Some("ppaction://hlinksldjump"));
8669 assert!(hl.rid.is_none());
8670 }
8671
8672 #[test]
8676 fn parse_run_properties_hlink_hover() {
8677 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8678 <a:hlinkHover r:id="rId5" tooltip="悬停提示"/>
8679</a:rPr>"#;
8680 let rp = parse_run_properties(xml).expect("parse ok");
8681 let hl = rp.hlink_hover.expect("hlink_hover 应存在");
8682 assert_eq!(hl.rid.as_deref(), Some("rId5"));
8683 assert_eq!(hl.tooltip.as_deref(), Some("悬停提示"));
8684 }
8685
8686 #[test]
8690 fn parse_run_properties_both_hlinks() {
8691 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8692 <a:hlinkClick r:id="rId1" tooltip="click"/>
8693 <a:hlinkHover r:id="rId2" tooltip="hover"/>
8694</a:rPr>"#;
8695 let rp = parse_run_properties(xml).expect("parse ok");
8696 assert_eq!(
8697 rp.hlink_click.as_ref().unwrap().rid.as_deref(),
8698 Some("rId1")
8699 );
8700 assert_eq!(
8701 rp.hlink_hover.as_ref().unwrap().rid.as_deref(),
8702 Some("rId2")
8703 );
8704 }
8705
8706 #[test]
8710 fn parse_run_properties_hlink_click_empty() {
8711 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8712 <a:hlinkClick/>
8713</a:rPr>"#;
8714 let rp = parse_run_properties(xml).expect("parse ok");
8715 let hl = rp.hlink_click.expect("hlink_click 应存在");
8716 assert!(hl.invalid);
8717 assert!(hl.rid.is_none());
8718 }
8719
8720 #[test]
8724 fn parse_sp_with_locks() {
8725 let xml = r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8726 <p:nvSpPr>
8727 <p:cNvPr id="2" name="Locked"/>
8728 <p:cNvSpPr>
8729 <a:spLocks noGrp="1" noSelect="1" noResize="1"/>
8730 </p:cNvSpPr>
8731 <p:nvPr/>
8732 </p:nvSpPr>
8733 <p:spPr/>
8734 <p:txBody><a:bodyPr/><a:p/></p:txBody>
8735</p:sp>"#;
8736 let sp = parse_sp(xml).expect("parse ok");
8737 let locks = sp.locks.as_ref().expect("locks 应存在");
8738 assert!(locks.no_grp);
8739 assert!(locks.no_select);
8740 assert!(locks.no_resize);
8741 assert!(!locks.no_move);
8742 assert!(!locks.no_rot);
8743 }
8744
8745 #[test]
8749 fn parse_sp_locks_with_txbox() {
8750 let xml = r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8751 <p:nvSpPr>
8752 <p:cNvPr id="3" name="TB"/>
8753 <p:cNvSpPr txBox="1">
8754 <a:spLocks noChangeAspect="1"/>
8755 </p:cNvSpPr>
8756 <p:nvPr/>
8757 </p:nvSpPr>
8758 <p:spPr/>
8759 <p:txBody><a:bodyPr/><a:p/></p:txBody>
8760</p:sp>"#;
8761 let sp = parse_sp(xml).expect("parse ok");
8762 assert!(sp.c_nv_sp_pr_tx_box, "txBox 应为 true");
8763 let locks = sp.locks.as_ref().expect("locks 应存在");
8764 assert!(locks.no_change_aspect);
8765 assert!(!locks.no_grp);
8766 }
8767
8768 #[test]
8772 fn parse_sp_with_style() {
8773 let xml = r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8774 <p:nvSpPr>
8775 <p:cNvPr id="4" name="Styled"/>
8776 <p:cNvSpPr/>
8777 <p:nvPr/>
8778 </p:nvSpPr>
8779 <p:spPr/>
8780 <p:style>
8781 <a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef>
8782 <a:fillRef idx="2"><a:schemeClr val="accent2"/></a:fillRef>
8783 <a:effectRef idx="0"><a:schemeClr val="accent3"/></a:effectRef>
8784 <a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef>
8785 </p:style>
8786 <p:txBody><a:bodyPr/><a:p/></p:txBody>
8787</p:sp>"#;
8788 let sp = parse_sp(xml).expect("parse ok");
8789 let style = sp.style.as_ref().expect("style 应存在");
8790 let ln = style.line_ref.as_ref().expect("line_ref 应存在");
8791 assert_eq!(ln.idx.as_deref(), Some("1"));
8792 assert_eq!(ln.scheme_color.as_deref(), Some("accent1"));
8793 let fill = style.fill_ref.as_ref().expect("fill_ref 应存在");
8794 assert_eq!(fill.idx.as_deref(), Some("2"));
8795 assert_eq!(fill.scheme_color.as_deref(), Some("accent2"));
8796 let eff = style.effect_ref.as_ref().expect("effect_ref 应存在");
8797 assert_eq!(eff.idx.as_deref(), Some("0"));
8798 let font = style.font_ref.as_ref().expect("font_ref 应存在");
8799 assert_eq!(font.idx.as_deref(), Some("minor"));
8800 assert_eq!(font.scheme_color.as_deref(), Some("tx1"));
8801 }
8802
8803 #[test]
8807 fn parse_sp_style_self_closing_refs() {
8808 let xml = r#"<p:style xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8809 <a:lnRef idx="3"/>
8810 <a:fillRef idx="1"/>
8811</p:style>"#;
8812 let style = parse_shape_style(xml).expect("parse ok");
8813 assert_eq!(style.line_ref.as_ref().unwrap().idx.as_deref(), Some("3"));
8814 assert!(style.line_ref.as_ref().unwrap().scheme_color.is_none());
8815 assert_eq!(style.fill_ref.as_ref().unwrap().idx.as_deref(), Some("1"));
8816 assert!(style.effect_ref.is_none());
8817 assert!(style.font_ref.is_none());
8818 }
8819
8820 #[test]
8824 fn parse_transition_fade() {
8825 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" spd="slow" advClick="0" advTm="5000">
8826 <p:fade thruBlk="1"/>
8827</p:transition>"#;
8828 let tr = parse_transition(xml).expect("parse ok");
8829 assert_eq!(tr.speed, TransitionSpeed::Slow);
8830 assert!(!tr.advance_click);
8831 assert_eq!(tr.advance_after_ms, Some(5000));
8832 match &tr.transition_type {
8833 TransitionType::Fade { thru_blk } => assert!(*thru_blk),
8834 other => panic!("expected Fade, got {:?}", other),
8835 }
8836 }
8837
8838 #[test]
8840 fn parse_transition_push() {
8841 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" spd="fast">
8842 <p:push dir="l"/>
8843</p:transition>"#;
8844 let tr = parse_transition(xml).expect("parse ok");
8845 assert_eq!(tr.speed, TransitionSpeed::Fast);
8846 assert!(tr.advance_click);
8848 assert!(tr.advance_after_ms.is_none());
8849 match &tr.transition_type {
8850 TransitionType::Push { dir } => assert_eq!(*dir, TransitionDirection::Left),
8851 other => panic!("expected Push, got {:?}", other),
8852 }
8853 }
8854
8855 #[test]
8857 fn parse_transition_morph() {
8858 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8859 <p:morph option="byChar"/>
8860</p:transition>"#;
8861 let tr = parse_transition(xml).expect("parse ok");
8862 match &tr.transition_type {
8863 TransitionType::Morph { option } => assert_eq!(*option, MorphOption::ByChar),
8864 other => panic!("expected Morph, got {:?}", other),
8865 }
8866 }
8867
8868 #[test]
8870 fn parse_transition_split() {
8871 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8872 <p:split orient="vert" dir="d"/>
8873</p:transition>"#;
8874 let tr = parse_transition(xml).expect("parse ok");
8875 match &tr.transition_type {
8876 TransitionType::Split { orient, dir } => {
8877 assert_eq!(*orient, SplitOrientation::Vertical);
8878 assert_eq!(*dir, TransitionDirection::Down);
8879 }
8880 other => panic!("expected Split, got {:?}", other),
8881 }
8882 }
8883
8884 #[test]
8886 fn parse_transition_self_closing() {
8887 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" spd="med" advClick="1"/>"#;
8888 let tr = parse_transition(xml).expect("parse ok");
8889 assert_eq!(tr.speed, TransitionSpeed::Medium);
8890 assert!(tr.advance_click);
8891 assert_eq!(tr.transition_type, TransitionType::None);
8893 }
8894
8895 #[test]
8897 fn parse_sld_with_transition() {
8898 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8899<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8900 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8901 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8902 <p:cSld>
8903 <p:spTree>
8904 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8905 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8906 </p:spTree>
8907 </p:cSld>
8908 <p:clrMapOvr/>
8909 <p:transition spd="slow" advTm="3000">
8910 <p:fade/>
8911 </p:transition>
8912</p:sld>"#;
8913 let sld = parse_sld(xml).expect("parse ok");
8914 let tr = sld.transition.expect("transition should exist");
8915 assert_eq!(tr.speed, TransitionSpeed::Slow);
8916 assert_eq!(tr.advance_after_ms, Some(3000));
8917 match &tr.transition_type {
8918 TransitionType::Fade { thru_blk } => assert!(!*thru_blk),
8919 other => panic!("expected Fade, got {:?}", other),
8920 }
8921 }
8922
8923 #[test]
8925 fn transition_round_trip() {
8926 use crate::oxml::slide::Transition;
8927 use crate::oxml::writer::XmlWriter;
8928
8929 let original = Transition {
8930 speed: TransitionSpeed::Fast,
8931 advance_click: false,
8932 advance_after_ms: Some(8000),
8933 transition_type: TransitionType::Push {
8934 dir: TransitionDirection::Up,
8935 },
8936 };
8937 let mut w = XmlWriter::new();
8939 original.write_xml(&mut w);
8940 let xml = w.buf.clone();
8941 let parsed = parse_transition(&xml).expect("parse ok");
8943 assert_eq!(parsed.speed, original.speed);
8944 assert_eq!(parsed.advance_click, original.advance_click);
8945 assert_eq!(parsed.advance_after_ms, original.advance_after_ms);
8946 assert_eq!(parsed.transition_type, original.transition_type);
8947 }
8948
8949 #[test]
8953 fn parse_custom_geometry_basic() {
8954 let xml = r#"<a:custGeom>
8955<a:avLst/>
8956<a:rect l="0" t="0" r="100" b="100"/>
8957<a:pathLst>
8958<a:path w="100" h="100" fill="norm" stroke="norm">
8959<a:moveTo><a:pt x="0" y="0"/></a:moveTo>
8960<a:lnTo><a:pt x="100" y="0"/></a:lnTo>
8961<a:lnTo><a:pt x="50" y="100"/></a:lnTo>
8962<a:close/>
8963</a:path>
8964</a:pathLst>
8965</a:custGeom>"#;
8966 let geom = parse_custom_geometry(xml).expect("parse ok");
8967 assert_eq!(
8968 geom.rect,
8969 Some(GeomRect {
8970 l: "0".to_string(),
8971 t: "0".to_string(),
8972 r: "100".to_string(),
8973 b: "100".to_string(),
8974 })
8975 );
8976 assert_eq!(geom.path_list.len(), 1);
8977 let p = &geom.path_list[0];
8978 assert_eq!(p.width, 100);
8979 assert_eq!(p.height, 100);
8980 assert_eq!(p.fill.as_deref(), Some("norm"));
8981 assert_eq!(p.stroke.as_deref(), Some("norm"));
8982 assert_eq!(p.segments.len(), 4);
8983 match &p.segments[0] {
8985 PathSegment::MoveTo { x, y } => {
8986 assert_eq!(*x, 0);
8987 assert_eq!(*y, 0);
8988 }
8989 other => panic!("expected MoveTo, got {:?}", other),
8990 }
8991 match &p.segments[1] {
8992 PathSegment::LineTo { x, y } => {
8993 assert_eq!(*x, 100);
8994 assert_eq!(*y, 0);
8995 }
8996 other => panic!("expected LineTo, got {:?}", other),
8997 }
8998 match &p.segments[2] {
8999 PathSegment::LineTo { x, y } => {
9000 assert_eq!(*x, 50);
9001 assert_eq!(*y, 100);
9002 }
9003 other => panic!("expected LineTo, got {:?}", other),
9004 }
9005 match &p.segments[3] {
9006 PathSegment::Close => {}
9007 other => panic!("expected Close, got {:?}", other),
9008 }
9009 }
9010
9011 #[test]
9013 fn parse_custom_geometry_with_bez() {
9014 let xml = r#"<a:custGeom>
9015<a:avLst/>
9016<a:pathLst>
9017<a:path w="200" h="200">
9018<a:moveTo><a:pt x="10" y="10"/></a:moveTo>
9019<a:cubicBezTo>
9020<a:pt x="50" y="0"/>
9021<a:pt x="150" y="0"/>
9022<a:pt x="190" y="10"/>
9023</a:cubicBezTo>
9024<a:quadBezTo>
9025<a:pt x="190" y="100"/>
9026<a:pt x="100" y="190"/>
9027</a:quadBezTo>
9028</a:path>
9029</a:pathLst>
9030</a:custGeom>"#;
9031 let geom = parse_custom_geometry(xml).expect("parse ok");
9032 assert_eq!(geom.path_list.len(), 1);
9033 let p = &geom.path_list[0];
9034 assert_eq!(p.segments.len(), 3);
9035 match &p.segments[1] {
9036 PathSegment::CubicBezTo {
9037 x1,
9038 y1,
9039 x2,
9040 y2,
9041 x3,
9042 y3,
9043 } => {
9044 assert_eq!(*x1, 50);
9045 assert_eq!(*y1, 0);
9046 assert_eq!(*x2, 150);
9047 assert_eq!(*y2, 0);
9048 assert_eq!(*x3, 190);
9049 assert_eq!(*y3, 10);
9050 }
9051 other => panic!("expected CubicBezTo, got {:?}", other),
9052 }
9053 match &p.segments[2] {
9054 PathSegment::QuadBezTo { x1, y1, x2, y2 } => {
9055 assert_eq!(*x1, 190);
9056 assert_eq!(*y1, 100);
9057 assert_eq!(*x2, 100);
9058 assert_eq!(*y2, 190);
9059 }
9060 other => panic!("expected QuadBezTo, got {:?}", other),
9061 }
9062 }
9063
9064 #[test]
9066 fn parse_custom_geometry_with_arc() {
9067 let xml = r#"<a:custGeom>
9068<a:avLst/>
9069<a:pathLst>
9070<a:path w="100" h="100">
9071<a:moveTo><a:pt x="0" y="50"/></a:moveTo>
9072<a:arcTo wR="50" hR="50" stAng="0" swAng="5400000"/>
9073<a:close/>
9074</a:path>
9075</a:pathLst>
9076</a:custGeom>"#;
9077 let geom = parse_custom_geometry(xml).expect("parse ok");
9078 let p = &geom.path_list[0];
9079 assert_eq!(p.segments.len(), 3);
9080 match &p.segments[1] {
9081 PathSegment::ArcTo {
9082 w_r,
9083 h_r,
9084 st_ang,
9085 sw_ang,
9086 } => {
9087 assert_eq!(*w_r, 50);
9088 assert_eq!(*h_r, 50);
9089 assert_eq!(*st_ang, 0);
9090 assert_eq!(*sw_ang, 5400000);
9091 }
9092 other => panic!("expected ArcTo, got {:?}", other),
9093 }
9094 }
9095
9096 #[test]
9098 fn parse_custom_geometry_fill_stroke() {
9099 let xml = r#"<a:custGeom>
9100<a:avLst/>
9101<a:fill>norm</a:fill>
9102<a:stroke>none</a:stroke>
9103<a:pathLst>
9104<a:path w="10" h="10">
9105<a:moveTo><a:pt x="0" y="0"/></a:moveTo>
9106</a:path>
9107</a:pathLst>
9108</a:custGeom>"#;
9109 let geom = parse_custom_geometry(xml).expect("parse ok");
9110 assert_eq!(geom.fill.as_deref(), Some("norm"));
9111 assert_eq!(geom.stroke.as_deref(), Some("none"));
9112 }
9113
9114 #[test]
9116 fn custom_geometry_round_trip() {
9117 use crate::oxml::writer::XmlWriter;
9118
9119 let original = CustomGeometry {
9120 fill: Some("norm".to_string()),
9121 stroke: None,
9122 rect: Some(GeomRect {
9123 l: "0".to_string(),
9124 t: "0".to_string(),
9125 r: "100".to_string(),
9126 b: "100".to_string(),
9127 }),
9128 path_list: vec![Path {
9129 width: 100,
9130 height: 100,
9131 fill: Some("norm".to_string()),
9132 stroke: None,
9133 segments: vec![
9134 PathSegment::MoveTo { x: 0, y: 0 },
9135 PathSegment::LineTo { x: 100, y: 0 },
9136 PathSegment::LineTo { x: 100, y: 100 },
9137 PathSegment::LineTo { x: 0, y: 100 },
9138 PathSegment::Close,
9139 ],
9140 }],
9141 };
9142 let mut w = XmlWriter::new();
9144 original.write_xml(&mut w);
9145 let xml = w.buf.clone();
9146 let parsed = parse_custom_geometry(&xml).expect("parse ok");
9148 assert_eq!(parsed.fill, original.fill);
9149 assert_eq!(parsed.stroke, original.stroke);
9150 assert_eq!(parsed.rect, original.rect);
9151 assert_eq!(parsed.path_list.len(), 1);
9152 let p0 = &parsed.path_list[0];
9153 assert_eq!(p0.width, 100);
9154 assert_eq!(p0.height, 100);
9155 assert_eq!(p0.fill.as_deref(), Some("norm"));
9156 assert_eq!(p0.segments.len(), 5);
9157 match &p0.segments[0] {
9159 PathSegment::MoveTo { x, y } => {
9160 assert_eq!(*x, 0);
9161 assert_eq!(*y, 0);
9162 }
9163 other => panic!("expected MoveTo, got {:?}", other),
9164 }
9165 match &p0.segments[4] {
9167 PathSegment::Close => {}
9168 other => panic!("expected Close, got {:?}", other),
9169 }
9170 }
9171
9172 #[test]
9174 fn parse_sp_with_custgeom() {
9175 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9176<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9177 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9178 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9179 <p:cSld>
9180 <p:spTree>
9181 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9182 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9183 <p:sp>
9184 <p:nvSpPr>
9185 <p:cNvPr id="10" name="Freeform 1"/>
9186 <p:cNvSpPr/>
9187 <p:nvPr/>
9188 </p:nvSpPr>
9189 <p:spPr>
9190 <a:xfrm>
9191 <a:off x="100" y="100"/>
9192 <a:ext cx="200" cy="200"/>
9193 </a:xfrm>
9194 <a:custGeom>
9195 <a:avLst/>
9196 <a:pathLst>
9197 <a:path w="200" h="200">
9198 <a:moveTo><a:pt x="0" y="0"/></a:moveTo>
9199 <a:lnTo><a:pt x="200" y="0"/></a:lnTo>
9200 <a:lnTo><a:pt x="100" y="200"/></a:lnTo>
9201 <a:close/>
9202 </a:path>
9203 </a:pathLst>
9204 </a:custGeom>
9205 </p:spPr>
9206 </p:sp>
9207 </p:spTree>
9208 </p:cSld>
9209 <p:clrMapOvr/>
9210</p:sld>"#;
9211 let sld = parse_sld(xml).expect("parse ok");
9212 assert_eq!(sld.shapes.len(), 1);
9213 match &sld.shapes[0] {
9214 OxmlSlideShape::Sp(sp) => {
9215 let geom = sp
9217 .properties
9218 .geometry
9219 .as_ref()
9220 .expect("geometry should exist");
9221 match geom {
9222 Geometry::Custom(cg) => {
9223 assert_eq!(cg.path_list.len(), 1);
9224 let p = &cg.path_list[0];
9225 assert_eq!(p.width, 200);
9226 assert_eq!(p.height, 200);
9227 assert_eq!(p.segments.len(), 4);
9228 match &p.segments[0] {
9229 PathSegment::MoveTo { x, y } => {
9230 assert_eq!(*x, 0);
9231 assert_eq!(*y, 0);
9232 }
9233 other => panic!("expected MoveTo, got {:?}", other),
9234 }
9235 }
9236 other => panic!("expected Custom geometry, got {:?}", other),
9237 }
9238 }
9239 other => panic!("expected Sp, got {:?}", other),
9240 }
9241 }
9242
9243 #[test]
9245 fn geometry_preset_write_xml() {
9246 use crate::oxml::simpletypes::PresetGeometry;
9247 use crate::oxml::writer::XmlWriter;
9248
9249 let geom = Geometry::preset(PresetGeometry::Rectangle);
9250 let mut w = XmlWriter::new();
9251 geom.write_xml(&mut w);
9252 let xml = w.buf.clone();
9253 assert!(xml.contains("<a:prstGeom prst=\"rect\">"), "xml: {}", xml);
9254 assert!(xml.contains("<a:avLst/>"), "xml: {}", xml);
9255 assert!(xml.contains("</a:prstGeom>"), "xml: {}", xml);
9256 }
9257
9258 #[test]
9260 fn geometry_custom_write_xml() {
9261 use crate::oxml::writer::XmlWriter;
9262
9263 let geom = Geometry::Custom(CustomGeometry {
9264 fill: None,
9265 stroke: None,
9266 rect: None,
9267 path_list: vec![Path {
9268 width: 50,
9269 height: 50,
9270 fill: None,
9271 stroke: None,
9272 segments: vec![
9273 PathSegment::MoveTo { x: 0, y: 0 },
9274 PathSegment::LineTo { x: 50, y: 50 },
9275 PathSegment::Close,
9276 ],
9277 }],
9278 });
9279 let mut w = XmlWriter::new();
9280 geom.write_xml(&mut w);
9281 let xml = w.buf.clone();
9282 assert!(xml.contains("<a:custGeom>"), "xml: {}", xml);
9283 assert!(xml.contains("<a:avLst/>"), "xml: {}", xml);
9284 assert!(xml.contains("<a:pathLst>"), "xml: {}", xml);
9285 assert!(xml.contains("<a:path w=\"50\" h=\"50\">"), "xml: {}", xml);
9286 assert!(xml.contains("<a:moveTo>"), "xml: {}", xml);
9287 assert!(xml.contains("<a:lnTo>"), "xml: {}", xml);
9288 assert!(xml.contains("<a:close/>"), "xml: {}", xml);
9289 }
9290
9291 #[test]
9297 fn round_trip_prst_geom_with_adjustments() {
9298 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9299<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9300 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9301 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9302 <p:cSld><p:spTree>
9303 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9304 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9305 <p:sp>
9306 <p:nvSpPr><p:cNvPr id="2" name="RoundRect"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9307 <p:spPr>
9308 <a:xfrm><a:off x="100000" y="100000"/><a:ext cx="500000" cy="300000"/></a:xfrm>
9309 <a:prstGeom prst="roundRect">
9310 <a:avLst>
9311 <a:gd name="adj" fmla="val 16667"/>
9312 </a:avLst>
9313 </a:prstGeom>
9314 </p:spPr>
9315 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9316 </p:sp>
9317 </p:spTree></p:cSld>
9318</p:sld>"#;
9319 let sld = parse_sld(xml).expect("parse ok");
9320 let sp = sld
9321 .shapes
9322 .iter()
9323 .find_map(|s| {
9324 if let OxmlSlideShape::Sp(sp) = s {
9325 Some(sp)
9326 } else {
9327 None
9328 }
9329 })
9330 .expect("应有 Sp");
9331 match &sp.properties.geometry {
9333 Some(Geometry::Preset(prst, adjustments)) => {
9334 assert_eq!(*prst, PresetGeometry::RoundRectangle);
9335 assert_eq!(adjustments.len(), 1, "应有 1 个调整值");
9336 let adj = &adjustments[0];
9337 assert_eq!(adj.name, "adj");
9338 assert_eq!(adj.raw_value, 16667);
9339 assert!((adj.effective_value() - 0.16667).abs() < 1e-6);
9340 }
9341 other => panic!("期望 Preset 几何,得到 {:?}", other),
9342 }
9343 let mut w = crate::oxml::writer::XmlWriter::new();
9345 sp.properties.geometry.as_ref().unwrap().write_xml(&mut w);
9346 let out = &w.buf;
9347 assert!(
9348 out.contains("<a:prstGeom prst=\"roundRect\">"),
9349 "xml: {}",
9350 out
9351 );
9352 assert!(out.contains("<a:avLst>"), "xml: {}", out);
9353 assert!(
9354 out.contains("<a:gd name=\"adj\" fmla=\"val 16667\"/>"),
9355 "xml: {}",
9356 out
9357 );
9358 assert!(out.contains("</a:prstGeom>"), "xml: {}", out);
9359 }
9360
9361 #[test]
9366 fn round_trip_prst_geom_with_multiple_adjustments() {
9367 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9368<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9369 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9370 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9371 <p:cSld><p:spTree>
9372 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9373 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9374 <p:sp>
9375 <p:nvSpPr><p:cNvPr id="3" name="MultiAdj"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9376 <p:spPr>
9377 <a:xfrm><a:off x="0" y="0"/><a:ext cx="1000000" cy="500000"/></a:xfrm>
9378 <a:prstGeom prst="rect">
9379 <a:avLst>
9380 <a:gd name="adj1" fmla="val 50000"/>
9381 <a:gd name="adj2" fmla="val 25000"/>
9382 </a:avLst>
9383 </a:prstGeom>
9384 </p:spPr>
9385 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9386 </p:sp>
9387 </p:spTree></p:cSld>
9388</p:sld>"#;
9389 let sld = parse_sld(xml).expect("parse ok");
9390 let sp = sld
9391 .shapes
9392 .iter()
9393 .find_map(|s| {
9394 if let OxmlSlideShape::Sp(sp) = s {
9395 Some(sp)
9396 } else {
9397 None
9398 }
9399 })
9400 .expect("应有 Sp");
9401 match &sp.properties.geometry {
9402 Some(Geometry::Preset(_, adjustments)) => {
9403 assert_eq!(adjustments.len(), 2, "应有 2 个调整值");
9404 assert_eq!(adjustments[0].name, "adj1");
9405 assert_eq!(adjustments[0].raw_value, 50000);
9406 assert_eq!(adjustments[1].name, "adj2");
9407 assert_eq!(adjustments[1].raw_value, 25000);
9408 }
9409 other => panic!("期望 Preset 几何,得到 {:?}", other),
9410 }
9411 let mut w = crate::oxml::writer::XmlWriter::new();
9413 sp.properties.geometry.as_ref().unwrap().write_xml(&mut w);
9414 let out = &w.buf;
9415 assert!(
9416 out.contains("<a:gd name=\"adj1\" fmla=\"val 50000\"/>"),
9417 "xml: {}",
9418 out
9419 );
9420 assert!(
9421 out.contains("<a:gd name=\"adj2\" fmla=\"val 25000\"/>"),
9422 "xml: {}",
9423 out
9424 );
9425 }
9426
9427 #[test]
9429 fn parse_prst_geom_with_empty_av_lst() {
9430 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9431<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9432 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9433 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9434 <p:cSld><p:spTree>
9435 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9436 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9437 <p:sp>
9438 <p:nvSpPr><p:cNvPr id="4" name="EmptyAdj"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9439 <p:spPr>
9440 <a:xfrm><a:off x="0" y="0"/><a:ext cx="1000000" cy="500000"/></a:xfrm>
9441 <a:prstGeom prst="rect">
9442 <a:avLst/>
9443 </a:prstGeom>
9444 </p:spPr>
9445 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9446 </p:sp>
9447 </p:spTree></p:cSld>
9448</p:sld>"#;
9449 let sld = parse_sld(xml).expect("parse ok");
9450 let sp = sld
9451 .shapes
9452 .iter()
9453 .find_map(|s| {
9454 if let OxmlSlideShape::Sp(sp) = s {
9455 Some(sp)
9456 } else {
9457 None
9458 }
9459 })
9460 .expect("应有 Sp");
9461 match &sp.properties.geometry {
9462 Some(Geometry::Preset(prst, adjustments)) => {
9463 assert_eq!(*prst, PresetGeometry::Rectangle);
9464 assert!(adjustments.is_empty(), "空 avLst 应解析为空列表");
9465 }
9466 other => panic!("期望 Preset 几何,得到 {:?}", other),
9467 }
9468 }
9469
9470 #[test]
9472 fn parse_prst_geom_without_av_lst() {
9473 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9474<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9475 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9476 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9477 <p:cSld><p:spTree>
9478 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9479 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9480 <p:sp>
9481 <p:nvSpPr><p:cNvPr id="5" name="NoAdj"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9482 <p:spPr>
9483 <a:xfrm><a:off x="0" y="0"/><a:ext cx="1000000" cy="500000"/></a:xfrm>
9484 <a:prstGeom prst="rect"/>
9485 </p:spPr>
9486 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9487 </p:sp>
9488 </p:spTree></p:cSld>
9489</p:sld>"#;
9490 let sld = parse_sld(xml).expect("parse ok");
9491 let sp = sld
9492 .shapes
9493 .iter()
9494 .find_map(|s| {
9495 if let OxmlSlideShape::Sp(sp) = s {
9496 Some(sp)
9497 } else {
9498 None
9499 }
9500 })
9501 .expect("应有 Sp");
9502 match &sp.properties.geometry {
9503 Some(Geometry::Preset(prst, adjustments)) => {
9504 assert_eq!(*prst, PresetGeometry::Rectangle);
9505 assert!(adjustments.is_empty(), "无 avLst 应解析为空列表");
9506 }
9507 other => panic!("期望 Preset 几何,得到 {:?}", other),
9508 }
9509 }
9510
9511 #[test]
9513 fn parse_fmla_val_formats() {
9514 assert_eq!(parse_fmla_val("val 16667"), Some(16667));
9516 assert_eq!(parse_fmla_val("val16667"), Some(16667));
9518 assert_eq!(parse_fmla_val(" val 16667 "), Some(16667));
9520 assert_eq!(parse_fmla_val("val -5000"), Some(-5000));
9522 assert_eq!(parse_fmla_val("val abc"), None);
9524 assert_eq!(parse_fmla_val("*/ adj1 100000 50000"), None);
9526 assert_eq!(parse_fmla_val(""), None);
9528 }
9529
9530 #[test]
9537 fn parse_graphic_frame_with_smartart() {
9538 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9539<p:graphicFrame xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9540 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9541 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
9542 xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram">
9543 <p:nvGraphicFramePr>
9544 <p:cNvPr id="300" name="SmartArt 1"/>
9545 <p:cNvGraphicFramePr/>
9546 <p:nvPr/>
9547 </p:nvGraphicFramePr>
9548 <p:xfrm>
9549 <a:off x="457200" y="1828800"/>
9550 <a:ext cx="8229600" cy="1143000"/>
9551 </p:xfrm>
9552 <a:graphic>
9553 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/diagram">
9554 <dgm:relIds r:dm="rIdDm1" r:lo="rIdLo1" r:qs="rIdQs1" r:cs="rIdCs1"/>
9555 </a:graphicData>
9556 </a:graphic>
9557</p:graphicFrame>"#;
9558 let frame = parse_graphic_frame(xml).expect("parse ok");
9559 assert_eq!(frame.id, 300);
9560 assert_eq!(frame.name, "SmartArt 1");
9561 match &frame.graphic {
9562 crate::oxml::shape::Graphic::SmartArt(s) => {
9563 assert!(
9565 s.raw_xml.contains("<a:graphicData"),
9566 "raw_xml 应包含 graphicData 外壳,实际: {}",
9567 s.raw_xml
9568 );
9569 assert!(
9570 s.raw_xml.contains(
9571 "uri=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\""
9572 ),
9573 "raw_xml 应包含 diagram uri,实际: {}",
9574 s.raw_xml
9575 );
9576 assert_eq!(s.dm_rid.as_deref(), Some("rIdDm1"));
9578 assert_eq!(s.lo_rid.as_deref(), Some("rIdLo1"));
9579 assert_eq!(s.qs_rid.as_deref(), Some("rIdQs1"));
9580 assert_eq!(s.cs_rid.as_deref(), Some("rIdCs1"));
9581 }
9582 other => panic!("期望 SmartArt,得到 {:?}", other),
9583 }
9584 }
9585
9586 #[test]
9588 fn parse_smartart_rel_ids_formats() {
9589 let raw = r#"<a:graphicData uri=".../diagram"><dgm:relIds r:dm="rId1" r:lo="rId2" r:qs="rId3" r:cs="rId4"/></a:graphicData>"#;
9591 let (dm, lo, qs, cs) = parse_smartart_rel_ids(raw);
9592 assert_eq!(dm.as_deref(), Some("rId1"));
9593 assert_eq!(lo.as_deref(), Some("rId2"));
9594 assert_eq!(qs.as_deref(), Some("rId3"));
9595 assert_eq!(cs.as_deref(), Some("rId4"));
9596
9597 let raw_partial =
9599 r#"<a:graphicData uri=".../diagram"><dgm:relIds r:dm="rId1"/></a:graphicData>"#;
9600 let (dm, lo, qs, cs) = parse_smartart_rel_ids(raw_partial);
9601 assert_eq!(dm.as_deref(), Some("rId1"));
9602 assert!(lo.is_none(), "lo 应为 None");
9603 assert!(qs.is_none(), "qs 应为 None");
9604 assert!(cs.is_none(), "cs 应为 None");
9605
9606 let raw_empty = r#"<a:graphicData uri=".../diagram"></a:graphicData>"#;
9608 let (dm, lo, qs, cs) = parse_smartart_rel_ids(raw_empty);
9609 assert!(dm.is_none() && lo.is_none() && qs.is_none() && cs.is_none());
9610 }
9611}