1use quick_xml::events::{BytesEnd, BytesStart, Event};
4use quick_xml::{Reader, Writer, XmlVersion};
5
6use std::borrow::Cow;
7
8use quick_xml::events::attributes::Attribute;
9use quick_xml::name::QName;
10
11use crate::borders::CT_BorderEdge;
12use crate::error::Result;
13use crate::namespace::matches_local_name;
14use crate::properties::{
15 CT_Shd, append_modeled_toggle_attributes, get_word_val_attr, is_word_attribute,
16 is_word_element, parse_integer_measurement, parse_uchar_hex, parse_word_toggle, push_uchar_hex,
17 raw_is_modeled_attribute_carrier, raw_occurrence, record_modeled_toggle_candidate,
18 remove_redundant_modeled_toggle_candidate, replay_modeled_toggle_raw,
19 toggle_element_is_explicitly_empty, toggle_has_unsupported_attributes, word_prefixes_at,
20 write_toggle,
21};
22use crate::raw_xml::{capture_element, capture_empty_element};
23use crate::revision::CT_Revision;
24use crate::shared::{ST_HighlightColor, ST_Underline};
25use crate::units::{HalfPoint, Twips};
26
27const RPR_STYLE_SLOT: u8 = 0;
28const RPR_FONTS_SLOT: u8 = 1;
29const RPR_BOLD_SLOT: u8 = 2;
30const RPR_BOLD_CS_SLOT: u8 = 3;
31const RPR_ITALIC_SLOT: u8 = 4;
32const RPR_ITALIC_CS_SLOT: u8 = 5;
33const RPR_CAPS_SLOT: u8 = 6;
34const RPR_SMALL_CAPS_SLOT: u8 = 7;
35const RPR_STRIKE_SLOT: u8 = 8;
36const RPR_DSTRIKE_SLOT: u8 = 9;
37const RPR_OUTLINE_SLOT: u8 = 10;
38const RPR_SHADOW_SLOT: u8 = 11;
39const RPR_EMBOSS_SLOT: u8 = 12;
40const RPR_IMPRINT_SLOT: u8 = 13;
41const RPR_NO_PROOF_SLOT: u8 = 14;
42const RPR_SNAP_TO_GRID_SLOT: u8 = 15;
43const RPR_VANISH_SLOT: u8 = 16;
44const RPR_WEB_HIDDEN_SLOT: u8 = 17;
45const RPR_COLOR_SLOT: u8 = 18;
46const RPR_SPACING_SLOT: u8 = 19;
47const RPR_WIDTH_SLOT: u8 = 20;
48const RPR_KERN_SLOT: u8 = 21;
49const RPR_POSITION_SLOT: u8 = 22;
50const RPR_SIZE_SLOT: u8 = 23;
51const RPR_SIZE_CS_SLOT: u8 = 24;
52const RPR_HIGHLIGHT_SLOT: u8 = 25;
53const RPR_UNDERLINE_SLOT: u8 = 26;
54const RPR_EFFECT_SLOT: u8 = 27;
55const RPR_BORDER_SLOT: u8 = 28;
56const RPR_SHADING_SLOT: u8 = 29;
57const RPR_FIT_TEXT_SLOT: u8 = 30;
58const RPR_VERT_ALIGN_SLOT: u8 = 31;
59const RPR_RTL_SLOT: u8 = 32;
60const RPR_COMPLEX_SCRIPT_SLOT: u8 = 33;
61const RPR_EMPHASIS_MARK_SLOT: u8 = 34;
62const RPR_LANG_SLOT: u8 = 35;
63const RPR_EAST_ASIAN_LAYOUT_SLOT: u8 = 36;
64const RPR_SPEC_VANISH_SLOT: u8 = 37;
65const RPR_OFFICE_MATH_SLOT: u8 = 38;
66const RPR_MARKER_SLOT: u8 = 39;
67const RPR_CHANGE_SLOT: u8 = 40;
68const RPR_END_SLOT: u8 = 41;
69
70fn rpr_toggle_slot<'a>(
77 rpr: &'a mut CT_RPr,
78 name: &[u8],
79 prefixes: &[String],
80) -> Option<(u8, &'a mut Option<bool>)> {
81 if is_word_element(name, b"outline", prefixes) {
82 Some((RPR_OUTLINE_SLOT, &mut rpr.outline))
83 } else if is_word_element(name, b"shadow", prefixes) {
84 Some((RPR_SHADOW_SLOT, &mut rpr.shadow))
85 } else if is_word_element(name, b"emboss", prefixes) {
86 Some((RPR_EMBOSS_SLOT, &mut rpr.emboss))
87 } else if is_word_element(name, b"imprint", prefixes) {
88 Some((RPR_IMPRINT_SLOT, &mut rpr.imprint))
89 } else if is_word_element(name, b"noProof", prefixes) {
90 Some((RPR_NO_PROOF_SLOT, &mut rpr.no_proof))
91 } else if is_word_element(name, b"snapToGrid", prefixes) {
92 Some((RPR_SNAP_TO_GRID_SLOT, &mut rpr.snap_to_grid))
93 } else if is_word_element(name, b"webHidden", prefixes) {
94 Some((RPR_WEB_HIDDEN_SLOT, &mut rpr.web_hidden))
95 } else if is_word_element(name, b"cs", prefixes) {
96 Some((RPR_COMPLEX_SCRIPT_SLOT, &mut rpr.complex_script))
97 } else if is_word_element(name, b"specVanish", prefixes) {
98 Some((RPR_SPEC_VANISH_SLOT, &mut rpr.spec_vanish))
99 } else if is_word_element(name, b"oMath", prefixes) {
100 Some((RPR_OFFICE_MATH_SLOT, &mut rpr.office_math))
101 } else {
102 None
103 }
104}
105
106fn record_rpr_modeled(
107 rpr: &mut CT_RPr,
108 pending_raw: &mut Vec<Vec<u8>>,
109 occurrences: &mut [usize],
110 slot: u8,
111) {
112 let occurrence = if slot == RPR_MARKER_SLOT {
113 occurrences[slot as usize]
114 } else {
115 0
116 };
117 flush_rpr_raw(rpr, pending_raw, slot, occurrence);
118 occurrences[slot as usize] += 1;
119}
120
121fn record_rpr_raw_at(rpr: &mut CT_RPr, raw: Vec<u8>, slot: u8, occurrence: usize) {
122 rpr.revision_xml.push(raw);
123 rpr.revision_xml_positions.push((slot, occurrence));
124}
125
126fn flush_rpr_raw(rpr: &mut CT_RPr, pending_raw: &mut Vec<Vec<u8>>, slot: u8, occurrence: usize) {
127 for raw in pending_raw.drain(..) {
128 rpr.revision_xml.push(raw);
129 rpr.revision_xml_positions.push((slot, occurrence));
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
138#[allow(non_camel_case_types)]
139pub enum ST_TextEffect {
140 None,
141 BlinkBackground,
142 Lights,
143 AntsBlack,
144 AntsRed,
145 Shimmer,
146 SparkleText,
147 Other(String),
149}
150
151impl ST_TextEffect {
152 pub fn from_str(value: &str) -> Self {
153 match value {
154 "none" => ST_TextEffect::None,
155 "blinkBackground" => ST_TextEffect::BlinkBackground,
156 "lights" => ST_TextEffect::Lights,
157 "antsBlack" => ST_TextEffect::AntsBlack,
158 "antsRed" => ST_TextEffect::AntsRed,
159 "shimmer" => ST_TextEffect::Shimmer,
160 "sparkleText" => ST_TextEffect::SparkleText,
161 other => ST_TextEffect::Other(other.to_owned()),
162 }
163 }
164
165 pub fn as_str(&self) -> &str {
166 match self {
167 ST_TextEffect::None => "none",
168 ST_TextEffect::BlinkBackground => "blinkBackground",
169 ST_TextEffect::Lights => "lights",
170 ST_TextEffect::AntsBlack => "antsBlack",
171 ST_TextEffect::AntsRed => "antsRed",
172 ST_TextEffect::Shimmer => "shimmer",
173 ST_TextEffect::SparkleText => "sparkleText",
174 ST_TextEffect::Other(value) => value.as_str(),
175 }
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181#[allow(non_camel_case_types)]
182pub enum ST_Em {
183 None,
184 Dot,
185 Comma,
186 Circle,
187 UnderDot,
188 Other(String),
190}
191
192impl ST_Em {
193 pub fn from_str(value: &str) -> Self {
194 match value {
195 "none" => ST_Em::None,
196 "dot" => ST_Em::Dot,
197 "comma" => ST_Em::Comma,
198 "circle" => ST_Em::Circle,
199 "underDot" => ST_Em::UnderDot,
200 other => ST_Em::Other(other.to_owned()),
201 }
202 }
203
204 pub fn as_str(&self) -> &str {
205 match self {
206 ST_Em::None => "none",
207 ST_Em::Dot => "dot",
208 ST_Em::Comma => "comma",
209 ST_Em::Circle => "circle",
210 ST_Em::UnderDot => "underDot",
211 ST_Em::Other(value) => value.as_str(),
212 }
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
218#[allow(non_camel_case_types)]
219pub struct CT_FitText {
220 pub val: Twips,
222 pub id: Option<i32>,
224 pub extra_attributes: Vec<(String, String)>,
226}
227
228#[derive(Debug, Clone, Default, PartialEq, Eq)]
231#[allow(non_camel_case_types)]
232pub struct CT_EastAsianLayout {
233 pub id: Option<i32>,
235 pub combine: Option<bool>,
237 pub combine_brackets: Option<String>,
239 pub vert: Option<bool>,
241 pub vert_compress: Option<bool>,
243 pub extra_attributes: Vec<(String, String)>,
245}
246
247fn parse_on_off(value: &str) -> bool {
249 crate::shared::ST_OnOff::from_str_or_default(Some(value)).is_on()
250}
251
252fn push_on_off(e: &mut BytesStart<'_>, name: &str, value: bool) {
253 e.push_attribute((name, if value { "1" } else { "0" }));
254}
255
256fn push_retained_attributes(e: &mut BytesStart<'_>, attributes: &[(String, String)]) {
257 for (name, value) in attributes {
258 e.push_attribute(Attribute {
259 key: QName(name.as_bytes()),
260 value: Cow::Borrowed(value.as_bytes()),
261 });
262 }
263}
264
265impl CT_FitText {
266 fn from_xml_attrs(e: &BytesStart<'_>, prefixes: &[String]) -> Result<Option<Self>> {
268 let mut fit_text = CT_FitText {
269 val: Twips(0),
270 id: None,
271 extra_attributes: Vec::new(),
272 };
273 let mut width_seen = false;
274 for attribute in e.attributes() {
275 let attribute = attribute?;
276 let key = attribute.key.as_ref();
277 let value = std::str::from_utf8(&attribute.value)?;
278 if is_word_attribute(key, b"val", prefixes) {
279 fit_text.val = Twips(parse_integer_measurement(e.name().as_ref(), key, value)?);
280 width_seen = true;
281 } else if is_word_attribute(key, b"id", prefixes) {
282 fit_text.id = Some(value.parse()?);
283 } else {
284 fit_text
285 .extra_attributes
286 .push((std::str::from_utf8(key)?.to_owned(), value.to_owned()));
287 }
288 }
289 Ok(width_seen.then_some(fit_text))
290 }
291
292 fn write_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
293 let mut buf = itoa::Buffer::new();
294 let mut e = BytesStart::new("w:fitText");
295 push_retained_attributes(&mut e, &self.extra_attributes);
296 e.push_attribute(("w:val", buf.format(self.val.0)));
297 if let Some(id) = self.id {
298 e.push_attribute(("w:id", buf.format(id)));
299 }
300 writer.write_event(Event::Empty(e))?;
301 Ok(())
302 }
303}
304
305impl CT_EastAsianLayout {
306 fn from_xml_attrs(e: &BytesStart<'_>, prefixes: &[String]) -> Result<Self> {
307 let mut layout = CT_EastAsianLayout::default();
308 for attribute in e.attributes() {
309 let attribute = attribute?;
310 let key = attribute.key.as_ref();
311 let value = std::str::from_utf8(&attribute.value)?;
312 if is_word_attribute(key, b"id", prefixes) {
313 layout.id = Some(value.parse()?);
314 } else if is_word_attribute(key, b"combine", prefixes) {
315 layout.combine = Some(parse_on_off(value));
316 } else if is_word_attribute(key, b"combineBrackets", prefixes) {
317 layout.combine_brackets = Some(value.to_owned());
318 } else if is_word_attribute(key, b"vert", prefixes) {
319 layout.vert = Some(parse_on_off(value));
320 } else if is_word_attribute(key, b"vertCompress", prefixes) {
321 layout.vert_compress = Some(parse_on_off(value));
322 } else {
323 layout
324 .extra_attributes
325 .push((std::str::from_utf8(key)?.to_owned(), value.to_owned()));
326 }
327 }
328 Ok(layout)
329 }
330
331 fn write_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
332 let mut buf = itoa::Buffer::new();
333 let mut e = BytesStart::new("w:eastAsianLayout");
334 push_retained_attributes(&mut e, &self.extra_attributes);
335 if let Some(id) = self.id {
336 e.push_attribute(("w:id", buf.format(id)));
337 }
338 if let Some(combine) = self.combine {
339 push_on_off(&mut e, "w:combine", combine);
340 }
341 if let Some(ref brackets) = self.combine_brackets {
342 e.push_attribute(("w:combineBrackets", brackets.as_str()));
343 }
344 if let Some(vert) = self.vert {
345 push_on_off(&mut e, "w:vert", vert);
346 }
347 if let Some(vert_compress) = self.vert_compress {
348 push_on_off(&mut e, "w:vertCompress", vert_compress);
349 }
350 writer.write_event(Event::Empty(e))?;
351 Ok(())
352 }
353}
354
355#[derive(Debug, Clone, Default, PartialEq)]
357#[allow(non_snake_case)]
358pub struct CT_RPr {
359 pub style_id: Option<String>,
361 pub font_ascii: Option<String>,
363 pub font_hansi: Option<String>,
365 pub font_east_asia: Option<String>,
367 pub font_cs: Option<String>,
369 pub font_ascii_theme: Option<String>,
371 pub font_hansi_theme: Option<String>,
373 pub font_east_asia_theme: Option<String>,
375 pub font_cs_theme: Option<String>,
377 pub font_hint: Option<String>,
379 #[doc(hidden)]
381 pub font_extra_attributes: Vec<(String, String)>,
382 pub bold: Option<bool>,
384 pub bold_cs: Option<bool>,
386 pub italic: Option<bool>,
388 pub italic_cs: Option<bool>,
390 pub underline: Option<ST_Underline>,
392 pub strike: Option<bool>,
394 pub dstrike: Option<bool>,
396 pub sz: Option<HalfPoint>,
398 pub sz_cs: Option<HalfPoint>,
400 pub color: Option<String>,
402 pub color_theme: Option<String>,
404 pub color_theme_tint: Option<u8>,
406 pub color_theme_shade: Option<u8>,
408 #[doc(hidden)]
410 pub color_extra_attributes: Vec<(String, String)>,
411 pub highlight: Option<ST_HighlightColor>,
413 pub caps: Option<bool>,
415 pub small_caps: Option<bool>,
417 pub vert_align: Option<String>,
419 pub spacing: Option<Twips>,
421 pub width_scale: Option<u32>,
423 pub position: Option<i32>,
425 pub shading: Option<Box<CT_Shd>>,
427 pub vanish: Option<bool>,
429 pub outline: Option<bool>,
431 pub shadow: Option<bool>,
433 pub emboss: Option<bool>,
435 pub imprint: Option<bool>,
437 pub no_proof: Option<bool>,
439 pub snap_to_grid: Option<bool>,
441 pub web_hidden: Option<bool>,
443 pub kern: Option<HalfPoint>,
445 pub effect: Option<ST_TextEffect>,
447 pub border: Option<Box<CT_BorderEdge>>,
453 pub fit_text: Option<Box<CT_FitText>>,
455 pub complex_script: Option<bool>,
457 pub emphasis_mark: Option<ST_Em>,
459 pub east_asian_layout: Option<Box<CT_EastAsianLayout>>,
461 pub spec_vanish: Option<bool>,
463 pub office_math: Option<bool>,
465 pub rtl: Option<bool>,
467 pub language: Option<String>,
469 pub language_east_asia: Option<String>,
471 pub language_bidi: Option<String>,
473 #[doc(hidden)]
475 pub language_extra_attributes: Vec<(String, String)>,
476 pub revision_markers: Vec<CT_Revision>,
478 pub change: Option<Box<CT_Revision>>,
482 pub revision_xml: Vec<Vec<u8>>,
484 #[doc(hidden)]
486 pub revision_xml_positions: Vec<(u8, usize)>,
487}
488
489#[allow(non_snake_case)]
490impl CT_RPr {
491 pub fn solid_text_fill(&self) -> Option<(String, f64)> {
494 const NS: &[u8] = b"http://schemas.microsoft.com/office/word/2010/wordml";
495 for raw in &self.revision_xml {
496 let mut probe = quick_xml::NsReader::from_reader(raw.as_slice());
497 let Ok((namespace, event)) = probe.read_resolved_event() else {
498 continue;
499 };
500 if !matches!(namespace, quick_xml::name::ResolveResult::Bound(ns) if ns.as_ref() == NS)
501 || !matches!(event, Event::Start(ref element) if element.local_name().as_ref() == b"textFill")
502 {
503 continue;
504 }
505 let mut reader = quick_xml::NsReader::from_reader(raw.as_slice());
506 let mut path = Vec::new();
507 let mut color = None;
508 let mut alpha = 1.0;
509 let mut valid = true;
510 loop {
511 let (namespace, event) = reader.read_resolved_event().ok()?;
512 match event {
513 Event::Start(ref element) | Event::Empty(ref element) => {
514 let name = element.local_name().as_ref().to_vec();
515 let qualified = matches!(namespace, quick_xml::name::ResolveResult::Bound(ns) if ns.as_ref() == NS);
516 if !qualified {
517 valid = false;
518 }
519 path.push(name);
520 let expected: &[&[u8]] = &[b"textFill", b"solidFill", b"srgbClr", b"alpha"];
521 if path.len() > expected.len()
522 || !path
523 .iter()
524 .zip(expected)
525 .all(|(actual, expected)| actual.as_slice() == *expected)
526 {
527 valid = false;
528 }
529 if path.len() == 3 || path.len() == 4 {
530 let mut value = None;
531 for attribute in element.attributes() {
532 let attribute = attribute.ok()?;
533 let (ns, local) =
534 reader.resolver().resolve_attribute(attribute.key);
535 if matches!(ns, quick_xml::name::ResolveResult::Bound(ns) if ns.as_ref() == NS)
536 && local.as_ref() == b"val"
537 {
538 if value.is_some() {
539 valid = false;
540 }
541 value = Some(
542 attribute
543 .decoded_and_normalized_value(
544 quick_xml::XmlVersion::Implicit1_0,
545 reader.decoder(),
546 )
547 .ok()?
548 .into_owned(),
549 );
550 }
551 }
552 if path.len() == 3 {
553 let hex = value?;
554 if hex.len() != 6
555 || !hex.bytes().all(|byte| byte.is_ascii_hexdigit())
556 || color.is_some()
557 {
558 valid = false;
559 }
560 color = Some(hex);
561 } else {
562 let amount: u32 = value?.parse().ok()?;
563 if amount > 100_000 {
564 valid = false;
565 }
566 alpha = f64::from(amount) / 100_000.0;
567 }
568 }
569 if matches!(event, Event::Empty(_)) {
570 path.pop();
571 }
572 }
573 Event::End(_) => {
574 path.pop();
575 }
576 Event::Eof => break,
577 Event::Text(ref text) if text.as_ref().iter().all(u8::is_ascii_whitespace) => {}
578 _ => {
579 valid = false;
580 }
581 }
582 }
583 if valid && let Some(color) = color {
584 return Some((color, alpha));
585 }
586 }
587 None
588 }
589
590 pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
591 Self::from_xml_with_prefixes(reader, &["w".to_owned()])
592 }
593
594 pub(crate) fn from_xml_with_prefixes(
595 reader: &mut Reader<&[u8]>,
596 word_prefixes: &[String],
597 ) -> Result<Self> {
598 Self::from_xml_with_prefixes_and_owner_bindings(reader, word_prefixes, &[])
599 }
600
601 pub(crate) fn from_xml_with_prefixes_and_owner_bindings(
602 reader: &mut Reader<&[u8]>,
603 word_prefixes: &[String],
604 owner_bindings: &[(String, String)],
605 ) -> Result<Self> {
606 let mut rpr = CT_RPr::default();
607 let mut change_raw_index = 0usize;
608 let mut pending_raw = Vec::new();
609 let mut occurrences = [0usize; RPR_END_SLOT as usize + 1];
610 let mut rtl_carrier_required = false;
611 let mut buf = Vec::new();
612
613 loop {
614 match reader.read_event_into(&mut buf) {
615 Ok(Event::Empty(ref e)) => {
616 let name = e.name();
617 let prefixes = word_prefixes_at(e, word_prefixes)?;
618 if is_word_element(name.as_ref(), b"rStyle", &prefixes) {
619 record_rpr_modeled(
620 &mut rpr,
621 &mut pending_raw,
622 &mut occurrences,
623 RPR_STYLE_SLOT,
624 );
625 rpr.style_id = get_word_val_attr(e, &prefixes)?;
626 } else if is_word_element(name.as_ref(), b"rFonts", &prefixes) {
627 record_rpr_modeled(
628 &mut rpr,
629 &mut pending_raw,
630 &mut occurrences,
631 RPR_FONTS_SLOT,
632 );
633 for attr in e.attributes() {
634 let attr = attr?;
635 let key = attr.key.as_ref();
636 let val = std::str::from_utf8(&attr.value)?.to_string();
637 if is_word_attribute(key, b"ascii", &prefixes) {
638 rpr.font_ascii = Some(val);
639 } else if is_word_attribute(key, b"hAnsi", &prefixes) {
640 rpr.font_hansi = Some(val);
641 } else if is_word_attribute(key, b"eastAsia", &prefixes) {
642 rpr.font_east_asia = Some(val);
643 } else if is_word_attribute(key, b"cs", &prefixes) {
644 rpr.font_cs = Some(val);
645 } else if is_word_attribute(key, b"asciiTheme", &prefixes) {
646 rpr.font_ascii_theme = Some(val);
647 } else if is_word_attribute(key, b"hAnsiTheme", &prefixes) {
648 rpr.font_hansi_theme = Some(val);
649 } else if is_word_attribute(key, b"eastAsiaTheme", &prefixes) {
650 rpr.font_east_asia_theme = Some(val);
651 } else if is_word_attribute(key, b"cstheme", &prefixes) {
652 rpr.font_cs_theme = Some(val);
653 } else if is_word_attribute(key, b"hint", &prefixes) {
654 rpr.font_hint = Some(val);
655 } else {
656 rpr.font_extra_attributes
657 .push((std::str::from_utf8(key)?.to_owned(), val));
658 }
659 }
660 } else if is_word_element(name.as_ref(), b"b", &prefixes) {
661 record_rpr_modeled(
662 &mut rpr,
663 &mut pending_raw,
664 &mut occurrences,
665 RPR_BOLD_SLOT,
666 );
667 rpr.bold = Some(parse_word_toggle(e, &prefixes)?);
668 } else if is_word_element(name.as_ref(), b"bCs", &prefixes) {
669 record_rpr_modeled(
670 &mut rpr,
671 &mut pending_raw,
672 &mut occurrences,
673 RPR_BOLD_CS_SLOT,
674 );
675 rpr.bold_cs = Some(parse_word_toggle(e, &prefixes)?);
676 } else if is_word_element(name.as_ref(), b"i", &prefixes) {
677 record_rpr_modeled(
678 &mut rpr,
679 &mut pending_raw,
680 &mut occurrences,
681 RPR_ITALIC_SLOT,
682 );
683 rpr.italic = Some(parse_word_toggle(e, &prefixes)?);
684 } else if is_word_element(name.as_ref(), b"iCs", &prefixes) {
685 record_rpr_modeled(
686 &mut rpr,
687 &mut pending_raw,
688 &mut occurrences,
689 RPR_ITALIC_CS_SLOT,
690 );
691 rpr.italic_cs = Some(parse_word_toggle(e, &prefixes)?);
692 } else if is_word_element(name.as_ref(), b"u", &prefixes) {
693 record_rpr_modeled(
694 &mut rpr,
695 &mut pending_raw,
696 &mut occurrences,
697 RPR_UNDERLINE_SLOT,
698 );
699 if let Some(val) = get_word_val_attr(e, &prefixes)? {
700 rpr.underline = ST_Underline::from_str(&val).ok();
701 } else {
702 rpr.underline = Some(ST_Underline::Single);
703 }
704 } else if is_word_element(name.as_ref(), b"strike", &prefixes) {
705 record_rpr_modeled(
706 &mut rpr,
707 &mut pending_raw,
708 &mut occurrences,
709 RPR_STRIKE_SLOT,
710 );
711 rpr.strike = Some(parse_word_toggle(e, &prefixes)?);
712 } else if is_word_element(name.as_ref(), b"dstrike", &prefixes) {
713 record_rpr_modeled(
714 &mut rpr,
715 &mut pending_raw,
716 &mut occurrences,
717 RPR_DSTRIKE_SLOT,
718 );
719 rpr.dstrike = Some(parse_word_toggle(e, &prefixes)?);
720 } else if is_word_element(name.as_ref(), b"sz", &prefixes) {
721 record_rpr_modeled(
722 &mut rpr,
723 &mut pending_raw,
724 &mut occurrences,
725 RPR_SIZE_SLOT,
726 );
727 if let Some(val) = get_word_val_attr(e, &prefixes)? {
728 rpr.sz = Some(HalfPoint(parse_integer_measurement(
729 name.as_ref(),
730 b"w:val",
731 &val,
732 )?));
733 }
734 } else if is_word_element(name.as_ref(), b"szCs", &prefixes) {
735 record_rpr_modeled(
736 &mut rpr,
737 &mut pending_raw,
738 &mut occurrences,
739 RPR_SIZE_CS_SLOT,
740 );
741 if let Some(val) = get_word_val_attr(e, &prefixes)? {
742 rpr.sz_cs = Some(HalfPoint(parse_integer_measurement(
743 name.as_ref(),
744 b"w:val",
745 &val,
746 )?));
747 }
748 } else if is_word_element(name.as_ref(), b"color", &prefixes) {
749 record_rpr_modeled(
750 &mut rpr,
751 &mut pending_raw,
752 &mut occurrences,
753 RPR_COLOR_SLOT,
754 );
755 for attr in e.attributes() {
756 let attr = attr?;
757 let key = attr.key.as_ref();
758 let v = std::str::from_utf8(&attr.value)?.to_string();
759 if is_word_attribute(key, b"val", &prefixes) {
760 rpr.color = Some(v);
761 } else if is_word_attribute(key, b"themeColor", &prefixes) {
762 rpr.color_theme = Some(v);
763 } else if is_word_attribute(key, b"themeTint", &prefixes) {
764 match parse_uchar_hex(&v) {
765 Some(tint) => rpr.color_theme_tint = Some(tint),
766 None => rpr
767 .color_extra_attributes
768 .push((std::str::from_utf8(key)?.to_owned(), v)),
769 }
770 } else if is_word_attribute(key, b"themeShade", &prefixes) {
771 match parse_uchar_hex(&v) {
772 Some(shade) => rpr.color_theme_shade = Some(shade),
773 None => rpr
774 .color_extra_attributes
775 .push((std::str::from_utf8(key)?.to_owned(), v)),
776 }
777 } else {
778 rpr.color_extra_attributes
779 .push((std::str::from_utf8(key)?.to_owned(), v));
780 }
781 }
782 } else if is_word_element(name.as_ref(), b"highlight", &prefixes) {
783 record_rpr_modeled(
784 &mut rpr,
785 &mut pending_raw,
786 &mut occurrences,
787 RPR_HIGHLIGHT_SLOT,
788 );
789 if let Some(val) = get_word_val_attr(e, &prefixes)? {
790 rpr.highlight = ST_HighlightColor::from_str(&val).ok();
791 }
792 } else if is_word_element(name.as_ref(), b"caps", &prefixes) {
793 record_rpr_modeled(
794 &mut rpr,
795 &mut pending_raw,
796 &mut occurrences,
797 RPR_CAPS_SLOT,
798 );
799 rpr.caps = Some(parse_word_toggle(e, &prefixes)?);
800 } else if is_word_element(name.as_ref(), b"smallCaps", &prefixes) {
801 record_rpr_modeled(
802 &mut rpr,
803 &mut pending_raw,
804 &mut occurrences,
805 RPR_SMALL_CAPS_SLOT,
806 );
807 rpr.small_caps = Some(parse_word_toggle(e, &prefixes)?);
808 } else if is_word_element(name.as_ref(), b"vertAlign", &prefixes) {
809 record_rpr_modeled(
810 &mut rpr,
811 &mut pending_raw,
812 &mut occurrences,
813 RPR_VERT_ALIGN_SLOT,
814 );
815 rpr.vert_align = get_word_val_attr(e, &prefixes)?;
816 } else if is_word_element(name.as_ref(), b"spacing", &prefixes) {
817 record_rpr_modeled(
818 &mut rpr,
819 &mut pending_raw,
820 &mut occurrences,
821 RPR_SPACING_SLOT,
822 );
823 if let Some(val) = get_word_val_attr(e, &prefixes)? {
824 rpr.spacing = Some(Twips(parse_integer_measurement(
825 name.as_ref(),
826 b"w:val",
827 &val,
828 )?));
829 }
830 } else if is_word_element(name.as_ref(), b"w", &prefixes) {
831 record_rpr_modeled(
832 &mut rpr,
833 &mut pending_raw,
834 &mut occurrences,
835 RPR_WIDTH_SLOT,
836 );
837 if let Some(val) = get_word_val_attr(e, &prefixes)? {
838 rpr.width_scale = Some(val.parse()?);
839 }
840 } else if is_word_element(name.as_ref(), b"position", &prefixes) {
841 record_rpr_modeled(
842 &mut rpr,
843 &mut pending_raw,
844 &mut occurrences,
845 RPR_POSITION_SLOT,
846 );
847 if let Some(val) = get_word_val_attr(e, &prefixes)? {
848 rpr.position =
849 Some(parse_integer_measurement(name.as_ref(), b"w:val", &val)?);
850 }
851 } else if is_word_element(name.as_ref(), b"shd", &prefixes) {
852 record_rpr_modeled(
853 &mut rpr,
854 &mut pending_raw,
855 &mut occurrences,
856 RPR_SHADING_SLOT,
857 );
858 rpr.shading = Some(Box::new(CT_Shd::from_xml_attrs_with_prefixes(
859 e, &prefixes,
860 )?));
861 } else if is_word_element(name.as_ref(), b"vanish", &prefixes) {
862 record_rpr_modeled(
863 &mut rpr,
864 &mut pending_raw,
865 &mut occurrences,
866 RPR_VANISH_SLOT,
867 );
868 rpr.vanish = Some(parse_word_toggle(e, &prefixes)?);
869 } else if let Some((slot, toggle)) =
870 rpr_toggle_slot(&mut rpr, name.as_ref(), &prefixes)
871 {
872 let value = parse_word_toggle(e, &prefixes)?;
873 *toggle = Some(value);
874 record_rpr_modeled(&mut rpr, &mut pending_raw, &mut occurrences, slot);
875 } else if is_word_element(name.as_ref(), b"kern", &prefixes)
876 && let Some(val) = get_word_val_attr(e, &prefixes)?
877 {
878 record_rpr_modeled(
882 &mut rpr,
883 &mut pending_raw,
884 &mut occurrences,
885 RPR_KERN_SLOT,
886 );
887 rpr.kern = Some(HalfPoint(parse_integer_measurement(
888 name.as_ref(),
889 b"w:val",
890 &val,
891 )?));
892 } else if is_word_element(name.as_ref(), b"effect", &prefixes) {
893 record_rpr_modeled(
894 &mut rpr,
895 &mut pending_raw,
896 &mut occurrences,
897 RPR_EFFECT_SLOT,
898 );
899 rpr.effect = Some(
900 get_word_val_attr(e, &prefixes)?
901 .map_or(ST_TextEffect::None, |val| ST_TextEffect::from_str(&val)),
902 );
903 } else if is_word_element(name.as_ref(), b"bdr", &prefixes) {
904 record_rpr_modeled(
905 &mut rpr,
906 &mut pending_raw,
907 &mut occurrences,
908 RPR_BORDER_SLOT,
909 );
910 rpr.border = Some(Box::new(CT_BorderEdge::from_xml_attrs_with_prefixes(
911 e, &prefixes,
912 )?));
913 } else if is_word_element(name.as_ref(), b"fitText", &prefixes)
914 && let Some(fit_text) = CT_FitText::from_xml_attrs(e, &prefixes)?
915 {
916 record_rpr_modeled(
919 &mut rpr,
920 &mut pending_raw,
921 &mut occurrences,
922 RPR_FIT_TEXT_SLOT,
923 );
924 rpr.fit_text = Some(Box::new(fit_text));
925 } else if is_word_element(name.as_ref(), b"em", &prefixes) {
926 record_rpr_modeled(
927 &mut rpr,
928 &mut pending_raw,
929 &mut occurrences,
930 RPR_EMPHASIS_MARK_SLOT,
931 );
932 rpr.emphasis_mark = Some(
933 get_word_val_attr(e, &prefixes)?
934 .map_or(ST_Em::None, |val| ST_Em::from_str(&val)),
935 );
936 } else if is_word_element(name.as_ref(), b"eastAsianLayout", &prefixes) {
937 record_rpr_modeled(
938 &mut rpr,
939 &mut pending_raw,
940 &mut occurrences,
941 RPR_EAST_ASIAN_LAYOUT_SLOT,
942 );
943 rpr.east_asian_layout =
944 Some(Box::new(CT_EastAsianLayout::from_xml_attrs(e, &prefixes)?));
945 } else if is_word_element(name.as_ref(), b"rtl", &prefixes) {
946 record_rpr_modeled(
947 &mut rpr,
948 &mut pending_raw,
949 &mut occurrences,
950 RPR_RTL_SLOT,
951 );
952 rpr.rtl = Some(parse_word_toggle(e, &prefixes)?);
953 rtl_carrier_required = toggle_has_unsupported_attributes(e, &prefixes)?;
954 let raw = crate::text::raw_with_external_bindings(
955 &capture_empty_element(e)?,
956 owner_bindings,
957 )?;
958 record_modeled_toggle_candidate(
959 &mut rpr.revision_xml,
960 &mut rpr.revision_xml_positions,
961 raw,
962 RPR_RTL_SLOT,
963 occurrences[RPR_RTL_SLOT as usize] - 1,
964 );
965 } else if is_word_element(name.as_ref(), b"lang", &prefixes) {
966 record_rpr_modeled(
967 &mut rpr,
968 &mut pending_raw,
969 &mut occurrences,
970 RPR_LANG_SLOT,
971 );
972 parse_language_attributes(&mut rpr, e, &prefixes)?;
973 } else if is_word_element(name.as_ref(), b"ins", &prefixes)
974 || is_word_element(name.as_ref(), b"del", &prefixes)
975 {
976 let raw = crate::text::raw_with_external_bindings(
977 &capture_empty_element(e)?,
978 owner_bindings,
979 )?;
980 if let Some(revision) = CT_Revision::from_raw(raw.clone(), &prefixes) {
981 record_rpr_modeled(
982 &mut rpr,
983 &mut pending_raw,
984 &mut occurrences,
985 RPR_MARKER_SLOT,
986 );
987 rpr.revision_markers.push(revision);
988 } else {
989 pending_raw.push(raw);
990 }
991 } else if is_word_element(name.as_ref(), b"rPrChange", &prefixes) {
992 let raw = crate::text::raw_with_external_bindings(
993 &capture_empty_element(e)?,
994 owner_bindings,
995 )?;
996 if let Some(revision) = CT_Revision::from_raw(raw.clone(), &prefixes) {
997 if let Some(previous) = rpr.change.take() {
998 rpr.revision_xml
999 .insert(change_raw_index, previous.into_raw_xml());
1000 rpr.revision_xml_positions
1001 .insert(change_raw_index, (RPR_CHANGE_SLOT, 0));
1002 }
1003 record_rpr_modeled(
1004 &mut rpr,
1005 &mut pending_raw,
1006 &mut occurrences,
1007 RPR_CHANGE_SLOT,
1008 );
1009 rpr.change = Some(Box::new(revision));
1010 change_raw_index = rpr.revision_xml.len();
1011 } else {
1012 flush_rpr_raw(&mut rpr, &mut pending_raw, RPR_CHANGE_SLOT, 0);
1013 record_rpr_raw_at(&mut rpr, raw, RPR_CHANGE_SLOT, 0);
1014 }
1015 } else {
1016 let raw = crate::text::raw_with_external_bindings(
1017 &capture_empty_element(e)?,
1018 owner_bindings,
1019 )?;
1020 if let Some(slot) = rpr_schema_slot(name.as_ref(), &prefixes) {
1021 record_rpr_raw_at(&mut rpr, raw, slot, 0);
1022 } else {
1023 pending_raw.push(raw);
1024 }
1025 }
1026 }
1027 Ok(Event::Start(ref e)) => {
1028 let prefixes = word_prefixes_at(e, word_prefixes)?;
1029 if is_word_element(e.name().as_ref(), b"rtl", &prefixes) {
1030 let captured = capture_element(reader, e)?;
1031 let raw =
1032 crate::text::raw_with_external_bindings(&captured, owner_bindings)?;
1033 if toggle_element_is_explicitly_empty(&captured)? {
1034 record_rpr_modeled(
1035 &mut rpr,
1036 &mut pending_raw,
1037 &mut occurrences,
1038 RPR_RTL_SLOT,
1039 );
1040 rpr.rtl = Some(parse_word_toggle(e, &prefixes)?);
1041 rtl_carrier_required = toggle_has_unsupported_attributes(e, &prefixes)?;
1042 record_modeled_toggle_candidate(
1043 &mut rpr.revision_xml,
1044 &mut rpr.revision_xml_positions,
1045 raw,
1046 RPR_RTL_SLOT,
1047 occurrences[RPR_RTL_SLOT as usize] - 1,
1048 );
1049 } else {
1050 let occurrence = occurrences[RPR_RTL_SLOT as usize];
1051 occurrences[RPR_RTL_SLOT as usize] += 1;
1052 record_rpr_raw_at(&mut rpr, raw, RPR_RTL_SLOT, occurrence);
1053 }
1054 } else if is_word_element(e.name().as_ref(), b"lang", &prefixes) {
1055 let captured = capture_element(reader, e)?;
1056 let raw =
1057 crate::text::raw_with_external_bindings(&captured, owner_bindings)?;
1058 if language_element_is_explicitly_empty(&captured)? {
1059 record_rpr_modeled(
1060 &mut rpr,
1061 &mut pending_raw,
1062 &mut occurrences,
1063 RPR_LANG_SLOT,
1064 );
1065 parse_language_attributes(&mut rpr, e, &prefixes)?;
1066 } else {
1067 record_rpr_raw_at(
1068 &mut rpr,
1069 raw,
1070 RPR_LANG_SLOT,
1071 occurrences[RPR_LANG_SLOT as usize],
1072 );
1073 }
1074 } else if is_word_element(e.name().as_ref(), b"ins", &prefixes)
1075 || is_word_element(e.name().as_ref(), b"del", &prefixes)
1076 {
1077 let raw = crate::text::raw_with_external_bindings(
1078 &capture_element(reader, e)?,
1079 owner_bindings,
1080 )?;
1081 if let Some(revision) = CT_Revision::from_raw(raw.clone(), &prefixes) {
1082 record_rpr_modeled(
1083 &mut rpr,
1084 &mut pending_raw,
1085 &mut occurrences,
1086 RPR_MARKER_SLOT,
1087 );
1088 rpr.revision_markers.push(revision);
1089 } else {
1090 pending_raw.push(raw);
1091 }
1092 } else if is_word_element(e.name().as_ref(), b"rPrChange", &prefixes) {
1093 let raw = crate::text::raw_with_external_bindings(
1094 &capture_element(reader, e)?,
1095 owner_bindings,
1096 )?;
1097 if let Some(revision) = CT_Revision::from_raw(raw.clone(), &prefixes) {
1098 if let Some(previous) = rpr.change.take() {
1099 rpr.revision_xml
1100 .insert(change_raw_index, previous.into_raw_xml());
1101 rpr.revision_xml_positions
1102 .insert(change_raw_index, (RPR_CHANGE_SLOT, 0));
1103 }
1104 record_rpr_modeled(
1105 &mut rpr,
1106 &mut pending_raw,
1107 &mut occurrences,
1108 RPR_CHANGE_SLOT,
1109 );
1110 rpr.change = Some(Box::new(revision));
1111 change_raw_index = rpr.revision_xml.len();
1112 } else {
1113 flush_rpr_raw(&mut rpr, &mut pending_raw, RPR_CHANGE_SLOT, 0);
1114 record_rpr_raw_at(&mut rpr, raw, RPR_CHANGE_SLOT, 0);
1115 }
1116 } else {
1117 let raw = crate::text::raw_with_external_bindings(
1118 &capture_element(reader, e)?,
1119 owner_bindings,
1120 )?;
1121 if let Some(slot) = rpr_schema_slot(e.name().as_ref(), &prefixes) {
1122 record_rpr_raw_at(&mut rpr, raw, slot, 0);
1123 } else {
1124 pending_raw.push(raw);
1125 }
1126 }
1127 }
1128 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"rPr") => {
1129 break;
1130 }
1131 Ok(Event::Eof) => break,
1132 Err(e) => return Err(e.into()),
1133 _ => {}
1134 }
1135 buf.clear();
1136 }
1137
1138 let final_slot = if rpr.change.is_some() {
1139 RPR_CHANGE_SLOT
1140 } else {
1141 RPR_END_SLOT
1142 };
1143 flush_rpr_raw(&mut rpr, &mut pending_raw, final_slot, 0);
1144 remove_redundant_modeled_toggle_candidate(
1145 &mut rpr.revision_xml,
1146 &mut rpr.revision_xml_positions,
1147 RPR_RTL_SLOT,
1148 rtl_carrier_required,
1149 );
1150
1151 Ok(rpr)
1152 }
1153
1154 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1155 self.to_xml_with_word_override(writer, None)
1156 }
1157
1158 pub(crate) fn to_xml_with_word_override<W: std::io::Write>(
1159 &self,
1160 writer: &mut Writer<W>,
1161 foreign_word_namespace: Option<&str>,
1162 ) -> Result<()> {
1163 if self.is_empty() {
1164 return Ok(());
1165 }
1166
1167 if !self.revision_xml.is_empty()
1168 && self.revision_xml_positions.len() == self.revision_xml.len()
1169 {
1170 let mut modeled = self.clone();
1171 modeled.revision_xml.clear();
1172 modeled.revision_xml_positions.clear();
1173 let mut generated = Writer::new(Vec::new());
1174 if modeled.is_empty() {
1175 generated.write_event(Event::Start(BytesStart::new("w:rPr")))?;
1176 generated.write_event(Event::End(BytesEnd::new("w:rPr")))?;
1177 } else {
1178 modeled.to_xml_with_word_override(&mut generated, foreign_word_namespace)?;
1179 }
1180 return write_rpr_with_positioned_raw(
1181 writer,
1182 &generated.into_inner(),
1183 self,
1184 foreign_word_namespace,
1185 );
1186 }
1187
1188 let mut buf = itoa::Buffer::new();
1189 writer.write_event(Event::Start(BytesStart::new("w:rPr")))?;
1190
1191 if let Some(ref style_id) = self.style_id {
1192 let mut e = BytesStart::new("w:rStyle");
1193 e.push_attribute(("w:val", style_id.as_str()));
1194 writer.write_event(Event::Empty(e))?;
1195 }
1196
1197 if self.has_fonts() {
1199 let mut e = BytesStart::new("w:rFonts");
1200 push_retained_attributes(&mut e, &self.font_extra_attributes);
1201 if let Some(ref f) = self.font_hint {
1202 e.push_attribute(("w:hint", f.as_str()));
1203 }
1204 if let Some(ref f) = self.font_ascii {
1205 e.push_attribute(("w:ascii", f.as_str()));
1206 }
1207 if let Some(ref f) = self.font_hansi {
1208 e.push_attribute(("w:hAnsi", f.as_str()));
1209 }
1210 if let Some(ref f) = self.font_east_asia {
1211 e.push_attribute(("w:eastAsia", f.as_str()));
1212 }
1213 if let Some(ref f) = self.font_cs {
1214 e.push_attribute(("w:cs", f.as_str()));
1215 }
1216 if let Some(ref f) = self.font_ascii_theme {
1217 e.push_attribute(("w:asciiTheme", f.as_str()));
1218 }
1219 if let Some(ref f) = self.font_hansi_theme {
1220 e.push_attribute(("w:hAnsiTheme", f.as_str()));
1221 }
1222 if let Some(ref f) = self.font_east_asia_theme {
1223 e.push_attribute(("w:eastAsiaTheme", f.as_str()));
1224 }
1225 if let Some(ref f) = self.font_cs_theme {
1226 e.push_attribute(("w:cstheme", f.as_str()));
1227 }
1228 writer.write_event(Event::Empty(e))?;
1229 }
1230
1231 if let Some(bold) = self.bold {
1232 write_toggle(writer, "w:b", bold)?;
1233 }
1234 if let Some(bold_cs) = self.bold_cs {
1235 write_toggle(writer, "w:bCs", bold_cs)?;
1236 }
1237 if let Some(italic) = self.italic {
1238 write_toggle(writer, "w:i", italic)?;
1239 }
1240 if let Some(italic_cs) = self.italic_cs {
1241 write_toggle(writer, "w:iCs", italic_cs)?;
1242 }
1243 if let Some(caps) = self.caps {
1244 write_toggle(writer, "w:caps", caps)?;
1245 }
1246 if let Some(small_caps) = self.small_caps {
1247 write_toggle(writer, "w:smallCaps", small_caps)?;
1248 }
1249 if let Some(strike) = self.strike {
1250 write_toggle(writer, "w:strike", strike)?;
1251 }
1252 if let Some(dstrike) = self.dstrike {
1253 write_toggle(writer, "w:dstrike", dstrike)?;
1254 }
1255 if let Some(outline) = self.outline {
1256 write_toggle(writer, "w:outline", outline)?;
1257 }
1258 if let Some(shadow) = self.shadow {
1259 write_toggle(writer, "w:shadow", shadow)?;
1260 }
1261 if let Some(emboss) = self.emboss {
1262 write_toggle(writer, "w:emboss", emboss)?;
1263 }
1264 if let Some(imprint) = self.imprint {
1265 write_toggle(writer, "w:imprint", imprint)?;
1266 }
1267 if let Some(no_proof) = self.no_proof {
1268 write_toggle(writer, "w:noProof", no_proof)?;
1269 }
1270 if let Some(snap_to_grid) = self.snap_to_grid {
1271 write_toggle(writer, "w:snapToGrid", snap_to_grid)?;
1272 }
1273 if let Some(vanish) = self.vanish {
1274 write_toggle(writer, "w:vanish", vanish)?;
1275 }
1276 if let Some(web_hidden) = self.web_hidden {
1277 write_toggle(writer, "w:webHidden", web_hidden)?;
1278 }
1279
1280 if self.has_color() {
1281 let mut e = BytesStart::new("w:color");
1282 push_retained_attributes(&mut e, &self.color_extra_attributes);
1283 if let Some(ref color) = self.color {
1284 e.push_attribute(("w:val", color.as_str()));
1285 }
1286 if let Some(ref tc) = self.color_theme {
1287 e.push_attribute(("w:themeColor", tc.as_str()));
1288 }
1289 if let Some(tint) = self.color_theme_tint {
1290 push_uchar_hex(&mut e, "w:themeTint", tint);
1291 }
1292 if let Some(shade) = self.color_theme_shade {
1293 push_uchar_hex(&mut e, "w:themeShade", shade);
1294 }
1295 writer.write_event(Event::Empty(e))?;
1296 }
1297
1298 if let Some(ref spacing) = self.spacing {
1299 let mut e = BytesStart::new("w:spacing");
1300 e.push_attribute(("w:val", buf.format(spacing.0)));
1301 writer.write_event(Event::Empty(e))?;
1302 }
1303 if let Some(ws) = self.width_scale {
1304 let mut e = BytesStart::new("w:w");
1305 e.push_attribute(("w:val", buf.format(ws)));
1306 writer.write_event(Event::Empty(e))?;
1307 }
1308 if let Some(ref kern) = self.kern {
1309 let mut e = BytesStart::new("w:kern");
1310 e.push_attribute(("w:val", buf.format(kern.0)));
1311 writer.write_event(Event::Empty(e))?;
1312 }
1313 if let Some(pos) = self.position {
1314 let mut e = BytesStart::new("w:position");
1315 e.push_attribute(("w:val", buf.format(pos)));
1316 writer.write_event(Event::Empty(e))?;
1317 }
1318
1319 if let Some(ref sz) = self.sz {
1320 let mut e = BytesStart::new("w:sz");
1321 e.push_attribute(("w:val", buf.format(sz.0)));
1322 writer.write_event(Event::Empty(e))?;
1323 }
1324 if let Some(ref sz_cs) = self.sz_cs {
1325 let mut e = BytesStart::new("w:szCs");
1326 e.push_attribute(("w:val", buf.format(sz_cs.0)));
1327 writer.write_event(Event::Empty(e))?;
1328 }
1329
1330 if let Some(ref highlight) = self.highlight {
1331 let mut e = BytesStart::new("w:highlight");
1332 e.push_attribute(("w:val", highlight.to_str()));
1333 writer.write_event(Event::Empty(e))?;
1334 }
1335
1336 if let Some(underline) = self.underline {
1337 let mut e = BytesStart::new("w:u");
1338 e.push_attribute(("w:val", underline.to_str()));
1339 writer.write_event(Event::Empty(e))?;
1340 }
1341
1342 if let Some(ref effect) = self.effect {
1343 let mut e = BytesStart::new("w:effect");
1344 e.push_attribute(("w:val", effect.as_str()));
1345 writer.write_event(Event::Empty(e))?;
1346 }
1347 if let Some(ref border) = self.border {
1348 border.to_xml(writer, "w:bdr")?;
1349 }
1350
1351 if let Some(ref shd) = self.shading {
1352 shd.write_xml(writer, "w:shd")?;
1353 }
1354
1355 if let Some(ref fit_text) = self.fit_text {
1356 fit_text.write_xml(writer)?;
1357 }
1358
1359 if let Some(ref vert_align) = self.vert_align {
1360 let mut e = BytesStart::new("w:vertAlign");
1361 e.push_attribute(("w:val", vert_align.as_str()));
1362 writer.write_event(Event::Empty(e))?;
1363 }
1364
1365 if let Some(rtl) = self.rtl {
1366 write_toggle(writer, "w:rtl", rtl)?;
1367 }
1368 if let Some(complex_script) = self.complex_script {
1369 write_toggle(writer, "w:cs", complex_script)?;
1370 }
1371 if let Some(ref emphasis_mark) = self.emphasis_mark {
1372 let mut e = BytesStart::new("w:em");
1373 e.push_attribute(("w:val", emphasis_mark.as_str()));
1374 writer.write_event(Event::Empty(e))?;
1375 }
1376
1377 if self.language.is_some()
1378 || self.language_east_asia.is_some()
1379 || self.language_bidi.is_some()
1380 || !self.language_extra_attributes.is_empty()
1381 {
1382 let mut e = BytesStart::new("w:lang");
1383 if let Some(language) = &self.language {
1384 e.push_attribute(("w:val", language.as_str()));
1385 }
1386 if let Some(language) = &self.language_east_asia {
1387 e.push_attribute(("w:eastAsia", language.as_str()));
1388 }
1389 if let Some(language) = &self.language_bidi {
1390 e.push_attribute(("w:bidi", language.as_str()));
1391 }
1392 for (name, value) in &self.language_extra_attributes {
1393 e.push_attribute((name.as_str(), value.as_str()));
1394 }
1395 writer.write_event(Event::Empty(e))?;
1396 }
1397
1398 if let Some(ref layout) = self.east_asian_layout {
1399 layout.write_xml(writer)?;
1400 }
1401 if let Some(spec_vanish) = self.spec_vanish {
1402 write_toggle(writer, "w:specVanish", spec_vanish)?;
1403 }
1404 if let Some(office_math) = self.office_math {
1405 write_toggle(writer, "w:oMath", office_math)?;
1406 }
1407
1408 for revision in &self.revision_markers {
1409 revision.write_xml_with_word_override(writer, foreign_word_namespace)?;
1410 }
1411 for raw in &self.revision_xml {
1412 crate::text::write_raw_with_word_override(writer, raw, foreign_word_namespace)?;
1413 }
1414 if let Some(change) = &self.change {
1415 change.write_xml_with_word_override(writer, foreign_word_namespace)?;
1416 }
1417
1418 writer.write_event(Event::End(BytesEnd::new("w:rPr")))?;
1419 Ok(())
1420 }
1421
1422 fn has_fonts(&self) -> bool {
1424 self.font_ascii.is_some()
1425 || self.font_hansi.is_some()
1426 || self.font_east_asia.is_some()
1427 || self.font_cs.is_some()
1428 || self.font_ascii_theme.is_some()
1429 || self.font_hansi_theme.is_some()
1430 || self.font_east_asia_theme.is_some()
1431 || self.font_cs_theme.is_some()
1432 || self.font_hint.is_some()
1433 || !self.font_extra_attributes.is_empty()
1434 }
1435
1436 fn has_color(&self) -> bool {
1438 self.color.is_some()
1439 || self.color_theme.is_some()
1440 || self.color_theme_tint.is_some()
1441 || self.color_theme_shade.is_some()
1442 || !self.color_extra_attributes.is_empty()
1443 }
1444
1445 fn is_empty(&self) -> bool {
1446 self.style_id.is_none()
1447 && self.font_ascii.is_none()
1448 && self.font_hansi.is_none()
1449 && self.font_east_asia.is_none()
1450 && self.font_cs.is_none()
1451 && self.font_ascii_theme.is_none()
1452 && self.font_hansi_theme.is_none()
1453 && self.font_east_asia_theme.is_none()
1454 && self.font_cs_theme.is_none()
1455 && self.font_hint.is_none()
1456 && self.font_extra_attributes.is_empty()
1457 && self.bold.is_none()
1458 && self.bold_cs.is_none()
1459 && self.italic.is_none()
1460 && self.italic_cs.is_none()
1461 && self.underline.is_none()
1462 && self.strike.is_none()
1463 && self.dstrike.is_none()
1464 && self.sz.is_none()
1465 && self.sz_cs.is_none()
1466 && self.color.is_none()
1467 && self.color_theme.is_none()
1468 && self.color_theme_tint.is_none()
1469 && self.color_theme_shade.is_none()
1470 && self.color_extra_attributes.is_empty()
1471 && self.highlight.is_none()
1472 && self.caps.is_none()
1473 && self.small_caps.is_none()
1474 && self.vert_align.is_none()
1475 && self.spacing.is_none()
1476 && self.width_scale.is_none()
1477 && self.position.is_none()
1478 && self.shading.is_none()
1479 && self.vanish.is_none()
1480 && self.outline.is_none()
1481 && self.shadow.is_none()
1482 && self.emboss.is_none()
1483 && self.imprint.is_none()
1484 && self.no_proof.is_none()
1485 && self.snap_to_grid.is_none()
1486 && self.web_hidden.is_none()
1487 && self.kern.is_none()
1488 && self.effect.is_none()
1489 && self.border.is_none()
1490 && self.fit_text.is_none()
1491 && self.complex_script.is_none()
1492 && self.emphasis_mark.is_none()
1493 && self.east_asian_layout.is_none()
1494 && self.spec_vanish.is_none()
1495 && self.office_math.is_none()
1496 && self.rtl.is_none()
1497 && self.language.is_none()
1498 && self.language_east_asia.is_none()
1499 && self.language_bidi.is_none()
1500 && self.language_extra_attributes.is_empty()
1501 && self.revision_markers.is_empty()
1502 && self.change.is_none()
1503 && self.revision_xml.is_empty()
1504 }
1505
1506 pub fn merge_from(&mut self, other: &CT_RPr) {
1509 if let Some((color, alpha)) = other.solid_text_fill() {
1512 let mut retained = Vec::new();
1513 let mut positions = Vec::new();
1514 for (index, raw) in self.revision_xml.iter().enumerate() {
1515 let probe = CT_RPr {
1516 revision_xml: vec![raw.clone()],
1517 ..CT_RPr::default()
1518 };
1519 if probe.solid_text_fill().is_none() {
1520 retained.push(raw.clone());
1521 positions.push(
1522 self.revision_xml_positions
1523 .get(index)
1524 .copied()
1525 .unwrap_or((RPR_END_SLOT, 0)),
1526 );
1527 }
1528 }
1529 let amount = (alpha * 100_000.0).round() as u32;
1530 retained.push(format!("<w14:textFill xmlns:w14=\"http://schemas.microsoft.com/office/word/2010/wordml\"><w14:solidFill><w14:srgbClr w14:val=\"{color}\"><w14:alpha w14:val=\"{amount}\"/></w14:srgbClr></w14:solidFill></w14:textFill>").into_bytes());
1531 positions.push((RPR_END_SLOT, 0));
1532 self.revision_xml = retained;
1533 self.revision_xml_positions = positions;
1534 }
1535 if other.style_id.is_some() {
1536 self.style_id = other.style_id.clone();
1537 }
1538 if other.font_ascii.is_some() {
1539 self.font_ascii = other.font_ascii.clone();
1540 }
1541 if other.font_hansi.is_some() {
1542 self.font_hansi = other.font_hansi.clone();
1543 }
1544 if other.font_east_asia.is_some() {
1545 self.font_east_asia = other.font_east_asia.clone();
1546 }
1547 if other.font_cs.is_some() {
1548 self.font_cs = other.font_cs.clone();
1549 }
1550 if other.font_ascii_theme.is_some() {
1551 self.font_ascii_theme = other.font_ascii_theme.clone();
1552 }
1553 if other.font_hansi_theme.is_some() {
1554 self.font_hansi_theme = other.font_hansi_theme.clone();
1555 }
1556 if other.font_east_asia_theme.is_some() {
1557 self.font_east_asia_theme = other.font_east_asia_theme.clone();
1558 }
1559 if other.font_cs_theme.is_some() {
1560 self.font_cs_theme = other.font_cs_theme.clone();
1561 }
1562 if other.font_hint.is_some() {
1563 self.font_hint = other.font_hint.clone();
1564 }
1565 if !other.font_extra_attributes.is_empty() {
1566 self.font_extra_attributes = other.font_extra_attributes.clone();
1567 }
1568 if other.bold.is_some() {
1569 self.bold = other.bold;
1570 }
1571 if other.bold_cs.is_some() {
1572 self.bold_cs = other.bold_cs;
1573 }
1574 if other.italic.is_some() {
1575 self.italic = other.italic;
1576 }
1577 if other.italic_cs.is_some() {
1578 self.italic_cs = other.italic_cs;
1579 }
1580 if other.underline.is_some() {
1581 self.underline = other.underline;
1582 }
1583 if other.strike.is_some() {
1584 self.strike = other.strike;
1585 }
1586 if other.dstrike.is_some() {
1587 self.dstrike = other.dstrike;
1588 }
1589 if other.sz.is_some() {
1590 self.sz = other.sz;
1591 }
1592 if other.sz_cs.is_some() {
1593 self.sz_cs = other.sz_cs;
1594 }
1595 if other.color.is_some() {
1596 self.color = other.color.clone();
1597 }
1598 if other.color_theme.is_some() {
1599 self.color_theme = other.color_theme.clone();
1600 }
1601 if other.color_theme_tint.is_some() {
1602 self.color_theme_tint = other.color_theme_tint;
1603 }
1604 if other.color_theme_shade.is_some() {
1605 self.color_theme_shade = other.color_theme_shade;
1606 }
1607 if !other.color_extra_attributes.is_empty() {
1608 self.color_extra_attributes = other.color_extra_attributes.clone();
1609 }
1610 if other.highlight.is_some() {
1611 self.highlight = other.highlight;
1612 }
1613 if other.caps.is_some() {
1614 self.caps = other.caps;
1615 }
1616 if other.small_caps.is_some() {
1617 self.small_caps = other.small_caps;
1618 }
1619 if other.vert_align.is_some() {
1620 self.vert_align = other.vert_align.clone();
1621 }
1622 if other.spacing.is_some() {
1623 self.spacing = other.spacing;
1624 }
1625 if other.width_scale.is_some() {
1626 self.width_scale = other.width_scale;
1627 }
1628 if other.position.is_some() {
1629 self.position = other.position;
1630 }
1631 if other.shading.is_some() {
1632 self.shading = other.shading.clone();
1633 }
1634 if other.vanish.is_some() {
1635 self.vanish = other.vanish;
1636 }
1637 if other.outline.is_some() {
1638 self.outline = other.outline;
1639 }
1640 if other.shadow.is_some() {
1641 self.shadow = other.shadow;
1642 }
1643 if other.emboss.is_some() {
1644 self.emboss = other.emboss;
1645 }
1646 if other.imprint.is_some() {
1647 self.imprint = other.imprint;
1648 }
1649 if other.no_proof.is_some() {
1650 self.no_proof = other.no_proof;
1651 }
1652 if other.snap_to_grid.is_some() {
1653 self.snap_to_grid = other.snap_to_grid;
1654 }
1655 if other.web_hidden.is_some() {
1656 self.web_hidden = other.web_hidden;
1657 }
1658 if other.kern.is_some() {
1659 self.kern = other.kern;
1660 }
1661 if other.effect.is_some() {
1662 self.effect = other.effect.clone();
1663 }
1664 if other.border.is_some() {
1665 self.border = other.border.clone();
1666 }
1667 if other.fit_text.is_some() {
1668 self.fit_text = other.fit_text.clone();
1669 }
1670 if other.complex_script.is_some() {
1671 self.complex_script = other.complex_script;
1672 }
1673 if other.emphasis_mark.is_some() {
1674 self.emphasis_mark = other.emphasis_mark.clone();
1675 }
1676 if other.east_asian_layout.is_some() {
1677 self.east_asian_layout = other.east_asian_layout.clone();
1678 }
1679 if other.spec_vanish.is_some() {
1680 self.spec_vanish = other.spec_vanish;
1681 }
1682 if other.office_math.is_some() {
1683 self.office_math = other.office_math;
1684 }
1685 if other.rtl.is_some() {
1686 self.rtl = other.rtl;
1687 }
1688 if other.language.is_some() {
1689 self.language = other.language.clone();
1690 }
1691 if other.language_east_asia.is_some() {
1692 self.language_east_asia = other.language_east_asia.clone();
1693 }
1694 if other.language_bidi.is_some() {
1695 self.language_bidi = other.language_bidi.clone();
1696 }
1697 if !other.language_extra_attributes.is_empty() {
1698 self.language_extra_attributes = other.language_extra_attributes.clone();
1699 }
1700 }
1701}
1702
1703fn parse_language_attributes(
1704 rpr: &mut CT_RPr,
1705 element: &BytesStart<'_>,
1706 prefixes: &[String],
1707) -> Result<()> {
1708 for attribute in element.attributes() {
1709 let attribute = attribute?;
1710 let key = attribute.key.as_ref();
1711 let value = attribute
1712 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
1713 .into_owned();
1714 if is_word_attribute(key, b"val", prefixes) {
1715 rpr.language = Some(value);
1716 } else if is_word_attribute(key, b"eastAsia", prefixes) {
1717 rpr.language_east_asia = Some(value);
1718 } else if is_word_attribute(key, b"bidi", prefixes) {
1719 rpr.language_bidi = Some(value);
1720 } else {
1721 rpr.language_extra_attributes
1722 .push((std::str::from_utf8(key)?.to_owned(), value));
1723 }
1724 }
1725 Ok(())
1726}
1727
1728pub(crate) fn language_element_is_explicitly_empty(xml: &[u8]) -> Result<bool> {
1729 let mut reader = Reader::from_reader(xml);
1730 let mut buffer = Vec::new();
1731 if !matches!(reader.read_event_into(&mut buffer)?, Event::Start(_)) {
1732 return Ok(false);
1733 }
1734 buffer.clear();
1735 Ok(matches!(
1736 reader.read_event_into(&mut buffer)?,
1737 Event::End(_)
1738 ))
1739}
1740
1741fn write_rpr_with_positioned_raw<W: std::io::Write>(
1742 writer: &mut Writer<W>,
1743 generated: &[u8],
1744 rpr: &CT_RPr,
1745 foreign_word_namespace: Option<&str>,
1746) -> Result<()> {
1747 let mut raw_order = (0..rpr.revision_xml.len())
1748 .filter(|index| {
1749 replay_modeled_toggle_raw(
1750 rpr.revision_xml_positions[*index],
1751 rpr.revision_xml_positions[*index].0 != RPR_RTL_SLOT || rpr.rtl.is_some(),
1752 )
1753 })
1754 .collect::<Vec<_>>();
1755 raw_order.sort_by_key(|index| {
1756 let (slot, occurrence) = effective_rpr_raw_position(rpr, *index);
1757 (slot, occurrence, *index)
1758 });
1759 let mut raw_index = 0usize;
1760 let mut occurrences = [0usize; RPR_END_SLOT as usize + 1];
1761 let mut reader = Reader::from_reader(generated);
1762 reader.config_mut().trim_text(false);
1763 let mut buffer = Vec::new();
1764 let mut inside = false;
1765 loop {
1766 match reader.read_event_into(&mut buffer)? {
1767 Event::Start(element) if !inside => {
1768 inside = true;
1769 writer.write_event(Event::Start(element.into_owned()))?;
1770 }
1771 Event::Start(element) => {
1772 let slot = rpr_slot_for_name(element.local_name().as_ref());
1773 write_rpr_raw_before(
1774 writer,
1775 rpr,
1776 &raw_order,
1777 &mut raw_index,
1778 slot,
1779 occurrences[slot as usize],
1780 foreign_word_namespace,
1781 )?;
1782 let raw = capture_element(&mut reader, &element)?;
1783 writer.get_mut().write_all(&raw)?;
1784 occurrences[slot as usize] += 1;
1785 }
1786 Event::Empty(mut element) => {
1787 let slot = rpr_slot_for_name(element.local_name().as_ref());
1788 write_rpr_raw_before(
1789 writer,
1790 rpr,
1791 &raw_order,
1792 &mut raw_index,
1793 slot,
1794 occurrences[slot as usize],
1795 foreign_word_namespace,
1796 )?;
1797 append_modeled_toggle_attributes(
1798 &mut element,
1799 &rpr.revision_xml,
1800 &rpr.revision_xml_positions,
1801 slot,
1802 occurrences[slot as usize],
1803 )?;
1804 writer.write_event(Event::Empty(element.into_owned()))?;
1805 occurrences[slot as usize] += 1;
1806 }
1807 Event::End(element) if inside => {
1808 write_rpr_raw_before(
1809 writer,
1810 rpr,
1811 &raw_order,
1812 &mut raw_index,
1813 RPR_END_SLOT,
1814 0,
1815 foreign_word_namespace,
1816 )?;
1817 while let Some(index) = raw_order.get(raw_index).copied() {
1818 crate::text::write_raw_with_word_override(
1819 writer,
1820 &rpr.revision_xml[index],
1821 foreign_word_namespace,
1822 )?;
1823 raw_index += 1;
1824 }
1825 writer.write_event(Event::End(element.into_owned()))?;
1826 return Ok(());
1827 }
1828 Event::Eof => return Ok(()),
1829 event => writer.write_event(event.into_owned())?,
1830 }
1831 buffer.clear();
1832 }
1833}
1834
1835fn write_rpr_raw_before<W: std::io::Write>(
1836 writer: &mut Writer<W>,
1837 rpr: &CT_RPr,
1838 raw_order: &[usize],
1839 raw_index: &mut usize,
1840 slot: u8,
1841 occurrence: usize,
1842 foreign_word_namespace: Option<&str>,
1843) -> Result<()> {
1844 while let Some(index) = raw_order.get(*raw_index).copied() {
1845 let position = effective_rpr_raw_position(rpr, index);
1846 if position.0 > slot || (position.0 == slot && position.1 > occurrence) {
1847 break;
1848 }
1849 crate::text::write_raw_with_word_override(
1850 writer,
1851 &rpr.revision_xml[index],
1852 foreign_word_namespace,
1853 )?;
1854 *raw_index += 1;
1855 }
1856 Ok(())
1857}
1858
1859fn effective_rpr_raw_position(rpr: &CT_RPr, index: usize) -> (u8, usize) {
1860 let mut position = rpr.revision_xml_positions[index];
1861 position.1 = raw_occurrence(position);
1862 if position.0 == RPR_RTL_SLOT
1863 && let Some((carrier_index, carrier_occurrence)) = rpr
1864 .revision_xml_positions
1865 .iter()
1866 .enumerate()
1867 .find(|candidate| {
1868 candidate.1.0 == RPR_RTL_SLOT && raw_is_modeled_attribute_carrier(*candidate.1)
1869 })
1870 .map(|(carrier_index, candidate)| (carrier_index, raw_occurrence(*candidate)))
1871 {
1872 position.1 = usize::from(
1873 position.1 > carrier_occurrence
1874 || (position.1 == carrier_occurrence && index > carrier_index),
1875 );
1876 }
1877 if rpr.change.is_some() && position.0 >= RPR_CHANGE_SLOT {
1878 (RPR_CHANGE_SLOT, 0)
1879 } else {
1880 position
1881 }
1882}
1883
1884fn rpr_slot_for_name(local: &[u8]) -> u8 {
1885 match local {
1886 b"rStyle" => RPR_STYLE_SLOT,
1887 b"rFonts" => RPR_FONTS_SLOT,
1888 b"b" => RPR_BOLD_SLOT,
1889 b"bCs" => RPR_BOLD_CS_SLOT,
1890 b"i" => RPR_ITALIC_SLOT,
1891 b"iCs" => RPR_ITALIC_CS_SLOT,
1892 b"caps" => RPR_CAPS_SLOT,
1893 b"smallCaps" => RPR_SMALL_CAPS_SLOT,
1894 b"strike" => RPR_STRIKE_SLOT,
1895 b"dstrike" => RPR_DSTRIKE_SLOT,
1896 b"outline" => 10,
1897 b"shadow" => 11,
1898 b"emboss" => 12,
1899 b"imprint" => 13,
1900 b"noProof" => 14,
1901 b"snapToGrid" => 15,
1902 b"vanish" => RPR_VANISH_SLOT,
1903 b"webHidden" => 17,
1904 b"color" => RPR_COLOR_SLOT,
1905 b"spacing" => RPR_SPACING_SLOT,
1906 b"w" => RPR_WIDTH_SLOT,
1907 b"kern" => 21,
1908 b"position" => RPR_POSITION_SLOT,
1909 b"sz" => RPR_SIZE_SLOT,
1910 b"szCs" => RPR_SIZE_CS_SLOT,
1911 b"highlight" => RPR_HIGHLIGHT_SLOT,
1912 b"u" => RPR_UNDERLINE_SLOT,
1913 b"effect" => 27,
1914 b"bdr" => 28,
1915 b"shd" => RPR_SHADING_SLOT,
1916 b"fitText" => 30,
1917 b"vertAlign" => RPR_VERT_ALIGN_SLOT,
1918 b"rtl" => 32,
1919 b"cs" => 33,
1920 b"em" => 34,
1921 b"lang" => RPR_LANG_SLOT,
1922 b"eastAsianLayout" => 36,
1923 b"specVanish" => 37,
1924 b"oMath" => 38,
1925 b"ins" | b"del" => RPR_MARKER_SLOT,
1926 b"rPrChange" => RPR_CHANGE_SLOT,
1927 _ => RPR_END_SLOT,
1928 }
1929}
1930
1931pub(crate) fn validate_checked_property_child(
1933 element: &BytesStart<'_>,
1934 prefixes: &[String],
1935) -> Result<()> {
1936 let local = element.local_name();
1937 let name = local.as_ref();
1938 let value = get_word_val_attr(element, prefixes)?;
1939 if matches!(
1940 name,
1941 b"b" | b"bCs"
1942 | b"i"
1943 | b"iCs"
1944 | b"caps"
1945 | b"smallCaps"
1946 | b"strike"
1947 | b"dstrike"
1948 | b"outline"
1949 | b"shadow"
1950 | b"emboss"
1951 | b"imprint"
1952 | b"noProof"
1953 | b"snapToGrid"
1954 | b"vanish"
1955 | b"webHidden"
1956 | b"rtl"
1957 | b"cs"
1958 | b"specVanish"
1959 | b"oMath"
1960 ) {
1961 if value
1962 .as_deref()
1963 .is_some_and(|value| !matches!(value, "true" | "false" | "on" | "off" | "1" | "0"))
1964 {
1965 return Err(crate::error::OxmlError::InvalidValue(
1966 "element XML run toggle has an invalid Word boolean".into(),
1967 ));
1968 }
1969 } else if name == b"u" {
1970 if value
1971 .as_deref()
1972 .is_some_and(|value| ST_Underline::from_str(value).is_err())
1973 {
1974 return Err(crate::error::OxmlError::InvalidValue(
1975 "element XML underline has an invalid w:val".into(),
1976 ));
1977 }
1978 } else if matches!(
1979 name,
1980 b"sz"
1981 | b"szCs"
1982 | b"kern"
1983 | b"spacing"
1984 | b"position"
1985 | b"w"
1986 | b"rStyle"
1987 | b"highlight"
1988 | b"vertAlign"
1989 | b"color"
1990 ) {
1991 let value = value.ok_or_else(|| {
1992 crate::error::OxmlError::InvalidValue(format!(
1993 "element XML {} requires w:val",
1994 String::from_utf8_lossy(name)
1995 ))
1996 })?;
1997 let valid = match name {
1998 b"sz" | b"szCs" | b"kern" | b"w" => {
1999 parse_integer_measurement::<i32>(element.name().as_ref(), b"w:val", &value)
2000 .is_ok_and(|value| value >= 0)
2001 }
2002 b"spacing" | b"position" => {
2003 parse_integer_measurement::<i32>(element.name().as_ref(), b"w:val", &value).is_ok()
2004 }
2005 b"highlight" => ST_HighlightColor::from_str(&value).is_ok(),
2006 b"vertAlign" => matches!(value.as_str(), "baseline" | "superscript" | "subscript"),
2007 b"color" => {
2008 value == "auto"
2009 || (value.len() == 6 && value.bytes().all(|byte| byte.is_ascii_hexdigit()))
2010 }
2011 _ => !value.is_empty(),
2012 };
2013 if !valid {
2014 return Err(crate::error::OxmlError::InvalidValue(format!(
2015 "element XML {} has an invalid w:val",
2016 String::from_utf8_lossy(name)
2017 )));
2018 }
2019 }
2020 Ok(())
2021}
2022
2023pub(crate) fn rpr_schema_slot(name: &[u8], word_prefixes: &[String]) -> Option<u8> {
2024 let local = name.rsplit(|byte| *byte == b':').next().unwrap_or(name);
2025 let slot = rpr_slot_for_name(local);
2026 (slot != RPR_END_SLOT && is_word_element(name, local, word_prefixes)).then_some(slot)
2027}
2028
2029#[cfg(test)]
2030mod tests {
2031 use super::*;
2032 use crate::namespace::W_NS;
2033
2034 fn parse_rpr(xml: &str) -> CT_RPr {
2035 let full = format!("<w:rPr>{xml}</w:rPr>");
2036 let mut reader = Reader::from_str(&full);
2037 reader.config_mut().trim_text(true);
2038 let mut buf = Vec::new();
2039 loop {
2040 match reader.read_event_into(&mut buf) {
2041 Ok(Event::Start(ref e)) if matches_local_name(e.name().as_ref(), b"rPr") => break,
2042 _ => {}
2043 }
2044 buf.clear();
2045 }
2046 CT_RPr::from_xml(&mut reader).unwrap()
2047 }
2048
2049 #[test]
2050 fn parse_basic_rpr() {
2051 let rpr = parse_rpr(r#"<w:b/><w:i/><w:sz w:val="24"/><w:color w:val="FF0000"/>"#);
2052 assert_eq!(rpr.bold, Some(true));
2053 assert_eq!(rpr.italic, Some(true));
2054 assert_eq!(rpr.sz, Some(HalfPoint(24)));
2055 assert_eq!(rpr.color, Some("FF0000".to_string()));
2056 }
2057
2058 #[test]
2059 fn parse_rpr_spacing() {
2060 let rpr = parse_rpr(r#"<w:spacing w:val="20"/><w:w w:val="150"/><w:position w:val="-4"/>"#);
2061 assert_eq!(rpr.spacing, Some(Twips(20)));
2062 assert_eq!(rpr.width_scale, Some(150));
2063 assert_eq!(rpr.position, Some(-4));
2064 }
2065
2066 #[test]
2067 fn round_trip_rpr() {
2068 let original = CT_RPr {
2069 bold: Some(true),
2070 italic: Some(true),
2071 sz: Some(HalfPoint(24)),
2072 color: Some("FF0000".to_string()),
2073 underline: Some(ST_Underline::Single),
2074 spacing: Some(Twips(20)),
2075 ..Default::default()
2076 };
2077
2078 let mut output = Vec::new();
2079 let mut writer = Writer::new(&mut output);
2080 original.to_xml(&mut writer).unwrap();
2081 let xml = String::from_utf8(output).unwrap();
2082
2083 let inner = xml
2084 .strip_prefix("<w:rPr>")
2085 .unwrap()
2086 .strip_suffix("</w:rPr>")
2087 .unwrap();
2088 let parsed = parse_rpr(inner);
2089 assert_eq!(parsed.bold, original.bold);
2090 assert_eq!(parsed.italic, original.italic);
2091 assert_eq!(parsed.sz, original.sz);
2092 assert_eq!(parsed.color, original.color);
2093 assert_eq!(parsed.underline, original.underline);
2094 assert_eq!(parsed.spacing, original.spacing);
2095 }
2096
2097 #[test]
2098 fn merge_rpr() {
2099 let mut base = CT_RPr {
2100 bold: Some(true),
2101 sz: Some(HalfPoint(24)),
2102 ..Default::default()
2103 };
2104 let override_rpr = CT_RPr {
2105 sz: Some(HalfPoint(28)),
2106 italic: Some(true),
2107 ..Default::default()
2108 };
2109 base.merge_from(&override_rpr);
2110 assert_eq!(base.bold, Some(true)); assert_eq!(base.sz, Some(HalfPoint(28))); assert_eq!(base.italic, Some(true)); }
2114
2115 #[test]
2116 fn run_language_parses_aliases_and_rejects_foreign_same_local_attributes() {
2117 let rpr = parse_rpr(&format!(
2118 r#"<q:lang xmlns:q="{}" xmlns:x="urn:foreign" q:val="en-US" q:eastAsia="zh-CN" q:bidi="ar-SA" x:val="ignored" x:kept="raw"/>"#,
2119 W_NS
2120 ));
2121 assert_eq!(rpr.language.as_deref(), Some("en-US"));
2122 assert_eq!(rpr.language_east_asia.as_deref(), Some("zh-CN"));
2123 assert_eq!(rpr.language_bidi.as_deref(), Some("ar-SA"));
2124
2125 let mut output = Vec::new();
2126 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2127 let output = String::from_utf8(output).unwrap();
2128 assert!(output.contains(r#"<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA""#));
2129 assert!(output.contains(r#"x:kept="raw""#));
2130 assert!(!output.contains(r#"w:val="ignored""#));
2131
2132 let explicit = parse_rpr(&format!(
2133 r#"<q:lang xmlns:q="{}" q:val="en-GB"></q:lang>"#,
2134 W_NS
2135 ));
2136 assert_eq!(explicit.language.as_deref(), Some("en-GB"));
2137 }
2138
2139 #[test]
2140 fn run_language_round_trips_at_its_schema_slot_and_cascades() {
2141 let mut base = CT_RPr {
2142 language: Some("fr-FR".to_owned()),
2143 language_east_asia: Some("ja-JP".to_owned()),
2144 ..Default::default()
2145 };
2146 base.merge_from(&CT_RPr {
2147 language: Some("de-DE".to_owned()),
2148 language_bidi: Some("ar-SA".to_owned()),
2149 ..Default::default()
2150 });
2151 assert_eq!(base.language.as_deref(), Some("de-DE"));
2152 assert_eq!(base.language_east_asia.as_deref(), Some("ja-JP"));
2153 assert_eq!(base.language_bidi.as_deref(), Some("ar-SA"));
2154
2155 let mut output = Vec::new();
2156 base.to_xml(&mut Writer::new(&mut output)).unwrap();
2157 let output = String::from_utf8(output).unwrap();
2158 assert!(output.find("w:vertAlign").is_none());
2159 assert!(output.find("w:lang").unwrap() < output.rfind("</w:rPr>").unwrap());
2160 }
2161
2162 const F265_EVERY_NEW_CHILD: &str = concat!(
2165 r#"<w:outline/><w:shadow/><w:emboss/><w:imprint/><w:noProof/><w:snapToGrid/>"#,
2166 r#"<w:webHidden/><w:kern w:val="16"/><w:effect w:val="antsRed"/>"#,
2167 r#"<w:bdr w:val="single" w:sz="4" w:space="1" w:color="FF0000"/>"#,
2168 r#"<w:fitText w:val="1440" w:id="3"/><w:cs/><w:em w:val="dot"/>"#,
2169 r#"<w:eastAsianLayout w:id="7" w:combine="1" w:combineBrackets="round" w:vert="1" w:vertCompress="1"/>"#,
2170 r#"<w:specVanish/><w:oMath/>"#,
2171 );
2172
2173 #[test]
2174 fn every_new_run_property_parses_writes_and_merges() {
2175 let rpr = parse_rpr(F265_EVERY_NEW_CHILD);
2176 assert_eq!(rpr.outline, Some(true));
2177 assert_eq!(rpr.shadow, Some(true));
2178 assert_eq!(rpr.emboss, Some(true));
2179 assert_eq!(rpr.imprint, Some(true));
2180 assert_eq!(rpr.no_proof, Some(true));
2181 assert_eq!(rpr.snap_to_grid, Some(true));
2182 assert_eq!(rpr.web_hidden, Some(true));
2183 assert_eq!(rpr.kern, Some(HalfPoint(16)));
2184 assert_eq!(rpr.effect, Some(ST_TextEffect::AntsRed));
2185 assert_eq!(rpr.border.as_ref().map(|border| border.sz), Some(Some(4)));
2186 assert_eq!(
2187 rpr.fit_text.as_deref(),
2188 Some(&CT_FitText {
2189 val: Twips(1440),
2190 id: Some(3),
2191 extra_attributes: Vec::new(),
2192 })
2193 );
2194 assert_eq!(rpr.complex_script, Some(true));
2195 assert_eq!(rpr.emphasis_mark, Some(ST_Em::Dot));
2196 assert_eq!(
2197 rpr.east_asian_layout.as_deref(),
2198 Some(&CT_EastAsianLayout {
2199 id: Some(7),
2200 combine: Some(true),
2201 combine_brackets: Some("round".to_owned()),
2202 vert: Some(true),
2203 vert_compress: Some(true),
2204 extra_attributes: Vec::new(),
2205 })
2206 );
2207 assert_eq!(rpr.spec_vanish, Some(true));
2208 assert_eq!(rpr.office_math, Some(true));
2209
2210 for single in [
2213 "<w:outline/>",
2214 "<w:webHidden/>",
2215 r#"<w:kern w:val="18"/>"#,
2216 r#"<w:effect w:val="shimmer"/>"#,
2217 r#"<w:bdr w:val="double"/>"#,
2218 r#"<w:fitText w:val="720"/>"#,
2219 "<w:cs/>",
2220 r#"<w:em w:val="circle"/>"#,
2221 r#"<w:eastAsianLayout w:id="1"/>"#,
2222 "<w:specVanish/>",
2223 "<w:oMath/>",
2224 ] {
2225 let single = parse_rpr(single);
2226 assert!(!single.is_empty(), "{single:?}");
2227 let mut output = Vec::new();
2228 single.to_xml(&mut Writer::new(&mut output)).unwrap();
2229 assert!(!output.is_empty());
2230 }
2231
2232 let mut merged = CT_RPr::default();
2233 merged.merge_from(&rpr);
2234 assert_eq!(merged, rpr);
2235 }
2236
2237 #[test]
2238 fn new_run_properties_write_at_their_schema_ordinals() {
2239 let rpr = parse_rpr(F265_EVERY_NEW_CHILD);
2240 let mut output = Vec::new();
2241 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2242 let output = String::from_utf8(output).unwrap();
2243 let ordered = [
2244 "<w:outline/>",
2245 "<w:shadow/>",
2246 "<w:emboss/>",
2247 "<w:imprint/>",
2248 "<w:noProof/>",
2249 "<w:snapToGrid/>",
2250 "<w:webHidden/>",
2251 "<w:kern ",
2252 "<w:effect ",
2253 "<w:bdr ",
2254 "<w:fitText ",
2255 "<w:cs/>",
2256 "<w:em ",
2257 "<w:eastAsianLayout ",
2258 "<w:specVanish/>",
2259 "<w:oMath/>",
2260 ]
2261 .map(|needle| {
2262 output
2263 .find(needle)
2264 .unwrap_or_else(|| panic!("{needle} in {output}"))
2265 });
2266 assert!(ordered.windows(2).all(|pair| pair[0] < pair[1]), "{output}");
2267
2268 let reparsed = parse_rpr(
2269 output
2270 .strip_prefix("<w:rPr>")
2271 .unwrap()
2272 .strip_suffix("</w:rPr>")
2273 .unwrap(),
2274 );
2275 assert_eq!(reparsed, rpr);
2276 }
2277
2278 #[test]
2279 fn a_new_element_missing_its_required_value_stays_raw() {
2280 for source in ["<w:kern/>", r#"<w:fitText w:id="4"/>"#] {
2281 let rpr = parse_rpr(source);
2282 assert_eq!(rpr.kern, None, "{source}");
2283 assert_eq!(rpr.fit_text, None, "{source}");
2284
2285 let mut output = Vec::new();
2286 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2287 let output = String::from_utf8(output).unwrap();
2288 assert!(output.contains(source), "{source} missing from {output}");
2289 }
2290 }
2291
2292 #[test]
2293 fn new_run_properties_read_under_an_aliased_word_prefix() {
2294 let rpr = parse_rpr(&format!(
2295 concat!(
2296 r#"<q:outline xmlns:q="{ns}"/><q:kern xmlns:q="{ns}" q:val="20"/>"#,
2297 r#"<q:effect xmlns:q="{ns}" q:val="lights"/>"#,
2298 r#"<q:fitText xmlns:q="{ns}" q:val="960" q:id="4"/>"#,
2299 r#"<q:em xmlns:q="{ns}" q:val="comma"/>"#,
2300 r#"<q:eastAsianLayout xmlns:q="{ns}" q:vert="0"/>"#,
2301 ),
2302 ns = W_NS
2303 ));
2304 assert_eq!(rpr.outline, Some(true));
2305 assert_eq!(rpr.kern, Some(HalfPoint(20)));
2306 assert_eq!(rpr.effect, Some(ST_TextEffect::Lights));
2307 assert_eq!(rpr.fit_text.as_ref().map(|fit| fit.val), Some(Twips(960)));
2308 assert_eq!(rpr.emphasis_mark, Some(ST_Em::Comma));
2309 assert_eq!(
2310 rpr.east_asian_layout
2311 .as_ref()
2312 .and_then(|layout| layout.vert),
2313 Some(false)
2314 );
2315
2316 let mut output = Vec::new();
2317 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2318 let output = String::from_utf8(output).unwrap();
2319 assert!(output.contains("<w:outline/>"), "{output}");
2320 assert!(output.contains(r#"<w:kern w:val="20"/>"#), "{output}");
2321 assert!(output.contains(r#"<w:effect w:val="lights"/>"#), "{output}");
2322 }
2323
2324 #[test]
2325 fn producer_tokens_and_foreign_attributes_survive_the_new_elements() {
2326 let rpr = parse_rpr(concat!(
2327 r#"<w:rFonts xmlns:x="urn:producer" w:ascii="Arial" w:eastAsiaTheme="minorEastAsia" w:cstheme="minorBidi" w:hint="eastAsia" x:kept="rfonts"/>"#,
2328 r#"<w:color xmlns:x="urn:producer" w:val="4472C4" w:themeColor="accent1" w:themeTint="66" w:themeShade="zz" x:kept="color"/>"#,
2329 r#"<w:effect w:val="producerEffect"/><w:em w:val="producerMark"/>"#,
2330 r#"<w:fitText xmlns:x="urn:producer" w:val="600" x:kept="fit"/>"#,
2331 r#"<w:eastAsianLayout xmlns:x="urn:producer" w:id="2" x:kept="layout"/>"#,
2332 ));
2333 assert_eq!(rpr.font_east_asia_theme.as_deref(), Some("minorEastAsia"));
2334 assert_eq!(rpr.font_cs_theme.as_deref(), Some("minorBidi"));
2335 assert_eq!(rpr.font_hint.as_deref(), Some("eastAsia"));
2336 assert_eq!(rpr.color_theme_tint, Some(0x66));
2337 assert_eq!(rpr.color_theme_shade, None);
2340 assert_eq!(
2341 rpr.effect,
2342 Some(ST_TextEffect::Other("producerEffect".to_owned()))
2343 );
2344 assert_eq!(
2345 rpr.emphasis_mark,
2346 Some(ST_Em::Other("producerMark".to_owned()))
2347 );
2348
2349 let mut output = Vec::new();
2350 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2351 let output = String::from_utf8(output).unwrap();
2352 for retained in [
2353 r#"x:kept="rfonts""#,
2354 r#"x:kept="color""#,
2355 r#"x:kept="fit""#,
2356 r#"x:kept="layout""#,
2357 r#"w:themeShade="zz""#,
2358 r#"w:eastAsiaTheme="minorEastAsia""#,
2359 r#"w:cstheme="minorBidi""#,
2360 r#"w:hint="eastAsia""#,
2361 r#"w:themeTint="66""#,
2362 r#"w:val="producerEffect""#,
2363 r#"w:val="producerMark""#,
2364 ] {
2365 assert!(
2366 output.contains(retained),
2367 "{retained} missing from {output}"
2368 );
2369 }
2370 }
2371
2372 #[test]
2373 fn a_theme_only_colour_reaches_the_serialized_element() {
2374 let rpr = parse_rpr(r#"<w:color w:themeColor="accent2" w:themeShade="BF"/>"#);
2375 assert_eq!(rpr.color, None);
2376 assert_eq!(rpr.color_theme.as_deref(), Some("accent2"));
2377 assert_eq!(rpr.color_theme_shade, Some(0xBF));
2378
2379 let mut output = Vec::new();
2380 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2381 let output = String::from_utf8(output).unwrap();
2382 assert!(
2383 output.contains(r#"<w:color w:themeColor="accent2" w:themeShade="BF"/>"#),
2384 "{output}"
2385 );
2386 }
2387
2388 #[test]
2389 fn malformed_language_after_the_modeled_occurrence_keeps_its_relative_position() {
2390 let rpr = parse_rpr(r#"<w:lang w:val="en-US"/><w:lang><w:producerExtension/></w:lang>"#);
2391
2392 let mut output = Vec::new();
2393 rpr.to_xml(&mut Writer::new(&mut output)).unwrap();
2394 let output_text = std::str::from_utf8(&output).unwrap();
2395 assert!(
2396 output_text.find(r#"w:val="en-US""#).unwrap()
2397 < output_text.find("w:producerExtension").unwrap()
2398 );
2399
2400 let mut reader = Reader::from_reader(output.as_slice());
2401 let mut buffer = Vec::new();
2402 loop {
2403 match reader.read_event_into(&mut buffer).unwrap() {
2404 Event::Start(element) if element.local_name().as_ref() == b"rPr" => break,
2405 Event::Eof => panic!("serialized run properties have no root"),
2406 _ => {}
2407 }
2408 buffer.clear();
2409 }
2410 let reparsed = CT_RPr::from_xml(&mut reader).unwrap();
2411 let mut repeated = Vec::new();
2412 reparsed.to_xml(&mut Writer::new(&mut repeated)).unwrap();
2413 assert_eq!(repeated, output);
2414 }
2415}