1use std::fmt;
2use std::io::Write;
3
4use oxml_core::OxmlError;
5use oxml_core::raw_xml::{capture_element, capture_empty_element};
6use oxml_core::xml::{local_name, matches_local_name};
7use quick_xml::events::{BytesEnd, BytesStart, Event};
8use quick_xml::{Reader, Writer, XmlVersion};
9
10use crate::effect::{
11 CT_EffectList, EffectError, raw_contains_placeholder_color, raw_is_effect_dag,
12};
13use crate::fill::{Fill, FillError};
14use crate::geometry::{CT_CustomGeometry2D, CT_PresetGeometry2D, GeometryError};
15use crate::line::{CT_LineProperties, LineError};
16use crate::namespace::{
17 property_is_drawing, property_namespace_scope, property_reject_writer_conflicts,
18};
19use crate::order::OrderedRawChildren;
20use crate::xfrm::{CT_Transform2D, TransformError};
21
22#[derive(Debug)]
24pub enum ShapePropertiesError {
25 Xml(OxmlError),
26 Transform(TransformError),
27 Geometry(GeometryError),
28 Fill(FillError),
29 Line(LineError),
30 Effect(EffectError),
31 UnexpectedElement(String),
32}
33
34impl fmt::Display for ShapePropertiesError {
35 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Xml(error) => error.fmt(formatter),
38 Self::Transform(error) => error.fmt(formatter),
39 Self::Geometry(error) => error.fmt(formatter),
40 Self::Fill(error) => error.fmt(formatter),
41 Self::Line(error) => error.fmt(formatter),
42 Self::Effect(error) => error.fmt(formatter),
43 Self::UnexpectedElement(element) => {
44 write!(
45 formatter,
46 "unexpected DrawingML shape-properties element: {element}"
47 )
48 }
49 }
50 }
51}
52
53impl std::error::Error for ShapePropertiesError {
54 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55 match self {
56 Self::Xml(error) => Some(error),
57 Self::Transform(error) => Some(error),
58 Self::Geometry(error) => Some(error),
59 Self::Fill(error) => Some(error),
60 Self::Line(error) => Some(error),
61 Self::Effect(error) => Some(error),
62 Self::UnexpectedElement(_) => None,
63 }
64 }
65}
66
67impl From<OxmlError> for ShapePropertiesError {
68 fn from(error: OxmlError) -> Self {
69 Self::Xml(error)
70 }
71}
72
73impl From<TransformError> for ShapePropertiesError {
74 fn from(error: TransformError) -> Self {
75 Self::Transform(error)
76 }
77}
78
79impl From<GeometryError> for ShapePropertiesError {
80 fn from(error: GeometryError) -> Self {
81 Self::Geometry(error)
82 }
83}
84
85impl From<FillError> for ShapePropertiesError {
86 fn from(error: FillError) -> Self {
87 Self::Fill(error)
88 }
89}
90
91impl From<LineError> for ShapePropertiesError {
92 fn from(error: LineError) -> Self {
93 Self::Line(error)
94 }
95}
96
97impl From<EffectError> for ShapePropertiesError {
98 fn from(error: EffectError) -> Self {
99 Self::Effect(error)
100 }
101}
102
103pub type Result<T> = std::result::Result<T, ShapePropertiesError>;
104
105#[allow(non_camel_case_types)]
107#[derive(Clone, Debug, Default)]
108pub struct CT_ShapeProperties {
109 pub transform: Option<CT_Transform2D>,
110 pub custom_geometry: Option<CT_CustomGeometry2D>,
111 pub preset_geometry: Option<CT_PresetGeometry2D>,
112 pub fill: Option<Fill>,
113 pub line: Option<CT_LineProperties>,
114 pub effects: Option<CT_EffectList>,
115 raw_attributes: Vec<(String, String)>,
116 raw_children: OrderedRawChildren,
117 source_namespaces: Vec<(String, String)>,
118 source_xml: Option<Vec<u8>>,
119 source_projection: Vec<u8>,
120}
121
122impl PartialEq for CT_ShapeProperties {
123 fn eq(&self, other: &Self) -> bool {
124 self.transform == other.transform
125 && self.custom_geometry == other.custom_geometry
126 && self.preset_geometry == other.preset_geometry
127 && self.fill == other.fill
128 && self.line == other.line
129 && self.effects == other.effects
130 && self.raw_attributes == other.raw_attributes
131 && self.raw_children == other.raw_children
132 }
133}
134
135impl CT_ShapeProperties {
136 pub fn with_inherited_defaults(&self, defaults: &Self) -> Result<Self> {
138 let scoped_defaults = defaults.scoped_inheritance_defaults()?;
139 let defaults = &scoped_defaults;
140 let mut result = self.scoped_inheritance_defaults()?;
141 for (name, value) in &defaults.raw_attributes {
142 if name == "xmlns" || name.starts_with("xmlns:") {
143 continue;
144 }
145 let identity = shape_property_attribute_identity(name, &defaults.source_namespaces)?;
146 let mut existing = None;
147 for (key, local) in &self.raw_attributes {
148 if key == "xmlns" || key.starts_with("xmlns:") {
149 continue;
150 }
151 if shape_property_attribute_identity(key, &self.source_namespaces)? == identity {
152 existing = Some(local);
153 break;
154 }
155 }
156 if let Some(local) = existing {
157 if name != "bwMode"
158 && (local != value
159 || !shape_property_context_equal(
160 &self.source_namespaces,
161 &defaults.source_namespaces,
162 ))
163 {
164 return Err(OxmlError::InvalidValue(
165 "conflicting inherited opaque shape-property attribute".into(),
166 )
167 .into());
168 }
169 } else {
170 if name != "bwMode"
171 && !shape_property_context_equal(
172 &self.source_namespaces,
173 &defaults.source_namespaces,
174 )
175 {
176 return Err(OxmlError::InvalidValue(
177 "unrepresentable inherited shape-property namespace authority".into(),
178 )
179 .into());
180 }
181 result.raw_attributes.push((name.clone(), value.clone()));
182 }
183 }
184
185 match (&mut result.transform, &defaults.transform) {
186 (Some(local), Some(parent)) => {
187 if local.offset.is_none() {
188 local.offset = parent.offset;
189 }
190 if local.extent.is_none() {
191 local.extent = parent.extent;
192 }
193 if local.child_offset.is_none() {
194 local.child_offset = parent.child_offset;
195 }
196 if local.child_extent.is_none() {
197 local.child_extent = parent.child_extent;
198 }
199 }
200 (local @ None, Some(parent)) => *local = Some(parent.clone()),
201 _ => {}
202 }
203 if result.custom_geometry.is_none() && result.preset_geometry.is_none() {
204 result.custom_geometry = defaults.custom_geometry.clone();
205 result.preset_geometry = defaults.preset_geometry.clone();
206 }
207 if result.line.is_none() {
208 result.line = defaults.line.clone();
209 }
210 let mut occupied = Vec::new();
211 for boundary in 0..=8 {
212 for raw in self.raw_children.at(boundary) {
213 if let Some(slot) = inherited_shape_property_slot(raw, &self.source_namespaces)? {
214 occupied.push(slot);
215 }
216 }
217 }
218 if result.fill.is_none() && !occupied.contains(&2) {
219 result.fill = defaults.fill.clone();
220 }
221 if result.effects.is_none() && !occupied.contains(&4) {
222 result.effects = defaults.effects.clone();
223 }
224 if self.transform.is_some() {
225 occupied.push(0);
226 }
227 if self.custom_geometry.is_some() || self.preset_geometry.is_some() {
228 occupied.push(1);
229 }
230 if self.fill.is_some() {
231 occupied.push(2);
232 }
233 if self.line.is_some() {
234 occupied.push(3);
235 }
236 if self.effects.is_some() {
237 occupied.push(4);
238 }
239 for boundary in 0..=8 {
240 for raw in defaults.raw_children.at(boundary) {
241 if let Some(slot) = inherited_shape_property_slot(raw, &defaults.source_namespaces)?
242 {
243 if occupied.contains(&slot) {
244 continue;
245 }
246 occupied.push(slot);
247 } else if shape_property_context_equal(
248 &self.source_namespaces,
249 &defaults.source_namespaces,
250 ) && (0..=8).any(|at| self.raw_children.at(at).any(|local| local == raw))
251 {
252 continue;
253 }
254 result.raw_children.push(
255 boundary,
256 close_inherited_shape_property(raw, &defaults.source_namespaces)?,
257 );
258 }
259 }
260 let xml = result.to_xml()?;
261 let reparsed = Self::from_xml_with_inherited_namespaces(&xml, &self.source_namespaces)?;
262 if reparsed.transform != result.transform
263 || reparsed.fill != result.fill
264 || reparsed.line != result.line
265 {
266 return Err(OxmlError::InvalidValue(
267 "inherited shape properties changed during reparse".into(),
268 )
269 .into());
270 }
271 Ok(reparsed)
275 }
276
277 fn scoped_inheritance_defaults(&self) -> Result<Self> {
280 let projection = self.to_xml()?;
281 let original = self.source_xml.as_deref().unwrap_or(&projection);
282 let unchanged = projection == self.source_projection;
283 if !unchanged && self.source_xml.is_some() {
284 let mut reader = Reader::from_reader(original);
285 let mut depth = 0;
286 loop {
287 match reader.read_event().map_err(OxmlError::from)? {
288 event @ (Event::Start(_) | Event::Empty(_)) => {
289 let empty = matches!(&event, Event::Empty(_));
290 let start = match &event {
291 Event::Start(start) | Event::Empty(start) => start,
292 _ => unreachable!(),
293 };
294 if depth > 0
295 && capture_raw_attributes(start)?
296 .iter()
297 .any(|(name, _)| name == "xmlns" || name.starts_with("xmlns:"))
298 {
299 return Err(OxmlError::InvalidValue(
300 "edited inherited shape properties have nested namespace authority"
301 .into(),
302 )
303 .into());
304 }
305 if !empty {
306 depth += 1;
307 }
308 }
309 Event::End(_) => depth -= 1,
310 Event::Eof => break,
311 _ => {}
312 }
313 }
314 }
315 let input = if unchanged { original } else { &projection };
316 let closed = close_inherited_typed_shape_groups(input, &self.source_namespaces)?;
317 let represented =
318 Self::from_xml_with_inherited_namespaces(&closed, &self.source_namespaces)?;
319 let mut writer_scope = self.source_namespaces.clone();
320 if !writer_scope.iter().any(|(prefix, _)| prefix == "a") {
321 writer_scope.push((
322 "a".into(),
323 "http://schemas.openxmlformats.org/drawingml/2006/main".into(),
324 ));
325 }
326 let source_metadata = inherited_shape_metadata(original, &self.source_namespaces)?;
327 let represented_metadata = inherited_shape_metadata(&represented.to_xml()?, &writer_scope)?;
328 if let Some((key, _)) = source_metadata
329 .iter()
330 .find(|(key, count)| represented_metadata.get(*key).copied().unwrap_or(0) < **count)
331 {
332 return Err(OxmlError::InvalidValue(format!(
333 "unrepresentable opaque metadata in inherited shape properties at {key}"
334 ))
335 .into());
336 }
337 Ok(represented)
338 }
339
340 pub fn from_xml(xml: &[u8]) -> Result<Self> {
342 Self::from_xml_with_inherited_namespaces(xml, &[])
343 }
344
345 pub fn from_xml_with_inherited_namespaces(
347 xml: &[u8],
348 inherited: &[(String, String)],
349 ) -> Result<Self> {
350 let mut reader = Reader::from_reader(xml);
351 let mut buffer = Vec::new();
352 loop {
353 match reader
354 .read_event_into(&mut buffer)
355 .map_err(OxmlError::from)?
356 {
357 Event::Start(element) if matches_local_name(element.name().as_ref(), b"spPr") => {
358 let scope = property_namespace_scope(&element, inherited)?;
359 property_reject_writer_conflicts(&scope)?;
360 let mut properties = Self::from_element(&mut reader, &element, &scope)?;
361 properties.source_xml = Some(xml.to_vec());
362 properties.source_projection = properties.to_xml()?;
363 return Ok(properties);
364 }
365 Event::Empty(element) if matches_local_name(element.name().as_ref(), b"spPr") => {
366 property_reject_writer_conflicts(&property_namespace_scope(
367 &element, inherited,
368 )?)?;
369 let mut properties = Self::from_start(&element)?;
370 properties.source_namespaces = property_namespace_scope(&element, inherited)?;
371 properties.source_xml = Some(xml.to_vec());
372 properties.source_projection = properties.to_xml()?;
373 return Ok(properties);
374 }
375 Event::Start(element) | Event::Empty(element) => {
376 return Err(ShapePropertiesError::UnexpectedElement(element_name(
377 &element,
378 )));
379 }
380 Event::Eof => {
381 return Err(ShapePropertiesError::Xml(OxmlError::MissingElement(
382 "DrawingML shape properties".to_owned(),
383 )));
384 }
385 _ => {}
386 }
387 buffer.clear();
388 }
389 }
390
391 fn from_start(start: &BytesStart<'_>) -> Result<Self> {
392 Ok(Self {
393 raw_attributes: capture_raw_attributes(start)?,
394 ..Self::default()
395 })
396 }
397
398 fn from_element(
399 reader: &mut Reader<&[u8]>,
400 start: &BytesStart<'_>,
401 namespaces: &[(String, String)],
402 ) -> Result<Self> {
403 let mut properties = Self::from_start(start)?;
404 properties.source_namespaces = namespaces.to_vec();
405 let mut boundary = 0;
406 let mut buffer = Vec::new();
407
408 loop {
409 match reader
410 .read_event_into(&mut buffer)
411 .map_err(OxmlError::from)?
412 {
413 Event::Start(element) => {
414 let name = local_name(element.name().as_ref()).to_vec();
415 let owned = property_is_drawing(&element, namespaces)?;
416 let child_scope = property_namespace_scope(&element, namespaces)?;
417 let raw = capture_element(reader, &element)?;
418 if owned && properties.parse_modelled(&name, &raw, &child_scope)? {
419 boundary = boundary.max(modelled_boundary(&name));
420 } else {
421 properties.raw_children.push(
422 if owned {
423 boundary.max(raw_boundary_before(&name))
424 } else {
425 boundary
426 },
427 raw,
428 );
429 if owned {
430 boundary = boundary.max(raw_boundary_after(&name));
431 }
432 }
433 }
434 Event::Empty(element) => {
435 let name = local_name(element.name().as_ref()).to_vec();
436 let owned = property_is_drawing(&element, namespaces)?;
437 let child_scope = property_namespace_scope(&element, namespaces)?;
438 let raw = capture_empty_element(&element)?;
439 if owned && properties.parse_modelled(&name, &raw, &child_scope)? {
440 boundary = boundary.max(modelled_boundary(&name));
441 } else {
442 properties.raw_children.push(
443 if owned {
444 boundary.max(raw_boundary_before(&name))
445 } else {
446 boundary
447 },
448 raw,
449 );
450 if owned {
451 boundary = boundary.max(raw_boundary_after(&name));
452 }
453 }
454 }
455 Event::End(element) if matches_local_name(element.name().as_ref(), b"spPr") => {
456 break;
457 }
458 Event::Eof => return Err(missing_end()),
459 _ => {}
460 }
461 buffer.clear();
462 }
463 Ok(properties)
464 }
465
466 fn parse_modelled(
467 &mut self,
468 name: &[u8],
469 raw: &[u8],
470 namespaces: &[(String, String)],
471 ) -> Result<bool> {
472 if matches!(
473 name,
474 b"xfrm" | b"custGeom" | b"prstGeom" | b"ln" | b"effectLst"
475 ) || is_fill(name)
476 {
477 property_reject_writer_conflicts(namespaces)?;
478 }
479 match name {
480 b"xfrm" if self.transform.is_none() => {
481 self.transform = Some(CT_Transform2D::from_xml(raw)?);
482 }
483 b"custGeom" if self.custom_geometry.is_none() => {
484 self.custom_geometry = Some(CT_CustomGeometry2D::from_xml(raw)?);
485 }
486 b"prstGeom" if self.preset_geometry.is_none() => {
487 self.preset_geometry = Some(CT_PresetGeometry2D::from_xml(raw)?);
488 }
489 name if is_fill(name) && self.fill.is_none() => {
490 self.fill = Some(Fill::from_xml(raw)?);
491 }
492 b"ln" if self.line.is_none() => {
493 self.line = Some(CT_LineProperties::from_xml_with_inherited_namespaces(
494 raw, namespaces,
495 )?);
496 }
497 b"effectLst" if self.effects.is_none() => {
498 self.effects = Some(CT_EffectList::from_xml(raw)?);
499 }
500 _ => return Ok(false),
501 }
502 Ok(true)
503 }
504
505 pub fn to_xml(&self) -> Result<Vec<u8>> {
507 let mut writer = Writer::new(Vec::new());
508 self.write_xml(&mut writer)?;
509 Ok(writer.into_inner())
510 }
511
512 pub fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
514 self.write_xml_as(writer, "a:spPr")
515 }
516
517 pub fn write_xml_as<W: Write>(&self, writer: &mut Writer<W>, name: &str) -> Result<()> {
519 let mut start = BytesStart::new(name);
520 push_raw_attributes(&mut start, &self.raw_attributes);
521 if self.transform.is_none()
522 && self.custom_geometry.is_none()
523 && self.preset_geometry.is_none()
524 && self.fill.is_none()
525 && self.line.is_none()
526 && self.effects.is_none()
527 && self.raw_children.is_empty()
528 {
529 writer
530 .write_event(Event::Empty(start))
531 .map_err(OxmlError::from)?;
532 return Ok(());
533 }
534
535 writer
536 .write_event(Event::Start(start))
537 .map_err(OxmlError::from)?;
538 emit_raw(writer, self.raw_children.at(0))?;
539 if let Some(transform) = &self.transform {
540 transform.write_xml(writer)?;
541 }
542 emit_raw(writer, self.raw_children.at(1))?;
543 if let Some(geometry) = &self.custom_geometry {
544 writer
545 .get_mut()
546 .write_all(&geometry.to_xml()?)
547 .map_err(OxmlError::from)?;
548 } else if let Some(geometry) = &self.preset_geometry {
549 writer
550 .get_mut()
551 .write_all(&geometry.to_xml()?)
552 .map_err(OxmlError::from)?;
553 }
554 emit_raw(writer, self.raw_children.at(2))?;
555 if let Some(fill) = &self.fill {
556 fill.write_xml(writer)?;
557 }
558 emit_raw(writer, self.raw_children.at(3))?;
559 if let Some(line) = &self.line {
560 line.write_xml(writer)?;
561 }
562 emit_raw(writer, self.raw_children.at(4))?;
563 if let Some(effects) = &self.effects {
564 effects.write_xml(writer)?;
565 }
566 for boundary in 5..=8 {
567 emit_raw(writer, self.raw_children.at(boundary))?;
568 }
569 writer
570 .write_event(Event::End(BytesEnd::new(name)))
571 .map_err(OxmlError::from)?;
572 Ok(())
573 }
574
575 pub fn raw_children(&self) -> &OrderedRawChildren {
576 &self.raw_children
577 }
578
579 pub fn has_unmodelled_effect(&self) -> bool {
581 (0..=8).any(|boundary| self.raw_children.at(boundary).any(raw_is_effect_dag))
582 }
583
584 pub fn has_unmodelled_effect_placeholder_color(&self) -> bool {
586 (0..=8).any(|boundary| {
587 self.raw_children
588 .at(boundary)
589 .any(|xml| raw_is_effect_dag(xml) && raw_contains_placeholder_color(xml))
590 })
591 }
592}
593
594fn is_fill(name: &[u8]) -> bool {
595 matches!(
596 name,
597 b"noFill" | b"solidFill" | b"gradFill" | b"pattFill" | b"blipFill"
598 )
599}
600
601fn modelled_boundary(name: &[u8]) -> usize {
602 match name {
603 b"xfrm" => 1,
604 b"custGeom" | b"prstGeom" => 2,
605 name if is_fill(name) => 3,
606 b"ln" => 4,
607 b"effectLst" => 5,
608 _ => 0,
609 }
610}
611
612fn raw_boundary_after(name: &[u8]) -> usize {
613 match name {
614 b"xfrm" => 1,
615 b"custGeom" | b"prstGeom" => 2,
616 b"noFill" | b"solidFill" | b"gradFill" | b"blipFill" | b"pattFill" | b"grpFill" => 3,
617 b"ln" => 4,
618 b"effectLst" | b"effectDag" => 5,
619 b"scene3d" => 6,
620 b"sp3d" => 7,
621 b"extLst" => 8,
622 _ => 0,
623 }
624}
625
626fn raw_boundary_before(name: &[u8]) -> usize {
630 raw_boundary_after(name).saturating_sub(1)
631}
632
633fn shape_property_attribute_identity(
634 name: &str,
635 scope: &[(String, String)],
636) -> Result<(String, String)> {
637 if let Some((prefix, local)) = name.split_once(':') {
638 let uri = if prefix == "xml" {
639 "http://www.w3.org/XML/1998/namespace"
640 } else {
641 scope
642 .iter()
643 .rev()
644 .find(|(key, _)| key == prefix)
645 .map(|(_, uri)| uri.as_str())
646 .ok_or_else(|| {
647 OxmlError::InvalidValue(
648 "unbound inherited shape-property attribute prefix".into(),
649 )
650 })?
651 };
652 Ok((uri.to_owned(), local.to_owned()))
653 } else {
654 Ok((String::new(), name.to_owned()))
655 }
656}
657
658fn shape_property_context_equal(left: &[(String, String)], right: &[(String, String)]) -> bool {
659 let mut left: std::collections::BTreeMap<_, _> = left
660 .iter()
661 .map(|(prefix, uri)| (prefix.as_str(), uri.as_str()))
662 .collect();
663 let mut right: std::collections::BTreeMap<_, _> = right
664 .iter()
665 .map(|(prefix, uri)| (prefix.as_str(), uri.as_str()))
666 .collect();
667 left.entry("").or_insert("");
668 right.entry("").or_insert("");
669 left == right
670}
671
672fn inherited_shape_property_slot(
673 raw: &[u8],
674 namespaces: &[(String, String)],
675) -> Result<Option<usize>> {
676 let mut reader = Reader::from_reader(raw);
677 match reader.read_event().map_err(OxmlError::from)? {
678 Event::Start(start) | Event::Empty(start) if property_is_drawing(&start, namespaces)? => {
679 let name = local_name(start.name().as_ref()).to_vec();
680 Ok(match name.as_slice() {
681 b"xfrm" => Some(0),
682 b"custGeom" | b"prstGeom" => Some(1),
683 b"grpFill" => Some(2),
684 name if is_fill(name) => Some(2),
685 b"ln" => Some(3),
686 b"effectLst" | b"effectDag" => Some(4),
687 b"scene3d" => Some(5),
688 b"sp3d" => Some(6),
689 b"extLst" => Some(7),
690 _ => None,
691 })
692 }
693 _ => Ok(None),
694 }
695}
696
697fn inherited_shape_metadata(
702 xml: &[u8],
703 namespaces: &[(String, String)],
704) -> Result<std::collections::BTreeMap<String, usize>> {
705 let mut reader = Reader::from_reader(xml);
706 let mut scopes = vec![namespaces.to_vec()];
707 let mut paths = vec![String::new()];
708 let mut metadata = std::collections::BTreeMap::new();
709 loop {
710 match reader.read_event().map_err(OxmlError::from)? {
711 event @ (Event::Start(_) | Event::Empty(_)) => {
712 let empty = matches!(&event, Event::Empty(_));
713 let start = match &event {
714 Event::Start(start) | Event::Empty(start) => start,
715 _ => unreachable!(),
716 };
717 let scope = property_namespace_scope(start, scopes.last().unwrap())?;
718 let name = start.name();
719 let spelling = std::str::from_utf8(name.as_ref()).map_err(OxmlError::from)?;
720 let (prefix, local) = spelling.split_once(':').unwrap_or(("", spelling));
721 let uri = scope
722 .iter()
723 .find(|(key, _)| key == prefix)
724 .map_or("", |(_, uri)| uri.as_str());
725 if !prefix.is_empty() && uri.is_empty() {
726 return Err(OxmlError::InvalidValue(
727 "unbound shape-property metadata owner".into(),
728 )
729 .into());
730 }
731 let path = if paths.len() == 1 {
732 String::new()
733 } else {
734 format!(
735 "{}/{}:{uri}{}:{local}",
736 paths.last().unwrap(),
737 uri.len(),
738 local.len()
739 )
740 };
741 for (name, _) in capture_raw_attributes(start)? {
742 if name == "xmlns" || name.starts_with("xmlns:") {
743 continue;
744 }
745 let (attribute_uri, attribute_local) =
746 shape_property_attribute_identity(&name, &scope)?;
747 if uri == "http://schemas.openxmlformats.org/drawingml/2006/main"
748 && local == "xfrm"
749 && attribute_uri.is_empty()
750 && matches!(attribute_local.as_str(), "rot" | "flipH" | "flipV")
751 {
752 continue;
756 }
757 let key = format!(
758 "{path}@{}:{attribute_uri}{}:{attribute_local}",
759 attribute_uri.len(),
760 attribute_local.len()
761 );
762 *metadata.entry(key).or_insert(0) += 1;
763 }
764 if !empty {
765 scopes.push(scope);
766 paths.push(path);
767 }
768 }
769 Event::End(_) => {
770 scopes.pop();
771 paths.pop();
772 }
773 Event::Comment(text) => {
774 let key = format!("{}!comment:{:?}", paths.last().unwrap(), &*text);
775 *metadata.entry(key).or_insert(0) += 1;
776 }
777 Event::PI(text) => {
778 let key = format!("{}!pi:{:?}", paths.last().unwrap(), &*text);
779 *metadata.entry(key).or_insert(0) += 1;
780 }
781 Event::Text(text) if text.iter().any(|byte| !byte.is_ascii_whitespace()) => {
782 let key = format!("{}!text:{:?}", paths.last().unwrap(), &*text);
783 *metadata.entry(key).or_insert(0) += 1;
784 }
785 Event::CData(text) => {
786 let key = format!("{}!cdata:{:?}", paths.last().unwrap(), &*text);
787 *metadata.entry(key).or_insert(0) += 1;
788 }
789 Event::Eof => break,
790 _ => {}
791 }
792 }
793 Ok(metadata)
794}
795
796fn close_inherited_typed_shape_groups(
800 xml: &[u8],
801 namespaces: &[(String, String)],
802) -> Result<Vec<u8>> {
803 let mut reader = Reader::from_reader(xml);
804 let mut writer = Writer::new(Vec::new());
805 let mut scopes = vec![namespaces.to_vec()];
806 let mut transferring = Vec::new();
807 loop {
808 match reader.read_event().map_err(OxmlError::from)? {
809 event @ (Event::Start(_) | Event::Empty(_)) => {
810 let empty = matches!(&event, Event::Empty(_));
811 let start = match &event {
812 Event::Start(start) | Event::Empty(start) => start,
813 _ => unreachable!(),
814 };
815 let scope = property_namespace_scope(start, scopes.last().unwrap())?;
816 let transfer = transferring.last().copied().unwrap_or(false)
817 || (scopes.len() == 2
818 && property_is_drawing(start, &scope)?
819 && modelled_boundary(local_name(start.name().as_ref())) > 0);
820 if transfer {
821 let mut content = std::str::from_utf8(start.as_ref())
822 .map_err(OxmlError::from)?
823 .to_owned();
824 let attributes = capture_raw_attributes(start)?;
825 let mut closed_scope = scope.clone();
826 if !closed_scope.iter().any(|(prefix, _)| prefix.is_empty()) {
827 closed_scope.push((String::new(), String::new()));
828 }
829 for (prefix, uri) in closed_scope {
830 let name = if prefix.is_empty() {
831 "xmlns".to_owned()
832 } else {
833 format!("xmlns:{prefix}")
834 };
835 if !attributes.iter().any(|(key, _)| key == &name) {
836 content.push_str(&format!(
837 " {name}=\"{}\"",
838 quick_xml::escape::escape(&uri)
839 ));
840 }
841 }
842 let closed = BytesStart::from_content(content, start.name().as_ref().len());
843 writer
844 .write_event(if empty {
845 Event::Empty(closed)
846 } else {
847 Event::Start(closed)
848 })
849 .map_err(OxmlError::from)?;
850 } else {
851 writer.write_event(event).map_err(OxmlError::from)?;
852 }
853 if !empty {
854 scopes.push(scope);
855 transferring.push(transfer);
856 }
857 }
858 Event::End(end) => {
859 writer
860 .write_event(Event::End(end))
861 .map_err(OxmlError::from)?;
862 scopes.pop();
863 transferring.pop();
864 }
865 Event::Eof => break,
866 event => writer.write_event(event).map_err(OxmlError::from)?,
867 }
868 }
869 Ok(writer.into_inner())
870}
871
872fn close_inherited_shape_property(raw: &[u8], namespaces: &[(String, String)]) -> Result<Vec<u8>> {
873 let mut reader = Reader::from_reader(raw);
874 let (start, empty) = match reader.read_event().map_err(OxmlError::from)? {
875 Event::Start(start) => (start, false),
876 Event::Empty(start) => (start, true),
877 _ => return Ok(raw.to_vec()),
878 };
879 let mut content = std::str::from_utf8(start.as_ref())
880 .map_err(OxmlError::from)?
881 .to_owned();
882 let attributes = capture_raw_attributes(&start)?;
883 let mut scope = namespaces.to_vec();
884 if !scope.iter().any(|(prefix, _)| prefix.is_empty()) {
885 scope.push((String::new(), String::new()));
886 }
887 for (prefix, uri) in &scope {
888 let name = if prefix.is_empty() {
889 "xmlns".to_owned()
890 } else {
891 format!("xmlns:{prefix}")
892 };
893 if attributes.iter().any(|(key, _)| key == &name) {
894 continue;
895 }
896 content.push_str(&format!(" {name}=\"{}\"", quick_xml::escape::escape(uri)));
897 }
898 let name_len = start.name().as_ref().len();
899 let mut writer = Writer::new(Vec::new());
900 let closed = BytesStart::from_content(content, name_len);
901 if empty {
902 writer
903 .write_event(Event::Empty(closed))
904 .map_err(OxmlError::from)?;
905 } else {
906 writer
907 .write_event(Event::Start(closed))
908 .map_err(OxmlError::from)?;
909 writer
910 .get_mut()
911 .extend_from_slice(&raw[reader.buffer_position() as usize..]);
912 }
913 Ok(writer.into_inner())
914}
915
916fn emit_raw<'a, W: Write>(
917 writer: &mut Writer<W>,
918 children: impl Iterator<Item = &'a [u8]>,
919) -> Result<()> {
920 for child in children {
921 writer.get_mut().write_all(child).map_err(OxmlError::from)?;
922 }
923 Ok(())
924}
925
926fn capture_raw_attributes(start: &BytesStart<'_>) -> Result<Vec<(String, String)>> {
927 let mut raw = Vec::new();
928 for attribute in start.attributes() {
929 let attribute = attribute.map_err(OxmlError::from)?;
930 let name = std::str::from_utf8(attribute.key.as_ref())
931 .map_err(OxmlError::from)?
932 .to_owned();
933 let value = attribute
934 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
935 .map_err(OxmlError::from)?
936 .into_owned();
937 raw.push((name, value));
938 }
939 Ok(raw)
940}
941
942fn push_raw_attributes(start: &mut BytesStart<'_>, attributes: &[(String, String)]) {
943 for (name, value) in attributes {
944 start.push_attribute((name.as_str(), value.as_str()));
945 }
946}
947
948fn element_name(element: &BytesStart<'_>) -> String {
949 String::from_utf8_lossy(element.name().as_ref()).into_owned()
950}
951
952fn missing_end() -> ShapePropertiesError {
953 ShapePropertiesError::Xml(OxmlError::MissingElement(
954 "closing DrawingML shape properties".to_owned(),
955 ))
956}
957
958#[cfg(test)]
959mod tests {
960 use super::CT_ShapeProperties;
961
962 #[test]
963 fn shape_properties_round_trip_in_schema_order() {
964 let xml = br#"<z:spPr><x:before/><z:xfrm rot="60000"><z:off x="1" y="2"/></z:xfrm><x:afterXfrm/><z:custGeom><z:pathLst><z:path/></z:pathLst></z:custGeom><x:afterGeom/><z:solidFill><z:srgbClr val="112233"/></z:solidFill><x:afterFill/><z:ln w="12700"><z:noFill/></z:ln><x:afterLine/><z:effectLst><z:glow rad="40000"><z:srgbClr val="445566"/></z:glow></z:effectLst><x:afterEffects/></z:spPr>"#;
965 let properties = CT_ShapeProperties::from_xml(xml).unwrap();
966 assert!(properties.transform.is_some());
967 assert!(properties.custom_geometry.is_some());
968 assert!(properties.fill.is_some());
969 assert!(properties.line.is_some());
970 assert!(properties.effects.is_some());
971
972 let written = properties.to_xml().unwrap();
973 assert_eq!(written, br#"<a:spPr><x:before/><a:xfrm rot="60000"><a:off x="1" y="2"/></a:xfrm><x:afterXfrm/><a:custGeom><a:pathLst><a:path/></a:pathLst></a:custGeom><x:afterGeom/><a:solidFill><a:srgbClr val="112233"/></a:solidFill><x:afterFill/><a:ln w="12700"><a:noFill/></a:ln><x:afterLine/><a:effectLst><z:glow rad="40000"><z:srgbClr val="445566"/></z:glow></a:effectLst><x:afterEffects/></a:spPr>"#);
974 assert_eq!(CT_ShapeProperties::from_xml(&written).unwrap(), properties);
975 }
976
977 #[test]
978 fn modelled_children_added_later_precede_unmodelled_3d_and_extensions() {
979 let xml = br#"<a:spPr><a:prstGeom prst="rect"/><a:scene3d/><x:after/><a:sp3d/><a:extLst/></a:spPr>"#;
980 let mut properties = CT_ShapeProperties::from_xml(xml).unwrap();
981 assert_eq!(properties.to_xml().unwrap(), xml);
982
983 properties.fill = Some(crate::fill::Fill::from_xml(br#"<a:noFill/>"#).unwrap());
984 properties.line = Some(crate::line::CT_LineProperties::default());
985 properties.effects = Some(crate::effect::CT_EffectList::default());
986 assert_eq!(
987 properties.to_xml().unwrap(),
988 br#"<a:spPr><a:prstGeom prst="rect"/><a:noFill/><a:ln/><a:effectLst/><a:scene3d/><x:after/><a:sp3d/><a:extLst/></a:spPr>"#
989 );
990 }
991
992 #[test]
993 fn shape_property_root_attributes_round_trip_without_loss() {
994 let properties =
995 CT_ShapeProperties::from_xml(br#"<q:spPr bwMode="gray" x:future="keep & stay"/>"#)
996 .unwrap();
997 assert_eq!(
998 properties.to_xml().unwrap(),
999 br#"<a:spPr bwMode="gray" x:future="keep & stay"/>"#
1000 );
1001 }
1002
1003 #[test]
1004 fn preset_geometry_round_trips_with_unknown_children_verbatim() {
1005 let xml = br#"<p:spPr><u:before/><q:prstGeom xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main" prst="trapezoid"><u:first id="1"><u:nested/></u:first><q:avLst><u:inside/><q:gd name="adj" fmla="val 50000"/></q:avLst><u:last>keep & stay</u:last></q:prstGeom><u:after/></p:spPr>"#;
1006 for declaration in ["", " xmlns:q=\"urn:foreign\""] {
1007 let raw = format!("<q:prstGeom{declaration} prst=\"trapezoid\"/>");
1008 let source = format!("<p:spPr>{raw}</p:spPr>");
1009 let opaque = CT_ShapeProperties::from_xml(source.as_bytes()).unwrap();
1010 assert!(opaque.preset_geometry.is_none());
1011 assert!(
1012 String::from_utf8(opaque.to_xml().unwrap())
1013 .unwrap()
1014 .contains(&raw)
1015 );
1016 }
1017 let properties = CT_ShapeProperties::from_xml(xml).unwrap();
1018 let preset = properties.preset_geometry.as_ref().unwrap();
1019 assert_eq!(preset.preset, "trapezoid");
1020 assert_eq!(preset.adjust_values().len(), 1);
1021
1022 let written = String::from_utf8(properties.to_xml().unwrap()).unwrap();
1023 assert!(written.starts_with("<a:spPr>"));
1024 assert!(written.contains("<a:prstGeom prst=\"trapezoid\">"));
1025 for raw in [
1026 "<u:before/>",
1027 "<u:first id=\"1\"><u:nested/></u:first>",
1028 "<u:inside/>",
1029 "<u:last>keep & stay</u:last>",
1030 "<u:after/>",
1031 ] {
1032 assert!(written.contains(raw), "missing raw subtree {raw}");
1033 }
1034 let before = written.find("<u:before/>").unwrap();
1035 let preset_start = written.find("<a:prstGeom").unwrap();
1036 let first = written.find("<u:first").unwrap();
1037 let adjustments = written.find("<a:avLst").unwrap();
1038 let inside = written.find("<u:inside/>").unwrap();
1039 let guide = written.find("<a:gd ").unwrap();
1040 let last = written.find("<u:last>").unwrap();
1041 let preset_end = written.find("</a:prstGeom>").unwrap();
1042 let after = written.find("<u:after/>").unwrap();
1043 assert!(before < preset_start);
1044 assert!(preset_start < first && first < adjustments);
1045 assert!(adjustments < inside && inside < guide);
1046 assert!(guide < last && last < preset_end && preset_end < after);
1047 assert_eq!(
1048 CT_ShapeProperties::from_xml(written.as_bytes()).unwrap(),
1049 properties
1050 );
1051 }
1052}
1053
1054#[cfg(test)]
1055#[test]
1056fn fx216_local_group_fill_blocks_inherited_fill_choice() {
1057 let group = CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:grpFill/></a:spPr>"#).unwrap();
1058 let solid = CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:solidFill><a:srgbClr val="FF0000"/></a:solidFill></a:spPr>"#).unwrap();
1059 let before_group = group.to_xml().unwrap();
1060 let before_solid = solid.to_xml().unwrap();
1061 let result = group.with_inherited_defaults(&solid).unwrap();
1062 assert!(
1063 result.fill.is_none(),
1064 "raw local group fill must remain the only fill choice"
1065 );
1066 let xml = result.to_xml().unwrap();
1067 assert!(String::from_utf8_lossy(&xml).contains("<a:grpFill/>"));
1068 assert!(!String::from_utf8_lossy(&xml).contains("solidFill"));
1069 assert_eq!(
1070 CT_ShapeProperties::from_xml(&xml)
1071 .unwrap()
1072 .to_xml()
1073 .unwrap(),
1074 xml
1075 );
1076 let converse = solid
1077 .with_inherited_defaults(&group)
1078 .unwrap()
1079 .to_xml()
1080 .unwrap();
1081 assert!(!String::from_utf8_lossy(&converse).contains("grpFill"));
1082 assert!(String::from_utf8_lossy(&converse).contains("solidFill"));
1083 let foreign=CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:f="urn:foreign"><f:grpFill keep='A'/></a:spPr>"#).unwrap();
1084 let result = foreign.with_inherited_defaults(&solid).unwrap();
1085 assert!(result.fill.is_some());
1086 assert!(
1087 String::from_utf8_lossy(&result.to_xml().unwrap()).contains("<f:grpFill keep='A'/>")
1088 );
1089 assert_eq!(group.to_xml().unwrap(), before_group);
1090 assert_eq!(solid.to_xml().unwrap(), before_solid);
1091}
1092
1093#[cfg(test)]
1094#[test]
1095fn fx216_opaque_attribute_aliases_reuse_or_refuse_expanded_identity() {
1096 for inherited_value in ["shared", "different"] {
1097 let local=CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer" xmlns:y="urn:producer" y:keep="shared"/>"#).unwrap();
1098 let parent_xml = format!(
1099 r#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer" xmlns:y="urn:producer" x:keep="{inherited_value}"/>"#
1100 );
1101 let defaults = CT_ShapeProperties::from_xml(parent_xml.as_bytes()).unwrap();
1102 let before_local = local.to_xml().unwrap();
1103 let before_defaults = defaults.to_xml().unwrap();
1104 if inherited_value == "different" {
1105 assert!(local.with_inherited_defaults(&defaults).is_err());
1106 } else {
1107 let xml = local
1108 .with_inherited_defaults(&defaults)
1109 .unwrap()
1110 .to_xml()
1111 .unwrap();
1112 let mut reader = quick_xml::NsReader::from_reader(xml.as_slice());
1113 let mut count = 0;
1114 if let Event::Empty(element) = reader.read_event().unwrap() {
1115 for attribute in element.attributes() {
1116 let attribute = attribute.unwrap();
1117 let (namespace, name) = reader.resolver().resolve_attribute(attribute.key);
1118 if name.as_ref() == b"keep"
1119 && matches!(namespace,quick_xml::name::ResolveResult::Bound(uri) if uri.as_ref()==b"urn:producer")
1120 {
1121 count += 1;
1122 }
1123 }
1124 } else {
1125 panic!("expected unchanged empty property owner");
1126 }
1127 assert_eq!(
1128 count, 1,
1129 "one expanded opaque attribute must not become two alias spellings"
1130 );
1131 assert_eq!(
1132 CT_ShapeProperties::from_xml(&xml)
1133 .unwrap()
1134 .to_xml()
1135 .unwrap(),
1136 xml
1137 );
1138 }
1139 assert_eq!(local.to_xml().unwrap(), before_local);
1140 assert_eq!(defaults.to_xml().unwrap(), before_defaults);
1141 }
1142}
1143
1144#[cfg(test)]
1145#[test]
1146fn fx216_opaque_children_with_equal_bytes_keep_distinct_namespace_authority() {
1147 let local=CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:local"><z:payload meaning="z:item"/></a:spPr>"#).unwrap();
1148 let defaults=CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:source"><z:payload meaning="z:item"/></a:spPr>"#).unwrap();
1149 let before_local = local.to_xml().unwrap();
1150 let before_defaults = defaults.to_xml().unwrap();
1151 let xml = local
1152 .with_inherited_defaults(&defaults)
1153 .unwrap()
1154 .to_xml()
1155 .unwrap();
1156 let mut reader = quick_xml::NsReader::from_reader(xml.as_slice());
1157 let mut seen = std::collections::HashSet::new();
1158 loop {
1159 let (namespace, event) = reader.read_resolved_event().unwrap();
1160 match event {
1161 Event::Empty(element) if element.local_name().as_ref() == b"payload" => {
1162 if let quick_xml::name::ResolveResult::Bound(namespace) = namespace {
1163 seen.insert(String::from_utf8_lossy(namespace.as_ref()).into_owned());
1164 }
1165 }
1166 Event::Eof => break,
1167 _ => {}
1168 }
1169 }
1170 assert_eq!(
1171 seen,
1172 std::collections::HashSet::from(["urn:local".to_owned(), "urn:source".to_owned()])
1173 );
1174 assert_eq!(local.to_xml().unwrap(), before_local);
1175 assert_eq!(defaults.to_xml().unwrap(), before_defaults);
1176 let same = local
1177 .with_inherited_defaults(&local)
1178 .unwrap()
1179 .to_xml()
1180 .unwrap();
1181 assert_eq!(same, before_local);
1182 assert_eq!(
1183 CT_ShapeProperties::from_xml(&xml)
1184 .unwrap()
1185 .to_xml()
1186 .unwrap(),
1187 xml
1188 );
1189}
1190
1191#[cfg(test)]
1192#[test]
1193fn fx216_typed_inherited_fill_preserves_source_opaque_namespace() {
1194 let local=CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:local"/>"#).unwrap();
1195 let defaults=CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:source"><a:solidFill><a:srgbClr val="FF0000"/><z:payload meaning="z:item"/></a:solidFill></a:spPr>"#).unwrap();
1196 let before_local = local.to_xml().unwrap();
1197 let before_defaults = defaults.to_xml().unwrap();
1198 let result = local.with_inherited_defaults(&defaults).unwrap();
1199 let xml = result.to_xml().unwrap();
1200 let mut reader = quick_xml::NsReader::from_reader(xml.as_slice());
1201 let mut seen = false;
1202 loop {
1203 let (namespace, event) = reader.read_resolved_event().unwrap();
1204 match event {
1205 Event::Empty(element) if element.local_name().as_ref() == b"payload" => {
1206 assert!(
1207 matches!(namespace,quick_xml::name::ResolveResult::Bound(uri) if uri.as_ref()==b"urn:source")
1208 );
1209 seen = true;
1210 }
1211 Event::Eof => break,
1212 _ => {}
1213 }
1214 }
1215 assert!(seen);
1216 assert_eq!(local.to_xml().unwrap(), before_local);
1217 assert_eq!(defaults.to_xml().unwrap(), before_defaults);
1218}
1219
1220#[cfg(test)]
1221#[test]
1222fn fx216_typed_groups_close_actual_ancestor_scopes_and_preserve_edits() {
1223 let local = CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:local" xmlns="urn:local"><opaque/></a:spPr>"#).unwrap();
1224 let groups = [
1225 r#"<a:xfrm><a:off x="10" y="20"/><z:payload meaning="z:item"/></a:xfrm>"#,
1226 r#"<a:prstGeom prst="rect"><a:avLst/><z:payload meaning="z:item"/></a:prstGeom>"#,
1227 r#"<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:pathLst><z:payload meaning="z:item"/></a:pathLst></a:custGeom>"#,
1228 r#"<a:solidFill><a:srgbClr val="FF0000"/><z:payload meaning="z:item"/></a:solidFill>"#,
1229 r#"<a:solidFill><a:srgbClr val="FF0000"/><payload meaning="item"/></a:solidFill>"#,
1230 r#"<a:ln w="10" z:keep="z:item"><z:payload meaning="z:item"/></a:ln>"#,
1231 r#"<a:effectLst><z:payload meaning="z:item"/></a:effectLst>"#,
1232 r#"<a:solidFill><a:srgbClr val="FF0000"/><z:payload xmlns:z="urn:source" meaning="z:item"/></a:solidFill>"#,
1233 ];
1234 for (index, group) in groups.iter().enumerate() {
1235 let outer = format!(
1238 r#"<p:sp xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:source" xmlns="urn:source"><p:spPr>{group}</p:spPr></p:sp>"#
1239 );
1240 let mut reader = Reader::from_reader(outer.as_bytes());
1241 let Event::Start(owner) = reader.read_event().unwrap() else {
1242 panic!("shape owner")
1243 };
1244 let scope = property_namespace_scope(&owner, &[]).unwrap();
1245 let Event::Start(properties) = reader.read_event().unwrap() else {
1246 panic!("property owner")
1247 };
1248 let raw = capture_element(&mut reader, &properties).unwrap();
1249 let mut defaults =
1250 CT_ShapeProperties::from_xml_with_inherited_namespaces(&raw, &scope).unwrap();
1251 if index == 0 {
1252 defaults
1253 .transform
1254 .as_mut()
1255 .unwrap()
1256 .offset
1257 .as_mut()
1258 .unwrap()
1259 .x = oxml_core::units::Emu(123);
1260 }
1261 if index == 1 {
1262 defaults.preset_geometry.as_mut().unwrap().preset = "ellipse".into();
1263 }
1264 if index == 5 {
1265 defaults.line.as_mut().unwrap().width = Some(456);
1266 }
1267 let before = defaults.to_xml().unwrap();
1268 let result = local.with_inherited_defaults(&defaults).unwrap();
1269 let xml = result.to_xml().unwrap();
1270 let mut scan = quick_xml::NsReader::from_reader(xml.as_slice());
1271 let mut payloads = 0;
1272 let mut locals = 0;
1273 loop {
1274 let (namespace, event) = scan.read_resolved_event().unwrap();
1275 match event {
1276 Event::Empty(start) if start.local_name().as_ref() == b"payload" => {
1277 assert!(
1278 matches!(namespace, quick_xml::name::ResolveResult::Bound(uri) if uri.as_ref() == b"urn:source"),
1279 "{index}: {}",
1280 String::from_utf8_lossy(&xml)
1281 );
1282 payloads += 1;
1283 }
1284 Event::Empty(start) if start.local_name().as_ref() == b"opaque" => {
1285 assert!(
1286 matches!(namespace, quick_xml::name::ResolveResult::Bound(uri) if uri.as_ref() == b"urn:local")
1287 );
1288 locals += 1;
1289 }
1290 Event::Eof => break,
1291 _ => {}
1292 }
1293 }
1294 assert_eq!(payloads, 1, "group {index}");
1295 assert_eq!(locals, 1);
1296 let reparsed = CT_ShapeProperties::from_xml(&xml).unwrap();
1297 if index == 0 {
1298 assert_eq!(reparsed.transform.unwrap().offset.unwrap().x.0, 123);
1299 }
1300 if index == 1 {
1301 assert_eq!(reparsed.preset_geometry.unwrap().preset, "ellipse");
1302 }
1303 if index == 5 {
1304 assert_eq!(reparsed.line.unwrap().width, Some(456));
1305 }
1306 assert_eq!(defaults.to_xml().unwrap(), before);
1307 }
1308 let mut nested = CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:ln w="10" xmlns:z="urn:nested"><z:payload/></a:ln></a:spPr>"#).unwrap();
1309 nested.line.as_mut().unwrap().width = Some(99);
1310 let before = nested.to_xml().unwrap();
1311 assert!(local.with_inherited_defaults(&nested).is_err());
1312 assert_eq!(nested.to_xml().unwrap(), before);
1313}
1314
1315#[cfg(test)]
1316#[test]
1317fn fx216_unrepresentable_typed_metadata_refuses_local_and_parent_without_mutation() {
1318 let empty = CT_ShapeProperties::from_xml(br#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:source"/>"#).unwrap();
1319 for group in [
1320 r#"<a:solidFill z:meaning="z:item"><a:srgbClr val="FF0000"/></a:solidFill>"#,
1321 r#"<a:ln><a:solidFill z:meaning="z:item"><a:srgbClr val="FF0000"/></a:solidFill></a:ln>"#,
1322 r#"<a:prstGeom prst="rect"><a:avLst><a:gd name="adjust" fmla="val 100" z:meaning="z:item"/></a:avLst></a:prstGeom>"#,
1323 ] {
1324 let xml = format!(
1325 r#"<a:spPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:z="urn:source">{group}</a:spPr>"#
1326 );
1327 let properties = CT_ShapeProperties::from_xml(xml.as_bytes()).unwrap();
1328 let before = properties.to_xml().unwrap();
1329 if !String::from_utf8_lossy(&before).contains("z:meaning") {
1330 assert!(
1331 empty.with_inherited_defaults(&properties).is_err(),
1332 "unrepresentable inherited {group}"
1333 );
1334 assert!(
1335 properties.with_inherited_defaults(&empty).is_err(),
1336 "unrepresentable local {group}"
1337 );
1338 } else {
1339 let inherited = empty
1340 .with_inherited_defaults(&properties)
1341 .unwrap()
1342 .to_xml()
1343 .unwrap();
1344 assert!(String::from_utf8_lossy(&inherited).contains("z:meaning"));
1345 }
1346 assert_eq!(properties.to_xml().unwrap(), before);
1347 }
1348}