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(parse_run_properties_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 = parse_run_properties_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(parse_run_properties_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 = parse_run_properties_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
3968pub fn parse_run_properties_from_bytes_start(
3974 e: &quick_xml::events::BytesStart<'_>,
3975) -> RunProperties {
3976 let mut rp = RunProperties::default();
3977 for a in e.attributes().flatten() {
3978 let v = a
3979 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
3980 .unwrap_or_default();
3981 match a.key.as_ref() {
3982 b"sz" => {
3983 if let Ok(p) = v.parse::<i32>() {
3984 rp.size = Some(Pt(p as f64 / 100.0));
3985 }
3986 }
3987 b"b" => {
3988 if v == "1" || v == "true" {
3989 rp.bold = true;
3990 }
3991 }
3992 b"i" => {
3993 if v == "1" || v == "true" {
3994 rp.italic = true;
3995 }
3996 }
3997 b"u" => {
3998 if let Ok(u) = v.parse::<Underline>() {
3999 rp.underline = Some(u);
4000 }
4001 }
4002 b"strike" => {
4003 if v == "sngStrike" {
4004 rp.strike = true;
4005 }
4006 if v == "dblStrike" {
4007 rp.strike_dbl = true;
4008 }
4009 }
4010 b"baseline" => {
4011 if let Ok(v) = v.parse::<i32>() {
4012 rp.baseline = Some(v);
4013 }
4014 }
4015 b"kern" => {
4016 if let Ok(v) = v.parse::<i32>() {
4017 rp.kerning = Some(v);
4018 }
4019 }
4020 b"spc" => {
4021 if let Ok(v) = v.parse::<i32>() {
4022 rp.spc = Some(v);
4023 }
4024 }
4025 b"lang" => {
4026 rp.lang = Some(v.to_string());
4027 }
4028 _ => {}
4029 }
4030 }
4031 rp
4032}
4033
4034pub fn parse_pic(xml: &str) -> crate::Result<Pic> {
4039 let mut pic = Pic::default();
4040 let mut rd = Reader::from_str(xml);
4041 rd.config_mut().trim_text(true);
4042 let mut buf = Vec::new();
4043 let mut in_blipfill = false;
4044 let mut blipfill_depth: i32 = 0;
4045 loop {
4046 match rd.read_event_into(&mut buf) {
4047 Ok(Event::Start(e)) => {
4048 let name = e.name();
4049 let local = local_name(name.as_ref());
4050 if local == b"pic" {
4051 } else if local == b"nvPicPr" {
4054 let mut nv_depth = 1i32;
4057 loop {
4058 match rd.read_event_into(&mut buf) {
4059 Ok(Event::Start(_)) => nv_depth += 1,
4060 Ok(Event::End(_)) => {
4061 nv_depth -= 1;
4062 if nv_depth == 0 {
4063 break;
4064 }
4065 }
4066 Ok(Event::Empty(e2)) => {
4067 let name2 = e2.name();
4068 let local2 = local_name(name2.as_ref());
4069 if local2 == b"cNvPr" {
4070 for a in e2.attributes().flatten() {
4071 match a.key.as_ref() {
4072 b"id" => {
4073 if let Ok(v) = a
4074 .normalized_value(
4075 quick_xml::XmlVersion::Implicit1_0,
4076 )
4077 .unwrap_or_default()
4078 .parse::<u32>()
4079 {
4080 pic.id = v;
4081 }
4082 }
4083 b"name" => {
4084 pic.name = a
4085 .normalized_value(
4086 quick_xml::XmlVersion::Implicit1_0,
4087 )
4088 .unwrap_or_default()
4089 .to_string();
4090 }
4091 _ => {}
4092 }
4093 }
4094 }
4095 }
4096 Ok(Event::Eof) => break,
4097 Err(_) => break,
4098 _ => {}
4099 }
4100 buf.clear();
4101 }
4102 } else if local == b"blipFill" {
4103 in_blipfill = true;
4104 blipfill_depth = 1;
4105 } else if local == b"spPr" {
4108 let inner = collect_full_element(&mut rd, e.into_owned())?;
4109 pic.properties = parse_sppr(&inner)?;
4110 } else if in_blipfill && local == b"blip" {
4111 for a in e.attributes().flatten() {
4114 let key = a.key.as_ref();
4115 if key == b"r:embed" || key.ends_with(b":embed") {
4117 pic.rid = a
4118 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4119 .unwrap_or_default()
4120 .to_string();
4121 }
4122 }
4123 } else if in_blipfill && local == b"srcRect" {
4124 let (mut l, mut t, mut r, mut b) = (None, None, None, None);
4126 for a in e.attributes().flatten() {
4127 let v = a
4128 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4129 .unwrap_or_default();
4130 match a.key.as_ref() {
4131 b"l" => l = v.parse().ok(),
4132 b"t" => t = v.parse().ok(),
4133 b"r" => r = v.parse().ok(),
4134 b"b" => b = v.parse().ok(),
4135 _ => {}
4136 }
4137 }
4138 if let (Some(ll), Some(tt), Some(rr), Some(bb)) = (l, t, r, b) {
4139 pic.src_rect = Some((ll, tt, rr, bb));
4140 }
4141 } else if in_blipfill && local == b"stretch" {
4142 let _ = collect_full_element(&mut rd, e.into_owned())?;
4144 pic.fill_mode = crate::oxml::sppr::BlipFillMode::Stretch;
4145 } else if in_blipfill && local == b"tile" {
4146 let mode = parse_tile_attrs(&e);
4149 let _ = collect_full_element(&mut rd, e.into_owned())?;
4150 pic.fill_mode = mode;
4151 } else {
4152 let _ = collect_full_element(&mut rd, e.into_owned());
4153 }
4154 }
4155 Ok(Event::Empty(e)) => {
4156 let name = e.name();
4157 let local = local_name(name.as_ref());
4158 if in_blipfill && local == b"blip" {
4159 for a in e.attributes().flatten() {
4161 let key = a.key.as_ref();
4162 if key == b"r:embed" || key.ends_with(b":embed") {
4163 pic.rid = a
4164 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4165 .unwrap_or_default()
4166 .to_string();
4167 }
4168 }
4169 } else if in_blipfill && local == b"srcRect" {
4170 let (mut l, mut t, mut r, mut b) = (None, None, None, None);
4171 for a in e.attributes().flatten() {
4172 let v = a
4173 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4174 .unwrap_or_default();
4175 match a.key.as_ref() {
4176 b"l" => l = v.parse().ok(),
4177 b"t" => t = v.parse().ok(),
4178 b"r" => r = v.parse().ok(),
4179 b"b" => b = v.parse().ok(),
4180 _ => {}
4181 }
4182 }
4183 if let (Some(ll), Some(tt), Some(rr), Some(bb)) = (l, t, r, b) {
4184 pic.src_rect = Some((ll, tt, rr, bb));
4185 }
4186 } else if in_blipfill && local == b"stretch" {
4187 pic.fill_mode = crate::oxml::sppr::BlipFillMode::Stretch;
4189 } else if in_blipfill && local == b"tile" {
4190 pic.fill_mode = parse_tile_attrs(&e);
4192 }
4193 }
4194 Ok(Event::End(e)) => {
4195 let name = e.name();
4196 let local = local_name(name.as_ref());
4197 if in_blipfill && local == b"blipFill" {
4201 blipfill_depth -= 1;
4202 if blipfill_depth == 0 {
4203 in_blipfill = false;
4204 }
4205 }
4206 }
4207 Ok(Event::Eof) => break,
4208 Err(e) => return Err(crate::Error::Xml(format!("pic parse: {e}"))),
4209 _ => {}
4210 }
4211 buf.clear();
4212 }
4213 Ok(pic)
4214}
4215
4216pub fn parse_cxn_sp(xml: &str) -> crate::Result<Connector> {
4241 let mut cxn = Connector::default();
4242 let mut rd = Reader::from_str(xml);
4243 rd.config_mut().trim_text(true);
4244 let mut buf = Vec::new();
4245 loop {
4246 match rd.read_event_into(&mut buf) {
4247 Ok(Event::Start(e)) => {
4248 let name = e.name();
4249 let local = local_name(name.as_ref());
4250 if local == b"cxnSp" {
4251 } else if local == b"nvCxnSpPr" {
4253 let mut nv_depth = 1i32;
4255 loop {
4256 match rd.read_event_into(&mut buf) {
4257 Ok(Event::Start(_)) => nv_depth += 1,
4258 Ok(Event::End(_)) => {
4259 nv_depth -= 1;
4260 if nv_depth == 0 {
4261 break;
4262 }
4263 }
4264 Ok(Event::Empty(e2)) => {
4265 let name2 = e2.name();
4266 let local2 = local_name(name2.as_ref());
4267 if local2 == b"cNvPr" {
4268 for a in e2.attributes().flatten() {
4269 match a.key.as_ref() {
4270 b"id" => {
4271 if let Ok(v) = a
4272 .normalized_value(
4273 quick_xml::XmlVersion::Implicit1_0,
4274 )
4275 .unwrap_or_default()
4276 .parse::<u32>()
4277 {
4278 cxn.id = v;
4279 }
4280 }
4281 b"name" => {
4282 cxn.name = a
4283 .normalized_value(
4284 quick_xml::XmlVersion::Implicit1_0,
4285 )
4286 .unwrap_or_default()
4287 .to_string();
4288 }
4289 _ => {}
4290 }
4291 }
4292 } else if local2 == b"stCxn" {
4293 let mut sid: Option<u32> = None;
4295 let mut idx: Option<u32> = None;
4296 for a in e2.attributes().flatten() {
4297 match a.key.as_ref() {
4298 b"id" => {
4299 sid = a
4300 .normalized_value(
4301 quick_xml::XmlVersion::Implicit1_0,
4302 )
4303 .unwrap_or_default()
4304 .parse()
4305 .ok();
4306 }
4307 b"idx" => {
4308 idx = a
4309 .normalized_value(
4310 quick_xml::XmlVersion::Implicit1_0,
4311 )
4312 .unwrap_or_default()
4313 .parse()
4314 .ok();
4315 }
4316 _ => {}
4317 }
4318 }
4319 if let (Some(s), Some(i)) = (sid, idx) {
4320 cxn.st_cxn = Some((s, i));
4321 }
4322 } else if local2 == b"endCxn" {
4323 let mut sid: Option<u32> = None;
4325 let mut idx: Option<u32> = None;
4326 for a in e2.attributes().flatten() {
4327 match a.key.as_ref() {
4328 b"id" => {
4329 sid = a
4330 .normalized_value(
4331 quick_xml::XmlVersion::Implicit1_0,
4332 )
4333 .unwrap_or_default()
4334 .parse()
4335 .ok();
4336 }
4337 b"idx" => {
4338 idx = a
4339 .normalized_value(
4340 quick_xml::XmlVersion::Implicit1_0,
4341 )
4342 .unwrap_or_default()
4343 .parse()
4344 .ok();
4345 }
4346 _ => {}
4347 }
4348 }
4349 if let (Some(s), Some(i)) = (sid, idx) {
4350 cxn.end_cxn = Some((s, i));
4351 }
4352 }
4353 }
4354 Ok(Event::Eof) => break,
4355 Err(_) => break,
4356 _ => {}
4357 }
4358 buf.clear();
4359 }
4360 } else if local == b"spPr" {
4361 let inner = collect_full_element(&mut rd, e.into_owned())?;
4362 cxn.properties = parse_sppr(&inner)?;
4363 } else if local == b"style" {
4364 let inner = collect_full_element(&mut rd, e.into_owned())?;
4366 cxn.style = Some(parse_shape_style(&inner)?);
4367 } else {
4368 let _ = collect_full_element(&mut rd, e.into_owned());
4370 }
4371 }
4372 Ok(Event::End(e)) => {
4373 if local_name(e.name().as_ref()) == b"cxnSp" {
4374 break;
4375 }
4376 }
4377 Ok(Event::Eof) => break,
4378 Err(e) => return Err(crate::Error::Xml(format!("cxnSp parse: {e}"))),
4379 _ => {}
4380 }
4381 buf.clear();
4382 }
4383 Ok(cxn)
4384}
4385
4386pub fn parse_grp_sp(xml: &str) -> crate::Result<Group> {
4415 let mut grp = Group::default();
4416 let mut rd = Reader::from_str(xml);
4417 rd.config_mut().trim_text(true);
4418 let mut buf = Vec::new();
4419 let mut child_sp_bufs: Vec<String> = Vec::new();
4421 let mut child_pic_bufs: Vec<String> = Vec::new();
4422 let mut child_cxn_bufs: Vec<String> = Vec::new();
4423 let mut child_grp_bufs: Vec<String> = Vec::new();
4424 let mut child_gfx_bufs: Vec<String> = Vec::new();
4425 let mut entered_root = false;
4427
4428 loop {
4429 match rd.read_event_into(&mut buf) {
4430 Ok(Event::Start(e)) => {
4431 let name = e.name();
4432 let local = local_name(name.as_ref());
4433 if local == b"grpSp" {
4434 if !entered_root {
4435 entered_root = true;
4437 } else {
4438 let inner = collect_full_element(&mut rd, e.into_owned())?;
4440 child_grp_bufs.push(inner);
4441 }
4442 } else if local == b"nvGrpSpPr" {
4443 let mut nv_depth = 1i32;
4445 loop {
4446 match rd.read_event_into(&mut buf) {
4447 Ok(Event::Start(_)) => nv_depth += 1,
4448 Ok(Event::End(_)) => {
4449 nv_depth -= 1;
4450 if nv_depth == 0 {
4451 break;
4452 }
4453 }
4454 Ok(Event::Empty(e2)) => {
4455 let name2 = e2.name();
4456 let local2 = local_name(name2.as_ref());
4457 if local2 == b"cNvPr" {
4458 for a in e2.attributes().flatten() {
4459 match a.key.as_ref() {
4460 b"id" => {
4461 if let Ok(v) = a
4462 .normalized_value(
4463 quick_xml::XmlVersion::Implicit1_0,
4464 )
4465 .unwrap_or_default()
4466 .parse::<u32>()
4467 {
4468 grp.id = v;
4469 }
4470 }
4471 b"name" => {
4472 grp.name = a
4473 .normalized_value(
4474 quick_xml::XmlVersion::Implicit1_0,
4475 )
4476 .unwrap_or_default()
4477 .to_string();
4478 }
4479 _ => {}
4480 }
4481 }
4482 }
4483 }
4484 Ok(Event::Eof) => break,
4485 Err(_) => break,
4486 _ => {}
4487 }
4488 buf.clear();
4489 }
4490 } else if local == b"grpSpPr" {
4491 parse_grp_sppr_into(&mut rd, &mut grp, e.into_owned())?;
4493 } else if local == b"sp" {
4494 let inner = collect_full_element(&mut rd, e.into_owned())?;
4495 child_sp_bufs.push(inner);
4496 } else if local == b"pic" {
4497 let inner = collect_full_element(&mut rd, e.into_owned())?;
4498 child_pic_bufs.push(inner);
4499 } else if local == b"cxnSp" {
4500 let inner = collect_full_element(&mut rd, e.into_owned())?;
4501 child_cxn_bufs.push(inner);
4502 } else if local == b"graphicFrame" {
4503 let inner = collect_full_element(&mut rd, e.into_owned())?;
4504 child_gfx_bufs.push(inner);
4505 } else if local == b"style" {
4506 let inner = collect_full_element(&mut rd, e.into_owned())?;
4508 grp.style = Some(parse_shape_style(&inner)?);
4509 } else {
4510 let _ = collect_full_element(&mut rd, e.into_owned());
4512 }
4513 }
4514 Ok(Event::End(e)) => {
4515 if local_name(e.name().as_ref()) == b"grpSp" {
4516 break;
4517 }
4518 }
4519 Ok(Event::Eof) => break,
4520 Err(e) => return Err(crate::Error::Xml(format!("grpSp parse: {e}"))),
4521 _ => {}
4522 }
4523 buf.clear();
4524 }
4525 for s in child_sp_bufs {
4527 if let Ok(sp) = parse_sp(&s) {
4528 grp.children.push(GroupChild::Sp(sp));
4529 }
4530 }
4531 for p in child_pic_bufs {
4532 if let Ok(pic) = parse_pic(&p) {
4533 grp.children.push(GroupChild::Pic(pic));
4534 }
4535 }
4536 for c in child_cxn_bufs {
4537 if let Ok(cxn) = parse_cxn_sp(&c) {
4538 grp.children.push(GroupChild::CxnSp(cxn));
4539 }
4540 }
4541 for g in child_grp_bufs {
4542 if let Ok(sub) = parse_grp_sp(&g) {
4543 grp.children.push(GroupChild::Group(Box::new(sub)));
4544 }
4545 }
4546 for f in child_gfx_bufs {
4547 if let Ok(frame) = parse_graphic_frame(&f) {
4548 grp.children.push(GroupChild::GraphicFrame(frame));
4549 }
4550 }
4551 Ok(grp)
4552}
4553
4554fn parse_grp_sppr_into<R: std::io::BufRead>(
4556 rd: &mut Reader<R>,
4557 grp: &mut Group,
4558 _start: quick_xml::events::BytesStart<'static>,
4559) -> crate::Result<()> {
4560 let mut buf = Vec::new();
4561 loop {
4562 match rd.read_event_into(&mut buf) {
4563 Ok(Event::Empty(e)) => {
4564 let name = e.name();
4565 let local = local_name(name.as_ref());
4566 if local == b"off" {
4567 let (mut x, mut y) = (None, None);
4568 for a in e.attributes().flatten() {
4569 match a.key.as_ref() {
4570 b"x" => {
4571 x = a
4572 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4573 .unwrap_or_default()
4574 .parse()
4575 .ok()
4576 }
4577 b"y" => {
4578 y = a
4579 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4580 .unwrap_or_default()
4581 .parse()
4582 .ok()
4583 }
4584 _ => {}
4585 }
4586 }
4587 if let (Some(x), Some(y)) = (x, y) {
4588 grp.off = (Emu(x), Emu(y));
4589 }
4590 } else if local == b"ext" {
4591 let (mut cx, mut cy) = (None, None);
4592 for a in e.attributes().flatten() {
4593 match a.key.as_ref() {
4594 b"cx" => {
4595 cx = a
4596 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4597 .unwrap_or_default()
4598 .parse()
4599 .ok()
4600 }
4601 b"cy" => {
4602 cy = a
4603 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4604 .unwrap_or_default()
4605 .parse()
4606 .ok()
4607 }
4608 _ => {}
4609 }
4610 }
4611 if let (Some(cx), Some(cy)) = (cx, cy) {
4612 grp.ext = (Emu(cx), Emu(cy));
4613 }
4614 }
4615 }
4617 Ok(Event::End(e)) => {
4618 if local_name(e.name().as_ref()) == b"grpSpPr" {
4619 return Ok(());
4620 }
4621 }
4622 Ok(Event::Eof) => return Ok(()),
4623 _ => {}
4624 }
4625 buf.clear();
4626 }
4627}
4628
4629pub fn parse_graphic_frame(xml: &str) -> crate::Result<GraphicFrame> {
4658 let mut frame = GraphicFrame::default();
4659 let mut rd = Reader::from_str(xml);
4660 rd.config_mut().trim_text(true);
4661 let mut buf = Vec::new();
4662 loop {
4663 match rd.read_event_into(&mut buf) {
4664 Ok(Event::Start(e)) => {
4665 let name = e.name();
4666 let local = local_name(name.as_ref());
4667 if local == b"graphicFrame" {
4668 } else if local == b"nvGraphicFramePr" {
4670 let mut nv_depth = 1i32;
4672 loop {
4673 match rd.read_event_into(&mut buf) {
4674 Ok(Event::Start(_)) => nv_depth += 1,
4675 Ok(Event::End(_)) => {
4676 nv_depth -= 1;
4677 if nv_depth == 0 {
4678 break;
4679 }
4680 }
4681 Ok(Event::Empty(e2)) => {
4682 let name2 = e2.name();
4683 let local2 = local_name(name2.as_ref());
4684 if local2 == b"cNvPr" {
4685 for a in e2.attributes().flatten() {
4686 match a.key.as_ref() {
4687 b"id" => {
4688 if let Ok(v) = a
4689 .normalized_value(
4690 quick_xml::XmlVersion::Implicit1_0,
4691 )
4692 .unwrap_or_default()
4693 .parse::<u32>()
4694 {
4695 frame.id = v;
4696 }
4697 }
4698 b"name" => {
4699 frame.name = a
4700 .normalized_value(
4701 quick_xml::XmlVersion::Implicit1_0,
4702 )
4703 .unwrap_or_default()
4704 .to_string();
4705 }
4706 _ => {}
4707 }
4708 }
4709 }
4710 }
4711 Ok(Event::Eof) => break,
4712 Err(_) => break,
4713 _ => {}
4714 }
4715 buf.clear();
4716 }
4717 } else if local == b"xfrm" {
4718 for a in e.attributes().flatten() {
4720 match a.key.as_ref() {
4721 b"rot" => {
4722 if let Ok(v) = a
4723 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4724 .unwrap_or_default()
4725 .parse::<i32>()
4726 {
4727 frame.properties.xfrm.rot = Some(v);
4728 }
4729 }
4730 b"flipH" => {
4731 let v = a
4732 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4733 .unwrap_or_default();
4734 if v == "1" {
4735 frame.properties.xfrm.flip_h = true;
4736 }
4737 }
4738 b"flipV" => {
4739 let v = a
4740 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4741 .unwrap_or_default();
4742 if v == "1" {
4743 frame.properties.xfrm.flip_v = true;
4744 }
4745 }
4746 _ => {}
4747 }
4748 }
4749 parse_xfrm_into(&mut rd, &mut frame.properties);
4751 } else if local == b"graphic" {
4752 parse_graphic_into(&mut rd, &mut frame)?;
4754 } else {
4755 let _ = collect_full_element(&mut rd, e.into_owned());
4757 }
4758 }
4759 Ok(Event::End(e)) => {
4760 if local_name(e.name().as_ref()) == b"graphicFrame" {
4761 break;
4762 }
4763 }
4764 Ok(Event::Eof) => break,
4765 Err(e) => return Err(crate::Error::Xml(format!("graphicFrame parse: {e}"))),
4766 _ => {}
4767 }
4768 buf.clear();
4769 }
4770 Ok(frame)
4771}
4772
4773fn parse_graphic_into<R: std::io::BufRead>(
4775 rd: &mut Reader<R>,
4776 frame: &mut GraphicFrame,
4777) -> crate::Result<()> {
4778 let mut buf = Vec::new();
4779 loop {
4780 match rd.read_event_into(&mut buf) {
4781 Ok(Event::Start(e)) => {
4782 let name = e.name();
4783 let local = local_name(name.as_ref());
4784 if local == b"graphicData" {
4785 let mut uri = String::new();
4787 for a in e.attributes().flatten() {
4788 if a.key.as_ref() == b"uri" {
4789 uri = a
4790 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
4791 .unwrap_or_default()
4792 .to_string();
4793 }
4794 }
4795 if uri == "http://schemas.openxmlformats.org/drawingml/2006/table" {
4797 loop {
4799 match rd.read_event_into(&mut buf) {
4800 Ok(Event::Start(e2)) => {
4801 if local_name(e2.name().as_ref()) == b"tbl" {
4802 let tbl_xml = collect_full_element(rd, e2.into_owned())?;
4803 if let Ok(tbl) = parse_table(&tbl_xml) {
4804 frame.graphic = crate::oxml::shape::Graphic::Table(tbl);
4805 }
4806 } else {
4807 let _ = collect_full_element(rd, e2.into_owned());
4808 }
4809 }
4810 Ok(Event::End(e2)) => {
4811 if local_name(e2.name().as_ref()) == b"graphicData" {
4812 break;
4813 }
4814 }
4815 Ok(Event::Eof) => break,
4816 _ => {}
4817 }
4818 buf.clear();
4819 }
4820 } else if uri == "http://schemas.openxmlformats.org/drawingml/2006/diagram" {
4821 let raw_xml = collect_full_element(rd, e.into_owned())?;
4825 let (dm_rid, lo_rid, qs_rid, cs_rid) = parse_smartart_rel_ids(&raw_xml);
4828 frame.graphic = crate::oxml::shape::Graphic::SmartArt(
4829 crate::oxml::shape::SmartArtRef {
4830 raw_xml,
4831 dm_rid,
4832 lo_rid,
4833 qs_rid,
4834 cs_rid,
4835 },
4836 );
4837 } else if uri == "http://schemas.openxmlformats.org/drawingml/2006/chart" {
4838 let raw_xml = collect_full_element(rd, e.into_owned())?;
4843 let rid = parse_chart_rid(&raw_xml);
4844 frame.graphic =
4845 crate::oxml::shape::Graphic::Chart(crate::oxml::chart::Chart::new(
4846 crate::oxml::chart::ChartType::Column,
4847 crate::oxml::chart::ChartData::default(),
4848 ));
4849 if let crate::oxml::shape::Graphic::Chart(c) = &mut frame.graphic {
4851 c.rid = rid;
4852 }
4853 } else {
4854 let _ = collect_full_element(rd, e.into_owned());
4856 }
4857 } else {
4858 let _ = collect_full_element(rd, e.into_owned());
4859 }
4860 }
4861 Ok(Event::End(e)) => {
4862 if local_name(e.name().as_ref()) == b"graphic" {
4863 return Ok(());
4864 }
4865 }
4866 Ok(Event::Eof) => return Ok(()),
4867 _ => {}
4868 }
4869 buf.clear();
4870 }
4871}
4872
4873fn parse_smartart_rel_ids(
4893 raw_xml: &str,
4894) -> (
4895 Option<String>,
4896 Option<String>,
4897 Option<String>,
4898 Option<String>,
4899) {
4900 fn find_attr(xml: &str, attr_name: &str) -> Option<String> {
4904 let pattern = format!("{}=\"", attr_name);
4906 let pos = xml.find(&pattern)?;
4907 let value_start = pos + pattern.len();
4908 let value_end = xml[value_start..].find('"')?;
4909 Some(xml[value_start..value_start + value_end].to_string())
4910 }
4911
4912 let dm = find_attr(raw_xml, "r:dm");
4913 let lo = find_attr(raw_xml, "r:lo");
4914 let qs = find_attr(raw_xml, "r:qs");
4915 let cs = find_attr(raw_xml, "r:cs");
4916 (dm, lo, qs, cs)
4917}
4918
4919fn parse_chart_rid(raw_xml: &str) -> String {
4939 let pattern = "r:id=\"";
4941 if let Some(pos) = raw_xml.find(pattern) {
4942 let value_start = pos + pattern.len();
4943 if let Some(end) = raw_xml[value_start..].find('"') {
4944 return raw_xml[value_start..value_start + end].to_string();
4945 }
4946 }
4947 String::new()
4948}
4949
4950fn parse_table(xml: &str) -> crate::Result<crate::oxml::table::Table> {
4972 use crate::oxml::table::{Col, Table};
4973
4974 let mut tbl = Table::default();
4975 let mut rd = Reader::from_str(xml);
4976 rd.config_mut().trim_text(true);
4977 let mut buf = Vec::new();
4978 let mut tr_bufs: Vec<String> = Vec::new();
4980 let mut grid_col_widths: Vec<Emu> = Vec::new();
4981
4982 loop {
4983 match rd.read_event_into(&mut buf) {
4984 Ok(Event::Start(e)) => {
4985 let name = e.name();
4986 let local = local_name(name.as_ref());
4987 if local == b"tbl" {
4988 } else if local == b"tblPr" {
4990 let inner = collect_full_element(&mut rd, e.into_owned())?;
4992 parse_tbl_pr_into(&inner, &mut tbl);
4993 } else if local == b"tblGrid" {
4994 loop {
4996 match rd.read_event_into(&mut buf) {
4997 Ok(Event::Empty(e2)) => {
4998 if local_name(e2.name().as_ref()) == b"gridCol" {
4999 let mut w: Option<i64> = None;
5000 for a in e2.attributes().flatten() {
5001 if a.key.as_ref() == b"w" {
5002 w = a
5003 .normalized_value(
5004 quick_xml::XmlVersion::Implicit1_0,
5005 )
5006 .unwrap_or_default()
5007 .parse()
5008 .ok();
5009 }
5010 }
5011 grid_col_widths.push(Emu(w.unwrap_or(0)));
5012 }
5013 }
5014 Ok(Event::End(e2)) => {
5015 if local_name(e2.name().as_ref()) == b"tblGrid" {
5016 break;
5017 }
5018 }
5019 Ok(Event::Eof) => break,
5020 _ => {}
5021 }
5022 buf.clear();
5023 }
5024 } else if local == b"tr" {
5025 let inner = collect_full_element(&mut rd, e.into_owned())?;
5026 tr_bufs.push(inner);
5027 } else {
5028 let _ = collect_full_element(&mut rd, e.into_owned());
5029 }
5030 }
5031 Ok(Event::Eof) => break,
5032 Err(e) => return Err(crate::Error::Xml(format!("tbl parse: {e}"))),
5033 _ => {}
5034 }
5035 buf.clear();
5036 }
5037
5038 tbl.cols = grid_col_widths.iter().map(|w| Col { width: *w }).collect();
5040
5041 for tr_xml in tr_bufs {
5043 if let Ok(row) = parse_table_row(&tr_xml) {
5044 tbl.rows.push(row);
5045 }
5046 }
5047
5048 Ok(tbl)
5049}
5050
5051fn parse_tbl_pr_into(xml: &str, tbl: &mut crate::oxml::table::Table) {
5055 use crate::oxml::table::TableStyle;
5056
5057 let look = &mut tbl.tbl_look;
5058 let mut rd = Reader::from_str(xml);
5059 let mut buf = Vec::new();
5060 loop {
5061 match rd.read_event_into(&mut buf) {
5062 Ok(Event::Empty(e)) => {
5063 if local_name(e.name().as_ref()) == b"tblLook" {
5064 for a in e.attributes().flatten() {
5065 let v = a
5066 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5067 .unwrap_or_default();
5068 match a.key.as_ref() {
5069 b"val" => look.val = v.to_string(),
5070 b"firstRow" => look.first_row = v == "1",
5071 b"lastRow" => look.last_row = v == "1",
5072 b"firstColumn" => look.first_column = v == "1",
5073 b"lastColumn" => look.last_column = v == "1",
5074 b"noHBand" => look.no_h_band = v == "1",
5075 b"noVBand" => look.no_v_band = v == "1",
5076 _ => {}
5077 }
5078 }
5079 }
5080 if local_name(e.name().as_ref()) == b"tableStyleId" {
5082 }
5084 }
5085 Ok(Event::Start(e)) => {
5086 if local_name(e.name().as_ref()) == b"tableStyleId" {
5087 let mut guid = String::new();
5089 loop {
5090 match rd.read_event_into(&mut buf) {
5091 Ok(Event::Text(t)) => {
5092 guid.push_str(&t.decode().unwrap_or_default());
5093 }
5094 Ok(Event::End(e2)) => {
5095 if local_name(e2.name().as_ref()) == b"tableStyleId" {
5096 break;
5097 }
5098 }
5099 Ok(Event::Eof) => break,
5100 _ => {}
5101 }
5102 buf.clear();
5103 }
5104 let trimmed = guid.trim();
5105 if !trimmed.is_empty() {
5106 tbl.table_style = Some(TableStyle::new(trimmed));
5107 }
5108 }
5109 }
5110 Ok(Event::Eof) => break,
5111 _ => {}
5112 }
5113 buf.clear();
5114 }
5115}
5116
5117fn parse_table_row(xml: &str) -> crate::Result<crate::oxml::table::Row> {
5119 use crate::oxml::table::Row;
5120
5121 let mut row = Row::default();
5122 let mut rd = Reader::from_str(xml);
5123 rd.config_mut().trim_text(true);
5124 let mut buf = Vec::new();
5125 let mut tc_bufs: Vec<String> = Vec::new();
5128
5129 loop {
5130 match rd.read_event_into(&mut buf) {
5131 Ok(Event::Start(e)) => {
5132 let name = e.name();
5133 let local = local_name(name.as_ref());
5134 if local == b"tr" {
5135 for a in e.attributes().flatten() {
5137 if a.key.as_ref() == b"h" {
5138 if let Ok(v) = a
5139 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5140 .unwrap_or_default()
5141 .parse::<i64>()
5142 {
5143 row.height = Emu(v);
5144 }
5145 }
5146 }
5147 } else if local == b"tc" {
5148 let inner = collect_full_element(&mut rd, e.into_owned())?;
5149 tc_bufs.push(inner);
5150 } else {
5151 let _ = collect_full_element(&mut rd, e.into_owned());
5152 }
5153 }
5154 Ok(Event::Empty(_e)) => {
5155 }
5157 Ok(Event::End(e)) => {
5158 if local_name(e.name().as_ref()) == b"tr" {
5159 break;
5160 }
5161 }
5162 Ok(Event::Eof) => break,
5163 Err(e) => return Err(crate::Error::Xml(format!("tr parse: {e}"))),
5164 _ => {}
5165 }
5166 buf.clear();
5167 }
5168
5169 for tc_xml in tc_bufs {
5171 if let Ok(cell) = parse_table_cell(&tc_xml) {
5172 row.cells.push(cell);
5173 }
5174 }
5175
5176 Ok(row)
5177}
5178
5179fn parse_table_cell(xml: &str) -> crate::Result<crate::oxml::table::Cell> {
5181 let mut cell = crate::oxml::table::Cell::default();
5182 let mut rd = Reader::from_str(xml);
5183 rd.config_mut().trim_text(true);
5184 let mut buf = Vec::new();
5185
5186 loop {
5187 match rd.read_event_into(&mut buf) {
5188 Ok(Event::Start(e)) => {
5189 let name = e.name();
5190 let local = local_name(name.as_ref());
5191 if local == b"tc" {
5192 for a in e.attributes().flatten() {
5194 let v = a
5195 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5196 .unwrap_or_default();
5197 match a.key.as_ref() {
5198 b"gridSpan" => {
5199 if let Ok(g) = v.parse::<u32>() {
5200 cell.grid_span = g;
5201 }
5202 }
5203 b"rowSpan" => {
5204 if let Ok(r) = v.parse::<u32>() {
5205 cell.row_span = r;
5206 }
5207 }
5208 b"hMerge" if v == "1" => {
5209 cell.h_merge = true;
5210 }
5211 b"vMerge" if v == "1" => {
5212 cell.v_merge = true;
5213 }
5214 _ => {}
5215 }
5216 }
5217 } else if local == b"txBody" {
5218 let inner = collect_full_element(&mut rd, e.into_owned())?;
5219 cell.text = parse_txbody(&inner)?;
5220 } else if local == b"tcPr" {
5221 let inner = collect_full_element(&mut rd, e.into_owned())?;
5223 parse_tc_pr_into(&inner, &mut cell);
5224 } else {
5225 let _ = collect_full_element(&mut rd, e.into_owned());
5226 }
5227 }
5228 Ok(Event::End(e)) => {
5229 if local_name(e.name().as_ref()) == b"tc" {
5230 break;
5231 }
5232 }
5233 Ok(Event::Eof) => break,
5234 Err(e) => return Err(crate::Error::Xml(format!("tc parse: {e}"))),
5235 _ => {}
5236 }
5237 buf.clear();
5238 }
5239
5240 Ok(cell)
5241}
5242
5243fn parse_tc_pr_into(xml: &str, cell: &mut crate::oxml::table::Cell) {
5245 use crate::oxml::table::VerticalAnchor;
5246
5247 let mut rd = Reader::from_str(xml);
5248 let mut buf = Vec::new();
5249
5250 loop {
5251 match rd.read_event_into(&mut buf) {
5252 Ok(Event::Start(e)) => {
5253 let name = e.name();
5254 let local = local_name(name.as_ref());
5255 if local == b"tcPr" {
5258 for a in e.attributes().flatten() {
5259 let v = a
5260 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5261 .unwrap_or_default();
5262 match a.key.as_ref() {
5263 b"marT" => {
5264 if let Ok(m) = v.parse::<i64>() {
5265 cell.margin.0 = Some(Emu(m));
5266 }
5267 }
5268 b"marL" => {
5269 if let Ok(m) = v.parse::<i64>() {
5270 cell.margin.1 = Some(Emu(m));
5271 }
5272 }
5273 b"marB" => {
5274 if let Ok(m) = v.parse::<i64>() {
5275 cell.margin.2 = Some(Emu(m));
5276 }
5277 }
5278 b"marR" => {
5279 if let Ok(m) = v.parse::<i64>() {
5280 cell.margin.3 = Some(Emu(m));
5281 }
5282 }
5283 b"anchor" => {
5284 cell.anchor = match &*v {
5286 "t" => VerticalAnchor::Top,
5287 "ctr" => VerticalAnchor::Middle,
5288 "b" => VerticalAnchor::Bottom,
5289 _ => VerticalAnchor::Middle,
5290 };
5291 }
5292 _ => {}
5293 }
5294 }
5295 } else if local == b"solidFill" {
5296 if let Ok(color) = parse_solid_fill(&mut rd, e.into_owned()) {
5298 cell.fill = color;
5299 }
5300 } else if local == b"lnL" {
5301 let border = parse_cell_border(&mut rd, e.into_owned());
5303 cell.border_left = Some(border);
5304 } else if local == b"lnR" {
5305 let border = parse_cell_border(&mut rd, e.into_owned());
5306 cell.border_right = Some(border);
5307 } else if local == b"lnT" {
5308 let border = parse_cell_border(&mut rd, e.into_owned());
5309 cell.border_top = Some(border);
5310 } else if local == b"lnB" {
5311 let border = parse_cell_border(&mut rd, e.into_owned());
5312 cell.border_bottom = Some(border);
5313 } else {
5314 let _ = collect_full_element(&mut rd, e.into_owned());
5315 }
5316 }
5317 Ok(Event::Empty(e)) => {
5318 let name = e.name();
5319 let local = local_name(name.as_ref());
5320 if local == b"tcPr" {
5321 for a in e.attributes().flatten() {
5323 let v = a
5324 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5325 .unwrap_or_default();
5326 match a.key.as_ref() {
5327 b"marT" => {
5328 if let Ok(m) = v.parse::<i64>() {
5329 cell.margin.0 = Some(Emu(m));
5330 }
5331 }
5332 b"marL" => {
5333 if let Ok(m) = v.parse::<i64>() {
5334 cell.margin.1 = Some(Emu(m));
5335 }
5336 }
5337 b"marB" => {
5338 if let Ok(m) = v.parse::<i64>() {
5339 cell.margin.2 = Some(Emu(m));
5340 }
5341 }
5342 b"marR" => {
5343 if let Ok(m) = v.parse::<i64>() {
5344 cell.margin.3 = Some(Emu(m));
5345 }
5346 }
5347 b"anchor" => {
5348 cell.anchor = match &*v {
5350 "t" => VerticalAnchor::Top,
5351 "ctr" => VerticalAnchor::Middle,
5352 "b" => VerticalAnchor::Bottom,
5353 _ => VerticalAnchor::Middle,
5354 };
5355 }
5356 _ => {}
5357 }
5358 }
5359 }
5360 }
5361 Ok(Event::End(e)) => {
5362 if local_name(e.name().as_ref()) == b"tcPr" {
5363 return;
5364 }
5365 }
5366 Ok(Event::Eof) => return,
5367 _ => {}
5368 }
5369 buf.clear();
5370 }
5371}
5372
5373fn parse_cell_border<R: std::io::BufRead>(
5375 rd: &mut Reader<R>,
5376 start: quick_xml::events::BytesStart<'static>,
5377) -> crate::oxml::table::CellBorder {
5378 use crate::oxml::table::CellBorder;
5379 let mut border = CellBorder::default();
5380 for a in start.attributes().flatten() {
5382 if a.key.as_ref() == b"w" {
5383 if let Ok(v) = a
5384 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5385 .unwrap_or_default()
5386 .parse::<i64>()
5387 {
5388 border.width = Emu(v);
5389 }
5390 }
5391 }
5392 let mut buf = Vec::new();
5393 loop {
5394 match rd.read_event_into(&mut buf) {
5395 Ok(Event::Empty(e)) => {
5396 let name = e.name();
5397 let local = local_name(name.as_ref());
5398 if local == b"noFill" {
5399 border.no_fill = true;
5400 }
5401 }
5403 Ok(Event::Start(e)) => {
5404 let name = e.name();
5405 let local = local_name(name.as_ref());
5406 if local == b"solidFill" {
5407 if let Ok(color) = parse_solid_fill(rd, e.into_owned()) {
5408 border.color = color;
5409 }
5410 } else {
5411 let _ = collect_full_element(rd, e.into_owned());
5412 }
5413 }
5414 Ok(Event::End(e)) => {
5415 let name = e.name();
5417 let local = local_name(name.as_ref());
5418 if local == b"lnL" || local == b"lnR" || local == b"lnT" || local == b"lnB" {
5419 return border;
5420 }
5421 }
5422 Ok(Event::Eof) => return border,
5423 _ => {}
5424 }
5425 buf.clear();
5426 }
5427}
5428
5429pub fn parse_notes(xml: &str) -> crate::Result<TextBody> {
5456 let mut rd = Reader::from_str(xml);
5457 rd.config_mut().trim_text(true);
5458 let mut buf = Vec::new();
5459 let mut tx_buf: Option<String> = None;
5460 loop {
5461 match rd.read_event_into(&mut buf) {
5462 Ok(Event::Start(e)) => {
5463 let name = e.name();
5464 let local = local_name(name.as_ref());
5465 if local == b"txBody" && tx_buf.is_none() {
5466 let inner = collect_full_element(&mut rd, e.into_owned())?;
5467 tx_buf = Some(inner);
5468 }
5469 }
5473 Ok(Event::Eof) => break,
5474 Err(e) => return Err(crate::Error::Xml(format!("notes parse: {e}"))),
5475 _ => {}
5476 }
5477 buf.clear();
5478 }
5479 if let Some(buf) = tx_buf {
5480 parse_txbody(&buf)
5481 } else {
5482 Ok(TextBody::new())
5483 }
5484}
5485
5486pub fn parse_comments(xml: &str) -> crate::Result<crate::oxml::comments::CommentList> {
5496 use crate::oxml::comments::{Comment, CommentList};
5497
5498 let mut lst = CommentList::new();
5499 let mut rd = Reader::from_str(xml);
5500 rd.config_mut().trim_text(true);
5501 let mut buf = Vec::new();
5502 let mut cur_author_id: Option<u32> = None;
5504 let mut cur_dt: String = String::new();
5505 let mut cur_idx: Option<u32> = None;
5506 let mut in_cm = false;
5508 let mut in_text = false;
5510 let mut cur_pos_x: i64 = 0;
5512 let mut cur_pos_y: i64 = 0;
5513 let mut cur_text = String::new();
5514
5515 loop {
5516 match rd.read_event_into(&mut buf) {
5517 Ok(Event::Start(e)) => {
5518 let name = e.name();
5519 let local = local_name(name.as_ref());
5520 if local == b"cm" {
5521 in_cm = true;
5523 cur_author_id = None;
5524 cur_dt.clear();
5525 cur_idx = None;
5526 cur_pos_x = 0;
5527 cur_pos_y = 0;
5528 cur_text.clear();
5529 for a in e.attributes().flatten() {
5531 let key = a.key.as_ref();
5532 let val = a
5533 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5534 .unwrap_or_default()
5535 .to_string();
5536 match key {
5537 b"authorId" => {
5538 cur_author_id = val.parse::<u32>().ok();
5539 }
5540 b"dt" => {
5541 cur_dt = val;
5542 }
5543 b"idx" => {
5544 cur_idx = val.parse::<u32>().ok();
5545 }
5546 _ => {}
5547 }
5548 }
5549 } else if in_cm && local == b"text" {
5550 in_text = true;
5551 cur_text.clear();
5552 }
5553 }
5554 Ok(Event::Empty(e)) => {
5555 let name = e.name();
5556 let local = local_name(name.as_ref());
5557 if in_cm && local == b"pos" {
5558 for a in e.attributes().flatten() {
5560 let key = a.key.as_ref();
5561 let val = a
5562 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5563 .unwrap_or_default();
5564 match key {
5565 b"x" => {
5566 cur_pos_x = val.parse::<i64>().unwrap_or(0);
5567 }
5568 b"y" => {
5569 cur_pos_y = val.parse::<i64>().unwrap_or(0);
5570 }
5571 _ => {}
5572 }
5573 }
5574 }
5575 }
5576 Ok(Event::Text(t)) => {
5577 if in_text {
5578 cur_text.push_str(&t.decode().unwrap_or_default());
5579 }
5580 }
5581 Ok(Event::End(e)) => {
5582 let name = e.name();
5583 let local = local_name(name.as_ref());
5584 if local == b"cm" && in_cm {
5585 let c = Comment {
5587 author_id: cur_author_id.unwrap_or(0),
5588 date_time: std::mem::take(&mut cur_dt),
5589 idx: cur_idx.unwrap_or(0),
5590 pos_x: cur_pos_x,
5591 pos_y: cur_pos_y,
5592 text: std::mem::take(&mut cur_text),
5593 };
5594 lst.push(c);
5595 in_cm = false;
5596 in_text = false;
5597 } else if local == b"text" && in_text {
5598 in_text = false;
5599 }
5600 }
5601 Ok(Event::Eof) => break,
5602 Err(e) => return Err(crate::Error::Xml(format!("comments parse: {e}"))),
5603 _ => {}
5604 }
5605 buf.clear();
5606 }
5607 Ok(lst)
5608}
5609
5610pub fn parse_comment_authors(xml: &str) -> crate::Result<crate::oxml::comments::CommentAuthorList> {
5615 use crate::oxml::comments::{CommentAuthor, CommentAuthorList};
5616
5617 let mut lst = CommentAuthorList::new();
5618 let mut rd = Reader::from_str(xml);
5619 rd.config_mut().trim_text(true);
5620 let mut buf = Vec::new();
5621
5622 loop {
5623 match rd.read_event_into(&mut buf) {
5624 Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
5625 let name = e.name();
5626 let local = local_name(name.as_ref());
5627 if local == b"cmAuthor" {
5628 let mut id: u32 = 0;
5629 let mut name_val = String::new();
5630 let mut initials = String::new();
5631 for a in e.attributes().flatten() {
5632 let key = a.key.as_ref();
5633 let val = a
5634 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5635 .unwrap_or_default()
5636 .to_string();
5637 match key {
5638 b"id" => {
5639 id = val.parse::<u32>().unwrap_or(0);
5640 }
5641 b"name" => {
5642 name_val = val;
5643 }
5644 b"initials" => {
5645 initials = val;
5646 }
5647 _ => {}
5648 }
5649 }
5650 lst.push(CommentAuthor::new(id, name_val, initials));
5651 }
5652 }
5653 Ok(Event::Eof) => break,
5654 Err(e) => return Err(crate::Error::Xml(format!("commentAuthors parse: {e}"))),
5655 _ => {}
5656 }
5657 buf.clear();
5658 }
5659 Ok(lst)
5660}
5661
5662#[allow(clippy::type_complexity)]
5675pub fn parse_pres_root(
5676 xml: &str,
5677) -> crate::Result<(
5678 Vec<(u32, String)>,
5679 Option<Emu>,
5680 Option<Emu>,
5681 Vec<(u32, String)>,
5682 crate::oxml::section::SectionList,
5683 Vec<(u32, String)>,
5684)> {
5685 let mut rd = Reader::from_str(xml);
5686 rd.config_mut().trim_text(true);
5687 let mut buf = Vec::new();
5688 let mut sld_ids: Vec<(u32, String)> = Vec::new();
5689 let mut sld_sz: (Option<Emu>, Option<Emu>) = (None, None);
5690 let mut sld_master_ids: Vec<(u32, String)> = Vec::new();
5692 let mut notes_master_ids: Vec<(u32, String)> = Vec::new();
5694 let mut sections = crate::oxml::section::SectionList::default();
5696 loop {
5697 match rd.read_event_into(&mut buf) {
5698 Ok(Event::Empty(e)) => {
5699 let name = e.name();
5700 let local = local_name(name.as_ref());
5701 if local == b"sldId" {
5702 let mut id: Option<u32> = None;
5703 let mut rid: Option<String> = None;
5704 for a in e.attributes().flatten() {
5705 let k = a.key.as_ref();
5706 if k == b"id" {
5708 if let Ok(v) = a
5709 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5710 .unwrap_or_default()
5711 .parse::<u32>()
5712 {
5713 id = Some(v);
5714 }
5715 } else if k == b"r:id" || k.ends_with(b":id") {
5716 rid = Some(
5717 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5718 .unwrap_or_default()
5719 .to_string(),
5720 );
5721 }
5722 }
5723 if let (Some(i), Some(r)) = (id, rid) {
5724 sld_ids.push((i, r));
5725 }
5726 } else if local == b"sldMasterId" {
5727 let mut id: Option<u32> = None;
5729 let mut rid: Option<String> = None;
5730 for a in e.attributes().flatten() {
5731 let k = a.key.as_ref();
5732 if k == b"id" {
5733 if let Ok(v) = a
5734 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5735 .unwrap_or_default()
5736 .parse::<u32>()
5737 {
5738 id = Some(v);
5739 }
5740 } else if k == b"r:id" || k.ends_with(b":id") {
5741 rid = Some(
5742 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5743 .unwrap_or_default()
5744 .to_string(),
5745 );
5746 }
5747 }
5748 if let (Some(i), Some(r)) = (id, rid) {
5749 sld_master_ids.push((i, r));
5750 }
5751 } else if local == b"notesMasterId" {
5752 let mut id: Option<u32> = None;
5755 let mut rid: Option<String> = None;
5756 for a in e.attributes().flatten() {
5757 let k = a.key.as_ref();
5758 if k == b"id" {
5759 if let Ok(v) = a
5760 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5761 .unwrap_or_default()
5762 .parse::<u32>()
5763 {
5764 id = Some(v);
5765 }
5766 } else if k == b"r:id" || k.ends_with(b":id") {
5767 rid = Some(
5768 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5769 .unwrap_or_default()
5770 .to_string(),
5771 );
5772 }
5773 }
5774 if let (Some(i), Some(r)) = (id, rid) {
5775 notes_master_ids.push((i, r));
5776 }
5777 } else if local == b"sldSz" {
5778 let mut cx: Option<i64> = None;
5779 let mut cy: Option<i64> = None;
5780 for a in e.attributes().flatten() {
5781 match a.key.as_ref() {
5782 b"cx" => {
5783 cx = a
5784 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5785 .unwrap_or_default()
5786 .parse()
5787 .ok()
5788 }
5789 b"cy" => {
5790 cy = a
5791 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5792 .unwrap_or_default()
5793 .parse()
5794 .ok()
5795 }
5796 _ => {}
5797 }
5798 }
5799 sld_sz = (cx.map(Emu), cy.map(Emu));
5800 }
5801 }
5802 Ok(Event::Start(e)) => {
5803 let name = e.name();
5804 let local = local_name(name.as_ref());
5805 if local == b"sldMasterId" {
5806 let mut id: Option<u32> = None;
5808 let mut rid: Option<String> = None;
5809 for a in e.attributes().flatten() {
5810 let k = a.key.as_ref();
5811 if k == b"id" {
5812 if let Ok(v) = a
5813 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5814 .unwrap_or_default()
5815 .parse::<u32>()
5816 {
5817 id = Some(v);
5818 }
5819 } else if k == b"r:id" || k.ends_with(b":id") {
5820 rid = Some(
5821 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5822 .unwrap_or_default()
5823 .to_string(),
5824 );
5825 }
5826 }
5827 if let (Some(i), Some(r)) = (id, rid) {
5828 sld_master_ids.push((i, r));
5829 }
5830 let _ = collect_full_element(&mut rd, e.into_owned());
5832 } else if local == b"notesMasterId" {
5833 let mut id: Option<u32> = None;
5836 let mut rid: Option<String> = None;
5837 for a in e.attributes().flatten() {
5838 let k = a.key.as_ref();
5839 if k == b"id" {
5840 if let Ok(v) = a
5841 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5842 .unwrap_or_default()
5843 .parse::<u32>()
5844 {
5845 id = Some(v);
5846 }
5847 } else if k == b"r:id" || k.ends_with(b":id") {
5848 rid = Some(
5849 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
5850 .unwrap_or_default()
5851 .to_string(),
5852 );
5853 }
5854 }
5855 if let (Some(i), Some(r)) = (id, rid) {
5856 notes_master_ids.push((i, r));
5857 }
5858 let _ = collect_full_element(&mut rd, e.into_owned());
5860 } else if local == b"extLst" {
5861 let ext_lst_xml = collect_full_element(&mut rd, e.into_owned())?;
5864 let parsed = parse_sections_from_ext_lst(&ext_lst_xml)?;
5865 if !parsed.is_empty() {
5866 sections = parsed;
5867 }
5868 }
5869 }
5870 Ok(Event::Eof) => break,
5871 Err(e) => return Err(crate::Error::Xml(format!("pres root parse: {e}"))),
5872 _ => {}
5873 }
5874 buf.clear();
5875 }
5876 Ok((
5877 sld_ids,
5878 sld_sz.0,
5879 sld_sz.1,
5880 sld_master_ids,
5881 sections,
5882 notes_master_ids,
5883 ))
5884}
5885
5886fn parse_sections_from_ext_lst(xml: &str) -> crate::Result<crate::oxml::section::SectionList> {
5913 let mut rd = Reader::from_str(xml);
5914 rd.config_mut().trim_text(true);
5915 let mut buf = Vec::new();
5916 let mut result = crate::oxml::section::SectionList::default();
5917 let mut state: u8 = 0;
5923 let mut cur_name = String::new();
5925 let mut cur_ids: Vec<u32> = Vec::new();
5926
5927 loop {
5928 match rd.read_event_into(&mut buf) {
5929 Ok(Event::Start(e)) => {
5930 let name = e.name();
5932 let local = local_name(name.as_ref());
5933 match state {
5934 0 if local == b"ext" => {
5935 let mut uri_val = String::new();
5937 for a in e.attributes().flatten() {
5938 if a.key.as_ref() == b"uri" {
5939 uri_val = a
5940 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5941 .unwrap_or_default()
5942 .to_string();
5943 }
5944 }
5945 if uri_val == crate::oxml::section::SECTION_EXT_URI {
5946 state = 1;
5947 } else {
5948 let _ = collect_full_element(&mut rd, e.into_owned());
5950 }
5951 }
5952 1 if local == b"sectionLst" => {
5953 state = 2;
5954 }
5955 2 if local == b"section" => {
5956 cur_name.clear();
5958 cur_ids.clear();
5959 for a in e.attributes().flatten() {
5960 if a.key.as_ref() == b"name" {
5961 cur_name = a
5962 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5963 .unwrap_or_default()
5964 .to_string();
5965 }
5966 }
5967 state = 3;
5968 }
5969 3 if local == b"sldIdLst" => {
5970 }
5972 _ => {
5973 let _ = collect_full_element(&mut rd, e.into_owned());
5975 }
5976 }
5977 }
5978 Ok(Event::Empty(e)) => {
5979 let name = e.name();
5981 let local = local_name(name.as_ref());
5982 if state == 3 && local == b"sldId" {
5984 for a in e.attributes().flatten() {
5985 if a.key.as_ref() == b"id" {
5986 if let Ok(v) = a
5987 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
5988 .unwrap_or_default()
5989 .parse::<u32>()
5990 {
5991 cur_ids.push(v);
5992 }
5993 }
5994 }
5995 }
5996 }
5997 Ok(Event::End(e)) => {
5998 let name = e.name();
6000 let local = local_name(name.as_ref());
6001 match state {
6002 3 if local == b"section" => {
6003 result.push(crate::oxml::section::Section {
6005 name: std::mem::take(&mut cur_name),
6006 slide_ids: std::mem::take(&mut cur_ids),
6007 });
6008 state = 2;
6009 }
6010 2 if local == b"sectionLst" => {
6011 state = 1;
6012 }
6013 1 if local == b"ext" => {
6014 state = 0;
6015 }
6016 0 if local == b"extLst" => {
6017 break;
6018 }
6019 _ => {}
6020 }
6021 }
6022 Ok(Event::Eof) => break,
6023 Err(e) => return Err(crate::Error::Xml(format!("sectionLst parse: {e}"))),
6024 _ => {}
6025 }
6026 buf.clear();
6027 }
6028 Ok(result)
6029}
6030
6031pub fn parse_sld_master(xml: &str) -> crate::Result<crate::oxml::slidemaster::SldMaster> {
6045 let mut rd = Reader::from_str(xml);
6046 rd.config_mut().trim_text(true);
6047 let mut buf = Vec::new();
6048 let mut master = crate::oxml::slidemaster::SldMaster::default();
6049 let mut state: u8 = 0;
6051 let mut sp_bufs: Vec<String> = Vec::new();
6052
6053 loop {
6054 match rd.read_event_into(&mut buf) {
6055 Ok(Event::Start(e)) => {
6056 let name = e.name();
6057 let local = local_name(name.as_ref());
6058 match state {
6059 0 if local == b"sldMaster" => {
6060 state = 1;
6061 }
6062 1 if local == b"cSld" => {}
6064 1 if local == b"spTree" => {
6065 state = 2;
6066 }
6067 1 if local == b"sldLayoutIdLst" => {
6068 state = 3;
6069 }
6070 2 if local == b"sp" => {
6071 let inner = collect_full_element(&mut rd, e.into_owned())?;
6073 sp_bufs.push(inner);
6074 }
6075 _ => {
6076 let _ = collect_full_element(&mut rd, e.into_owned());
6078 }
6079 }
6080 }
6081 Ok(Event::Empty(e)) => {
6082 let name = e.name();
6083 let local = local_name(name.as_ref());
6084 if state == 3 && local == b"sldLayoutId" {
6085 for a in e.attributes().flatten() {
6087 let k = a.key.as_ref();
6088 if k == b"r:id" || k.ends_with(b":id") {
6089 let rid = a
6090 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6091 .unwrap_or_default()
6092 .to_string();
6093 master.layout_rids.push(rid);
6094 }
6095 }
6096 }
6097 }
6098 Ok(Event::End(e)) => {
6099 let name = e.name();
6100 let local = local_name(name.as_ref());
6101 if (state == 2 && local == b"spTree") || (state == 3 && local == b"sldLayoutIdLst")
6103 {
6104 state = 1;
6105 } else if state == 1 && local == b"sldMaster" {
6106 break;
6107 }
6108 }
6109 Ok(Event::Eof) => break,
6110 Err(e) => return Err(crate::Error::Xml(format!("sldMaster parse: {e}"))),
6111 _ => {}
6112 }
6113 buf.clear();
6114 }
6115 for s in sp_bufs {
6117 if let Ok(sp) = parse_sp(&s) {
6118 master.shapes.push(sp);
6119 }
6120 }
6121 Ok(master)
6122}
6123
6124pub fn parse_notes_master(xml: &str) -> crate::Result<crate::oxml::notesmaster::NotesMaster> {
6140 let mut rd = Reader::from_str(xml);
6141 rd.config_mut().trim_text(true);
6142 let mut buf = Vec::new();
6143 let mut master = crate::oxml::notesmaster::NotesMaster::default();
6144 let mut state: u8 = 0;
6146 let mut sp_bufs: Vec<String> = Vec::new();
6147
6148 loop {
6149 match rd.read_event_into(&mut buf) {
6150 Ok(Event::Start(e)) => {
6151 let name = e.name();
6152 let local = local_name(name.as_ref());
6153 match state {
6154 0 if local == b"notesMaster" => {
6155 state = 1;
6156 }
6157 1 if local == b"cSld" => {}
6159 1 if local == b"spTree" => {
6160 state = 2;
6161 }
6162 2 if local == b"sp" => {
6163 let inner = collect_full_element(&mut rd, e.into_owned())?;
6165 sp_bufs.push(inner);
6166 }
6167 _ => {
6168 let is_clr_map = local == b"clrMap";
6177 let is_notes_style = local == b"notesStyle";
6178 let full_xml = collect_full_element(&mut rd, e.into_owned())?;
6179 if is_clr_map {
6180 master.clr_map_xml = Some(full_xml);
6181 } else if is_notes_style {
6182 master.notes_style_xml = Some(full_xml);
6183 }
6184 }
6186 }
6187 }
6188 Ok(Event::End(e)) => {
6189 let name = e.name();
6190 let local = local_name(name.as_ref());
6191 if state == 2 && local == b"spTree" {
6192 state = 1;
6193 } else if state == 1 && local == b"notesMaster" {
6194 break;
6195 }
6196 }
6197 Ok(Event::Eof) => break,
6198 Err(e) => return Err(crate::Error::Xml(format!("notesMaster parse: {e}"))),
6199 _ => {}
6200 }
6201 buf.clear();
6202 }
6203 for s in sp_bufs {
6205 if let Ok(sp) = parse_sp(&s) {
6206 master.shapes.push(sp);
6207 }
6208 }
6209 Ok(master)
6210}
6211
6212pub fn parse_sld_layout(xml: &str) -> crate::Result<crate::oxml::slidelayout::SldLayout> {
6226 let mut rd = Reader::from_str(xml);
6227 rd.config_mut().trim_text(true);
6228 let mut buf = Vec::new();
6229 let mut layout = crate::oxml::slidelayout::SldLayout::default();
6230 let mut state: u8 = 0;
6232 let mut sp_bufs: Vec<String> = Vec::new();
6233
6234 loop {
6235 match rd.read_event_into(&mut buf) {
6236 Ok(Event::Start(e)) => {
6237 let name = e.name();
6238 let local = local_name(name.as_ref());
6239 match state {
6240 0 if local == b"sldLayout" => {
6241 for a in e.attributes().flatten() {
6243 if a.key.as_ref() == b"type" {
6244 layout.type_ = a
6245 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6246 .unwrap_or_default()
6247 .to_string();
6248 }
6249 }
6250 state = 1;
6251 }
6252 1 if local == b"cSld" => {
6253 for a in e.attributes().flatten() {
6255 if a.key.as_ref() == b"name" {
6256 layout.name = a
6257 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6258 .unwrap_or_default()
6259 .to_string();
6260 }
6261 }
6262 }
6263 1 if local == b"spTree" => {
6264 state = 2;
6265 }
6266 2 if local == b"sp" => {
6267 let inner = collect_full_element(&mut rd, e.into_owned())?;
6268 sp_bufs.push(inner);
6269 }
6270 _ => {
6271 let _ = collect_full_element(&mut rd, e.into_owned());
6272 }
6273 }
6274 }
6275 Ok(Event::End(e)) => {
6276 let name = e.name();
6277 let local = local_name(name.as_ref());
6278 if state == 2 && local == b"spTree" {
6279 state = 1;
6280 } else if state == 1 && local == b"sldLayout" {
6281 break;
6282 }
6283 }
6284 Ok(Event::Eof) => break,
6285 Err(e) => return Err(crate::Error::Xml(format!("sldLayout parse: {e}"))),
6286 _ => {}
6287 }
6288 buf.clear();
6289 }
6290 for s in sp_bufs {
6292 if let Ok(sp) = parse_sp(&s) {
6293 layout.shapes.push(sp);
6294 }
6295 }
6296 Ok(layout)
6297}
6298
6299pub fn parse_theme(xml: &str) -> crate::Result<crate::oxml::theme::Theme> {
6314 let mut rd = Reader::from_str(xml);
6315 rd.config_mut().trim_text(true);
6316 let mut buf = Vec::new();
6317 let mut theme = crate::oxml::theme::Theme::default();
6318
6319 loop {
6320 match rd.read_event_into(&mut buf) {
6321 Ok(Event::Start(e)) => {
6322 let name = e.name();
6323 let local = local_name(name.as_ref());
6324 if local == b"theme" {
6325 for a in e.attributes().flatten() {
6327 if a.key.as_ref() == b"name" {
6328 theme.name = a
6329 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6330 .unwrap_or_default()
6331 .to_string();
6332 }
6333 }
6334 } else if local == b"clrScheme" {
6335 let inner = collect_full_element(&mut rd, e.into_owned())?;
6337 theme.color_scheme = parse_clr_scheme(&inner);
6338 } else if local == b"fontScheme" {
6339 let inner = collect_full_element(&mut rd, e.into_owned())?;
6341 theme.font_scheme = parse_font_scheme(&inner);
6342 } else if local == b"fmtScheme" {
6343 let inner = collect_full_element(&mut rd, e.into_owned())?;
6346 let (fmt_name, raw) = parse_fmt_scheme(&inner);
6347 theme.format_scheme.name = fmt_name;
6348 theme.format_scheme.raw_xml = raw;
6349 theme.format_scheme.parse_from_raw_xml();
6351 } else if local == b"themeElements" {
6352 } else {
6354 let _ = collect_full_element(&mut rd, e.into_owned());
6355 }
6356 }
6357 Ok(Event::Eof) => break,
6358 Err(e) => return Err(crate::Error::Xml(format!("theme parse: {e}"))),
6359 _ => {}
6360 }
6361 buf.clear();
6362 }
6363 Ok(theme)
6364}
6365
6366fn parse_fmt_scheme(xml: &str) -> (String, String) {
6372 let mut fmt_name = String::new();
6373 let mut raw_xml = String::new();
6374 let mut rd = Reader::from_str(xml);
6375 let mut buf = Vec::new();
6376 let mut in_fmt_scheme = false;
6377
6378 loop {
6379 match rd.read_event_into(&mut buf) {
6380 Ok(Event::Start(e)) => {
6381 let ev_name = e.name();
6382 let local = local_name(ev_name.as_ref());
6383 if !in_fmt_scheme && local == b"fmtScheme" {
6384 in_fmt_scheme = true;
6385 for a in e.attributes().flatten() {
6387 if a.key.as_ref() == b"name" {
6388 fmt_name = a
6389 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6390 .unwrap_or_default()
6391 .to_string();
6392 }
6393 }
6394 } else if in_fmt_scheme {
6395 let inner = collect_full_element(&mut rd, e.into_owned()).unwrap_or_default();
6397 raw_xml.push_str(&inner);
6398 }
6399 }
6400 Ok(Event::Empty(e)) => {
6401 let ev_name = e.name();
6402 let local = local_name(ev_name.as_ref());
6403 if in_fmt_scheme {
6404 let mut s = String::new();
6406 s.push('<');
6407 s.push_str(std::str::from_utf8(local).unwrap_or(""));
6408 for a in e.attributes().flatten() {
6409 s.push(' ');
6410 s.push_str(std::str::from_utf8(a.key.as_ref()).unwrap_or(""));
6411 s.push_str("=\"");
6412 s.push_str(
6413 &a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
6414 .unwrap_or_default(),
6415 );
6416 s.push('"');
6417 }
6418 s.push_str("/>");
6419 raw_xml.push_str(&s);
6420 }
6421 }
6422 Ok(Event::End(e)) => {
6423 let ev_name = e.name();
6424 let local = local_name(ev_name.as_ref());
6425 if in_fmt_scheme && local == b"fmtScheme" {
6426 break;
6427 }
6428 }
6429 Ok(Event::Eof) => break,
6430 _ => {}
6431 }
6432 buf.clear();
6433 }
6434 (fmt_name, raw_xml)
6435}
6436
6437fn parse_clr_scheme(xml: &str) -> crate::oxml::theme::ColorScheme {
6439 use crate::oxml::theme::{ColorScheme, ThemeColor};
6440
6441 let mut scheme = ColorScheme::default();
6442 let mut rd = Reader::from_str(xml);
6443 let mut buf = Vec::new();
6444
6445 loop {
6446 match rd.read_event_into(&mut buf) {
6447 Ok(Event::Start(e)) => {
6448 let name = e.name();
6449 let local = local_name(name.as_ref());
6450 let slot: &mut Option<ThemeColor> = match local {
6452 b"dk1" => &mut scheme.dk1,
6453 b"lt1" => &mut scheme.lt1,
6454 b"dk2" => &mut scheme.dk2,
6455 b"lt2" => &mut scheme.lt2,
6456 b"accent1" => &mut scheme.accent1,
6457 b"accent2" => &mut scheme.accent2,
6458 b"accent3" => &mut scheme.accent3,
6459 b"accent4" => &mut scheme.accent4,
6460 b"accent5" => &mut scheme.accent5,
6461 b"accent6" => &mut scheme.accent6,
6462 b"hlink" => &mut scheme.hlink,
6463 b"folHlink" => &mut scheme.fol_hlink,
6464 b"clrScheme" => {
6466 for a in e.attributes().flatten() {
6468 if a.key.as_ref() == b"name" {
6469 scheme.name = a
6470 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6471 .unwrap_or_default()
6472 .to_string();
6473 }
6474 }
6475 continue;
6476 }
6477 _ => {
6478 let _ = collect_full_element(&mut rd, e.into_owned());
6480 continue;
6481 }
6482 };
6483 *slot = Some(read_color_child(&mut rd, local));
6484 }
6485 Ok(Event::End(e)) => {
6486 if local_name(e.name().as_ref()) == b"clrScheme" {
6487 break;
6488 }
6489 }
6490 Ok(Event::Eof) => break,
6491 _ => {}
6492 }
6493 buf.clear();
6494 }
6495 scheme
6496}
6497
6498fn read_color_child<R: std::io::BufRead>(
6502 rd: &mut Reader<R>,
6503 parent_local: &[u8],
6504) -> crate::oxml::theme::ThemeColor {
6505 use crate::oxml::theme::ThemeColor;
6506 let mut buf = Vec::new();
6507 loop {
6508 match rd.read_event_into(&mut buf) {
6509 Ok(Event::Start(e)) => {
6510 let name = e.name();
6511 let local = local_name(name.as_ref());
6512 if local == b"srgbClr" {
6513 let mut val = String::new();
6514 for a in e.attributes().flatten() {
6515 if a.key.as_ref() == b"val" {
6516 val = a
6517 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6518 .unwrap_or_default()
6519 .to_string();
6520 }
6521 }
6522 let _ = collect_full_element(rd, e.into_owned());
6524 return ThemeColor::Srgb(val);
6525 } else if local == b"sysClr" {
6526 let mut val = String::new();
6527 let mut last_clr = String::new();
6528 for a in e.attributes().flatten() {
6529 match a.key.as_ref() {
6530 b"val" => {
6531 val = a
6532 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6533 .unwrap_or_default()
6534 .to_string();
6535 }
6536 b"lastClr" => {
6537 last_clr = a
6538 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6539 .unwrap_or_default()
6540 .to_string();
6541 }
6542 _ => {}
6543 }
6544 }
6545 let _ = collect_full_element(rd, e.into_owned());
6546 return ThemeColor::Sys(val, last_clr);
6547 } else {
6548 let _ = collect_full_element(rd, e.into_owned());
6549 }
6550 }
6551 Ok(Event::Empty(e)) => {
6552 let name = e.name();
6553 let local = local_name(name.as_ref());
6554 if local == b"srgbClr" {
6555 for a in e.attributes().flatten() {
6556 if a.key.as_ref() == b"val" {
6557 return ThemeColor::Srgb(
6558 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
6559 .unwrap_or_default()
6560 .to_string(),
6561 );
6562 }
6563 }
6564 } else if local == b"sysClr" {
6565 let mut val = String::new();
6566 let mut last_clr = String::new();
6567 for a in e.attributes().flatten() {
6568 match a.key.as_ref() {
6569 b"val" => {
6570 val = a
6571 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6572 .unwrap_or_default()
6573 .to_string();
6574 }
6575 b"lastClr" => {
6576 last_clr = a
6577 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6578 .unwrap_or_default()
6579 .to_string();
6580 }
6581 _ => {}
6582 }
6583 }
6584 return ThemeColor::Sys(val, last_clr);
6585 }
6586 }
6587 Ok(Event::End(e)) => {
6588 if local_name(e.name().as_ref()) == parent_local {
6590 return ThemeColor::None;
6591 }
6592 }
6593 Ok(Event::Eof) => return ThemeColor::None,
6594 _ => {}
6595 }
6596 buf.clear();
6597 }
6598}
6599
6600fn parse_font_scheme(xml: &str) -> crate::oxml::theme::FontScheme {
6602 let mut scheme = crate::oxml::theme::FontScheme::default();
6603 let mut rd = Reader::from_str(xml);
6604 let mut buf = Vec::new();
6605 let mut state: u8 = 0;
6607
6608 loop {
6609 match rd.read_event_into(&mut buf) {
6610 Ok(Event::Start(e)) => {
6611 let name = e.name();
6612 let local = local_name(name.as_ref());
6613 if local == b"fontScheme" {
6614 state = 1;
6615 for a in e.attributes().flatten() {
6617 if a.key.as_ref() == b"name" {
6618 scheme.name = a
6619 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6620 .unwrap_or_default()
6621 .to_string();
6622 }
6623 }
6624 } else if state == 1 && local == b"majorFont" {
6625 let (latin, ea, cs) = read_font_typefaces(&mut rd);
6627 scheme.major_latin = latin;
6628 scheme.major_ea = ea;
6629 scheme.major_cs = cs;
6630 } else if state == 1 && local == b"minorFont" {
6631 let (latin, ea, cs) = read_font_typefaces(&mut rd);
6632 scheme.minor_latin = latin;
6633 scheme.minor_ea = ea;
6634 scheme.minor_cs = cs;
6635 }
6636 }
6637 Ok(Event::End(e)) => {
6638 if local_name(e.name().as_ref()) == b"fontScheme" {
6639 break;
6640 }
6641 }
6642 Ok(Event::Eof) => break,
6643 _ => {}
6644 }
6645 buf.clear();
6646 }
6647 scheme
6648}
6649
6650fn read_font_typefaces<R: std::io::BufRead>(rd: &mut Reader<R>) -> (String, String, String) {
6655 let mut buf = Vec::new();
6656 let mut latin = String::new();
6657 let mut ea = String::new();
6658 let mut cs = String::new();
6659 loop {
6660 match rd.read_event_into(&mut buf) {
6661 Ok(Event::Empty(e)) => {
6662 let name = e.name();
6663 let local = local_name(name.as_ref());
6664 let typeface = extract_typeface(&e);
6666 match local {
6667 b"latin" => latin = typeface,
6668 b"ea" => ea = typeface,
6669 b"cs" => cs = typeface,
6670 _ => {}
6671 }
6672 }
6673 Ok(Event::Start(e)) => {
6674 let name = e.name();
6675 let local = local_name(name.as_ref());
6676 let typeface = extract_typeface(&e);
6677 match local {
6678 b"latin" => latin = typeface,
6679 b"ea" => ea = typeface,
6680 b"cs" => cs = typeface,
6681 _ => {}
6682 }
6683 let _ = collect_full_element(rd, e.into_owned());
6685 }
6686 Ok(Event::End(e)) => {
6687 let name = e.name();
6688 let local = local_name(name.as_ref());
6689 if local == b"majorFont" || local == b"minorFont" {
6690 return (latin, ea, cs);
6691 }
6692 }
6693 Ok(Event::Eof) => return (latin, ea, cs),
6694 _ => {}
6695 }
6696 buf.clear();
6697 }
6698}
6699
6700fn extract_typeface(e: &quick_xml::events::BytesStart<'_>) -> String {
6702 for a in e.attributes().flatten() {
6703 if a.key.as_ref() == b"typeface" {
6704 return a
6705 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
6706 .unwrap_or_default()
6707 .to_string();
6708 }
6709 }
6710 String::new()
6711}
6712
6713#[cfg(test)]
6714mod tests {
6715 use super::*;
6716 use crate::oxml::txbody::TextBody;
6717
6718 #[test]
6722 fn parse_backdrop_full() {
6723 let xml = r#"<?xml version="1.0"?>
6724<a:backdrop xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6725 <a:anchor x="100000" y="200000" z="300000"/>
6726 <a:floor/>
6727 <a:wall/>
6728 <a:l/>
6729 <a:r/>
6730 <a:t/>
6731 <a:b/>
6732</a:backdrop>"#;
6733 let mut rd = Reader::from_str(xml);
6734 rd.config_mut().trim_text(true);
6735 let mut buf = Vec::new();
6736 let start = loop {
6737 match rd.read_event_into(&mut buf) {
6738 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"backdrop" => {
6739 break e.into_owned();
6740 }
6741 Ok(Event::Eof) => panic!("backdrop not found"),
6742 _ => {}
6743 }
6744 buf.clear();
6745 };
6746 let bd = parse_backdrop(&mut rd, start).expect("parse backdrop");
6747
6748 assert_eq!(
6749 bd.anchor,
6750 Some(Point3d {
6751 x: 100000,
6752 y: 200000,
6753 z: 300000
6754 })
6755 );
6756 assert!(bd.floor);
6757 assert!(bd.wall);
6758 assert!(bd.left);
6759 assert!(bd.right);
6760 assert!(bd.top);
6761 assert!(bd.bottom);
6762 }
6763
6764 #[test]
6766 fn parse_backdrop_partial() {
6767 let xml = r#"<?xml version="1.0"?>
6768<a:backdrop xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6769 <a:floor/>
6770 <a:wall/>
6771</a:backdrop>"#;
6772 let mut rd = Reader::from_str(xml);
6773 rd.config_mut().trim_text(true);
6774 let mut buf = Vec::new();
6775 let start = loop {
6776 match rd.read_event_into(&mut buf) {
6777 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"backdrop" => {
6778 break e.into_owned();
6779 }
6780 Ok(Event::Eof) => panic!("backdrop not found"),
6781 _ => {}
6782 }
6783 buf.clear();
6784 };
6785 let bd = parse_backdrop(&mut rd, start).expect("parse backdrop");
6786
6787 assert!(bd.anchor.is_none());
6788 assert!(bd.floor);
6789 assert!(bd.wall);
6790 assert!(!bd.left);
6791 assert!(!bd.right);
6792 assert!(!bd.top);
6793 assert!(!bd.bottom);
6794 }
6795
6796 #[test]
6798 fn parse_scene_3d_with_backdrop() {
6799 let xml = r#"<?xml version="1.0"?>
6800<a:scene3d xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6801 <a:camera prst="orthographicFront"/>
6802 <a:lightRig rig="balanced" dir="t"/>
6803 <a:backdrop>
6804 <a:anchor x="0" y="0" z="0"/>
6805 <a:floor/>
6806 <a:wall/>
6807 </a:backdrop>
6808</a:scene3d>"#;
6809 let mut rd = Reader::from_str(xml);
6810 rd.config_mut().trim_text(true);
6811 let mut buf = Vec::new();
6812 let start = loop {
6813 match rd.read_event_into(&mut buf) {
6814 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"scene3d" => {
6815 break e.into_owned();
6816 }
6817 Ok(Event::Eof) => panic!("scene3d not found"),
6818 _ => {}
6819 }
6820 buf.clear();
6821 };
6822 let scene = parse_scene_3d(&mut rd, start).expect("parse scene3d");
6823
6824 let bd = scene.backdrop.expect("backdrop should be parsed");
6826 assert!(bd.floor);
6827 assert!(bd.wall);
6828 assert!(!bd.left);
6829 assert_eq!(bd.anchor, Some(Point3d { x: 0, y: 0, z: 0 }));
6830 }
6831
6832 #[test]
6834 fn parse_scene_3d_without_backdrop() {
6835 let xml = r#"<?xml version="1.0"?>
6836<a:scene3d xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6837 <a:camera prst="orthographicFront"/>
6838 <a:lightRig rig="balanced" dir="t"/>
6839</a:scene3d>"#;
6840 let mut rd = Reader::from_str(xml);
6841 rd.config_mut().trim_text(true);
6842 let mut buf = Vec::new();
6843 let start = loop {
6844 match rd.read_event_into(&mut buf) {
6845 Ok(Event::Start(e)) if local_name(e.name().as_ref()) == b"scene3d" => {
6846 break e.into_owned();
6847 }
6848 Ok(Event::Eof) => panic!("scene3d not found"),
6849 _ => {}
6850 }
6851 buf.clear();
6852 };
6853 let scene = parse_scene_3d(&mut rd, start).expect("parse scene3d");
6854 assert!(scene.backdrop.is_none());
6855 }
6856
6857 #[test]
6858 fn parse_minimal_sld() {
6859 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6860<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6861 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6862 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6863 <p:cSld>
6864 <p:spTree>
6865 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
6866 <p:grpSpPr><a:xfrm/></p:grpSpPr>
6867 <p:sp>
6868 <p:nvSpPr>
6869 <p:cNvPr id="2" name="TextBox 1"/>
6870 <p:cNvSpPr txBox="1"/>
6871 <p:nvPr/>
6872 </p:nvSpPr>
6873 <p:spPr>
6874 <a:xfrm>
6875 <a:off x="914400" y="914400"/>
6876 <a:ext cx="3657600" cy="457200"/>
6877 </a:xfrm>
6878 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
6879 <a:noFill/>
6880 </p:spPr>
6881 <p:txBody>
6882 <a:bodyPr wrap="square" rtlCol="0"><a:noAutofit/></a:bodyPr>
6883 <a:p>
6884 <a:r><a:rPr lang="en-US"/><a:t>Hello</a:t></a:r>
6885 </a:p>
6886 </p:txBody>
6887 </p:sp>
6888 </p:spTree>
6889 </p:cSld>
6890 <p:clrMapOvr/>
6891</p:sld>"#;
6892 let sld = parse_sld(xml).expect("parse ok");
6893 assert_eq!(sld.shapes.len(), 1);
6894 if let OxmlSlideShape::Sp(sp) = &sld.shapes[0] {
6895 assert_eq!(sp.id, 2);
6896 assert_eq!(sp.name, "TextBox 1");
6897 assert!(sp.c_nv_sp_pr_tx_box);
6898 assert_eq!(sp.text.paragraphs.len(), 1);
6899 assert_eq!(sp.text.paragraphs[0].runs.len(), 1);
6900 assert_eq!(sp.text.paragraphs[0].runs[0].text, "Hello");
6901 } else {
6902 panic!("expected Sp");
6903 }
6904 }
6905
6906 #[test]
6907 fn parse_empty_txbody_keeps_one_paragraph() {
6908 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
6909 <a:bodyPr/>
6910 </p:txBody>"#;
6911 let tb = parse_txbody(xml).unwrap();
6912 assert!(tb.paragraphs.is_empty());
6913 let tb2 = TextBody::new();
6916 assert!(tb2.paragraphs.is_empty());
6917 }
6918
6919 #[test]
6920 fn parse_pres_root_extracts_sld_id_lst_and_size() {
6921 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6922<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6923 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6924 <p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
6925 <p:sldIdLst>
6926 <p:sldId id="256" r:id="rId2"/>
6927 <p:sldId id="257" r:id="rId3"/>
6928 </p:sldIdLst>
6929 <p:sldSz cx="9144000" cy="6858000"/>
6930</p:presentation>"#;
6931 let (ids, w, h, master_ids, _sections, _notes_master_ids) =
6932 parse_pres_root(xml).expect("parse ok");
6933 assert_eq!(ids.len(), 2);
6934 assert_eq!(ids[0], (256, "rId2".to_string()));
6935 assert_eq!(ids[1], (257, "rId3".to_string()));
6936 assert_eq!(w.map(|v| v.0), Some(9_144_000));
6937 assert_eq!(h.map(|v| v.0), Some(6_858_000));
6938 assert_eq!(master_ids.len(), 1);
6940 assert_eq!(master_ids[0], (2147483648, "rId1".to_string()));
6941 }
6942
6943 #[test]
6944 fn parse_notes_returns_text_body() {
6945 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
6946<p:notes xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6947 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
6948 <p:cSld>
6949 <p:spTree>
6950 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
6951 <p:grpSpPr/>
6952 <p:sp>
6953 <p:nvSpPr>
6954 <p:cNvPr id="2" name="Notes Placeholder"/>
6955 <p:cNvSpPr txBox="1"/>
6956 <p:nvPr><p:ph type="body" idx="1"/></p:nvPr>
6957 </p:nvSpPr>
6958 <p:spPr/>
6959 <p:txBody>
6960 <a:bodyPr/>
6961 <a:p><a:r><a:t>hello notes</a:t></a:r></a:p>
6962 </p:txBody>
6963 </p:sp>
6964 </p:spTree>
6965 </p:cSld>
6966</p:notes>"#;
6967 let tb = parse_notes(xml).expect("parse notes ok");
6968 assert_eq!(tb.paragraphs.len(), 1);
6969 assert_eq!(tb.paragraphs[0].runs.len(), 1);
6970 assert_eq!(tb.paragraphs[0].runs[0].text, "hello notes");
6971 }
6972
6973 #[test]
6978 fn parse_pic_extracts_rid_and_src_rect() {
6979 let xml1 = r#"<p:pic xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6981 xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6982 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
6983 <p:nvPicPr><p:cNvPr id="42" name="MyPic"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>
6984 <p:blipFill>
6985 <a:blip r:embed="rIdImg5"/>
6986 <a:srcRect l="1000" t="2000" r="3000" b="4000"/>
6987 </p:blipFill>
6988 <p:spPr/>
6989</p:pic>"#;
6990 let p1 = parse_pic(xml1).expect("parse pic 1");
6991 assert_eq!(p1.id, 42);
6992 assert_eq!(p1.name, "MyPic");
6993 assert_eq!(p1.rid, "rIdImg5");
6994 assert_eq!(p1.src_rect, Some((1000, 2000, 3000, 4000)));
6995
6996 let xml2 = r#"<p:pic xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
6998 xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
6999 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7000<p:nvPicPr><p:cNvPr id="7" name="p2"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>
7001<p:blipFill><a:blip r:embed="rIdImg99"></a:blip></p:blipFill>
7002<p:spPr/>
7003</p:pic>"#;
7004 let p2 = parse_pic(xml2).expect("parse pic 2");
7005 assert_eq!(p2.id, 7);
7006 assert_eq!(p2.rid, "rIdImg99");
7007 }
7008
7009 #[test]
7012 fn parse_run_with_typeface_works() {
7013 let xml = r#"<a:r xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
7014 <a:rPr>
7015 <a:latin typeface="Calibri"/>
7016 <a:ea typeface="宋体"/>
7017 <a:cs typeface="Times New Roman"/>
7018 </a:rPr>
7019 <a:t>hello</a:t>
7020</a:r>"#;
7021 let r = parse_run(xml).expect("parse run");
7022 assert_eq!(r.text, "hello");
7023 assert_eq!(r.properties.latin_font.as_deref(), Some("Calibri"));
7024 assert_eq!(r.properties.eastasia_font.as_deref(), Some("宋体"));
7025 assert_eq!(r.properties.cs_font.as_deref(), Some("Times New Roman"));
7026 }
7027
7028 #[test]
7031 fn write_sp_placeholder_defaults_to_body() {
7032 use crate::oxml::shape::Sp;
7033 use crate::oxml::writer::XmlWriter;
7034 let sp = Sp {
7035 id: 3,
7036 name: "Ph".into(),
7037 is_placeholder: true,
7038 ph_idx: Some(0),
7039 ph_type: None, ..Default::default()
7041 };
7042 let mut w = XmlWriter::new();
7043 sp.write_xml(&mut w);
7044 let s = w.into_string();
7045 assert!(
7046 s.contains(r#"<p:ph type="body" idx="0"/>"#),
7047 "应写出 type=\"body\",实际:{s}"
7048 );
7049 }
7050
7051 #[test]
7054 fn connector_set_begin_end_recomputes_xfrm() {
7055 use crate::oxml::simpletypes::MsoConnectorType;
7056 use crate::shape::connector::Connector;
7057 use crate::units::EmuPoint;
7058 let mut c = Connector::new_with_type("c1", MsoConnectorType::Straight);
7059 c.set_begin(EmuPoint::new(1000, 2000));
7060 c.set_end(EmuPoint::new(5000, 6000));
7061 assert_eq!(c.properties().xfrm.off_x.unwrap().value(), 1000);
7063 assert_eq!(c.properties().xfrm.off_y.unwrap().value(), 2000);
7064 assert_eq!(c.properties().xfrm.ext_cx.unwrap().value(), 4000);
7065 assert_eq!(c.properties().xfrm.ext_cy.unwrap().value(), 4000);
7066 }
7067
7068 #[test]
7070 fn parse_minimal_sld_master() {
7071 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7072<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7073 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7074 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7075 <p:cSld>
7076 <p:spTree>
7077 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7078 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7079 <p:sp>
7080 <p:nvSpPr>
7081 <p:cNvPr id="2" name="Title Placeholder 1"/>
7082 <p:cNvSpPr txBox="1"/>
7083 <p:nvPr><p:ph type="title" idx="0"/></p:nvPr>
7084 </p:nvSpPr>
7085 <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>
7086 <p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody>
7087 </p:sp>
7088 </p:spTree>
7089 </p:cSld>
7090 <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"/>
7091 <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
7092</p:sldMaster>"#;
7093 let master = parse_sld_master(xml).unwrap();
7094 assert_eq!(master.shapes.len(), 1, "应解析出 1 个 shape");
7095 assert_eq!(master.shapes[0].id, 2);
7096 assert_eq!(master.shapes[0].name, "Title Placeholder 1");
7097 assert!(master.shapes[0].is_placeholder);
7098 assert_eq!(master.shapes[0].ph_type.as_deref(), Some("title"));
7099 assert_eq!(master.layout_rids.len(), 1, "应解析出 1 个 layout rid");
7100 assert_eq!(master.layout_rids[0], "rId1");
7101 }
7102
7103 #[test]
7105 fn parse_minimal_sld_layout() {
7106 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7107<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7108 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7109 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7110 type="title" preserve="1">
7111 <p:cSld name="Title Slide">
7112 <p:spTree>
7113 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7114 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7115 <p:sp>
7116 <p:nvSpPr>
7117 <p:cNvPr id="2" name="Title 1"/>
7118 <p:cNvSpPr txBox="1"/>
7119 <p:nvPr><p:ph type="title" idx="0"/></p:nvPr>
7120 </p:nvSpPr>
7121 <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>
7122 <p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody>
7123 </p:sp>
7124 </p:spTree>
7125 </p:cSld>
7126 <p:clrMapOvr bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2"/>
7127</p:sldLayout>"#;
7128 let layout = parse_sld_layout(xml).unwrap();
7129 assert_eq!(layout.type_, "title");
7130 assert_eq!(layout.name, "Title Slide");
7131 assert_eq!(layout.shapes.len(), 1, "应解析出 1 个 shape");
7132 assert_eq!(layout.shapes[0].name, "Title 1");
7133 assert!(layout.shapes[0].is_placeholder);
7134 }
7135
7136 #[test]
7138 fn parse_theme_extracts_name_and_colors() {
7139 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7140<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
7141 <a:themeElements>
7142 <a:clrScheme name="Office">
7143 <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
7144 <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
7145 <a:dk2><a:srgbClr val="1F497D"/></a:dk2>
7146 <a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
7147 <a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
7148 <a:accent2><a:srgbClr val="C0504D"/></a:accent2>
7149 <a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
7150 <a:accent4><a:srgbClr val="8064A2"/></a:accent4>
7151 <a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
7152 <a:accent6><a:srgbClr val="F79646"/></a:accent6>
7153 <a:hlink><a:srgbClr val="0000FF"/></a:hlink>
7154 <a:folHlink><a:srgbClr val="800080"/></a:folHlink>
7155 </a:clrScheme>
7156 <a:fontScheme name="Office">
7157 <a:majorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
7158 <a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
7159 </a:fontScheme>
7160 <a:fmtScheme name="Office">
7161 <a:fillStyleLst>
7162 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
7163 </a:fillStyleLst>
7164 <a:lnStyleLst/>
7165 <a:effectStyleLst/>
7166 <a:bgFillStyleLst/>
7167 </a:fmtScheme>
7168 </a:themeElements>
7169</a:theme>"#;
7170 let theme = parse_theme(xml).unwrap();
7171 assert_eq!(theme.name, "Office Theme");
7172 assert_eq!(theme.color_scheme.name, "Office");
7174 assert_eq!(
7176 format!("{:?}", theme.color_scheme.dk1),
7177 r#"Some(Sys("windowText", "000000"))"#,
7178 "dk1 mismatch"
7179 );
7180 assert_eq!(
7181 format!("{:?}", theme.color_scheme.lt1),
7182 r#"Some(Sys("window", "FFFFFF"))"#,
7183 "lt1 mismatch"
7184 );
7185 assert_eq!(
7186 format!("{:?}", theme.color_scheme.accent1),
7187 r#"Some(Srgb("4F81BD"))"#,
7188 "accent1 mismatch"
7189 );
7190 assert_eq!(
7191 format!("{:?}", theme.color_scheme.hlink),
7192 r#"Some(Srgb("0000FF"))"#,
7193 "hlink mismatch"
7194 );
7195 assert_eq!(theme.font_scheme.name, "Office");
7197 assert_eq!(theme.font_scheme.major_latin, "Calibri");
7199 assert_eq!(theme.font_scheme.minor_latin, "Calibri");
7200 assert_eq!(theme.format_scheme.name, "Office");
7202 assert!(
7203 !theme.format_scheme.raw_xml.is_empty(),
7204 "fmtScheme raw_xml should not be empty"
7205 );
7206 assert!(
7207 theme.format_scheme.raw_xml.contains("<a:fillStyleLst>"),
7208 "fmtScheme raw_xml should contain fillStyleLst"
7209 );
7210 }
7211
7212 #[test]
7214 fn parse_theme_font_scheme_ea_cs() {
7215 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7216<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Test">
7217 <a:themeElements>
7218 <a:clrScheme name="Test"/>
7219 <a:fontScheme name="Test">
7220 <a:majorFont>
7221 <a:latin typeface="Calibri Light"/>
7222 <a:ea typeface="宋体"/>
7223 <a:cs typeface="Times New Roman"/>
7224 </a:majorFont>
7225 <a:minorFont>
7226 <a:latin typeface="Calibri"/>
7227 <a:ea typeface="黑体"/>
7228 <a:cs typeface="Arial"/>
7229 </a:minorFont>
7230 </a:fontScheme>
7231 <a:fmtScheme name="Test"/>
7232 </a:themeElements>
7233</a:theme>"#;
7234 let theme = parse_theme(xml).unwrap();
7235 assert_eq!(theme.font_scheme.major_latin, "Calibri Light");
7236 assert_eq!(theme.font_scheme.major_ea, "宋体");
7237 assert_eq!(theme.font_scheme.major_cs, "Times New Roman");
7238 assert_eq!(theme.font_scheme.minor_latin, "Calibri");
7239 assert_eq!(theme.font_scheme.minor_ea, "黑体");
7240 assert_eq!(theme.font_scheme.minor_cs, "Arial");
7241 }
7242
7243 #[test]
7245 fn theme_round_trip() {
7246 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7247<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="RoundTrip">
7248 <a:themeElements>
7249 <a:clrScheme name="RT">
7250 <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
7251 <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
7252 <a:dk2><a:srgbClr val="1F497D"/></a:dk2>
7253 <a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
7254 <a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
7255 <a:accent2><a:srgbClr val="C0504D"/></a:accent2>
7256 <a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
7257 <a:accent4><a:srgbClr val="8064A2"/></a:accent4>
7258 <a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
7259 <a:accent6><a:srgbClr val="F79646"/></a:accent6>
7260 <a:hlink><a:srgbClr val="0000FF"/></a:hlink>
7261 <a:folHlink><a:srgbClr val="800080"/></a:folHlink>
7262 </a:clrScheme>
7263 <a:fontScheme name="RT">
7264 <a:majorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
7265 <a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
7266 </a:fontScheme>
7267 <a:fmtScheme name="Office"/>
7268 </a:themeElements>
7269</a:theme>"#;
7270 let theme1 = parse_theme(xml).unwrap();
7272 assert_eq!(theme1.name, "RoundTrip");
7273 assert_eq!(theme1.color_scheme.name, "RT");
7274 assert_eq!(theme1.font_scheme.name, "RT");
7275 let serialized = theme1.to_xml();
7277 let theme2 = parse_theme(&serialized).unwrap();
7279 assert_eq!(theme2.name, theme1.name);
7281 assert_eq!(theme2.color_scheme.name, theme1.color_scheme.name);
7282 assert_eq!(theme2.color_scheme.dk1, theme1.color_scheme.dk1);
7283 assert_eq!(theme2.color_scheme.accent1, theme1.color_scheme.accent1);
7284 assert_eq!(theme2.font_scheme.name, theme1.font_scheme.name);
7285 assert_eq!(
7286 theme2.font_scheme.major_latin,
7287 theme1.font_scheme.major_latin
7288 );
7289 assert_eq!(
7290 theme2.font_scheme.minor_latin,
7291 theme1.font_scheme.minor_latin
7292 );
7293 }
7294
7295 #[test]
7300 fn parse_cxn_sp_extracts_geometry_and_connections() {
7301 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7302<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7303 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7304 <p:nvCxnSpPr>
7305 <p:cNvPr id="10" name="Connector1"/>
7306 <p:cNvCxnSpPr/>
7307 <p:nvPr>
7308 <p:stCxn id="2" idx="3"/>
7309 <p:endCxn id="5" idx="1"/>
7310 </p:nvPr>
7311 </p:nvCxnSpPr>
7312 <p:spPr>
7313 <a:xfrm>
7314 <a:off x="1000" y="2000"/>
7315 <a:ext cx="5000" cy="4000"/>
7316 </a:xfrm>
7317 <a:prstGeom prst="line"><a:avLst/></a:prstGeom>
7318 </p:spPr>
7319</p:cxnSp>"#;
7320 let cxn = parse_cxn_sp(xml).expect("parse cxnSp ok");
7321 assert_eq!(cxn.id, 10);
7322 assert_eq!(cxn.name, "Connector1");
7323 assert_eq!(cxn.st_cxn, Some((2, 3)), "stCxn 应解析出 (id=2, idx=3)");
7325 assert_eq!(cxn.end_cxn, Some((5, 1)), "endCxn 应解析出 (id=5, idx=1)");
7326 assert_eq!(cxn.properties.xfrm.off_x.unwrap().value(), 1000);
7328 assert_eq!(cxn.properties.xfrm.off_y.unwrap().value(), 2000);
7329 assert_eq!(cxn.properties.xfrm.ext_cx.unwrap().value(), 5000);
7330 assert_eq!(cxn.properties.xfrm.ext_cy.unwrap().value(), 4000);
7331 }
7332
7333 #[test]
7335 fn parse_cxn_sp_without_connections() {
7336 let xml = r#"<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7337 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7338 <p:nvCxnSpPr><p:cNvPr id="20" name="FreeLine"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7339 <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>
7340</p:cxnSp>"#;
7341 let cxn = parse_cxn_sp(xml).unwrap();
7342 assert_eq!(cxn.id, 20);
7343 assert_eq!(cxn.name, "FreeLine");
7344 assert!(cxn.st_cxn.is_none(), "无 stCxn 时应为 None");
7345 assert!(cxn.end_cxn.is_none(), "无 endCxn 时应为 None");
7346 }
7347
7348 #[test]
7350 fn parse_cxn_sp_with_style() {
7351 let xml = r#"<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7352 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7353 <p:nvCxnSpPr><p:cNvPr id="30" name="StyledConn"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7354 <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>
7355 <p:style>
7356 <a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef>
7357 <a:fillRef idx="0"><a:schemeClr val="accent2"/></a:fillRef>
7358 </p:style>
7359</p:cxnSp>"#;
7360 let cxn = parse_cxn_sp(xml).expect("parse cxnSp ok");
7361 let style = cxn.style.as_ref().expect("style 应存在");
7362 let ln = style.line_ref.as_ref().expect("line_ref 应存在");
7363 assert_eq!(ln.idx.as_deref(), Some("2"));
7364 assert_eq!(ln.scheme_color.as_deref(), Some("accent1"));
7365 let fill = style.fill_ref.as_ref().expect("fill_ref 应存在");
7366 assert_eq!(fill.idx.as_deref(), Some("0"));
7367 assert_eq!(fill.scheme_color.as_deref(), Some("accent2"));
7368 }
7369
7370 #[test]
7372 fn parse_grp_sp_with_style() {
7373 let xml = r#"<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7374 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7375 <p:nvGrpSpPr><p:cNvPr id="40" name="StyledGroup"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7376 <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>
7377 <p:style>
7378 <a:lnRef idx="1"><a:schemeClr val="tx1"/></a:lnRef>
7379 <a:fontRef idx="minor"><a:schemeClr val="dk1"/></a:fontRef>
7380 </p:style>
7381</p:grpSp>"#;
7382 let grp = parse_grp_sp(xml).expect("parse grpSp ok");
7383 assert_eq!(grp.id, 40);
7384 let style = grp.style.as_ref().expect("style 应存在");
7385 let ln = style.line_ref.as_ref().expect("line_ref 应存在");
7386 assert_eq!(ln.idx.as_deref(), Some("1"));
7387 assert_eq!(ln.scheme_color.as_deref(), Some("tx1"));
7388 let font = style.font_ref.as_ref().expect("font_ref 应存在");
7389 assert_eq!(font.idx.as_deref(), Some("minor"));
7390 assert_eq!(font.scheme_color.as_deref(), Some("dk1"));
7391 }
7392
7393 #[test]
7398 fn parse_grp_sp_extracts_children() {
7399 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7400<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7401 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7402 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7403 <p:nvGrpSpPr>
7404 <p:cNvPr id="100" name="Group1"/>
7405 <p:cNvGrpSpPr/>
7406 <p:nvPr/>
7407 </p:nvGrpSpPr>
7408 <p:grpSpPr>
7409 <a:xfrm>
7410 <a:off x="0" y="0"/>
7411 <a:ext cx="8000" cy="6000"/>
7412 <a:chOff x="0" y="0"/>
7413 <a:chExt cx="8000" cy="6000"/>
7414 </a:xfrm>
7415 </p:grpSpPr>
7416 <p:sp>
7417 <p:nvSpPr><p:cNvPr id="101" name="Child1"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7418 <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>
7419 <p:txBody><a:bodyPr/><a:p><a:r><a:t>child text</a:t></a:r></a:p></p:txBody>
7420 </p:sp>
7421 <p:cxnSp>
7422 <p:nvCxnSpPr><p:cNvPr id="102" name="Link"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7423 <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>
7424 </p:cxnSp>
7425</p:grpSp>"#;
7426 let grp = parse_grp_sp(xml).expect("parse grpSp ok");
7427 assert_eq!(grp.id, 100);
7428 assert_eq!(grp.name, "Group1");
7429 assert_eq!(grp.children.len(), 2, "应解析出 2 个子形状");
7430
7431 match &grp.children[0] {
7433 GroupChild::Sp(sp) => {
7434 assert_eq!(sp.id, 101);
7435 assert_eq!(sp.name, "Child1");
7436 assert_eq!(sp.text.paragraphs[0].runs[0].text, "child text");
7437 }
7438 other => panic!("第一个子形状应为 Sp,实际:{other:?}"),
7439 }
7440 match &grp.children[1] {
7442 GroupChild::CxnSp(cxn) => {
7443 assert_eq!(cxn.id, 102);
7444 assert_eq!(cxn.name, "Link");
7445 }
7446 other => panic!("第二个子形状应为 CxnSp,实际:{other:?}"),
7447 }
7448 }
7449
7450 #[test]
7456 fn parse_graphic_frame_with_table() {
7457 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7458<p:graphicFrame xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7459 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7460 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7461 <p:nvGraphicFramePr>
7462 <p:cNvPr id="200" name="TableFrame"/>
7463 <p:cNvGraphicFramePr/>
7464 <p:nvPr/>
7465 </p:nvGraphicFramePr>
7466 <p:xfrm>
7467 <a:off x="457200" y="1828800"/>
7468 <a:ext cx="8229600" cy="1143000"/>
7469 </p:xfrm>
7470 <a:graphic>
7471 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
7472 <a:tbl>
7473 <a:tblPr firstRow="1" bandRow="1"/>
7474 <a:tblGrid>
7475 <a:gridCol w="4114800"/>
7476 <a:gridCol w="4114800"/>
7477 </a:tblGrid>
7478 <a:tr h="370840" h="0">
7479 <a:tc>
7480 <a:txBody><a:bodyPr/><a:p><a:r><a:t>A1</a:t></a:r></a:p></a:txBody>
7481 <a:tcPr/>
7482 </a:tc>
7483 <a:tc>
7484 <a:txBody><a:bodyPr/><a:p><a:r><a:t>B1</a:t></a:r></a:p></a:txBody>
7485 <a:tcPr/>
7486 </a:tc>
7487 </a:tr>
7488 <a:tr h="370840" h="0">
7489 <a:tc>
7490 <a:txBody><a:bodyPr/><a:p><a:r><a:t>A2</a:t></a:r></a:p></a:txBody>
7491 <a:tcPr/>
7492 </a:tc>
7493 <a:tc>
7494 <a:txBody><a:bodyPr/><a:p><a:r><a:t>B2</a:t></a:r></a:p></a:txBody>
7495 <a:tcPr/>
7496 </a:tc>
7497 </a:tr>
7498 </a:tbl>
7499 </a:graphicData>
7500 </a:graphic>
7501</p:graphicFrame>"#;
7502 let frame = parse_graphic_frame(xml).expect("parse graphicFrame ok");
7503 assert_eq!(frame.id, 200);
7504 assert_eq!(frame.name, "TableFrame");
7505 assert_eq!(frame.properties.xfrm.off_x.unwrap().value(), 457200);
7507 assert_eq!(frame.properties.xfrm.off_y.unwrap().value(), 1828800);
7508 assert_eq!(frame.properties.xfrm.ext_cx.unwrap().value(), 8229600);
7509 assert_eq!(frame.properties.xfrm.ext_cy.unwrap().value(), 1143000);
7510 match &frame.graphic {
7512 crate::oxml::shape::Graphic::Table(tbl) => {
7513 assert_eq!(tbl.cols.len(), 2, "应有 2 列");
7514 assert_eq!(tbl.rows.len(), 2, "应有 2 行");
7515 assert_eq!(tbl.cols[0].width.value(), 4114800);
7516 let cell_a1 = &tbl.rows[0].cells[0];
7518 let text_a1: String = cell_a1
7519 .text
7520 .paragraphs
7521 .iter()
7522 .flat_map(|p| p.runs.iter().map(|r| r.text.as_str()))
7523 .collect();
7524 assert_eq!(text_a1, "A1");
7525 let cell_b2 = &tbl.rows[1].cells[1];
7526 let text_b2: String = cell_b2
7527 .text
7528 .paragraphs
7529 .iter()
7530 .flat_map(|p| p.runs.iter().map(|r| r.text.as_str()))
7531 .collect();
7532 assert_eq!(text_b2, "B2");
7533 }
7534 _ => panic!("期望 Table 变体"),
7535 }
7536 }
7537
7538 #[test]
7542 fn parse_sld_with_mixed_shape_types() {
7543 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7544<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7545 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7546 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7547 <p:cSld>
7548 <p:spTree>
7549 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7550 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7551 <p:sp>
7552 <p:nvSpPr><p:cNvPr id="2" name="TextBox"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7553 <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>
7554 <p:txBody><a:bodyPr/><a:p><a:r><a:t>text</a:t></a:r></a:p></p:txBody>
7555 </p:sp>
7556 <p:pic>
7557 <p:nvPicPr><p:cNvPr id="3" name="Pic1"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>
7558 <p:blipFill><a:blip r:embed="rIdImg1"/></p:blipFill>
7559 <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>
7560 </p:pic>
7561 <p:cxnSp>
7562 <p:nvCxnSpPr><p:cNvPr id="4" name="Conn1"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
7563 <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>
7564 </p:cxnSp>
7565 <p:grpSp>
7566 <p:nvGrpSpPr><p:cNvPr id="5" name="Group1"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7567 <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>
7568 <p:sp>
7569 <p:nvSpPr><p:cNvPr id="6" name="Inner"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7570 <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>
7571 <p:txBody><a:bodyPr/><a:p><a:r><a:t>inner</a:t></a:r></a:p></p:txBody>
7572 </p:sp>
7573 </p:grpSp>
7574 </p:spTree>
7575 </p:cSld>
7576 <p:clrMapOvr/>
7577</p:sld>"#;
7578 let sld = parse_sld(xml).expect("parse sld ok");
7579 assert_eq!(sld.shapes.len(), 4, "应解析出 4 个形状");
7580
7581 assert!(
7583 matches!(&sld.shapes[0], OxmlSlideShape::Sp(s) if s.id == 2 && s.name == "TextBox")
7584 );
7585 assert!(
7586 matches!(&sld.shapes[1], OxmlSlideShape::Pic(p) if p.id == 3 && p.rid == "rIdImg1")
7587 );
7588 assert!(
7589 matches!(&sld.shapes[2], OxmlSlideShape::CxnSp(c) if c.id == 4 && c.name == "Conn1")
7590 );
7591 match &sld.shapes[3] {
7592 OxmlSlideShape::Group(grp) => {
7593 assert_eq!(grp.id, 5);
7594 assert_eq!(grp.name, "Group1");
7595 assert_eq!(grp.children.len(), 1, "组合内应有 1 个子形状");
7596 assert!(
7597 matches!(&grp.children[0], GroupChild::Sp(s) if s.id == 6 && s.name == "Inner")
7598 );
7599 }
7600 other => panic!("第 4 个形状应为 Group,实际:{other:?}"),
7601 }
7602 }
7603
7604 #[test]
7609 fn round_trip_sld_preserves_shapes() {
7610 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7611<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7612 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7613 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7614 <p:cSld>
7615 <p:spTree>
7616 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7617 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7618 <p:sp>
7619 <p:nvSpPr><p:cNvPr id="2" name="TB1"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7620 <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>
7621 <p:txBody><a:bodyPr/><a:p><a:r><a:t>round-trip</a:t></a:r></a:p></p:txBody>
7622 </p:sp>
7623 <p:cxnSp>
7624 <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>
7625 <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>
7626 </p:cxnSp>
7627 </p:spTree>
7628 </p:cSld>
7629 <p:clrMapOvr/>
7630</p:sld>"#;
7631
7632 let sld1 = parse_sld(xml).expect("first parse ok");
7634 assert_eq!(sld1.shapes.len(), 2);
7635
7636 let rewritten = sld1.to_xml();
7638
7639 let sld2 = parse_sld(&rewritten).expect("second parse ok");
7641 assert_eq!(sld2.shapes.len(), 2, "round-trip 后形状数应不变");
7642
7643 match (&sld1.shapes[0], &sld2.shapes[0]) {
7645 (OxmlSlideShape::Sp(a), OxmlSlideShape::Sp(b)) => {
7646 assert_eq!(a.id, b.id, "Sp id 应保留");
7647 assert_eq!(a.name, b.name, "Sp name 应保留");
7648 assert_eq!(
7649 a.text.paragraphs[0].runs[0].text, b.text.paragraphs[0].runs[0].text,
7650 "Sp 文本应保留"
7651 );
7652 assert_eq!(
7654 a.properties.xfrm.off_x, b.properties.xfrm.off_x,
7655 "Sp off_x 应保留"
7656 );
7657 assert_eq!(
7658 a.properties.xfrm.ext_cx, b.properties.xfrm.ext_cx,
7659 "Sp ext_cx 应保留"
7660 );
7661 }
7662 _ => panic!("形状类型不匹配"),
7663 }
7664
7665 match (&sld1.shapes[1], &sld2.shapes[1]) {
7667 (OxmlSlideShape::CxnSp(a), OxmlSlideShape::CxnSp(b)) => {
7668 assert_eq!(a.id, b.id, "CxnSp id 应保留");
7669 assert_eq!(a.name, b.name, "CxnSp name 应保留");
7670 assert_eq!(a.st_cxn, b.st_cxn, "stCxn 挂接应保留");
7671 assert_eq!(a.end_cxn, b.end_cxn, "endCxn 挂接应保留");
7672 }
7673 _ => panic!("形状类型不匹配"),
7674 }
7675 }
7676
7677 #[test]
7679 fn round_trip_table_preserves_cells() {
7680 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7681<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7682 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
7683 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
7684 <p:cSld>
7685 <p:spTree>
7686 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7687 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7688 <p:graphicFrame>
7689 <p:nvGraphicFramePr><p:cNvPr id="10" name="Tbl1"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
7690 <p:xfrm><a:off x="0" y="0"/><a:ext cx="8000" cy="4000"/></p:xfrm>
7691 <a:graphic>
7692 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
7693 <a:tbl>
7694 <a:tblPr/>
7695 <a:tblGrid><a:gridCol w="4000"/><a:gridCol w="4000"/></a:tblGrid>
7696 <a:tr h="2000">
7697 <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>
7698 <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>
7699 </a:tr>
7700 </a:tbl>
7701 </a:graphicData>
7702 </a:graphic>
7703 </p:graphicFrame>
7704 </p:spTree>
7705 </p:cSld>
7706 <p:clrMapOvr/>
7707</p:sld>"#;
7708
7709 let sld1 = parse_sld(xml).expect("first parse ok");
7710 let rewritten = sld1.to_xml();
7711 let sld2 = parse_sld(&rewritten).expect("second parse ok");
7712
7713 assert_eq!(sld1.shapes.len(), sld2.shapes.len(), "形状数应一致");
7714 match (&sld1.shapes[0], &sld2.shapes[0]) {
7715 (OxmlSlideShape::GraphicFrame(f1), OxmlSlideShape::GraphicFrame(f2)) => {
7716 assert_eq!(f1.id, f2.id, "GraphicFrame id 应保留");
7717 assert_eq!(f1.name, f2.name, "GraphicFrame name 应保留");
7718 match (&f1.graphic, &f2.graphic) {
7719 (
7720 crate::oxml::shape::Graphic::Table(t1),
7721 crate::oxml::shape::Graphic::Table(t2),
7722 ) => {
7723 assert_eq!(t1.cols.len(), t2.cols.len(), "列数应保留");
7724 assert_eq!(t1.rows.len(), t2.rows.len(), "行数应保留");
7725 let get_text = |t: &crate::oxml::table::Table| -> String {
7727 t.rows[0].cells[0]
7728 .text
7729 .paragraphs
7730 .iter()
7731 .flat_map(|p| p.runs.iter().map(|r| r.text.as_str()))
7732 .collect()
7733 };
7734 assert_eq!(get_text(t1), get_text(t2), "单元格文本应保留");
7735 assert_eq!(get_text(t1), "R1C1");
7736 }
7737 _ => panic!("期望两侧均为 Table 变体"),
7738 }
7739 }
7740 _ => panic!("形状类型不匹配"),
7741 }
7742 }
7743
7744 #[test]
7746 fn parse_sld_empty_sp_tree() {
7747 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7748<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7749 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7750 <p:cSld>
7751 <p:spTree>
7752 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7753 <p:grpSpPr><a:xfrm/></p:grpSpPr>
7754 </p:spTree>
7755 </p:cSld>
7756 <p:clrMapOvr/>
7757</p:sld>"#;
7758 let sld = parse_sld(xml).expect("parse ok");
7759 assert_eq!(sld.shapes.len(), 0, "空 spTree 应解析出 0 个形状");
7760 }
7761
7762 #[test]
7767 fn parse_grp_sp_nested_group() {
7768 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
7769<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7770 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7771 <p:nvGrpSpPr><p:cNvPr id="1" name="Outer"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7772 <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>
7773 <p:sp>
7774 <p:nvSpPr><p:cNvPr id="2" name="OuterChild"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7775 <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>
7776 <p:txBody><a:bodyPr/><a:p><a:r><a:t>outer</a:t></a:r></a:p></p:txBody>
7777 </p:sp>
7778 <p:grpSp>
7779 <p:nvGrpSpPr><p:cNvPr id="3" name="Inner"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
7780 <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>
7781 <p:sp>
7782 <p:nvSpPr><p:cNvPr id="4" name="InnerChild"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
7783 <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>
7784 <p:txBody><a:bodyPr/><a:p><a:r><a:t>inner</a:t></a:r></a:p></p:txBody>
7785 </p:sp>
7786 </p:grpSp>
7787</p:grpSp>"#;
7788 let grp = parse_grp_sp(xml).expect("parse ok");
7789 assert_eq!(grp.id, 1);
7790 assert_eq!(grp.name, "Outer");
7791 assert_eq!(
7792 grp.children.len(),
7793 2,
7794 "外层组合应有 2 个子形状(sp + 嵌套 grpSp)"
7795 );
7796
7797 assert!(
7799 matches!(&grp.children[0], GroupChild::Sp(s) if s.id == 2 && s.name == "OuterChild")
7800 );
7801
7802 match &grp.children[1] {
7804 GroupChild::Group(inner) => {
7805 assert_eq!(inner.id, 3);
7806 assert_eq!(inner.name, "Inner");
7807 assert_eq!(inner.children.len(), 1, "内层组合应有 1 个子形状");
7808 assert!(
7809 matches!(&inner.children[0], GroupChild::Sp(s) if s.id == 4 && s.name == "InnerChild")
7810 );
7811 }
7812 other => panic!("第二个子形状应为 Group,实际:{other:?}"),
7813 }
7814 }
7815
7816 #[test]
7820 fn parse_sppr_grad_fill_linear() {
7821 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7822 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7823 <a:xfrm><a:off x="100" y="200"/><a:ext cx="300" cy="400"/></a:xfrm>
7824 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7825 <a:gradFill flip="none" rotWithShape="1">
7826 <a:gsLst>
7827 <a:gs pos="0"><a:srgbClr val="FF0000"/></a:gs>
7828 <a:gs pos="100000"><a:srgbClr val="0000FF"/></a:gs>
7829 </a:gsLst>
7830 <a:lin ang="5400000" scaled="1"/>
7831 </a:gradFill>
7832</p:spPr>"#;
7833 let sp = parse_sppr(xml).expect("parse ok");
7834 match &sp.fill {
7835 Fill::Gradient(grad) => {
7836 assert_eq!(grad.stops.len(), 2, "应有 2 个渐变光轨");
7837 assert_eq!(grad.stops[0].pos, 0);
7838 assert_eq!(grad.stops[1].pos, 100000);
7839 assert!(
7841 matches!(grad.stops[0].color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0x00)
7842 );
7843 assert!(
7844 matches!(grad.stops[1].color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0x00 && c.2 == 0xFF)
7845 );
7846 assert_eq!(grad.gradient_type, GradientType::Linear(5400000));
7848 assert_eq!(grad.flip.as_deref(), Some("none"));
7850 assert_eq!(grad.rot_with_shape, Some(true));
7851 }
7852 other => panic!("fill 应为 Gradient,实际:{other:?}"),
7853 }
7854 }
7855
7856 #[test]
7858 fn parse_sppr_grad_fill_path() {
7859 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7860 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7861 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7862 <a:gradFill>
7863 <a:gsLst>
7864 <a:gs pos="0"><a:srgbClr val="00FF00"/></a:gs>
7865 <a:gs pos="50000"><a:srgbClr val="FFFF00"/></a:gs>
7866 <a:gs pos="100000"><a:srgbClr val="FF00FF"/></a:gs>
7867 </a:gsLst>
7868 <a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>
7869 </a:gradFill>
7870</p:spPr>"#;
7871 let sp = parse_sppr(xml).expect("parse ok");
7872 match &sp.fill {
7873 Fill::Gradient(grad) => {
7874 assert_eq!(grad.stops.len(), 3, "应有 3 个渐变光轨");
7875 assert_eq!(grad.stops[1].pos, 50000);
7876 assert_eq!(grad.gradient_type, GradientType::Path(GradientPath::Circle));
7878 }
7879 other => panic!("fill 应为 Gradient,实际:{other:?}"),
7880 }
7881 }
7882
7883 #[test]
7885 fn parse_sppr_patt_fill() {
7886 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7887 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7888 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7889 <a:pattFill prst="pct5">
7890 <a:fgClr><a:srgbClr val="FF0000"/></a:fgClr>
7891 <a:bgClr><a:srgbClr val="FFFFFF"/></a:bgClr>
7892 </a:pattFill>
7893</p:spPr>"#;
7894 let sp = parse_sppr(xml).expect("parse ok");
7895 match &sp.fill {
7896 Fill::Pattern(patt) => {
7897 assert_eq!(patt.prst, "pct5", "预置图案应为 pct5");
7898 assert!(
7900 matches!(patt.fg_color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0x00)
7901 );
7902 assert!(
7904 matches!(patt.bg_color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0xFF && c.2 == 0xFF)
7905 );
7906 }
7907 other => panic!("fill 应为 Pattern,实际:{other:?}"),
7908 }
7909 }
7910
7911 #[test]
7916 fn parse_sppr_blip_fill_stretch() {
7917 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7918 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7919 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7920 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7921 <a:blipFill>
7922 <a:blip r:embed="rId1"/>
7923 <a:stretch><a:fillRect/></a:stretch>
7924 </a:blipFill>
7925</p:spPr>"#;
7926 let sp = parse_sppr(xml).expect("parse ok");
7927 match &sp.fill {
7928 Fill::Blip { rid, mode } => {
7929 assert_eq!(rid, "rId1", "rid 应为 rId1");
7930 assert!(
7931 matches!(mode, crate::oxml::sppr::BlipFillMode::Stretch),
7932 "mode 应为 Stretch,实际:{:?}",
7933 mode
7934 );
7935 }
7936 other => panic!("fill 应为 Blip,实际:{other:?}"),
7937 }
7938 }
7939
7940 #[test]
7942 fn parse_sppr_blip_fill_tile() {
7943 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7944 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7945 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7946 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7947 <a:blipFill>
7948 <a:blip r:embed="rIdImg7"/>
7949 <a:tile tx="100" ty="200" sx="50000" sy="50000" flip="x" algn="ctr"/>
7950 </a:blipFill>
7951</p:spPr>"#;
7952 let sp = parse_sppr(xml).expect("parse ok");
7953 match &sp.fill {
7954 Fill::Blip { rid, mode } => {
7955 assert_eq!(rid, "rIdImg7", "rid 应为 rIdImg7");
7956 match mode {
7957 crate::oxml::sppr::BlipFillMode::Tile {
7958 tx,
7959 ty,
7960 sx,
7961 sy,
7962 flip,
7963 algn,
7964 } => {
7965 assert_eq!(*tx, Some(100), "tx 应为 100");
7966 assert_eq!(*ty, Some(200), "ty 应为 200");
7967 assert_eq!(*sx, Some(50000), "sx 应为 50000");
7968 assert_eq!(*sy, Some(50000), "sy 应为 50000");
7969 assert_eq!(flip.as_deref(), Some("x"), "flip 应为 x");
7970 assert_eq!(algn.as_deref(), Some("ctr"), "algn 应为 ctr");
7971 }
7972 other => panic!("mode 应为 Tile,实际:{other:?}"),
7973 }
7974 }
7975 other => panic!("fill 应为 Blip,实际:{other:?}"),
7976 }
7977 }
7978
7979 #[test]
7981 fn parse_sppr_blip_fill_self_closing() {
7982 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
7983 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
7984 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
7985 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
7986 <a:blipFill>
7987 <a:blip r:embed="rId2"/>
7988 <a:stretch/>
7989 </a:blipFill>
7990</p:spPr>"#;
7991 let sp = parse_sppr(xml).expect("parse ok");
7992 match &sp.fill {
7993 Fill::Blip { rid, mode } => {
7994 assert_eq!(rid, "rId2");
7995 assert!(matches!(mode, crate::oxml::sppr::BlipFillMode::Stretch));
7996 }
7997 other => panic!("fill 应为 Blip,实际:{other:?}"),
7998 }
7999 }
8000
8001 #[test]
8006 fn parse_sppr_grad_fill_self_closing_lin() {
8007 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8008 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8009 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8010 <a:gradFill>
8011 <a:gsLst>
8012 <a:gs pos="0"><a:srgbClr val="FF0000"/></a:gs>
8013 <a:gs pos="100000"><a:srgbClr val="0000FF"/></a:gs>
8014 </a:gsLst>
8015 <a:lin ang="2700000" scaled="0"/>
8016 </a:gradFill>
8017</p:spPr>"#;
8018 let sp = parse_sppr(xml).expect("parse ok");
8019 match &sp.fill {
8020 Fill::Gradient(grad) => {
8021 assert_eq!(grad.gradient_type, GradientType::Linear(2700000));
8022 assert_eq!(grad.stops.len(), 2);
8023 }
8024 other => panic!("fill 应为 Gradient,实际:{other:?}"),
8025 }
8026 }
8027
8028 #[test]
8032 fn parse_sppr_ln_with_arrow_heads() {
8033 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8034 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8035 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8036 <a:ln w="12700">
8037 <a:solidFill><a:srgbClr val="000000"/></a:solidFill>
8038 <a:headEnd type="triangle" w="lg" len="med"/>
8039 <a:tailEnd type="stealth" w="sm" len="lg"/>
8040 </a:ln>
8041</p:spPr>"#;
8042 let sp = parse_sppr(xml).expect("parse ok");
8043 let ln = sp.line.expect("应有 line");
8044 let head = ln.head_end.expect("应有 headEnd");
8046 assert_eq!(head.arrow_type, ArrowType::Triangle);
8047 assert_eq!(head.width, ArrowSize::Large);
8048 assert_eq!(head.length, ArrowSize::Medium);
8049 let tail = ln.tail_end.expect("应有 tailEnd");
8051 assert_eq!(tail.arrow_type, ArrowType::Stealth);
8052 assert_eq!(tail.width, ArrowSize::Small);
8053 assert_eq!(tail.length, ArrowSize::Large);
8054 }
8055
8056 #[test]
8058 fn parse_sppr_ln_with_join() {
8059 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8061 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8062 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8063 <a:ln><a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:round/></a:ln>
8064</p:spPr>"#;
8065 let sp = parse_sppr(xml).expect("parse ok");
8066 let ln = sp.line.expect("应有 line");
8067 assert_eq!(ln.join, Some(LineJoin::Round));
8068
8069 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8071 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8072 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8073 <a:ln><a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:miter lim="600000"/></a:ln>
8074</p:spPr>"#;
8075 let sp = parse_sppr(xml).expect("parse ok");
8076 let ln = sp.line.expect("应有 line");
8077 assert_eq!(ln.join, Some(LineJoin::Miter(600000)));
8078
8079 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8081 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8082 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8083 <a:ln><a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:bevel/></a:ln>
8084</p:spPr>"#;
8085 let sp = parse_sppr(xml).expect("parse ok");
8086 let ln = sp.line.expect("应有 line");
8087 assert_eq!(ln.join, Some(LineJoin::Bevel));
8088 }
8089
8090 #[test]
8092 fn parse_sppr_ln_with_grad_fill() {
8093 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8094 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8095 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8096 <a:ln w="12700">
8097 <a:gradFill>
8098 <a:gsLst>
8099 <a:gs pos="0"><a:srgbClr val="FF0000"/></a:gs>
8100 <a:gs pos="100000"><a:srgbClr val="00FF00"/></a:gs>
8101 </a:gsLst>
8102 <a:lin ang="0" scaled="1"/>
8103 </a:gradFill>
8104 </a:ln>
8105</p:spPr>"#;
8106 let sp = parse_sppr(xml).expect("parse ok");
8107 let ln = sp.line.expect("应有 line");
8108 match &ln.fill {
8109 Fill::Gradient(grad) => {
8110 assert_eq!(grad.stops.len(), 2);
8111 assert_eq!(grad.gradient_type, GradientType::Linear(0));
8112 }
8113 other => panic!("line fill 应为 Gradient,实际:{other:?}"),
8114 }
8115 }
8116
8117 #[test]
8119 fn parse_sppr_ln_with_patt_fill() {
8120 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8121 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8122 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8123 <a:ln w="12700">
8124 <a:pattFill prst="cross">
8125 <a:fgClr><a:srgbClr val="FF0000"/></a:fgClr>
8126 <a:bgClr><a:srgbClr val="FFFFFF"/></a:bgClr>
8127 </a:pattFill>
8128 </a:ln>
8129</p:spPr>"#;
8130 let sp = parse_sppr(xml).expect("parse ok");
8131 let ln = sp.line.expect("应有 line");
8132 match &ln.fill {
8133 Fill::Pattern(patt) => {
8134 assert_eq!(patt.prst, "cross");
8135 assert!(matches!(patt.fg_color, Color::RGB(c) if c.0 == 0xFF));
8136 assert!(
8137 matches!(patt.bg_color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0xFF && c.2 == 0xFF)
8138 );
8139 }
8140 other => panic!("line fill 应为 Pattern,实际:{other:?}"),
8141 }
8142 }
8143
8144 #[test]
8146 fn parse_sppr_effect_lst_outer_shadow() {
8147 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8148 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8149 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8150 <a:effectLst>
8151 <a:outerShdw blurRad="40000" dist="38100" dir="2700000" rotWithShape="0">
8152 <a:srgbClr val="000000"/>
8153 </a:outerShdw>
8154 </a:effectLst>
8155</p:spPr>"#;
8156 let sp = parse_sppr(xml).expect("parse ok");
8157 let effects = sp.effects.expect("应有 effects");
8158 let shdw = effects.outer_shadow.expect("应有 outer_shadow");
8159 assert_eq!(shdw.dir, 2_700_000);
8160 assert_eq!(shdw.dist, 38100);
8161 assert_eq!(shdw.blur_rad, 40000);
8162 assert_eq!(shdw.rot_with_shape, Some(false));
8163 assert!(matches!(shdw.color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0x00 && c.2 == 0x00));
8164 }
8165
8166 #[test]
8168 fn parse_sppr_effect_lst_glow_and_soft_edge() {
8169 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8170 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8171 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8172 <a:effectLst>
8173 <a:glow rad="50000">
8174 <a:srgbClr val="FF00FF"/>
8175 </a:glow>
8176 <a:softEdge rad="25000"/>
8177 </a:effectLst>
8178</p:spPr>"#;
8179 let sp = parse_sppr(xml).expect("parse ok");
8180 let effects = sp.effects.expect("应有 effects");
8181 let glow = effects.glow.expect("应有 glow");
8182 assert_eq!(glow.rad, 50000);
8183 assert!(matches!(glow.color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0xFF));
8184 let se = effects.soft_edge.expect("应有 soft_edge");
8185 assert_eq!(se.rad, 25000);
8186 }
8187
8188 #[test]
8190 fn parse_sppr_effect_lst_reflection() {
8191 let xml = r#"<p:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8192 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8193 <a:prstGeom prst="rect"><a:avLst/></a:prstGeom>
8194 <a:effectLst>
8195 <a:reflection blurRad="50000" stA="52000" stPos="0" endA="30000" endPos="50000" dist="38100" dir="5400000"/>
8196 </a:effectLst>
8197</p:spPr>"#;
8198 let sp = parse_sppr(xml).expect("parse ok");
8199 let effects = sp.effects.expect("应有 effects");
8200 let refl = effects.reflection.expect("应有 reflection");
8201 assert_eq!(refl.blur_rad, Some(50000));
8202 assert_eq!(refl.st_a, Some(52000));
8203 assert_eq!(refl.st_pos, Some(0));
8204 assert_eq!(refl.end_a, Some(30000));
8205 assert_eq!(refl.end_pos, Some(50000));
8206 assert_eq!(refl.dist, Some(38100));
8207 assert_eq!(refl.dir, Some(5_400_000));
8208 }
8209
8210 #[test]
8214 fn round_trip_cell_merge_attributes() {
8215 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8216<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8217 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8218 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8219 <p:cSld><p:spTree>
8220 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8221 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8222 <p:graphicFrame>
8223 <p:nvGraphicFramePr><p:cNvPr id="2" name="MergedTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8224 <p:xfrm><a:off x="0" y="0"/><a:ext cx="6000000" cy="4000000"/></p:xfrm>
8225 <a:graphic>
8226 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8227 <a:tbl>
8228 <a:tblPr><a:tableStyleId>{5940675A-B579-460E-94D1-54222C63F5DA}</a:tableStyleId></a:tblPr>
8229 <a:tblGrid>
8230 <a:gridCol w="2000000"/>
8231 <a:gridCol w="2000000"/>
8232 <a:gridCol w="2000000"/>
8233 </a:tblGrid>
8234 <a:tr h="2000000">
8235 <a:tc gridSpan="3" rowSpan="2">
8236 <a:txBody><a:bodyPr/><a:p><a:r><a:t>Merged</a:t></a:r></a:p></a:txBody>
8237 <a:tcPr/>
8238 </a:tc>
8239 </a:tr>
8240 <a:tr h="2000000">
8241 <a:tc hMerge="1"><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8242 <a:tc hMerge="1"><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8243 <a:tc vMerge="1"><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8244 </a:tr>
8245 </a:tbl>
8246 </a:graphicData>
8247 </a:graphic>
8248 </p:graphicFrame>
8249 </p:spTree></p:cSld>
8250</p:sld>"#;
8251 let sld = parse_sld(xml).expect("parse ok");
8252 let frame = sld
8254 .shapes
8255 .iter()
8256 .find_map(|s| {
8257 if let OxmlSlideShape::GraphicFrame(g) = s {
8258 Some(g)
8259 } else {
8260 None
8261 }
8262 })
8263 .expect("应有 graphicFrame");
8264 match &frame.graphic {
8265 crate::oxml::shape::Graphic::Table(tbl) => {
8266 assert_eq!(tbl.rows.len(), 2, "应有 2 行");
8267 assert_eq!(
8269 tbl.table_style
8270 .as_ref()
8271 .expect("应有 table_style")
8272 .style_id(),
8273 "{5940675A-B579-460E-94D1-54222C63F5DA}"
8274 );
8275 let origin = &tbl.rows[0].cells[0];
8277 assert_eq!(origin.grid_span, 3, "gridSpan 应为 3");
8278 assert_eq!(origin.row_span, 2, "rowSpan 应为 2");
8279 assert!(!origin.h_merge, "合并源不应有 hMerge");
8280 assert!(!origin.v_merge, "合并源不应有 vMerge");
8281 let h1 = &tbl.rows[1].cells[0];
8283 assert!(h1.h_merge, "应有 hMerge");
8284 let v1 = &tbl.rows[1].cells[2];
8285 assert!(v1.v_merge, "应有 vMerge");
8286 }
8287 _ => panic!("期望 Table 变体"),
8288 }
8289 }
8290
8291 #[test]
8295 fn round_trip_table_style_id() {
8296 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8297<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8298 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8299 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8300 <p:cSld><p:spTree>
8301 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8302 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8303 <p:graphicFrame>
8304 <p:nvGraphicFramePr><p:cNvPr id="2" name="StyledTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8305 <p:xfrm><a:off x="0" y="0"/><a:ext cx="4000000" cy="2000000"/></p:xfrm>
8306 <a:graphic>
8307 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8308 <a:tbl>
8309 <a:tblPr><a:tableStyleId>{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}</a:tableStyleId></a:tblPr>
8310 <a:tblGrid><a:gridCol w="2000000"/><a:gridCol w="2000000"/></a:tblGrid>
8311 <a:tr h="2000000">
8312 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8313 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8314 </a:tr>
8315 </a:tbl>
8316 </a:graphicData>
8317 </a:graphic>
8318 </p:graphicFrame>
8319 </p:spTree></p:cSld>
8320</p:sld>"#;
8321 let sld = parse_sld(xml).expect("parse ok");
8322 let frame = sld
8323 .shapes
8324 .iter()
8325 .find_map(|s| {
8326 if let OxmlSlideShape::GraphicFrame(g) = s {
8327 Some(g)
8328 } else {
8329 None
8330 }
8331 })
8332 .expect("应有 graphicFrame");
8333 match &frame.graphic {
8334 crate::oxml::shape::Graphic::Table(tbl) => {
8335 let style = tbl.table_style.as_ref().expect("应有 table_style");
8337 assert_eq!(style.style_id(), "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}");
8338 let mut w = crate::oxml::writer::XmlWriter::new();
8340 tbl.write_xml(&mut w);
8341 let out = &w.buf;
8342 assert!(out.contains(
8343 "<a:tableStyleId>{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}</a:tableStyleId>"
8344 ));
8345 }
8346 _ => panic!("期望 Table 变体"),
8347 }
8348 }
8349
8350 #[test]
8352 fn parse_table_without_style_id() {
8353 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8354<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8355 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8356 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8357 <p:cSld><p:spTree>
8358 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8359 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8360 <p:graphicFrame>
8361 <p:nvGraphicFramePr><p:cNvPr id="2" name="PlainTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8362 <p:xfrm><a:off x="0" y="0"/><a:ext cx="4000000" cy="2000000"/></p:xfrm>
8363 <a:graphic>
8364 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8365 <a:tbl>
8366 <a:tblPr/>
8367 <a:tblGrid><a:gridCol w="2000000"/><a:gridCol w="2000000"/></a:tblGrid>
8368 <a:tr h="2000000">
8369 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8370 <a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody><a:tcPr/></a:tc>
8371 </a:tr>
8372 </a:tbl>
8373 </a:graphicData>
8374 </a:graphic>
8375 </p:graphicFrame>
8376 </p:spTree></p:cSld>
8377</p:sld>"#;
8378 let sld = parse_sld(xml).expect("parse ok");
8379 let frame = sld
8380 .shapes
8381 .iter()
8382 .find_map(|s| {
8383 if let OxmlSlideShape::GraphicFrame(g) = s {
8384 Some(g)
8385 } else {
8386 None
8387 }
8388 })
8389 .expect("应有 graphicFrame");
8390 match &frame.graphic {
8391 crate::oxml::shape::Graphic::Table(tbl) => {
8392 assert!(
8393 tbl.table_style.is_none(),
8394 "无 tableStyleId 时 table_style 应为 None"
8395 );
8396 }
8397 _ => panic!("期望 Table 变体"),
8398 }
8399 }
8400
8401 #[test]
8406 fn round_trip_cell_borders_margins_anchor() {
8407 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8408<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8409 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8410 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8411 <p:cSld><p:spTree>
8412 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8413 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8414 <p:graphicFrame>
8415 <p:nvGraphicFramePr><p:cNvPr id="2" name="FormattedTable"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>
8416 <p:xfrm><a:off x="0" y="0"/><a:ext cx="4000000" cy="2000000"/></p:xfrm>
8417 <a:graphic>
8418 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
8419 <a:tbl>
8420 <a:tblPr><a:tableStyleId>{5940675A-B579-460E-94D1-54222C63F5DA}</a:tableStyleId></a:tblPr>
8421 <a:tblGrid><a:gridCol w="2000000"/><a:gridCol w="2000000"/></a:tblGrid>
8422 <a:tr h="2000000">
8423 <a:tc>
8424 <a:txBody><a:bodyPr/><a:p><a:r><a:t>A1</a:t></a:r></a:p></a:txBody>
8425 <a:tcPr marT="50000" marL="60000" marB="70000" marR="80000" anchor="ctr">
8426 <a:lnL w="9525"><a:solidFill><a:srgbClr val="FF0000"/></a:solidFill></a:lnL>
8427 <a:lnR w="9525"><a:solidFill><a:srgbClr val="00FF00"/></a:solidFill></a:lnR>
8428 <a:lnT w="9525"><a:solidFill><a:srgbClr val="0000FF"/></a:solidFill></a:lnT>
8429 <a:lnB w="9525"><a:noFill/></a:lnB>
8430 </a:tcPr>
8431 </a:tc>
8432 <a:tc>
8433 <a:txBody><a:bodyPr/><a:p><a:r><a:t>B1</a:t></a:r></a:p></a:txBody>
8434 <a:tcPr/>
8435 </a:tc>
8436 </a:tr>
8437 </a:tbl>
8438 </a:graphicData>
8439 </a:graphic>
8440 </p:graphicFrame>
8441 </p:spTree></p:cSld>
8442</p:sld>"#;
8443 let sld = parse_sld(xml).expect("parse ok");
8444 let frame = sld
8445 .shapes
8446 .iter()
8447 .find_map(|s| {
8448 if let OxmlSlideShape::GraphicFrame(g) = s {
8449 Some(g)
8450 } else {
8451 None
8452 }
8453 })
8454 .expect("应有 graphicFrame");
8455 let tbl = match &frame.graphic {
8456 crate::oxml::shape::Graphic::Table(t) => t,
8457 _ => panic!("期望 Table 变体"),
8458 };
8459 let cell = &tbl.rows[0].cells[0];
8460 assert_eq!(cell.margin.0.map(|m| m.value()), Some(50000), "marT");
8462 assert_eq!(cell.margin.1.map(|m| m.value()), Some(60000), "marL");
8463 assert_eq!(cell.margin.2.map(|m| m.value()), Some(70000), "marB");
8464 assert_eq!(cell.margin.3.map(|m| m.value()), Some(80000), "marR");
8465 assert_eq!(
8467 cell.anchor,
8468 crate::oxml::table::VerticalAnchor::Middle,
8469 "anchor=ctr"
8470 );
8471 let bl = cell.border_left.as_ref().expect("应有 lnL");
8473 assert_eq!(bl.width.value(), 9525);
8474 assert!(matches!(bl.color, Color::RGB(c) if c.0 == 0xFF && c.1 == 0x00 && c.2 == 0x00));
8475 let br = cell.border_right.as_ref().expect("应有 lnR");
8476 assert!(matches!(br.color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0xFF && c.2 == 0x00));
8477 let bt = cell.border_top.as_ref().expect("应有 lnT");
8478 assert!(matches!(bt.color, Color::RGB(c) if c.0 == 0x00 && c.1 == 0x00 && c.2 == 0xFF));
8479 let bb = cell.border_bottom.as_ref().expect("应有 lnB");
8480 assert!(bb.no_fill, "lnB 应为 noFill");
8481 }
8482
8483 #[test]
8487 fn parse_txbody_multi_column() {
8488 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8489 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8490 <a:bodyPr numCol="3" spcCol="91440" anchor="ctr" wrap="square" lIns="91440" tIns="45720" rIns="91440" bIns="45720"/>
8491 <a:p><a:r><a:t>multi-col text</a:t></a:r></a:p>
8492</p:txBody>"#;
8493 let tb = parse_txbody(xml).expect("parse ok");
8494 let bp = tb.body_properties.as_ref().expect("应有 body_properties");
8495 assert_eq!(bp.num_cols, Some(3), "numCol");
8496 assert_eq!(bp.col_spacing.map(|v| v.value()), Some(91440), "spcCol");
8497 assert_eq!(bp.anchor, Some(MsoAnchor::Middle), "anchor=ctr");
8498 assert_eq!(bp.wrap, Some(TextWrapping::Square), "wrap=square");
8499 let insets = bp.insets.as_ref().expect("应有 insets");
8500 assert_eq!(insets.left.value(), 91440, "lIns");
8501 assert_eq!(insets.top.value(), 45720, "tIns");
8502 assert_eq!(insets.right.value(), 91440, "rIns");
8503 assert_eq!(insets.bottom.value(), 45720, "bIns");
8504 assert_eq!(tb.paragraphs.len(), 1, "应有一个段落");
8506 assert_eq!(tb.paragraphs[0].runs.len(), 1, "应有一个 run");
8507 assert_eq!(tb.paragraphs[0].runs[0].text, "multi-col text");
8508 }
8509
8510 #[test]
8514 fn parse_txbody_self_closing_body_pr() {
8515 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8516 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8517 <a:bodyPr numCol="2" spcCol="45720"/>
8518 <a:p/>
8519</p:txBody>"#;
8520 let tb = parse_txbody(xml).expect("parse ok");
8521 let bp = tb.body_properties.as_ref().expect("应有 body_properties");
8522 assert_eq!(bp.num_cols, Some(2), "numCol");
8523 assert_eq!(bp.col_spacing.map(|v| v.value()), Some(45720), "spcCol");
8524 }
8525
8526 #[test]
8530 fn parse_txbody_with_sp_auto_fit() {
8531 let xml = r#"<p:txBody xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8532 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8533 <a:bodyPr numCol="1"><a:spAutoFit/></a:bodyPr>
8534 <a:p/>
8535</p:txBody>"#;
8536 let tb = parse_txbody(xml).expect("parse ok");
8537 let bp = tb.body_properties.as_ref().expect("应有 body_properties");
8538 assert_eq!(bp.num_cols, Some(1), "numCol=1");
8539 assert!(bp.sp_auto_fit, "应启用 spAutoFit");
8540 assert!(!bp.norm_autofit, "不应启用 normAutofit");
8541 }
8542
8543 #[test]
8547 fn parse_paragraph_bullet_styles() {
8548 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8550 <a:buChar char="•"/>
8551</a:pPr>"#;
8552 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8553 assert!(ppr.bullet, "bullet 应为 true");
8554 match &ppr.bullet_style {
8555 Some(BulletStyle::Char { char }) => assert_eq!(char, "•"),
8556 other => panic!("期望 Char,实际: {other:?}"),
8557 }
8558
8559 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8561 <a:buAutoNum type="arabicPeriod" startAt="3"/>
8562</a:pPr>"#;
8563 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8564 assert!(ppr.bullet);
8565 match &ppr.bullet_style {
8566 Some(BulletStyle::AutoNum {
8567 auto_num_type,
8568 start_at,
8569 }) => {
8570 assert_eq!(auto_num_type, "arabicPeriod");
8571 assert_eq!(*start_at, Some(3));
8572 }
8573 other => panic!("期望 AutoNum,实际: {other:?}"),
8574 }
8575
8576 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8578 <a:buAutoNum type="alphaLcParenR"/>
8579</a:pPr>"#;
8580 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8581 match &ppr.bullet_style {
8582 Some(BulletStyle::AutoNum {
8583 auto_num_type,
8584 start_at,
8585 }) => {
8586 assert_eq!(auto_num_type, "alphaLcParenR");
8587 assert_eq!(*start_at, None);
8588 }
8589 other => panic!("期望 AutoNum,实际: {other:?}"),
8590 }
8591
8592 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8594 <a:buNone/>
8595</a:pPr>"#;
8596 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8597 assert!(!ppr.bullet, "bullet 应为 false");
8598 assert!(matches!(ppr.bullet_style, Some(BulletStyle::None)));
8599 }
8600
8601 #[test]
8605 fn parse_paragraph_bullet_styles_self_closing() {
8606 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8608 <a:buChar char="▪"/>
8609</a:pPr>"#;
8610 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8611 match &ppr.bullet_style {
8612 Some(BulletStyle::Char { char }) => assert_eq!(char, "▪"),
8613 other => panic!("期望 Char,实际: {other:?}"),
8614 }
8615
8616 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8618 <a:buAutoNum type="romanLcParenBoth" startAt="5"/>
8619</a:pPr>"#;
8620 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8621 match &ppr.bullet_style {
8622 Some(BulletStyle::AutoNum {
8623 auto_num_type,
8624 start_at,
8625 }) => {
8626 assert_eq!(auto_num_type, "romanLcParenBoth");
8627 assert_eq!(*start_at, Some(5));
8628 }
8629 other => panic!("期望 AutoNum,实际: {other:?}"),
8630 }
8631 }
8632
8633 #[test]
8637 fn parse_paragraph_tab_stops() {
8638 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8639 <a:tabLst>
8640 <a:tab pos="914400" algn="l"/>
8641 <a:tab pos="1828800" algn="r"/>
8642 <a:tab pos="2743200" algn="ctr"/>
8643 <a:tab pos="3657600" algn="dec"/>
8644 </a:tabLst>
8645</a:pPr>"#;
8646 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8647 assert_eq!(ppr.tab_stops.len(), 4, "应有 4 个制表位");
8648 assert_eq!(ppr.tab_stops[0].pos.value(), 914400);
8649 assert_eq!(ppr.tab_stops[0].alignment, TabAlignment::Left);
8650 assert_eq!(ppr.tab_stops[1].pos.value(), 1828800);
8651 assert_eq!(ppr.tab_stops[1].alignment, TabAlignment::Right);
8652 assert_eq!(ppr.tab_stops[2].pos.value(), 2743200);
8653 assert_eq!(ppr.tab_stops[2].alignment, TabAlignment::Center);
8654 assert_eq!(ppr.tab_stops[3].pos.value(), 3657600);
8655 assert_eq!(ppr.tab_stops[3].alignment, TabAlignment::Decimal);
8656 }
8657
8658 #[test]
8662 fn parse_paragraph_empty_tab_lst() {
8663 let xml = r#"<a:pPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8664 <a:tabLst/>
8665</a:pPr>"#;
8666 let ppr = parse_paragraph_properties(xml).expect("parse ok");
8667 assert!(ppr.tab_stops.is_empty(), "空 tabLst 应无制表位");
8668 }
8669
8670 #[test]
8674 fn parse_paragraph_field() {
8675 let xml = r#"<a:p xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8676 <a:fld id="{12345678-ABCD-EF01-2345-678901234567}" type="slidenum">
8677 <a:rPr lang="en-US"/>
8678 <a:t>1</a:t>
8679 </a:fld>
8680</a:p>"#;
8681 let p = parse_paragraph(xml).expect("parse ok");
8682 assert_eq!(p.fields.len(), 1, "应有 1 个字段");
8683 let f = &p.fields[0];
8684 assert_eq!(f.id, "{12345678-ABCD-EF01-2345-678901234567}");
8685 assert_eq!(f.field_type, FieldType::SlideNumber);
8686 assert_eq!(f.text, "1");
8687 }
8688
8689 #[test]
8693 fn parse_paragraph_mixed_runs_and_fields() {
8694 let xml = r#"<a:p xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8695 <a:r><a:t>Page </a:t></a:r>
8696 <a:fld id="{AAA}" type="slidenum"><a:t>1</a:t></a:fld>
8697 <a:fld id="{BBB}" type="datetime"><a:t>1/1/2024</a:t></a:fld>
8698</a:p>"#;
8699 let p = parse_paragraph(xml).expect("parse ok");
8700 assert_eq!(p.runs.len(), 1, "应有 1 个 run");
8701 assert_eq!(p.runs[0].text, "Page");
8703 assert_eq!(p.fields.len(), 2, "应有 2 个字段");
8704 assert_eq!(p.fields[0].field_type, FieldType::SlideNumber);
8705 assert_eq!(p.fields[0].text, "1");
8706 assert_eq!(p.fields[1].field_type, FieldType::DateTime);
8707 assert_eq!(p.fields[1].text, "1/1/2024");
8708 }
8709
8710 #[test]
8714 fn parse_paragraph_custom_field_type() {
8715 let xml = r#"<a:p xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8716 <a:fld id="{CCC}" type="customField"><a:t>custom</a:t></a:fld>
8717</a:p>"#;
8718 let p = parse_paragraph(xml).expect("parse ok");
8719 assert_eq!(p.fields.len(), 1);
8720 assert_eq!(
8721 p.fields[0].field_type,
8722 FieldType::Custom("customField".to_string())
8723 );
8724 assert_eq!(p.fields[0].text, "custom");
8725 }
8726
8727 #[test]
8731 fn parse_run_properties_hlink_click_self_closing() {
8732 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">
8733 <a:hlinkClick r:id="rId3" tooltip="点击访问"/>
8734</a:rPr>"#;
8735 let rp = parse_run_properties(xml).expect("parse ok");
8736 let hl = rp.hlink_click.expect("hlink_click 应存在");
8737 assert_eq!(hl.rid.as_deref(), Some("rId3"));
8738 assert_eq!(hl.tooltip.as_deref(), Some("点击访问"));
8739 assert!(hl.action.is_none());
8740 assert!(!hl.invalid);
8741 }
8742
8743 #[test]
8747 fn parse_run_properties_hlink_click_action() {
8748 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8749 <a:hlinkClick action="ppaction://hlinksldjump"/>
8750</a:rPr>"#;
8751 let rp = parse_run_properties(xml).expect("parse ok");
8752 let hl = rp.hlink_click.expect("hlink_click 应存在");
8753 assert_eq!(hl.action.as_deref(), Some("ppaction://hlinksldjump"));
8754 assert!(hl.rid.is_none());
8755 }
8756
8757 #[test]
8761 fn parse_run_properties_hlink_hover() {
8762 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8763 <a:hlinkHover r:id="rId5" tooltip="悬停提示"/>
8764</a:rPr>"#;
8765 let rp = parse_run_properties(xml).expect("parse ok");
8766 let hl = rp.hlink_hover.expect("hlink_hover 应存在");
8767 assert_eq!(hl.rid.as_deref(), Some("rId5"));
8768 assert_eq!(hl.tooltip.as_deref(), Some("悬停提示"));
8769 }
8770
8771 #[test]
8775 fn parse_run_properties_both_hlinks() {
8776 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8777 <a:hlinkClick r:id="rId1" tooltip="click"/>
8778 <a:hlinkHover r:id="rId2" tooltip="hover"/>
8779</a:rPr>"#;
8780 let rp = parse_run_properties(xml).expect("parse ok");
8781 assert_eq!(
8782 rp.hlink_click.as_ref().unwrap().rid.as_deref(),
8783 Some("rId1")
8784 );
8785 assert_eq!(
8786 rp.hlink_hover.as_ref().unwrap().rid.as_deref(),
8787 Some("rId2")
8788 );
8789 }
8790
8791 #[test]
8795 fn parse_run_properties_hlink_click_empty() {
8796 let xml = r#"<a:rPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8797 <a:hlinkClick/>
8798</a:rPr>"#;
8799 let rp = parse_run_properties(xml).expect("parse ok");
8800 let hl = rp.hlink_click.expect("hlink_click 应存在");
8801 assert!(hl.invalid);
8802 assert!(hl.rid.is_none());
8803 }
8804
8805 #[test]
8809 fn parse_sp_with_locks() {
8810 let xml = r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8811 <p:nvSpPr>
8812 <p:cNvPr id="2" name="Locked"/>
8813 <p:cNvSpPr>
8814 <a:spLocks noGrp="1" noSelect="1" noResize="1"/>
8815 </p:cNvSpPr>
8816 <p:nvPr/>
8817 </p:nvSpPr>
8818 <p:spPr/>
8819 <p:txBody><a:bodyPr/><a:p/></p:txBody>
8820</p:sp>"#;
8821 let sp = parse_sp(xml).expect("parse ok");
8822 let locks = sp.locks.as_ref().expect("locks 应存在");
8823 assert!(locks.no_grp);
8824 assert!(locks.no_select);
8825 assert!(locks.no_resize);
8826 assert!(!locks.no_move);
8827 assert!(!locks.no_rot);
8828 }
8829
8830 #[test]
8834 fn parse_sp_locks_with_txbox() {
8835 let xml = r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8836 <p:nvSpPr>
8837 <p:cNvPr id="3" name="TB"/>
8838 <p:cNvSpPr txBox="1">
8839 <a:spLocks noChangeAspect="1"/>
8840 </p:cNvSpPr>
8841 <p:nvPr/>
8842 </p:nvSpPr>
8843 <p:spPr/>
8844 <p:txBody><a:bodyPr/><a:p/></p:txBody>
8845</p:sp>"#;
8846 let sp = parse_sp(xml).expect("parse ok");
8847 assert!(sp.c_nv_sp_pr_tx_box, "txBox 应为 true");
8848 let locks = sp.locks.as_ref().expect("locks 应存在");
8849 assert!(locks.no_change_aspect);
8850 assert!(!locks.no_grp);
8851 }
8852
8853 #[test]
8857 fn parse_sp_with_style() {
8858 let xml = r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8859 <p:nvSpPr>
8860 <p:cNvPr id="4" name="Styled"/>
8861 <p:cNvSpPr/>
8862 <p:nvPr/>
8863 </p:nvSpPr>
8864 <p:spPr/>
8865 <p:style>
8866 <a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef>
8867 <a:fillRef idx="2"><a:schemeClr val="accent2"/></a:fillRef>
8868 <a:effectRef idx="0"><a:schemeClr val="accent3"/></a:effectRef>
8869 <a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef>
8870 </p:style>
8871 <p:txBody><a:bodyPr/><a:p/></p:txBody>
8872</p:sp>"#;
8873 let sp = parse_sp(xml).expect("parse ok");
8874 let style = sp.style.as_ref().expect("style 应存在");
8875 let ln = style.line_ref.as_ref().expect("line_ref 应存在");
8876 assert_eq!(ln.idx.as_deref(), Some("1"));
8877 assert_eq!(ln.scheme_color.as_deref(), Some("accent1"));
8878 let fill = style.fill_ref.as_ref().expect("fill_ref 应存在");
8879 assert_eq!(fill.idx.as_deref(), Some("2"));
8880 assert_eq!(fill.scheme_color.as_deref(), Some("accent2"));
8881 let eff = style.effect_ref.as_ref().expect("effect_ref 应存在");
8882 assert_eq!(eff.idx.as_deref(), Some("0"));
8883 let font = style.font_ref.as_ref().expect("font_ref 应存在");
8884 assert_eq!(font.idx.as_deref(), Some("minor"));
8885 assert_eq!(font.scheme_color.as_deref(), Some("tx1"));
8886 }
8887
8888 #[test]
8892 fn parse_sp_style_self_closing_refs() {
8893 let xml = r#"<p:style xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
8894 <a:lnRef idx="3"/>
8895 <a:fillRef idx="1"/>
8896</p:style>"#;
8897 let style = parse_shape_style(xml).expect("parse ok");
8898 assert_eq!(style.line_ref.as_ref().unwrap().idx.as_deref(), Some("3"));
8899 assert!(style.line_ref.as_ref().unwrap().scheme_color.is_none());
8900 assert_eq!(style.fill_ref.as_ref().unwrap().idx.as_deref(), Some("1"));
8901 assert!(style.effect_ref.is_none());
8902 assert!(style.font_ref.is_none());
8903 }
8904
8905 #[test]
8909 fn parse_transition_fade() {
8910 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" spd="slow" advClick="0" advTm="5000">
8911 <p:fade thruBlk="1"/>
8912</p:transition>"#;
8913 let tr = parse_transition(xml).expect("parse ok");
8914 assert_eq!(tr.speed, TransitionSpeed::Slow);
8915 assert!(!tr.advance_click);
8916 assert_eq!(tr.advance_after_ms, Some(5000));
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 parse_transition_push() {
8926 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" spd="fast">
8927 <p:push dir="l"/>
8928</p:transition>"#;
8929 let tr = parse_transition(xml).expect("parse ok");
8930 assert_eq!(tr.speed, TransitionSpeed::Fast);
8931 assert!(tr.advance_click);
8933 assert!(tr.advance_after_ms.is_none());
8934 match &tr.transition_type {
8935 TransitionType::Push { dir } => assert_eq!(*dir, TransitionDirection::Left),
8936 other => panic!("expected Push, got {:?}", other),
8937 }
8938 }
8939
8940 #[test]
8942 fn parse_transition_morph() {
8943 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8944 <p:morph option="byChar"/>
8945</p:transition>"#;
8946 let tr = parse_transition(xml).expect("parse ok");
8947 match &tr.transition_type {
8948 TransitionType::Morph { option } => assert_eq!(*option, MorphOption::ByChar),
8949 other => panic!("expected Morph, got {:?}", other),
8950 }
8951 }
8952
8953 #[test]
8955 fn parse_transition_split() {
8956 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
8957 <p:split orient="vert" dir="d"/>
8958</p:transition>"#;
8959 let tr = parse_transition(xml).expect("parse ok");
8960 match &tr.transition_type {
8961 TransitionType::Split { orient, dir } => {
8962 assert_eq!(*orient, SplitOrientation::Vertical);
8963 assert_eq!(*dir, TransitionDirection::Down);
8964 }
8965 other => panic!("expected Split, got {:?}", other),
8966 }
8967 }
8968
8969 #[test]
8971 fn parse_transition_self_closing() {
8972 let xml = r#"<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" spd="med" advClick="1"/>"#;
8973 let tr = parse_transition(xml).expect("parse ok");
8974 assert_eq!(tr.speed, TransitionSpeed::Medium);
8975 assert!(tr.advance_click);
8976 assert_eq!(tr.transition_type, TransitionType::None);
8978 }
8979
8980 #[test]
8982 fn parse_sld_with_transition() {
8983 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8984<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
8985 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
8986 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
8987 <p:cSld>
8988 <p:spTree>
8989 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
8990 <p:grpSpPr><a:xfrm/></p:grpSpPr>
8991 </p:spTree>
8992 </p:cSld>
8993 <p:clrMapOvr/>
8994 <p:transition spd="slow" advTm="3000">
8995 <p:fade/>
8996 </p:transition>
8997</p:sld>"#;
8998 let sld = parse_sld(xml).expect("parse ok");
8999 let tr = sld.transition.expect("transition should exist");
9000 assert_eq!(tr.speed, TransitionSpeed::Slow);
9001 assert_eq!(tr.advance_after_ms, Some(3000));
9002 match &tr.transition_type {
9003 TransitionType::Fade { thru_blk } => assert!(!*thru_blk),
9004 other => panic!("expected Fade, got {:?}", other),
9005 }
9006 }
9007
9008 #[test]
9010 fn transition_round_trip() {
9011 use crate::oxml::slide::Transition;
9012 use crate::oxml::writer::XmlWriter;
9013
9014 let original = Transition {
9015 speed: TransitionSpeed::Fast,
9016 advance_click: false,
9017 advance_after_ms: Some(8000),
9018 transition_type: TransitionType::Push {
9019 dir: TransitionDirection::Up,
9020 },
9021 };
9022 let mut w = XmlWriter::new();
9024 original.write_xml(&mut w);
9025 let xml = w.buf.clone();
9026 let parsed = parse_transition(&xml).expect("parse ok");
9028 assert_eq!(parsed.speed, original.speed);
9029 assert_eq!(parsed.advance_click, original.advance_click);
9030 assert_eq!(parsed.advance_after_ms, original.advance_after_ms);
9031 assert_eq!(parsed.transition_type, original.transition_type);
9032 }
9033
9034 #[test]
9038 fn parse_custom_geometry_basic() {
9039 let xml = r#"<a:custGeom>
9040<a:avLst/>
9041<a:rect l="0" t="0" r="100" b="100"/>
9042<a:pathLst>
9043<a:path w="100" h="100" fill="norm" stroke="norm">
9044<a:moveTo><a:pt x="0" y="0"/></a:moveTo>
9045<a:lnTo><a:pt x="100" y="0"/></a:lnTo>
9046<a:lnTo><a:pt x="50" y="100"/></a:lnTo>
9047<a:close/>
9048</a:path>
9049</a:pathLst>
9050</a:custGeom>"#;
9051 let geom = parse_custom_geometry(xml).expect("parse ok");
9052 assert_eq!(
9053 geom.rect,
9054 Some(GeomRect {
9055 l: "0".to_string(),
9056 t: "0".to_string(),
9057 r: "100".to_string(),
9058 b: "100".to_string(),
9059 })
9060 );
9061 assert_eq!(geom.path_list.len(), 1);
9062 let p = &geom.path_list[0];
9063 assert_eq!(p.width, 100);
9064 assert_eq!(p.height, 100);
9065 assert_eq!(p.fill.as_deref(), Some("norm"));
9066 assert_eq!(p.stroke.as_deref(), Some("norm"));
9067 assert_eq!(p.segments.len(), 4);
9068 match &p.segments[0] {
9070 PathSegment::MoveTo { x, y } => {
9071 assert_eq!(*x, 0);
9072 assert_eq!(*y, 0);
9073 }
9074 other => panic!("expected MoveTo, got {:?}", other),
9075 }
9076 match &p.segments[1] {
9077 PathSegment::LineTo { x, y } => {
9078 assert_eq!(*x, 100);
9079 assert_eq!(*y, 0);
9080 }
9081 other => panic!("expected LineTo, got {:?}", other),
9082 }
9083 match &p.segments[2] {
9084 PathSegment::LineTo { x, y } => {
9085 assert_eq!(*x, 50);
9086 assert_eq!(*y, 100);
9087 }
9088 other => panic!("expected LineTo, got {:?}", other),
9089 }
9090 match &p.segments[3] {
9091 PathSegment::Close => {}
9092 other => panic!("expected Close, got {:?}", other),
9093 }
9094 }
9095
9096 #[test]
9098 fn parse_custom_geometry_with_bez() {
9099 let xml = r#"<a:custGeom>
9100<a:avLst/>
9101<a:pathLst>
9102<a:path w="200" h="200">
9103<a:moveTo><a:pt x="10" y="10"/></a:moveTo>
9104<a:cubicBezTo>
9105<a:pt x="50" y="0"/>
9106<a:pt x="150" y="0"/>
9107<a:pt x="190" y="10"/>
9108</a:cubicBezTo>
9109<a:quadBezTo>
9110<a:pt x="190" y="100"/>
9111<a:pt x="100" y="190"/>
9112</a:quadBezTo>
9113</a:path>
9114</a:pathLst>
9115</a:custGeom>"#;
9116 let geom = parse_custom_geometry(xml).expect("parse ok");
9117 assert_eq!(geom.path_list.len(), 1);
9118 let p = &geom.path_list[0];
9119 assert_eq!(p.segments.len(), 3);
9120 match &p.segments[1] {
9121 PathSegment::CubicBezTo {
9122 x1,
9123 y1,
9124 x2,
9125 y2,
9126 x3,
9127 y3,
9128 } => {
9129 assert_eq!(*x1, 50);
9130 assert_eq!(*y1, 0);
9131 assert_eq!(*x2, 150);
9132 assert_eq!(*y2, 0);
9133 assert_eq!(*x3, 190);
9134 assert_eq!(*y3, 10);
9135 }
9136 other => panic!("expected CubicBezTo, got {:?}", other),
9137 }
9138 match &p.segments[2] {
9139 PathSegment::QuadBezTo { x1, y1, x2, y2 } => {
9140 assert_eq!(*x1, 190);
9141 assert_eq!(*y1, 100);
9142 assert_eq!(*x2, 100);
9143 assert_eq!(*y2, 190);
9144 }
9145 other => panic!("expected QuadBezTo, got {:?}", other),
9146 }
9147 }
9148
9149 #[test]
9151 fn parse_custom_geometry_with_arc() {
9152 let xml = r#"<a:custGeom>
9153<a:avLst/>
9154<a:pathLst>
9155<a:path w="100" h="100">
9156<a:moveTo><a:pt x="0" y="50"/></a:moveTo>
9157<a:arcTo wR="50" hR="50" stAng="0" swAng="5400000"/>
9158<a:close/>
9159</a:path>
9160</a:pathLst>
9161</a:custGeom>"#;
9162 let geom = parse_custom_geometry(xml).expect("parse ok");
9163 let p = &geom.path_list[0];
9164 assert_eq!(p.segments.len(), 3);
9165 match &p.segments[1] {
9166 PathSegment::ArcTo {
9167 w_r,
9168 h_r,
9169 st_ang,
9170 sw_ang,
9171 } => {
9172 assert_eq!(*w_r, 50);
9173 assert_eq!(*h_r, 50);
9174 assert_eq!(*st_ang, 0);
9175 assert_eq!(*sw_ang, 5400000);
9176 }
9177 other => panic!("expected ArcTo, got {:?}", other),
9178 }
9179 }
9180
9181 #[test]
9183 fn parse_custom_geometry_fill_stroke() {
9184 let xml = r#"<a:custGeom>
9185<a:avLst/>
9186<a:fill>norm</a:fill>
9187<a:stroke>none</a:stroke>
9188<a:pathLst>
9189<a:path w="10" h="10">
9190<a:moveTo><a:pt x="0" y="0"/></a:moveTo>
9191</a:path>
9192</a:pathLst>
9193</a:custGeom>"#;
9194 let geom = parse_custom_geometry(xml).expect("parse ok");
9195 assert_eq!(geom.fill.as_deref(), Some("norm"));
9196 assert_eq!(geom.stroke.as_deref(), Some("none"));
9197 }
9198
9199 #[test]
9201 fn custom_geometry_round_trip() {
9202 use crate::oxml::writer::XmlWriter;
9203
9204 let original = CustomGeometry {
9205 fill: Some("norm".to_string()),
9206 stroke: None,
9207 rect: Some(GeomRect {
9208 l: "0".to_string(),
9209 t: "0".to_string(),
9210 r: "100".to_string(),
9211 b: "100".to_string(),
9212 }),
9213 path_list: vec![Path {
9214 width: 100,
9215 height: 100,
9216 fill: Some("norm".to_string()),
9217 stroke: None,
9218 segments: vec![
9219 PathSegment::MoveTo { x: 0, y: 0 },
9220 PathSegment::LineTo { x: 100, y: 0 },
9221 PathSegment::LineTo { x: 100, y: 100 },
9222 PathSegment::LineTo { x: 0, y: 100 },
9223 PathSegment::Close,
9224 ],
9225 }],
9226 };
9227 let mut w = XmlWriter::new();
9229 original.write_xml(&mut w);
9230 let xml = w.buf.clone();
9231 let parsed = parse_custom_geometry(&xml).expect("parse ok");
9233 assert_eq!(parsed.fill, original.fill);
9234 assert_eq!(parsed.stroke, original.stroke);
9235 assert_eq!(parsed.rect, original.rect);
9236 assert_eq!(parsed.path_list.len(), 1);
9237 let p0 = &parsed.path_list[0];
9238 assert_eq!(p0.width, 100);
9239 assert_eq!(p0.height, 100);
9240 assert_eq!(p0.fill.as_deref(), Some("norm"));
9241 assert_eq!(p0.segments.len(), 5);
9242 match &p0.segments[0] {
9244 PathSegment::MoveTo { x, y } => {
9245 assert_eq!(*x, 0);
9246 assert_eq!(*y, 0);
9247 }
9248 other => panic!("expected MoveTo, got {:?}", other),
9249 }
9250 match &p0.segments[4] {
9252 PathSegment::Close => {}
9253 other => panic!("expected Close, got {:?}", other),
9254 }
9255 }
9256
9257 #[test]
9259 fn parse_sp_with_custgeom() {
9260 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9261<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9262 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9263 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9264 <p:cSld>
9265 <p:spTree>
9266 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9267 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9268 <p:sp>
9269 <p:nvSpPr>
9270 <p:cNvPr id="10" name="Freeform 1"/>
9271 <p:cNvSpPr/>
9272 <p:nvPr/>
9273 </p:nvSpPr>
9274 <p:spPr>
9275 <a:xfrm>
9276 <a:off x="100" y="100"/>
9277 <a:ext cx="200" cy="200"/>
9278 </a:xfrm>
9279 <a:custGeom>
9280 <a:avLst/>
9281 <a:pathLst>
9282 <a:path w="200" h="200">
9283 <a:moveTo><a:pt x="0" y="0"/></a:moveTo>
9284 <a:lnTo><a:pt x="200" y="0"/></a:lnTo>
9285 <a:lnTo><a:pt x="100" y="200"/></a:lnTo>
9286 <a:close/>
9287 </a:path>
9288 </a:pathLst>
9289 </a:custGeom>
9290 </p:spPr>
9291 </p:sp>
9292 </p:spTree>
9293 </p:cSld>
9294 <p:clrMapOvr/>
9295</p:sld>"#;
9296 let sld = parse_sld(xml).expect("parse ok");
9297 assert_eq!(sld.shapes.len(), 1);
9298 match &sld.shapes[0] {
9299 OxmlSlideShape::Sp(sp) => {
9300 let geom = sp
9302 .properties
9303 .geometry
9304 .as_ref()
9305 .expect("geometry should exist");
9306 match geom {
9307 Geometry::Custom(cg) => {
9308 assert_eq!(cg.path_list.len(), 1);
9309 let p = &cg.path_list[0];
9310 assert_eq!(p.width, 200);
9311 assert_eq!(p.height, 200);
9312 assert_eq!(p.segments.len(), 4);
9313 match &p.segments[0] {
9314 PathSegment::MoveTo { x, y } => {
9315 assert_eq!(*x, 0);
9316 assert_eq!(*y, 0);
9317 }
9318 other => panic!("expected MoveTo, got {:?}", other),
9319 }
9320 }
9321 other => panic!("expected Custom geometry, got {:?}", other),
9322 }
9323 }
9324 other => panic!("expected Sp, got {:?}", other),
9325 }
9326 }
9327
9328 #[test]
9330 fn geometry_preset_write_xml() {
9331 use crate::oxml::simpletypes::PresetGeometry;
9332 use crate::oxml::writer::XmlWriter;
9333
9334 let geom = Geometry::preset(PresetGeometry::Rectangle);
9335 let mut w = XmlWriter::new();
9336 geom.write_xml(&mut w);
9337 let xml = w.buf.clone();
9338 assert!(xml.contains("<a:prstGeom prst=\"rect\">"), "xml: {}", xml);
9339 assert!(xml.contains("<a:avLst/>"), "xml: {}", xml);
9340 assert!(xml.contains("</a:prstGeom>"), "xml: {}", xml);
9341 }
9342
9343 #[test]
9345 fn geometry_custom_write_xml() {
9346 use crate::oxml::writer::XmlWriter;
9347
9348 let geom = Geometry::Custom(CustomGeometry {
9349 fill: None,
9350 stroke: None,
9351 rect: None,
9352 path_list: vec![Path {
9353 width: 50,
9354 height: 50,
9355 fill: None,
9356 stroke: None,
9357 segments: vec![
9358 PathSegment::MoveTo { x: 0, y: 0 },
9359 PathSegment::LineTo { x: 50, y: 50 },
9360 PathSegment::Close,
9361 ],
9362 }],
9363 });
9364 let mut w = XmlWriter::new();
9365 geom.write_xml(&mut w);
9366 let xml = w.buf.clone();
9367 assert!(xml.contains("<a:custGeom>"), "xml: {}", xml);
9368 assert!(xml.contains("<a:avLst/>"), "xml: {}", xml);
9369 assert!(xml.contains("<a:pathLst>"), "xml: {}", xml);
9370 assert!(xml.contains("<a:path w=\"50\" h=\"50\">"), "xml: {}", xml);
9371 assert!(xml.contains("<a:moveTo>"), "xml: {}", xml);
9372 assert!(xml.contains("<a:lnTo>"), "xml: {}", xml);
9373 assert!(xml.contains("<a:close/>"), "xml: {}", xml);
9374 }
9375
9376 #[test]
9382 fn round_trip_prst_geom_with_adjustments() {
9383 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9384<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9385 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9386 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9387 <p:cSld><p:spTree>
9388 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9389 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9390 <p:sp>
9391 <p:nvSpPr><p:cNvPr id="2" name="RoundRect"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9392 <p:spPr>
9393 <a:xfrm><a:off x="100000" y="100000"/><a:ext cx="500000" cy="300000"/></a:xfrm>
9394 <a:prstGeom prst="roundRect">
9395 <a:avLst>
9396 <a:gd name="adj" fmla="val 16667"/>
9397 </a:avLst>
9398 </a:prstGeom>
9399 </p:spPr>
9400 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9401 </p:sp>
9402 </p:spTree></p:cSld>
9403</p:sld>"#;
9404 let sld = parse_sld(xml).expect("parse ok");
9405 let sp = sld
9406 .shapes
9407 .iter()
9408 .find_map(|s| {
9409 if let OxmlSlideShape::Sp(sp) = s {
9410 Some(sp)
9411 } else {
9412 None
9413 }
9414 })
9415 .expect("应有 Sp");
9416 match &sp.properties.geometry {
9418 Some(Geometry::Preset(prst, adjustments)) => {
9419 assert_eq!(*prst, PresetGeometry::RoundRectangle);
9420 assert_eq!(adjustments.len(), 1, "应有 1 个调整值");
9421 let adj = &adjustments[0];
9422 assert_eq!(adj.name, "adj");
9423 assert_eq!(adj.raw_value, 16667);
9424 assert!((adj.effective_value() - 0.16667).abs() < 1e-6);
9425 }
9426 other => panic!("期望 Preset 几何,得到 {:?}", other),
9427 }
9428 let mut w = crate::oxml::writer::XmlWriter::new();
9430 sp.properties.geometry.as_ref().unwrap().write_xml(&mut w);
9431 let out = &w.buf;
9432 assert!(
9433 out.contains("<a:prstGeom prst=\"roundRect\">"),
9434 "xml: {}",
9435 out
9436 );
9437 assert!(out.contains("<a:avLst>"), "xml: {}", out);
9438 assert!(
9439 out.contains("<a:gd name=\"adj\" fmla=\"val 16667\"/>"),
9440 "xml: {}",
9441 out
9442 );
9443 assert!(out.contains("</a:prstGeom>"), "xml: {}", out);
9444 }
9445
9446 #[test]
9451 fn round_trip_prst_geom_with_multiple_adjustments() {
9452 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9453<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9454 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9455 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9456 <p:cSld><p:spTree>
9457 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9458 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9459 <p:sp>
9460 <p:nvSpPr><p:cNvPr id="3" name="MultiAdj"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9461 <p:spPr>
9462 <a:xfrm><a:off x="0" y="0"/><a:ext cx="1000000" cy="500000"/></a:xfrm>
9463 <a:prstGeom prst="rect">
9464 <a:avLst>
9465 <a:gd name="adj1" fmla="val 50000"/>
9466 <a:gd name="adj2" fmla="val 25000"/>
9467 </a:avLst>
9468 </a:prstGeom>
9469 </p:spPr>
9470 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9471 </p:sp>
9472 </p:spTree></p:cSld>
9473</p:sld>"#;
9474 let sld = parse_sld(xml).expect("parse ok");
9475 let sp = sld
9476 .shapes
9477 .iter()
9478 .find_map(|s| {
9479 if let OxmlSlideShape::Sp(sp) = s {
9480 Some(sp)
9481 } else {
9482 None
9483 }
9484 })
9485 .expect("应有 Sp");
9486 match &sp.properties.geometry {
9487 Some(Geometry::Preset(_, adjustments)) => {
9488 assert_eq!(adjustments.len(), 2, "应有 2 个调整值");
9489 assert_eq!(adjustments[0].name, "adj1");
9490 assert_eq!(adjustments[0].raw_value, 50000);
9491 assert_eq!(adjustments[1].name, "adj2");
9492 assert_eq!(adjustments[1].raw_value, 25000);
9493 }
9494 other => panic!("期望 Preset 几何,得到 {:?}", other),
9495 }
9496 let mut w = crate::oxml::writer::XmlWriter::new();
9498 sp.properties.geometry.as_ref().unwrap().write_xml(&mut w);
9499 let out = &w.buf;
9500 assert!(
9501 out.contains("<a:gd name=\"adj1\" fmla=\"val 50000\"/>"),
9502 "xml: {}",
9503 out
9504 );
9505 assert!(
9506 out.contains("<a:gd name=\"adj2\" fmla=\"val 25000\"/>"),
9507 "xml: {}",
9508 out
9509 );
9510 }
9511
9512 #[test]
9514 fn parse_prst_geom_with_empty_av_lst() {
9515 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9516<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9517 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9518 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9519 <p:cSld><p:spTree>
9520 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9521 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9522 <p:sp>
9523 <p:nvSpPr><p:cNvPr id="4" name="EmptyAdj"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9524 <p:spPr>
9525 <a:xfrm><a:off x="0" y="0"/><a:ext cx="1000000" cy="500000"/></a:xfrm>
9526 <a:prstGeom prst="rect">
9527 <a:avLst/>
9528 </a:prstGeom>
9529 </p:spPr>
9530 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9531 </p:sp>
9532 </p:spTree></p:cSld>
9533</p:sld>"#;
9534 let sld = parse_sld(xml).expect("parse ok");
9535 let sp = sld
9536 .shapes
9537 .iter()
9538 .find_map(|s| {
9539 if let OxmlSlideShape::Sp(sp) = s {
9540 Some(sp)
9541 } else {
9542 None
9543 }
9544 })
9545 .expect("应有 Sp");
9546 match &sp.properties.geometry {
9547 Some(Geometry::Preset(prst, adjustments)) => {
9548 assert_eq!(*prst, PresetGeometry::Rectangle);
9549 assert!(adjustments.is_empty(), "空 avLst 应解析为空列表");
9550 }
9551 other => panic!("期望 Preset 几何,得到 {:?}", other),
9552 }
9553 }
9554
9555 #[test]
9557 fn parse_prst_geom_without_av_lst() {
9558 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9559<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9560 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9561 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
9562 <p:cSld><p:spTree>
9563 <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
9564 <p:grpSpPr><a:xfrm/></p:grpSpPr>
9565 <p:sp>
9566 <p:nvSpPr><p:cNvPr id="5" name="NoAdj"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
9567 <p:spPr>
9568 <a:xfrm><a:off x="0" y="0"/><a:ext cx="1000000" cy="500000"/></a:xfrm>
9569 <a:prstGeom prst="rect"/>
9570 </p:spPr>
9571 <p:txBody><a:bodyPr/><a:p/></p:txBody>
9572 </p:sp>
9573 </p:spTree></p:cSld>
9574</p:sld>"#;
9575 let sld = parse_sld(xml).expect("parse ok");
9576 let sp = sld
9577 .shapes
9578 .iter()
9579 .find_map(|s| {
9580 if let OxmlSlideShape::Sp(sp) = s {
9581 Some(sp)
9582 } else {
9583 None
9584 }
9585 })
9586 .expect("应有 Sp");
9587 match &sp.properties.geometry {
9588 Some(Geometry::Preset(prst, adjustments)) => {
9589 assert_eq!(*prst, PresetGeometry::Rectangle);
9590 assert!(adjustments.is_empty(), "无 avLst 应解析为空列表");
9591 }
9592 other => panic!("期望 Preset 几何,得到 {:?}", other),
9593 }
9594 }
9595
9596 #[test]
9598 fn parse_fmla_val_formats() {
9599 assert_eq!(parse_fmla_val("val 16667"), Some(16667));
9601 assert_eq!(parse_fmla_val("val16667"), Some(16667));
9603 assert_eq!(parse_fmla_val(" val 16667 "), Some(16667));
9605 assert_eq!(parse_fmla_val("val -5000"), Some(-5000));
9607 assert_eq!(parse_fmla_val("val abc"), None);
9609 assert_eq!(parse_fmla_val("*/ adj1 100000 50000"), None);
9611 assert_eq!(parse_fmla_val(""), None);
9613 }
9614
9615 #[test]
9622 fn parse_graphic_frame_with_smartart() {
9623 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9624<p:graphicFrame xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
9625 xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
9626 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
9627 xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram">
9628 <p:nvGraphicFramePr>
9629 <p:cNvPr id="300" name="SmartArt 1"/>
9630 <p:cNvGraphicFramePr/>
9631 <p:nvPr/>
9632 </p:nvGraphicFramePr>
9633 <p:xfrm>
9634 <a:off x="457200" y="1828800"/>
9635 <a:ext cx="8229600" cy="1143000"/>
9636 </p:xfrm>
9637 <a:graphic>
9638 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/diagram">
9639 <dgm:relIds r:dm="rIdDm1" r:lo="rIdLo1" r:qs="rIdQs1" r:cs="rIdCs1"/>
9640 </a:graphicData>
9641 </a:graphic>
9642</p:graphicFrame>"#;
9643 let frame = parse_graphic_frame(xml).expect("parse ok");
9644 assert_eq!(frame.id, 300);
9645 assert_eq!(frame.name, "SmartArt 1");
9646 match &frame.graphic {
9647 crate::oxml::shape::Graphic::SmartArt(s) => {
9648 assert!(
9650 s.raw_xml.contains("<a:graphicData"),
9651 "raw_xml 应包含 graphicData 外壳,实际: {}",
9652 s.raw_xml
9653 );
9654 assert!(
9655 s.raw_xml.contains(
9656 "uri=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\""
9657 ),
9658 "raw_xml 应包含 diagram uri,实际: {}",
9659 s.raw_xml
9660 );
9661 assert_eq!(s.dm_rid.as_deref(), Some("rIdDm1"));
9663 assert_eq!(s.lo_rid.as_deref(), Some("rIdLo1"));
9664 assert_eq!(s.qs_rid.as_deref(), Some("rIdQs1"));
9665 assert_eq!(s.cs_rid.as_deref(), Some("rIdCs1"));
9666 }
9667 other => panic!("期望 SmartArt,得到 {:?}", other),
9668 }
9669 }
9670
9671 #[test]
9673 fn parse_smartart_rel_ids_formats() {
9674 let raw = r#"<a:graphicData uri=".../diagram"><dgm:relIds r:dm="rId1" r:lo="rId2" r:qs="rId3" r:cs="rId4"/></a:graphicData>"#;
9676 let (dm, lo, qs, cs) = parse_smartart_rel_ids(raw);
9677 assert_eq!(dm.as_deref(), Some("rId1"));
9678 assert_eq!(lo.as_deref(), Some("rId2"));
9679 assert_eq!(qs.as_deref(), Some("rId3"));
9680 assert_eq!(cs.as_deref(), Some("rId4"));
9681
9682 let raw_partial =
9684 r#"<a:graphicData uri=".../diagram"><dgm:relIds r:dm="rId1"/></a:graphicData>"#;
9685 let (dm, lo, qs, cs) = parse_smartart_rel_ids(raw_partial);
9686 assert_eq!(dm.as_deref(), Some("rId1"));
9687 assert!(lo.is_none(), "lo 应为 None");
9688 assert!(qs.is_none(), "qs 应为 None");
9689 assert!(cs.is_none(), "cs 应为 None");
9690
9691 let raw_empty = r#"<a:graphicData uri=".../diagram"></a:graphicData>"#;
9693 let (dm, lo, qs, cs) = parse_smartart_rel_ids(raw_empty);
9694 assert!(dm.is_none() && lo.is_none() && qs.is_none() && cs.is_none());
9695 }
9696}