1use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
4use quick_xml::{Reader, Writer, XmlVersion};
5
6use crate::error::{OxmlError, Result};
7use crate::namespace::{W_NS, matches_local_name};
8use crate::numbering::{parse_scoped_ppr, parse_scoped_rpr, word_prefixes_at};
9use crate::properties::{CT_PPr, CT_RPr, is_word_attribute, is_word_element};
10use crate::raw_xml::{capture_element, capture_empty_element};
11use crate::table::{CT_TblPr, CT_TcPr, CT_TrPr};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum StyleType {
16 Paragraph,
17 Character,
18 Table,
19 Numbering,
20}
21
22impl StyleType {
23 pub fn from_str(s: &str) -> Result<Self> {
24 match s {
25 "paragraph" => Ok(StyleType::Paragraph),
26 "character" => Ok(StyleType::Character),
27 "table" => Ok(StyleType::Table),
28 "numbering" => Ok(StyleType::Numbering),
29 _ => Err(OxmlError::InvalidValue(format!("invalid style type: {s}"))),
30 }
31 }
32
33 pub fn to_str(self) -> &'static str {
34 match self {
35 StyleType::Paragraph => "paragraph",
36 StyleType::Character => "character",
37 StyleType::Table => "table",
38 StyleType::Numbering => "numbering",
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
45#[allow(non_snake_case)]
46pub struct CT_Style {
47 pub style_id: String,
48 pub style_type: StyleType,
49 pub name: Option<String>,
50 pub based_on: Option<String>,
51 pub next_style: Option<String>,
52 pub linked_style: Option<String>,
53 pub auto_redefine: Option<bool>,
54 pub hidden: Option<bool>,
55 pub ui_priority: Option<u32>,
56 pub semi_hidden: Option<bool>,
57 pub unhide_when_used: Option<bool>,
58 pub quick_format: Option<bool>,
59 pub locked: Option<bool>,
60 pub is_default: bool,
61 pub ppr: Option<CT_PPr>,
62 pub rpr: Option<CT_RPr>,
63 pub table_properties: Option<CT_TblPr>,
65 #[doc(hidden)]
66 pub table_properties_original: Option<CT_TblPr>,
67 pub table_properties_xml: Option<Vec<u8>>,
69 pub table_row_properties: Option<Box<CT_TrPr>>,
75 pub table_cell_properties: Option<Box<CT_TcPr>>,
79 pub conditional_table_styles: Vec<CT_TblStylePr>,
81 #[doc(hidden)]
83 pub extra_attributes: Vec<(String, String)>,
84 #[doc(hidden)]
86 pub modeled_xml: Vec<(u8, Vec<u8>)>,
87 pub extra_xml: Vec<(u8, Vec<u8>)>,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
99pub enum TableStyleRegion {
100 WholeTable,
101 Band1Vert,
102 Band2Vert,
103 Band1Horz,
104 Band2Horz,
105 FirstCol,
106 LastCol,
107 FirstRow,
108 LastRow,
109 NwCell,
110 NeCell,
111 SwCell,
112 SeCell,
113}
114
115impl TableStyleRegion {
116 pub const ALL: [TableStyleRegion; 13] = [
118 TableStyleRegion::WholeTable,
119 TableStyleRegion::Band1Vert,
120 TableStyleRegion::Band2Vert,
121 TableStyleRegion::Band1Horz,
122 TableStyleRegion::Band2Horz,
123 TableStyleRegion::FirstCol,
124 TableStyleRegion::LastCol,
125 TableStyleRegion::FirstRow,
126 TableStyleRegion::LastRow,
127 TableStyleRegion::NwCell,
128 TableStyleRegion::NeCell,
129 TableStyleRegion::SwCell,
130 TableStyleRegion::SeCell,
131 ];
132
133 pub fn to_str(self) -> &'static str {
135 match self {
136 TableStyleRegion::WholeTable => "wholeTable",
137 TableStyleRegion::Band1Vert => "band1Vert",
138 TableStyleRegion::Band2Vert => "band2Vert",
139 TableStyleRegion::Band1Horz => "band1Horz",
140 TableStyleRegion::Band2Horz => "band2Horz",
141 TableStyleRegion::FirstCol => "firstCol",
142 TableStyleRegion::LastCol => "lastCol",
143 TableStyleRegion::FirstRow => "firstRow",
144 TableStyleRegion::LastRow => "lastRow",
145 TableStyleRegion::NwCell => "nwCell",
146 TableStyleRegion::NeCell => "neCell",
147 TableStyleRegion::SwCell => "swCell",
148 TableStyleRegion::SeCell => "seCell",
149 }
150 }
151
152 pub fn from_str(value: &str) -> Option<Self> {
157 Self::ALL
158 .into_iter()
159 .find(|region| region.to_str() == value)
160 }
161}
162
163#[derive(Debug, Clone, PartialEq)]
165pub struct CT_TblStylePr {
166 pub region: Option<TableStyleRegion>,
170 pub paragraph_properties: Option<CT_PPr>,
171 pub run_properties: Option<Box<CT_RPr>>,
176 pub table_properties: Option<CT_TblPr>,
177 pub row_properties: Option<Box<CT_TrPr>>,
182 pub cell_properties: Option<CT_TcPr>,
183 #[doc(hidden)]
185 pub extra_attributes: Vec<(String, String)>,
186 pub raw_xml: Vec<u8>,
187}
188
189#[allow(non_snake_case)]
190impl CT_Style {
191 fn style_property_bytes(
192 &self,
193 rank: u8,
194 local: &[u8],
195 generated: Option<&[u8]>,
196 ) -> Result<Vec<u8>> {
197 let sources = self
198 .modeled_xml
199 .iter()
200 .filter(|(owner_rank, _)| *owner_rank == rank)
201 .map(|(_, raw)| raw)
202 .collect::<Vec<_>>();
203 let mut selected = None;
204 for (index, source) in sources.iter().enumerate() {
205 if defaults_owned_child_name(source, &[])?.as_deref() == Some(local) {
206 selected = Some(index);
207 }
208 }
209 let Some(selected) = selected else {
210 let mut bytes = sources.into_iter().flatten().copied().collect::<Vec<_>>();
211 if let Some(generated) = generated {
212 bytes.extend_from_slice(generated);
213 }
214 return Ok(bytes);
215 };
216 let unchanged = if generated.is_none() {
217 false
218 } else if local == b"rPr" {
219 Some(parse_style_rpr(sources[selected], &[])?) == self.rpr
220 } else {
221 Some(parse_scoped_ppr(sources[selected], &[])?) == self.ppr
222 };
223 let mut bytes = Vec::new();
224 for (index, source) in sources.iter().enumerate() {
225 let owned = defaults_owned_child_name(source, &[])?.as_deref() == Some(local);
226 if !owned {
227 bytes.extend_from_slice(source);
228 } else if let Some(generated) = generated {
229 if unchanged || index != selected {
230 bytes.extend_from_slice(source);
231 } else {
232 bytes.extend_from_slice(&merge_defaults_property_root(
233 source,
234 generated,
235 &[],
236 local,
237 )?);
238 }
239 }
240 }
241 Ok(bytes)
242 }
243
244 pub fn from_xml(reader: &mut Reader<&[u8]>, attrs: &BytesStart) -> Result<Self> {
245 let prefixes = word_prefixes_at(attrs, &["w".to_string()])?;
246 let namespace_bindings = namespace_bindings_at(attrs, &[])?;
247 Self::from_xml_with_prefixes(reader, attrs, &prefixes, &namespace_bindings)
248 }
249
250 fn from_xml_with_prefixes(
251 reader: &mut Reader<&[u8]>,
252 attrs: &BytesStart,
253 word_prefixes: &[String],
254 namespace_bindings: &[(String, String)],
255 ) -> Result<Self> {
256 reject_conflicting_style_prefixes(attrs)?;
257 reject_conflicting_style_bindings(namespace_bindings)?;
258 let mut style_id = String::new();
259 let mut style_type = StyleType::Paragraph;
260 let mut is_default = false;
261
262 for attr in attrs.attributes() {
263 let attr = attr?;
264 let key = attr.key.as_ref();
265 if is_word_attribute(key, b"styleId", word_prefixes) {
266 style_id = std::str::from_utf8(&attr.value)?.to_string();
267 } else if is_word_attribute(key, b"type", word_prefixes) {
268 style_type = StyleType::from_str(std::str::from_utf8(&attr.value)?)?;
269 } else if is_word_attribute(key, b"default", word_prefixes) {
270 is_default = std::str::from_utf8(&attr.value)? == "1"
271 || std::str::from_utf8(&attr.value)? == "true";
272 }
273 }
274
275 let mut name = None;
276 let mut based_on = None;
277 let mut next_style = None;
278 let mut linked_style = None;
279 let mut auto_redefine = None;
280 let mut hidden = None;
281 let mut ui_priority = None;
282 let mut semi_hidden = None;
283 let mut unhide_when_used = None;
284 let mut quick_format = None;
285 let mut locked = None;
286 let mut ppr = None;
287 let mut rpr = None;
288 let mut table_properties = None;
289 let mut table_properties_xml = None;
290 let mut table_row_properties = None;
291 let mut table_cell_properties = None;
292 let mut conditional_table_styles = Vec::new();
293 let mut modeled_xml = Vec::new();
294 let mut extra_xml = Vec::new();
295 let extra_attributes = raw_style_attributes(
296 attrs,
297 &[b"styleId", b"type", b"default"],
298 word_prefixes,
299 namespace_bindings,
300 )?;
301 let mut buf = Vec::new();
302
303 loop {
304 match reader.read_event_into(&mut buf) {
305 Ok(Event::Empty(ref e)) => {
306 let ename = e.name();
307 let prefixes = word_prefixes_at(e, word_prefixes)?;
308 let bindings = namespace_bindings_at(e, namespace_bindings)?;
309 if is_word_element(ename.as_ref(), b"pPr", &prefixes)
310 || is_word_element(ename.as_ref(), b"rPr", &prefixes)
311 {
312 let raw = crate::text::raw_with_external_bindings(
313 &capture_empty_element(e)?,
314 &bindings,
315 )?;
316 if is_word_element(ename.as_ref(), b"pPr", &prefixes) {
317 ppr = Some(parse_scoped_ppr(&raw, &prefixes)?);
318 modeled_xml.push((20, raw));
319 } else {
320 rpr = Some(parse_style_rpr(&raw, &prefixes)?);
321 modeled_xml.push((21, raw));
322 }
323 } else if matches!(ename.local_name().as_ref(), b"pPr" | b"rPr") {
324 let rank = if ename.local_name().as_ref() == b"pPr" {
325 20
326 } else {
327 21
328 };
329 modeled_xml.push((
330 rank,
331 crate::text::raw_with_external_bindings(
332 &capture_empty_element(e)?,
333 &bindings,
334 )?,
335 ));
336 } else if let Some(rank) = style_scalar_rank(ename.as_ref(), &prefixes) {
337 let string_value = matches!(rank, 0 | 2 | 3 | 4)
338 .then(|| get_val_attr(e, &prefixes))
339 .transpose()?
340 .flatten();
341 let toggle_value = matches!(rank, 5 | 6 | 8 | 9 | 10 | 11)
342 .then(|| parse_style_toggle(e, &prefixes))
343 .transpose()?
344 .flatten();
345 let priority_value = (rank == 7)
346 .then(|| parse_style_priority(e, &prefixes))
347 .transpose()?
348 .flatten();
349 let raw = capture_empty_element(e)?;
350 if string_value.is_some()
351 || toggle_value.is_some()
352 || priority_value.is_some()
353 {
354 apply_style_scalar(
355 rank,
356 string_value,
357 toggle_value,
358 priority_value,
359 &mut name,
360 &mut based_on,
361 &mut next_style,
362 &mut linked_style,
363 &mut auto_redefine,
364 &mut hidden,
365 &mut ui_priority,
366 &mut semi_hidden,
367 &mut unhide_when_used,
368 &mut quick_format,
369 &mut locked,
370 );
371 if style_scalar_empty_requires_snapshot(e, rank)? {
372 modeled_xml
373 .push((rank, make_style_raw_self_contained(&raw, &bindings)?));
374 }
375 } else {
376 extra_xml.push((rank, make_style_raw_self_contained(&raw, &bindings)?));
377 }
378 } else {
379 extra_xml.push((
380 style_child_rank(ename.as_ref(), &prefixes),
381 make_style_raw_self_contained(&capture_empty_element(e)?, &bindings)?,
382 ));
383 }
384 }
385 Ok(Event::Start(ref e)) => {
386 let ename = e.name();
387 let prefixes = word_prefixes_at(e, word_prefixes)?;
388 let bindings = namespace_bindings_at(e, namespace_bindings)?;
389 let scalar_rank = style_scalar_rank(ename.as_ref(), &prefixes);
390 if let Some(rank) = scalar_rank {
391 let string_value = matches!(rank, 0 | 2 | 3 | 4)
392 .then(|| get_val_attr(e, &prefixes))
393 .transpose()?
394 .flatten();
395 let toggle_value = matches!(rank, 5 | 6 | 8 | 9 | 10 | 11)
396 .then(|| parse_style_toggle(e, &prefixes))
397 .transpose()?
398 .flatten();
399 let priority_value = (rank == 7)
400 .then(|| parse_style_priority(e, &prefixes))
401 .transpose()?
402 .flatten();
403 let raw = capture_element(reader, e)?;
404 if explicit_style_element_has_only_trivia(&raw)?
405 && (string_value.is_some()
406 || toggle_value.is_some()
407 || priority_value.is_some())
408 {
409 apply_style_scalar(
410 rank,
411 string_value,
412 toggle_value,
413 priority_value,
414 &mut name,
415 &mut based_on,
416 &mut next_style,
417 &mut linked_style,
418 &mut auto_redefine,
419 &mut hidden,
420 &mut ui_priority,
421 &mut semi_hidden,
422 &mut unhide_when_used,
423 &mut quick_format,
424 &mut locked,
425 );
426 modeled_xml
427 .push((rank, make_style_raw_self_contained(&raw, &bindings)?));
428 } else {
429 extra_xml.push((rank, make_style_raw_self_contained(&raw, &bindings)?));
430 }
431 } else if is_word_element(ename.as_ref(), b"pPr", &prefixes)
432 || is_word_element(ename.as_ref(), b"rPr", &prefixes)
433 {
434 let trim_start = reader.config().trim_text_start;
435 let trim_end = reader.config().trim_text_end;
436 reader.config_mut().trim_text(false);
437 let captured = capture_element(reader, e);
438 reader.config_mut().trim_text_start = trim_start;
439 reader.config_mut().trim_text_end = trim_end;
440 let raw = crate::text::raw_with_external_bindings(&captured?, &bindings)?;
441 if is_word_element(ename.as_ref(), b"pPr", &prefixes) {
442 ppr = Some(parse_scoped_ppr(&raw, &prefixes)?);
443 modeled_xml.push((20, raw));
444 } else {
445 rpr = Some(parse_style_rpr(&raw, &prefixes)?);
446 modeled_xml.push((21, raw));
447 }
448 } else if matches!(ename.local_name().as_ref(), b"pPr" | b"rPr") {
449 let trim_start = reader.config().trim_text_start;
450 let trim_end = reader.config().trim_text_end;
451 reader.config_mut().trim_text(false);
452 let captured = capture_element(reader, e);
453 reader.config_mut().trim_text_start = trim_start;
454 reader.config_mut().trim_text_end = trim_end;
455 let rank = if ename.local_name().as_ref() == b"pPr" {
456 20
457 } else {
458 21
459 };
460 modeled_xml.push((
461 rank,
462 crate::text::raw_with_external_bindings(&captured?, &bindings)?,
463 ));
464 } else if is_word_element(ename.as_ref(), b"tblPr", &prefixes) {
465 let raw = capture_element(reader, e)?;
466 let preserved = make_style_raw_self_contained(&raw, &bindings)?;
467 table_properties =
468 Some(parse_style_table_properties(&preserved, &prefixes)?);
469 table_properties_xml = Some(preserved);
470 } else if is_word_element(ename.as_ref(), b"trPr", &prefixes) {
471 table_row_properties = Some(Box::new(CT_TrPr::from_xml_with_prefixes(
472 reader, &prefixes,
473 )?));
474 } else if is_word_element(ename.as_ref(), b"tcPr", &prefixes) {
475 table_cell_properties = Some(Box::new(CT_TcPr::from_xml_with_prefixes(
476 reader, &prefixes,
477 )?));
478 } else if is_word_element(ename.as_ref(), b"tblStylePr", &prefixes) {
479 reject_conflicting_style_prefixes(e)?;
480 reject_conflicting_style_bindings(&bindings)?;
481 let region = get_word_attr(e, b"type", &prefixes)?
482 .as_deref()
483 .and_then(TableStyleRegion::from_str);
484 let raw =
485 make_style_raw_self_contained(&capture_element(reader, e)?, &bindings)?;
486 let layers = parse_conditional_style_properties(&raw, &prefixes)?;
487 conditional_table_styles.push(CT_TblStylePr {
488 region,
489 paragraph_properties: layers.paragraph_properties,
490 run_properties: layers.run_properties,
491 table_properties: layers.table_properties,
492 row_properties: layers.row_properties,
493 cell_properties: layers.cell_properties,
494 extra_attributes: raw_style_attributes(
495 e,
496 &[b"type"],
497 &prefixes,
498 &bindings,
499 )?,
500 raw_xml: raw,
501 });
502 } else {
503 extra_xml.push((
504 style_child_rank(ename.as_ref(), &prefixes),
505 make_style_raw_self_contained(&capture_element(reader, e)?, &bindings)?,
506 ));
507 }
508 }
509 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"style") => {
510 break;
511 }
512 Ok(Event::Eof) => break,
513 Err(e) => return Err(e.into()),
514 _ => {}
515 }
516 buf.clear();
517 }
518
519 Ok(CT_Style {
520 style_id,
521 style_type,
522 name,
523 based_on,
524 next_style,
525 linked_style,
526 auto_redefine,
527 hidden,
528 ui_priority,
529 semi_hidden,
530 unhide_when_used,
531 quick_format,
532 locked,
533 is_default,
534 ppr,
535 rpr,
536 table_properties: table_properties.clone(),
537 table_properties_original: table_properties.clone(),
538 table_properties_xml,
539 table_row_properties,
540 table_cell_properties,
541 conditional_table_styles,
542 extra_attributes,
543 modeled_xml,
544 extra_xml,
545 })
546 }
547
548 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
549 let mut e = BytesStart::new("w:style");
550 e.push_attribute(("w:type", self.style_type.to_str()));
551 e.push_attribute(("w:styleId", self.style_id.as_str()));
552 if self.is_default {
553 e.push_attribute(("w:default", "1"));
554 }
555 push_style_attributes(&mut e, &self.extra_attributes);
556 writer.write_event(Event::Start(e))?;
557
558 let mut extras = self.extra_xml.iter().collect::<Vec<_>>();
559 extras.sort_by_key(|(rank, _)| *rank);
560 let mut extra_index = 0;
561
562 write_style_extras(writer, &extras, &mut extra_index, 0)?;
563 write_style_string(writer, "w:name", 0, self.name.as_deref(), &self.modeled_xml)?;
564
565 write_style_extras(writer, &extras, &mut extra_index, 2)?;
566 write_style_string(
567 writer,
568 "w:basedOn",
569 2,
570 self.based_on.as_deref(),
571 &self.modeled_xml,
572 )?;
573
574 write_style_extras(writer, &extras, &mut extra_index, 3)?;
575 write_style_string(
576 writer,
577 "w:next",
578 3,
579 self.next_style.as_deref(),
580 &self.modeled_xml,
581 )?;
582
583 write_style_extras(writer, &extras, &mut extra_index, 4)?;
584 write_style_string(
585 writer,
586 "w:link",
587 4,
588 self.linked_style.as_deref(),
589 &self.modeled_xml,
590 )?;
591 write_style_extras(writer, &extras, &mut extra_index, 5)?;
592 write_style_toggle(
593 writer,
594 "w:autoRedefine",
595 5,
596 self.auto_redefine,
597 &self.modeled_xml,
598 )?;
599 write_style_extras(writer, &extras, &mut extra_index, 6)?;
600 write_style_toggle(writer, "w:hidden", 6, self.hidden, &self.modeled_xml)?;
601 write_style_extras(writer, &extras, &mut extra_index, 7)?;
602 write_style_priority(writer, self.ui_priority, &self.modeled_xml)?;
603 write_style_extras(writer, &extras, &mut extra_index, 8)?;
604 write_style_toggle(
605 writer,
606 "w:semiHidden",
607 8,
608 self.semi_hidden,
609 &self.modeled_xml,
610 )?;
611 write_style_extras(writer, &extras, &mut extra_index, 9)?;
612 write_style_toggle(
613 writer,
614 "w:unhideWhenUsed",
615 9,
616 self.unhide_when_used,
617 &self.modeled_xml,
618 )?;
619 write_style_extras(writer, &extras, &mut extra_index, 10)?;
620 write_style_toggle(
621 writer,
622 "w:qFormat",
623 10,
624 self.quick_format,
625 &self.modeled_xml,
626 )?;
627 write_style_extras(writer, &extras, &mut extra_index, 11)?;
628 write_style_toggle(writer, "w:locked", 11, self.locked, &self.modeled_xml)?;
629 write_style_extras(writer, &extras, &mut extra_index, 20)?;
630 let mut generated_ppr = Writer::new(Vec::new());
631 if let Some(ref ppr) = self.ppr {
632 ppr.to_xml(&mut generated_ppr)?;
633 }
634 let ppr = self.ppr.as_ref().map(|_| {
635 if generated_ppr.get_ref().is_empty() {
636 b"<w:pPr/>".as_slice()
637 } else {
638 generated_ppr.get_ref().as_slice()
639 }
640 });
641 writer
642 .get_mut()
643 .write_all(&self.style_property_bytes(20, b"pPr", ppr)?)?;
644 write_style_extras(writer, &extras, &mut extra_index, 21)?;
645 let mut generated_rpr = Writer::new(Vec::new());
646 if let Some(ref rpr) = self.rpr {
647 rpr.to_xml(&mut generated_rpr)?;
648 }
649 let rpr = self.rpr.as_ref().map(|_| {
650 if generated_rpr.get_ref().is_empty() {
651 b"<w:rPr/>".as_slice()
652 } else {
653 generated_rpr.get_ref().as_slice()
654 }
655 });
656 writer
657 .get_mut()
658 .write_all(&self.style_property_bytes(21, b"rPr", rpr)?)?;
659 write_style_extras(writer, &extras, &mut extra_index, 22)?;
660 if let Some(ref properties) = self.table_properties {
661 let preserved_matches = self.table_properties_original.as_ref() == Some(properties);
662 if preserved_matches {
663 writer
664 .get_mut()
665 .write_all(self.table_properties_xml.as_deref().unwrap_or_default())?;
666 } else {
667 properties.to_xml(writer)?;
668 }
669 }
670 write_style_extras(writer, &extras, &mut extra_index, 23)?;
671 if let Some(ref properties) = self.table_row_properties {
672 properties.to_xml(writer)?;
673 }
674 write_style_extras(writer, &extras, &mut extra_index, 24)?;
675 if let Some(ref properties) = self.table_cell_properties {
676 properties.to_xml(writer)?;
677 }
678 write_style_extras(writer, &extras, &mut extra_index, 25)?;
679 for conditional in &self.conditional_table_styles {
680 writer
681 .get_mut()
682 .write_all(&serialize_conditional_table_style(conditional)?)?;
683 }
684 write_style_extras(writer, &extras, &mut extra_index, u8::MAX)?;
685
686 writer.write_event(Event::End(BytesEnd::new("w:style")))?;
687 Ok(())
688 }
689}
690
691#[derive(Debug, Clone, Default, PartialEq)]
693#[allow(non_snake_case)]
694pub struct CT_DocDefaults {
695 pub rpr: Option<CT_RPr>,
696 pub ppr: Option<CT_PPr>,
697 pub source_xml: Option<Vec<u8>>,
699}
700
701#[allow(non_snake_case)]
702impl CT_DocDefaults {
703 fn from_source_xml(raw: &[u8], inherited: &[String]) -> Result<Self> {
704 let (root, children) = defaults_source_children(raw)?;
705 let prefixes = word_prefixes_at(&root, inherited)?;
706 let mut defaults = Self {
707 source_xml: Some(raw.to_vec()),
708 ..Self::default()
709 };
710 for child in children {
711 let Some((start, nested)) = defaults_optional_children(&child)? else {
712 continue;
713 };
714 let scope = word_prefixes_at(&start, &prefixes)?;
715 let run = is_word_element(start.name().as_ref(), b"rPrDefault", &scope);
716 let paragraph = is_word_element(start.name().as_ref(), b"pPrDefault", &scope);
717 if !run && !paragraph {
718 continue;
719 }
720 if run {
722 defaults.rpr = None;
723 }
724 if paragraph {
725 defaults.ppr = None;
726 }
727 for property in nested {
728 let Some((start, _)) = defaults_optional_children(&property)? else {
729 continue;
730 };
731 let property_scope = word_prefixes_at(&start, &scope)?;
732 if run && is_word_element(start.name().as_ref(), b"rPr", &property_scope) {
733 defaults.rpr = Some(parse_style_rpr(&property, &scope)?);
734 } else if paragraph
735 && is_word_element(start.name().as_ref(), b"pPr", &property_scope)
736 {
737 defaults.ppr = Some(parse_scoped_ppr(&property, &scope)?);
738 }
739 }
740 }
741 Ok(defaults)
742 }
743
744 pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
745 Self::from_xml_with_prefixes(reader, &["w".to_string()])
746 }
747
748 fn from_xml_with_prefixes(
749 reader: &mut Reader<&[u8]>,
750 word_prefixes: &[String],
751 ) -> Result<Self> {
752 let mut defaults = CT_DocDefaults::default();
753 let mut buf = Vec::new();
754
755 loop {
756 match reader.read_event_into(&mut buf) {
757 Ok(Event::Start(ref e)) => {
758 let name = e.name();
759 let prefixes = word_prefixes_at(e, word_prefixes)?;
760 if matches_local_name(name.as_ref(), b"rPrDefault") {
761 defaults.rpr = Self::parse_pr_default(reader, b"rPrDefault")?;
763 } else if matches_local_name(name.as_ref(), b"pPrDefault") {
764 defaults.ppr = Self::parse_ppr_default(reader, &prefixes)?;
765 } else {
766 reader.read_to_end_into(name, &mut Vec::new())?;
767 }
768 }
769 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"docDefaults") => {
770 break;
771 }
772 Ok(Event::Eof) => break,
773 Err(e) => return Err(e.into()),
774 _ => {}
775 }
776 buf.clear();
777 }
778
779 Ok(defaults)
780 }
781
782 fn parse_pr_default(reader: &mut Reader<&[u8]>, end_tag: &[u8]) -> Result<Option<CT_RPr>> {
783 let mut rpr = None;
784 let mut buf = Vec::new();
785
786 loop {
787 match reader.read_event_into(&mut buf) {
788 Ok(Event::Start(ref e)) => {
789 let name = e.name();
790 if matches_local_name(name.as_ref(), b"rPr") {
791 rpr = Some(CT_RPr::from_xml(reader)?);
792 } else {
793 reader.read_to_end_into(name, &mut Vec::new())?;
794 }
795 }
796 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), end_tag) => {
797 break;
798 }
799 Ok(Event::Eof) => break,
800 Err(e) => return Err(e.into()),
801 _ => {}
802 }
803 buf.clear();
804 }
805
806 Ok(rpr)
807 }
808
809 fn parse_ppr_default(
810 reader: &mut Reader<&[u8]>,
811 word_prefixes: &[String],
812 ) -> Result<Option<CT_PPr>> {
813 let mut ppr = None;
814 let mut buf = Vec::new();
815
816 loop {
817 match reader.read_event_into(&mut buf) {
818 Ok(Event::Start(ref e)) => {
819 let name = e.name();
820 let prefixes = word_prefixes_at(e, word_prefixes)?;
821 if is_word_element(name.as_ref(), b"pPr", &prefixes) {
822 let raw = capture_element(reader, e)?;
823 ppr = Some(parse_scoped_ppr(&raw, word_prefixes)?);
824 } else {
825 reader.read_to_end_into(name, &mut Vec::new())?;
826 }
827 }
828 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"pPrDefault") => {
829 break;
830 }
831 Ok(Event::Eof) => break,
832 Err(e) => return Err(e.into()),
833 _ => {}
834 }
835 buf.clear();
836 }
837
838 Ok(ppr)
839 }
840
841 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
842 if let Some(raw) = &self.source_xml {
843 let original = Self::from_source_xml(raw, &[])?;
844 if original.rpr == self.rpr && original.ppr == self.ppr {
845 writer.get_mut().write_all(raw)?;
846 return Ok(());
847 }
848 let mut run = Writer::new(Vec::new());
849 if let Some(properties) = &self.rpr {
850 properties.to_xml(&mut run)?;
851 }
852 let mut paragraph = Writer::new(Vec::new());
853 if let Some(properties) = &self.ppr {
854 properties.to_xml(&mut paragraph)?;
855 }
856 let run = self.rpr.as_ref().map(|_| {
858 if run.get_ref().is_empty() {
859 b"<w:rPr/>".to_vec()
860 } else {
861 run.into_inner()
862 }
863 });
864 let paragraph = self.ppr.as_ref().map(|_| {
865 if paragraph.get_ref().is_empty() {
866 b"<w:pPr/>".to_vec()
867 } else {
868 paragraph.into_inner()
869 }
870 });
871 let merged = merge_defaults_source(
872 raw,
873 run.as_deref(),
874 paragraph.as_deref(),
875 original.rpr != self.rpr,
876 original.ppr != self.ppr,
877 )?;
878 writer.get_mut().write_all(&merged)?;
879 return Ok(());
880 }
881 writer.write_event(Event::Start(BytesStart::new("w:docDefaults")))?;
882
883 if let Some(ref rpr) = self.rpr {
884 writer.write_event(Event::Start(BytesStart::new("w:rPrDefault")))?;
885 rpr.to_xml(writer)?;
886 writer.write_event(Event::End(BytesEnd::new("w:rPrDefault")))?;
887 }
888
889 if let Some(ref ppr) = self.ppr {
890 writer.write_event(Event::Start(BytesStart::new("w:pPrDefault")))?;
891 ppr.to_xml(writer)?;
892 writer.write_event(Event::End(BytesEnd::new("w:pPrDefault")))?;
893 }
894
895 writer.write_event(Event::End(BytesEnd::new("w:docDefaults")))?;
896 Ok(())
897 }
898}
899
900fn parse_style_rpr(raw: &[u8], inherited: &[String]) -> Result<CT_RPr> {
901 let (root, children) = defaults_source_children(raw)?;
902 let scope = word_prefixes_at(&root, inherited)?;
903 let end = std::str::from_utf8(root.name().as_ref())?.to_owned();
904 let mut projection = Writer::new(Vec::new());
905 projection.write_event(Event::Start(root))?;
906 for child in children {
907 let name = defaults_owned_child_name(&child, &scope)?;
908 if name.as_deref().is_some_and(|name| {
909 matches!(
910 name,
911 b"rFonts"
912 | b"sz"
913 | b"szCs"
914 | b"u"
915 | b"b"
916 | b"bCs"
917 | b"i"
918 | b"iCs"
919 | b"caps"
920 | b"smallCaps"
921 | b"strike"
922 | b"dstrike"
923 | b"vanish"
924 | b"rtl"
925 | b"lang"
926 | b"color"
927 | b"spacing"
928 | b"position"
929 | b"vertAlign"
930 )
931 }) {
932 let (leaf, _) = defaults_source_children(&child)?;
933 projection.write_event(Event::Empty(leaf))?;
934 } else {
935 projection.get_mut().extend_from_slice(&child);
936 }
937 }
938 projection.write_event(Event::End(BytesEnd::new(end)))?;
939 parse_scoped_rpr(projection.get_ref(), inherited)
940}
941
942fn defaults_optional_children(raw: &[u8]) -> Result<Option<(BytesStart<'static>, Vec<Vec<u8>>)>> {
944 let mut reader = Reader::from_reader(raw);
945 match reader.read_event()? {
946 Event::Start(_) | Event::Empty(_) => defaults_source_children(raw).map(Some),
947 _ => Ok(None),
948 }
949}
950
951fn defaults_source_children(raw: &[u8]) -> Result<(BytesStart<'static>, Vec<Vec<u8>>)> {
952 let mut reader = Reader::from_reader(raw);
953 reader.config_mut().trim_text(false);
954 let (start, empty) = match reader.read_event()? {
955 Event::Start(start) => (start.into_owned(), false),
956 Event::Empty(start) => (start.into_owned(), true),
957 _ => {
958 return Err(OxmlError::InvalidValue(
959 "default wrapper root missing".to_owned(),
960 ));
961 }
962 };
963 let mut children = Vec::new();
964 if !empty {
965 loop {
966 let begin = reader.buffer_position() as usize;
967 match reader.read_event()? {
968 Event::Start(ref child) => {
969 capture_element(&mut reader, child)?;
970 children.push(raw[begin..reader.buffer_position() as usize].to_vec());
971 }
972 Event::End(_) => break,
973 Event::Eof => {
974 return Err(OxmlError::InvalidValue(
975 "default wrapper end missing".to_owned(),
976 ));
977 }
978 _ => children.push(raw[begin..reader.buffer_position() as usize].to_vec()),
979 }
980 }
981 }
982 Ok((start, children))
983}
984
985fn defaults_owned_child_name(raw: &[u8], inherited: &[String]) -> Result<Option<Vec<u8>>> {
986 let Some((root, _)) = defaults_optional_children(raw)? else {
987 return Ok(None);
988 };
989 let scope = word_prefixes_at(&root, inherited)?;
990 let local = root.name().local_name().as_ref().to_vec();
991 Ok(is_word_element(root.name().as_ref(), &local, &scope).then_some(local))
992}
993
994fn defaults_owned_attributes(
997 raw: &[u8],
998 inherited: &[String],
999) -> Result<std::collections::HashMap<Vec<u8>, Vec<u8>>> {
1000 let mut values = std::collections::HashMap::new();
1001 if let Some((root, _)) = defaults_optional_children(raw)? {
1002 let scope = word_prefixes_at(&root, inherited)?;
1003 for attr in root.attributes() {
1004 let attr = attr?;
1005 let local = attr.key.local_name().as_ref().to_vec();
1006 if is_word_attribute(attr.key.as_ref(), &local, &scope) {
1007 values.insert(local, attr.value.to_vec());
1008 }
1009 }
1010 }
1011 Ok(values)
1012}
1013
1014pub(crate) fn merge_defaults_scalar_attributes(
1015 raw: &[u8],
1016 before: Option<&Vec<u8>>,
1017 after: Option<&Vec<u8>>,
1018 inherited: &[String],
1019 selected: bool,
1020) -> Result<Vec<u8>> {
1021 let canonical = vec!["w".to_owned()];
1022 let old = defaults_owned_attributes(before.map_or(&[], Vec::as_slice), &canonical)?;
1023 let new = defaults_owned_attributes(after.map_or(&[], Vec::as_slice), &canonical)?;
1024 let changed = old
1025 .keys()
1026 .chain(new.keys())
1027 .filter(|key| old.get(*key) != new.get(*key))
1028 .cloned()
1029 .collect::<std::collections::HashSet<_>>();
1030 if changed.is_empty() {
1031 return Ok(raw.to_vec());
1032 }
1033 let (root, _) = defaults_source_children(raw)?;
1034 let scope = word_prefixes_at(&root, inherited)?;
1035 let bindings = namespace_bindings_at(&root, &[])?;
1036 let existing_prefix = scope
1039 .iter()
1040 .find(|prefix| !prefix.is_empty() && !prefix.starts_with('\0'))
1041 .cloned();
1042 let mut prefix = existing_prefix
1043 .clone()
1044 .unwrap_or_else(|| "defaultsWord".to_owned());
1045 while existing_prefix.is_none()
1046 && (bindings.iter().any(|(name, _)| name == &prefix)
1047 || inherited.contains(&prefix)
1048 || raw
1049 .windows(prefix.len() + 1)
1050 .any(|bytes| bytes == format!("{prefix}:").as_bytes()))
1051 {
1052 prefix.push('_');
1053 }
1054 let mut result = Vec::new();
1055 let mut cursor = 1;
1056 while cursor < raw.len()
1057 && !raw[cursor].is_ascii_whitespace()
1058 && raw[cursor] != b'/'
1059 && raw[cursor] != b'>'
1060 {
1061 cursor += 1;
1062 }
1063 result.extend_from_slice(&raw[..cursor]);
1064 loop {
1065 let begin = cursor;
1066 while cursor < raw.len() && raw[cursor].is_ascii_whitespace() {
1067 cursor += 1;
1068 }
1069 if matches!(raw.get(cursor), Some(b'/') | Some(b'>')) {
1070 result.extend_from_slice(&raw[begin..cursor]);
1071 break;
1072 }
1073 let key_start = cursor;
1074 while cursor < raw.len() && !raw[cursor].is_ascii_whitespace() && raw[cursor] != b'=' {
1075 cursor += 1;
1076 }
1077 let key = &raw[key_start..cursor];
1078 while cursor < raw.len() && raw[cursor].is_ascii_whitespace() {
1079 cursor += 1;
1080 }
1081 if raw.get(cursor) != Some(&b'=') {
1082 return Err(OxmlError::InvalidValue(
1083 "default attribute assignment missing".to_owned(),
1084 ));
1085 }
1086 cursor += 1;
1087 while cursor < raw.len() && raw[cursor].is_ascii_whitespace() {
1088 cursor += 1;
1089 }
1090 let quote = *raw
1091 .get(cursor)
1092 .ok_or_else(|| OxmlError::InvalidValue("default attribute quote missing".to_owned()))?;
1093 if quote != b'\'' && quote != b'"' {
1094 return Err(OxmlError::InvalidValue(
1095 "default attribute quote invalid".to_owned(),
1096 ));
1097 }
1098 cursor += 1;
1099 while cursor < raw.len() && raw[cursor] != quote {
1100 cursor += 1;
1101 }
1102 if cursor == raw.len() {
1103 return Err(OxmlError::InvalidValue(
1104 "default attribute end missing".to_owned(),
1105 ));
1106 }
1107 cursor += 1;
1108 let local = key.rsplit(|byte| *byte == b':').next().unwrap_or(key);
1109 if !changed.contains(local) || !is_word_attribute(key, local, &scope) {
1110 result.extend_from_slice(&raw[begin..cursor]);
1111 }
1112 }
1113 if selected && changed.iter().any(|key| new.contains_key(key)) {
1114 if existing_prefix.is_none() {
1115 result.extend_from_slice(format!(" xmlns:{prefix}=\"{W_NS}\"").as_bytes());
1116 }
1117 let mut keys = changed.iter().collect::<Vec<_>>();
1118 keys.sort();
1119 for key in keys {
1120 if let Some(value) = new.get(key) {
1121 result.extend_from_slice(
1122 format!(" {prefix}:{}=\"", std::str::from_utf8(key)?).as_bytes(),
1123 );
1124 result.extend_from_slice(value);
1125 result.push(b'"');
1126 }
1127 }
1128 }
1129 result.extend_from_slice(&raw[cursor..]);
1130 Ok(result)
1131}
1132
1133fn style_border_semantic_key(name: Vec<u8>) -> Vec<u8> {
1136 match name.as_slice() {
1137 b"start" => b"left".to_vec(),
1138 b"end" => b"right".to_vec(),
1139 _ => name,
1140 }
1141}
1142
1143pub(crate) fn merge_style_border_source(
1144 raw: &[u8],
1145 before: Option<&Vec<u8>>,
1146 after: &[u8],
1147 inherited: &[String],
1148 selected_owner: bool,
1149) -> Result<Vec<u8>> {
1150 let (root, source) = defaults_source_children(raw)?;
1151 let scope = word_prefixes_at(&root, inherited)?;
1152 let canonical = vec!["w".to_owned()];
1153 let mut previous = std::collections::HashMap::new();
1154 if let Some(before) = before {
1155 let before =
1156 crate::text::raw_with_external_bindings(before, &[("w".to_owned(), W_NS.to_owned())])?;
1157 let (before_root, children) = defaults_source_children(&before)?;
1158 let before_scope = word_prefixes_at(&before_root, &canonical)?;
1159 let bindings = namespace_bindings_at(&before_root, &[])?;
1160 for child in children {
1161 if let Some(name) = defaults_owned_child_name(&child, &before_scope)? {
1162 previous.insert(
1163 style_border_semantic_key(name),
1164 crate::text::raw_with_external_bindings(&child, &bindings)?,
1165 );
1166 }
1167 }
1168 }
1169 let bindings = [("w".to_owned(), W_NS.to_owned())];
1170 let after = crate::text::raw_with_external_bindings(after, &bindings)?;
1171 let (generated_root, generated) = defaults_source_children(&after)?;
1172 let generated_scope = word_prefixes_at(&generated_root, &[])?;
1173 let mut supplied = std::collections::HashMap::new();
1174 let mut order = Vec::new();
1175 for child in generated {
1176 if let Some(name) = defaults_owned_child_name(&child, &generated_scope)? {
1177 let name = style_border_semantic_key(name);
1178 order.push(name.clone());
1179 supplied.insert(
1180 name,
1181 crate::text::raw_with_external_bindings(
1182 &child,
1183 &namespace_bindings_at(&generated_root, &[])?,
1184 )?,
1185 );
1186 }
1187 }
1188 let mut selected = std::collections::HashMap::new();
1189 for (index, child) in source.iter().enumerate() {
1190 if let Some(name) = defaults_owned_child_name(child, &scope)? {
1191 let name = style_border_semantic_key(name);
1192 selected.insert(name, index);
1193 }
1194 }
1195 let generated_bindings = namespace_bindings_at(&generated_root, &[])?;
1196 let end = std::str::from_utf8(root.name().as_ref())?.to_owned();
1197 let mut writer = Writer::new(Vec::new());
1198 writer.write_event(Event::Start(root))?;
1199 let mut inserted = std::collections::HashSet::new();
1200 for child in &source {
1201 if let Some(name) = defaults_owned_child_name(child, &scope)? {
1202 let name = style_border_semantic_key(name);
1203 if let Some(rank) = order.iter().position(|value| value == &name) {
1204 for new_name in order
1205 .iter()
1206 .take(rank)
1207 .filter(|value| !selected.contains_key(*value))
1208 {
1209 if selected_owner && inserted.insert(new_name.clone()) {
1210 writer.get_mut().extend_from_slice(
1211 &crate::text::raw_with_external_bindings(
1212 &supplied[new_name],
1213 &generated_bindings,
1214 )?,
1215 );
1216 }
1217 }
1218 }
1219 if previous.contains_key(&name) && !supplied.contains_key(&name) {
1222 continue;
1223 }
1224 if previous.get(&name) != supplied.get(&name) && supplied.contains_key(&name) {
1225 writer
1226 .get_mut()
1227 .extend_from_slice(&merge_defaults_scalar_attributes(
1228 child,
1229 previous.get(&name),
1230 supplied.get(&name),
1231 &scope,
1232 true,
1233 )?);
1234 inserted.insert(name);
1235 continue;
1236 }
1237 }
1238 writer.get_mut().extend_from_slice(child);
1239 }
1240 for name in order
1241 .iter()
1242 .filter(|name| selected_owner && !selected.contains_key(*name) && !inserted.contains(*name))
1243 {
1244 writer
1245 .get_mut()
1246 .extend_from_slice(&crate::text::raw_with_external_bindings(
1247 &supplied[name],
1248 &generated_bindings,
1249 )?);
1250 }
1251 writer.write_event(Event::End(BytesEnd::new(end)))?;
1252 Ok(writer.into_inner())
1253}
1254
1255fn merge_defaults_property_root(
1256 raw: &[u8],
1257 generated: &[u8],
1258 inherited: &[String],
1259 local: &[u8],
1260) -> Result<Vec<u8>> {
1261 let (root, source_children) = defaults_source_children(raw)?;
1262 let scope = word_prefixes_at(&root, inherited)?;
1263 let mut before = Writer::new(Vec::new());
1264 if local == b"rPr" {
1265 parse_style_rpr(raw, inherited)?.to_xml(&mut before)?;
1266 } else {
1267 parse_scoped_ppr(raw, inherited)?.to_xml(&mut before)?;
1268 }
1269 let bindings = [("w".to_owned(), W_NS.to_owned())];
1270 let generated = crate::text::raw_with_external_bindings(generated, &bindings)?;
1271 let (generated_root, children) = defaults_source_children(&generated)?;
1272 let generated_scope = word_prefixes_at(&generated_root, &[])?;
1273 let mut previous = std::collections::HashMap::new();
1274 if !before.get_ref().is_empty() {
1275 let raw_before = crate::text::raw_with_external_bindings(before.get_ref(), &bindings)?;
1276 let (before_root, before_children) = defaults_source_children(&raw_before)?;
1277 let before_scope = word_prefixes_at(&before_root, &[])?;
1278 for child in before_children {
1279 if let Some(name) = defaults_owned_child_name(&child, &before_scope)? {
1280 previous.insert(name, child);
1281 }
1282 }
1283 }
1284 let mut supplied = std::collections::HashMap::new();
1285 let mut order = Vec::new();
1286 for child in &children {
1287 if let Some(name) = defaults_owned_child_name(child, &generated_scope)? {
1288 order.push(name.clone());
1289 supplied.insert(name, child.clone());
1290 }
1291 }
1292 let mut changed = previous
1293 .keys()
1294 .chain(supplied.keys())
1295 .filter(|name| previous.get(*name) != supplied.get(*name))
1296 .cloned()
1297 .collect::<Vec<_>>();
1298 changed.sort();
1299 changed.dedup();
1300 let mut selected = std::collections::HashMap::new();
1301 for (index, child) in source_children.iter().enumerate() {
1302 if let Some(name) = defaults_owned_child_name(child, &scope)? {
1303 selected.insert(name, index);
1304 }
1305 }
1306 let generated_bindings = namespace_bindings_at(&generated_root, &[])?;
1307 let end = std::str::from_utf8(root.name().as_ref())?.to_owned();
1308 let source_bindings =
1309 namespace_bindings_at(&root, &crate::numbering::namespace_bindings(&scope))?;
1310 let mut writer = Writer::new(Vec::new());
1311 writer.write_event(Event::Start(root))?;
1312 let mut inserted = std::collections::HashSet::new();
1313 for (index, child) in source_children.iter().enumerate() {
1314 let name = defaults_owned_child_name(child, &scope)?;
1315 if let Some(ref name) = name {
1316 let rank = order.iter().position(|value| value == name);
1317 for new_name in order
1318 .iter()
1319 .filter(|value| changed.contains(value) && !selected.contains_key(*value))
1320 {
1321 if !inserted.contains(new_name)
1322 && rank.is_some_and(|rank| {
1323 order.iter().position(|value| value == new_name).unwrap() < rank
1324 })
1325 {
1326 writer
1327 .get_mut()
1328 .extend_from_slice(&crate::text::raw_with_external_bindings(
1329 &supplied[new_name],
1330 &generated_bindings,
1331 )?);
1332 inserted.insert(new_name.clone());
1333 }
1334 }
1335 if local == b"pPr" && name == b"pBdr" && changed.contains(name) {
1336 if let Some(generated) = supplied.get(name) {
1337 let generated_border =
1338 crate::text::raw_with_external_bindings(generated, &generated_bindings)?;
1339 if crate::borders::CT_PBdr::from_source_xml(&generated_border)?
1340 == crate::borders::CT_PBdr::default()
1341 {
1342 let child =
1343 crate::text::raw_with_external_bindings(child, &source_bindings)?;
1344 writer
1345 .get_mut()
1346 .extend_from_slice(&crate::borders::CT_PBdr::merge_source(
1347 &child, &child, None, false,
1348 )?);
1349 } else {
1350 writer
1351 .get_mut()
1352 .extend_from_slice(&merge_style_border_source(
1353 child,
1354 previous.get(name),
1355 generated,
1356 &scope,
1357 selected.get(name) == Some(&index),
1358 )?);
1359 }
1360 } else {
1361 let child = crate::text::raw_with_external_bindings(child, &source_bindings)?;
1364 writer
1365 .get_mut()
1366 .extend_from_slice(&crate::borders::CT_PBdr::merge_source(
1367 &child, &child, None, false,
1368 )?);
1369 }
1370 inserted.insert(name.clone());
1371 continue;
1372 }
1373 if changed.contains(name)
1374 && matches!(
1375 name.as_slice(),
1376 b"rFonts"
1377 | b"sz"
1378 | b"szCs"
1379 | b"u"
1380 | b"b"
1381 | b"bCs"
1382 | b"i"
1383 | b"iCs"
1384 | b"color"
1385 | b"lang"
1386 | b"spacing"
1387 | b"ind"
1388 | b"jc"
1389 | b"keepNext"
1390 | b"keepLines"
1391 | b"pageBreakBefore"
1392 | b"shd"
1393 | b"caps"
1394 | b"smallCaps"
1395 | b"strike"
1396 | b"dstrike"
1397 | b"vanish"
1398 | b"rtl"
1399 | b"position"
1400 | b"vertAlign"
1401 )
1402 {
1403 if !supplied.contains_key(name)
1404 && !matches!(name.as_slice(), b"rFonts" | b"sz" | b"szCs")
1405 {
1406 inserted.insert(name.clone());
1407 continue;
1408 }
1409 writer
1410 .get_mut()
1411 .extend_from_slice(&merge_defaults_scalar_attributes(
1412 child,
1413 previous.get(name),
1414 supplied.get(name),
1415 &scope,
1416 selected.get(name) == Some(&index),
1417 )?);
1418
1419 inserted.insert(name.clone());
1420 continue;
1421 }
1422 if changed.contains(name) && selected.get(name) == Some(&index) {
1423 if let Some(generated) = supplied.get(name) {
1424 writer
1425 .get_mut()
1426 .extend_from_slice(&crate::text::raw_with_external_bindings(
1427 generated,
1428 &generated_bindings,
1429 )?);
1430 }
1431 inserted.insert(name.clone());
1432 continue;
1433 }
1434 }
1435 writer.get_mut().extend_from_slice(child);
1436 }
1437 for name in order
1438 .iter()
1439 .filter(|name| changed.contains(name) && !inserted.contains(*name))
1440 {
1441 writer
1442 .get_mut()
1443 .extend_from_slice(&crate::text::raw_with_external_bindings(
1444 &supplied[name],
1445 &generated_bindings,
1446 )?);
1447 }
1448 writer.write_event(Event::End(BytesEnd::new(end)))?;
1449 Ok(writer.into_inner())
1450}
1451
1452fn merge_defaults_property_wrapper(
1453 raw: &[u8],
1454 inherited: &[String],
1455 local: &[u8],
1456 generated: Option<&[u8]>,
1457) -> Result<Vec<u8>> {
1458 let (root, children) = defaults_source_children(raw)?;
1459 let scope = word_prefixes_at(&root, inherited)?;
1460 let mut selected = None;
1461 for (index, child) in children.iter().enumerate() {
1462 if let Some((start, _)) = defaults_optional_children(child)? {
1463 let scope = word_prefixes_at(&start, &scope)?;
1464 if is_word_element(start.name().as_ref(), local, &scope) {
1465 selected = Some(index);
1466 }
1467 }
1468 }
1469 let mut writer = Writer::new(Vec::new());
1470 let end = std::str::from_utf8(root.name().as_ref())?.to_owned();
1471 writer.write_event(Event::Start(root))?;
1472 for (index, child) in children.iter().enumerate() {
1473 if Some(index) == selected {
1474 if let Some(generated) = generated {
1475 writer
1476 .get_mut()
1477 .extend_from_slice(&merge_defaults_property_root(
1478 child, generated, &scope, local,
1479 )?);
1480 }
1481 } else {
1482 writer.get_mut().extend_from_slice(child);
1483 }
1484 }
1485 if selected.is_none()
1486 && let Some(generated) = generated
1487 {
1488 writer
1489 .get_mut()
1490 .extend_from_slice(&crate::text::raw_with_external_bindings(
1491 generated,
1492 &[("w".to_owned(), W_NS.to_owned())],
1493 )?);
1494 }
1495 writer.write_event(Event::End(BytesEnd::new(end)))?;
1496 Ok(writer.into_inner())
1497}
1498
1499fn merge_defaults_source(
1500 raw: &[u8],
1501 run: Option<&[u8]>,
1502 paragraph: Option<&[u8]>,
1503 run_changed: bool,
1504 paragraph_changed: bool,
1505) -> Result<Vec<u8>> {
1506 let (root, children) = defaults_source_children(raw)?;
1507 let scope = word_prefixes_at(&root, &[])?;
1508 let mut run_slot = None;
1509 let mut paragraph_slot = None;
1510 for (index, child) in children.iter().enumerate() {
1511 if let Some((start, _)) = defaults_optional_children(child)? {
1512 let child_scope = word_prefixes_at(&start, &scope)?;
1513 if is_word_element(start.name().as_ref(), b"rPrDefault", &child_scope) {
1514 run_slot = Some(index);
1515 }
1516 if is_word_element(start.name().as_ref(), b"pPrDefault", &child_scope) {
1517 paragraph_slot = Some(index);
1518 }
1519 }
1520 }
1521 let mut writer = Writer::new(Vec::new());
1522 let end = std::str::from_utf8(root.name().as_ref())?.to_owned();
1523 writer.write_event(Event::Start(root))?;
1524 if run_slot.is_none()
1526 && let Some(run) = run
1527 {
1528 writer.get_mut().extend_from_slice(&merge_defaults_property_wrapper(b"<w:rPrDefault xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"/>", &scope, b"rPr", Some(run))?);
1529 }
1530 for (index, child) in children.iter().enumerate() {
1531 if Some(index) == run_slot && run_changed {
1532 writer
1533 .get_mut()
1534 .extend_from_slice(&merge_defaults_property_wrapper(
1535 child, &scope, b"rPr", run,
1536 )?);
1537 } else if Some(index) == paragraph_slot && paragraph_changed {
1538 writer
1539 .get_mut()
1540 .extend_from_slice(&merge_defaults_property_wrapper(
1541 child, &scope, b"pPr", paragraph,
1542 )?);
1543 } else {
1544 writer.get_mut().extend_from_slice(child);
1545 }
1546 }
1547 if paragraph_slot.is_none()
1548 && let Some(paragraph) = paragraph
1549 {
1550 writer.get_mut().extend_from_slice(&merge_defaults_property_wrapper(b"<w:pPrDefault xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"/>", &scope, b"pPr", Some(paragraph))?);
1551 }
1552 writer.write_event(Event::End(BytesEnd::new(end)))?;
1553 Ok(writer.into_inner())
1554}
1555
1556#[derive(Debug, Clone, PartialEq)]
1558#[allow(non_snake_case)]
1559pub struct CT_Styles {
1560 pub doc_defaults: Option<CT_DocDefaults>,
1561 pub styles: Vec<CT_Style>,
1562}
1563
1564#[allow(non_snake_case)]
1565impl CT_Styles {
1566 pub fn new() -> Self {
1567 CT_Styles {
1568 doc_defaults: None,
1569 styles: Vec::new(),
1570 }
1571 }
1572
1573 pub fn from_xml(xml: &[u8]) -> Result<Self> {
1575 let mut reader = Reader::from_reader(xml);
1576 reader.config_mut().trim_text(true);
1577
1578 let mut doc_defaults = None;
1579 let mut styles = Vec::new();
1580 let mut buf = Vec::new();
1581 let mut word_prefixes = Vec::new();
1582 let mut namespace_bindings = Vec::new();
1583
1584 loop {
1585 match reader.read_event_into(&mut buf) {
1586 Ok(Event::Start(ref e)) => {
1587 let name = e.name();
1588 let prefixes = word_prefixes_at(e, &word_prefixes)?;
1589 if is_word_element(name.as_ref(), b"docDefaults", &prefixes) {
1590 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1591 reader.config_mut().trim_text(false);
1592 let captured = capture_element(&mut reader, e)?;
1593 reader.config_mut().trim_text(true);
1594 let raw = crate::text::raw_with_external_bindings(&captured, &bindings)?;
1595 doc_defaults = Some(CT_DocDefaults::from_source_xml(&raw, &prefixes)?);
1596 } else if is_word_element(name.as_ref(), b"style", &prefixes) {
1597 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1598 styles.push(CT_Style::from_xml_with_prefixes(
1599 &mut reader,
1600 e,
1601 &prefixes,
1602 &bindings,
1603 )?);
1604 } else if is_word_element(name.as_ref(), b"styles", &prefixes) {
1605 word_prefixes = prefixes;
1607 namespace_bindings = namespace_bindings_at(e, &namespace_bindings)?;
1608 } else {
1609 reader.read_to_end_into(name, &mut Vec::new())?;
1610 }
1611 }
1612 Ok(Event::Empty(ref e)) => {
1613 let prefixes = word_prefixes_at(e, &word_prefixes)?;
1614 if is_word_element(e.name().as_ref(), b"docDefaults", &prefixes) {
1615 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1616 let raw = crate::text::raw_with_external_bindings(
1617 &capture_empty_element(e)?,
1618 &bindings,
1619 )?;
1620 doc_defaults = Some(CT_DocDefaults::from_source_xml(&raw, &prefixes)?);
1621 }
1622 }
1623 Ok(Event::Eof) => break,
1624 Err(e) => return Err(e.into()),
1625 _ => {}
1626 }
1627 buf.clear();
1628 }
1629
1630 Ok(CT_Styles {
1631 doc_defaults,
1632 styles,
1633 })
1634 }
1635
1636 pub fn to_xml(&self) -> Result<Vec<u8>> {
1638 let mut writer = Writer::new(Vec::new());
1639
1640 writer.write_event(Event::Decl(BytesDecl::new(
1641 "1.0",
1642 Some("UTF-8"),
1643 Some("yes"),
1644 )))?;
1645
1646 let mut styles_start = BytesStart::new("w:styles");
1647 styles_start.push_attribute(("xmlns:w", W_NS));
1648 styles_start.push_attribute((
1649 "xmlns:r",
1650 "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
1651 ));
1652 writer.write_event(Event::Start(styles_start))?;
1653
1654 if let Some(ref defaults) = self.doc_defaults {
1655 defaults.to_xml(&mut writer)?;
1656 }
1657
1658 for style in &self.styles {
1659 style.to_xml(&mut writer)?;
1660 }
1661
1662 writer.write_event(Event::End(BytesEnd::new("w:styles")))?;
1663
1664 Ok(writer.into_inner())
1665 }
1666
1667 pub fn get_by_id(&self, style_id: &str) -> Option<&CT_Style> {
1669 self.styles.iter().find(|s| s.style_id == style_id)
1670 }
1671
1672 pub fn get_default(&self, style_type: StyleType) -> Option<&CT_Style> {
1674 self.styles
1675 .iter()
1676 .find(|s| s.style_type == style_type && s.is_default)
1677 }
1678
1679 pub fn new_default() -> Self {
1681 use crate::units::HalfPoint;
1682
1683 let normal = CT_Style {
1684 style_id: "Normal".to_string(),
1685 style_type: StyleType::Paragraph,
1686 name: Some("Normal".to_string()),
1687 based_on: None,
1688 next_style: None,
1689 linked_style: None,
1690 auto_redefine: None,
1691 hidden: None,
1692 ui_priority: None,
1693 semi_hidden: None,
1694 unhide_when_used: None,
1695 quick_format: None,
1696 locked: None,
1697 is_default: true,
1698 ppr: None,
1699 rpr: None,
1700 table_properties: None,
1701 table_properties_original: None,
1702 table_properties_xml: None,
1703 table_row_properties: None,
1704 table_cell_properties: None,
1705 conditional_table_styles: Vec::new(),
1706 extra_attributes: Vec::new(),
1707 modeled_xml: Vec::new(),
1708 extra_xml: Vec::new(),
1709 };
1710
1711 let heading1 = CT_Style {
1712 style_id: "Heading1".to_string(),
1713 style_type: StyleType::Paragraph,
1714 name: Some("heading 1".to_string()),
1715 based_on: Some("Normal".to_string()),
1716 next_style: Some("Normal".to_string()),
1717 linked_style: None,
1718 auto_redefine: None,
1719 hidden: None,
1720 ui_priority: None,
1721 semi_hidden: None,
1722 unhide_when_used: None,
1723 quick_format: None,
1724 locked: None,
1725 is_default: false,
1726 ppr: Some(CT_PPr {
1727 keep_next: Some(true),
1728 keep_lines: Some(true),
1729 space_before: Some(crate::units::Twips(240)),
1730 space_after: Some(crate::units::Twips(0)),
1731 ..Default::default()
1732 }),
1733 rpr: Some(CT_RPr {
1734 sz: Some(HalfPoint(32)),
1735 sz_cs: Some(HalfPoint(32)),
1736 bold: Some(true),
1737 bold_cs: Some(true),
1738 color: Some("2F5496".to_string()),
1739 ..Default::default()
1740 }),
1741 table_properties: None,
1742 table_properties_original: None,
1743 table_properties_xml: None,
1744 table_row_properties: None,
1745 table_cell_properties: None,
1746 conditional_table_styles: Vec::new(),
1747 extra_attributes: Vec::new(),
1748 modeled_xml: Vec::new(),
1749 extra_xml: Vec::new(),
1750 };
1751
1752 let doc_defaults = CT_DocDefaults {
1753 source_xml: None,
1754 rpr: Some(CT_RPr {
1755 font_ascii: Some("Calibri".to_string()),
1756 font_hansi: Some("Calibri".to_string()),
1757 font_east_asia: Some("Calibri".to_string()),
1758 font_cs: Some("Times New Roman".to_string()),
1759 sz: Some(HalfPoint(22)),
1760 sz_cs: Some(HalfPoint(22)),
1761 ..Default::default()
1762 }),
1763 ppr: Some(CT_PPr {
1764 space_after: Some(crate::units::Twips(160)),
1765 line_spacing: Some(crate::units::Twips(259)),
1766 line_rule: Some("auto".to_string()),
1767 ..Default::default()
1768 }),
1769 };
1770
1771 CT_Styles {
1772 doc_defaults: Some(doc_defaults),
1773 styles: vec![normal, heading1],
1774 }
1775 }
1776}
1777
1778impl Default for CT_Styles {
1779 fn default() -> Self {
1780 Self::new()
1781 }
1782}
1783
1784fn style_child_rank(name: &[u8], word_prefixes: &[String]) -> u8 {
1785 let local = name.rsplit(|byte| *byte == b':').next().unwrap_or(name);
1786 if !is_word_element(name, local, word_prefixes) {
1787 return 26;
1788 }
1789 match local {
1790 b"name" => 0,
1791 b"aliases" => 1,
1792 b"basedOn" => 2,
1793 b"next" => 3,
1794 b"link" => 4,
1795 b"autoRedefine" => 5,
1796 b"hidden" => 6,
1797 b"uiPriority" => 7,
1798 b"semiHidden" => 8,
1799 b"unhideWhenUsed" => 9,
1800 b"qFormat" => 10,
1801 b"locked" => 11,
1802 b"personal" => 12,
1803 b"personalCompose" => 13,
1804 b"personalReply" => 14,
1805 b"rsid" => 19,
1806 b"pPr" => 20,
1807 b"rPr" => 21,
1808 b"tblPr" => 22,
1809 b"trPr" => 23,
1810 b"tcPr" => 24,
1811 b"tblStylePr" => 25,
1812 _ => 26,
1813 }
1814}
1815
1816fn style_scalar_rank(name: &[u8], word_prefixes: &[String]) -> Option<u8> {
1817 let rank = style_child_rank(name, word_prefixes);
1818 matches!(rank, 0 | 2..=11).then_some(rank)
1819}
1820
1821fn style_scalar_empty_requires_snapshot(element: &BytesStart<'_>, rank: u8) -> Result<bool> {
1822 let canonical_name: &[u8] = match rank {
1823 0 => b"w:name",
1824 2 => b"w:basedOn",
1825 3 => b"w:next",
1826 4 => b"w:link",
1827 5 => b"w:autoRedefine",
1828 6 => b"w:hidden",
1829 7 => b"w:uiPriority",
1830 8 => b"w:semiHidden",
1831 9 => b"w:unhideWhenUsed",
1832 10 => b"w:qFormat",
1833 11 => b"w:locked",
1834 _ => unreachable!("style scalar rank"),
1835 };
1836 if element.name().as_ref() != canonical_name {
1837 return Ok(true);
1838 }
1839
1840 for attribute in element.attributes() {
1841 if attribute?.key.as_ref() != b"w:val" {
1842 return Ok(true);
1843 }
1844 }
1845 Ok(false)
1846}
1847
1848#[allow(clippy::too_many_arguments)]
1849fn apply_style_scalar(
1850 rank: u8,
1851 string_value: Option<String>,
1852 toggle_value: Option<bool>,
1853 priority_value: Option<u32>,
1854 name: &mut Option<String>,
1855 based_on: &mut Option<String>,
1856 next_style: &mut Option<String>,
1857 linked_style: &mut Option<String>,
1858 auto_redefine: &mut Option<bool>,
1859 hidden: &mut Option<bool>,
1860 ui_priority: &mut Option<u32>,
1861 semi_hidden: &mut Option<bool>,
1862 unhide_when_used: &mut Option<bool>,
1863 quick_format: &mut Option<bool>,
1864 locked: &mut Option<bool>,
1865) {
1866 match rank {
1867 0 => *name = string_value,
1868 2 => *based_on = string_value,
1869 3 => *next_style = string_value,
1870 4 => *linked_style = string_value,
1871 5 => *auto_redefine = toggle_value,
1872 6 => *hidden = toggle_value,
1873 7 => *ui_priority = priority_value,
1874 8 => *semi_hidden = toggle_value,
1875 9 => *unhide_when_used = toggle_value,
1876 10 => *quick_format = toggle_value,
1877 11 => *locked = toggle_value,
1878 _ => unreachable!("style scalar rank"),
1879 }
1880}
1881
1882fn write_style_extras<W: std::io::Write>(
1883 writer: &mut Writer<W>,
1884 extras: &[&(u8, Vec<u8>)],
1885 index: &mut usize,
1886 before_rank: u8,
1887) -> Result<()> {
1888 while let Some((rank, raw)) = extras.get(*index).copied() {
1889 if *rank >= before_rank {
1890 break;
1891 }
1892 writer.get_mut().write_all(raw)?;
1893 *index += 1;
1894 }
1895 Ok(())
1896}
1897
1898fn namespace_bindings_at(
1899 element: &BytesStart,
1900 inherited: &[(String, String)],
1901) -> Result<Vec<(String, String)>> {
1902 let mut bindings = inherited.to_vec();
1903 for attribute in element.attributes() {
1904 let attribute = attribute?;
1905 let key = attribute.key.as_ref();
1906 let prefix = if key == b"xmlns" {
1907 Some(String::new())
1908 } else {
1909 key.strip_prefix(b"xmlns:")
1910 .map(|prefix| String::from_utf8_lossy(prefix).into_owned())
1911 };
1912 let Some(prefix) = prefix else {
1913 continue;
1914 };
1915 let uri = std::str::from_utf8(&attribute.value)?.to_owned();
1916 if let Some(binding) = bindings
1917 .iter_mut()
1918 .find(|(candidate, _)| candidate == &prefix)
1919 {
1920 binding.1 = uri;
1921 } else {
1922 bindings.push((prefix, uri));
1923 }
1924 }
1925 Ok(bindings)
1926}
1927
1928fn reject_conflicting_style_prefixes(element: &BytesStart<'_>) -> Result<()> {
1929 for attribute in element.attributes() {
1930 let attribute = attribute?;
1931 let expected = match attribute.key.as_ref() {
1932 b"xmlns:w" => Some(W_NS),
1933 b"xmlns:r" => {
1934 Some("http://schemas.openxmlformats.org/officeDocument/2006/relationships")
1935 }
1936 _ => None,
1937 };
1938 let Some(expected) = expected else {
1939 continue;
1940 };
1941 let value = std::str::from_utf8(&attribute.value)?;
1942 if value != expected {
1943 return Err(OxmlError::InvalidValue(format!(
1944 "{} conflicts with the fixed style writer namespace",
1945 String::from_utf8_lossy(attribute.key.as_ref())
1946 )));
1947 }
1948 }
1949 Ok(())
1950}
1951
1952fn raw_style_attributes(
1953 element: &BytesStart<'_>,
1954 modeled: &[&[u8]],
1955 word_prefixes: &[String],
1956 namespace_bindings: &[(String, String)],
1957) -> Result<Vec<(String, String)>> {
1958 let mut attributes = Vec::new();
1959 for attribute in element.attributes() {
1960 let attribute = attribute?;
1961 let key = attribute.key.as_ref();
1962 let value = attribute
1963 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
1964 .into_owned();
1965 let canonical_namespace = (key == b"xmlns:w" && value == W_NS)
1966 || (key == b"xmlns:r"
1967 && value == "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
1968 if !canonical_namespace
1969 && !modeled
1970 .iter()
1971 .any(|local| is_word_attribute(key, local, word_prefixes))
1972 {
1973 attributes.push((std::str::from_utf8(key)?.to_owned(), value));
1974 }
1975 }
1976 if !attributes.is_empty() {
1977 for (prefix, namespace) in namespace_bindings {
1978 if prefix.is_empty()
1979 || (prefix == "w" && namespace == W_NS)
1980 || (prefix == "r"
1981 && namespace
1982 == "http://schemas.openxmlformats.org/officeDocument/2006/relationships")
1983 {
1984 continue;
1985 }
1986 let declaration = format!("xmlns:{prefix}");
1987 if !attributes.iter().any(|(name, _)| name == &declaration) {
1988 attributes.push((declaration, namespace.clone()));
1989 }
1990 }
1991 }
1992 Ok(attributes)
1993}
1994
1995fn reject_conflicting_style_bindings(bindings: &[(String, String)]) -> Result<()> {
1996 for (prefix, namespace) in bindings {
1997 let expected = match prefix.as_str() {
1998 "w" => Some(W_NS),
1999 "r" => Some("http://schemas.openxmlformats.org/officeDocument/2006/relationships"),
2000 _ => None,
2001 };
2002 if expected.is_some_and(|expected| namespace != expected) {
2003 return Err(OxmlError::InvalidValue(format!(
2004 "xmlns:{prefix} conflicts with the fixed style writer namespace"
2005 )));
2006 }
2007 }
2008 Ok(())
2009}
2010
2011fn push_style_attributes(element: &mut BytesStart<'_>, attributes: &[(String, String)]) {
2012 for (name, value) in attributes {
2013 element.push_attribute((name.as_str(), value.as_str()));
2014 }
2015}
2016
2017fn make_style_raw_self_contained(
2018 raw: &[u8],
2019 namespace_bindings: &[(String, String)],
2020) -> Result<Vec<u8>> {
2021 let external_bindings = namespace_bindings
2022 .iter()
2023 .filter(|(prefix, namespace)| {
2024 !((prefix == "w" && namespace == W_NS)
2025 || (prefix == "r"
2026 && namespace
2027 == "http://schemas.openxmlformats.org/officeDocument/2006/relationships"))
2028 })
2029 .cloned()
2030 .collect::<Vec<_>>();
2031 crate::text::raw_with_external_bindings(raw, &external_bindings)
2032}
2033
2034fn parse_style_table_properties(raw: &[u8], word_prefixes: &[String]) -> Result<CT_TblPr> {
2035 let mut reader = Reader::from_reader(raw);
2036 let mut buf = Vec::new();
2037 loop {
2038 match reader.read_event_into(&mut buf)? {
2039 Event::Start(ref start)
2040 if is_word_element(start.name().as_ref(), b"tblPr", word_prefixes) =>
2041 {
2042 let prefixes = word_prefixes_at(start, word_prefixes)?;
2043 return CT_TblPr::from_xml_with_prefixes(&mut reader, &prefixes);
2044 }
2045 Event::Eof => return Ok(CT_TblPr::default()),
2046 _ => {}
2047 }
2048 buf.clear();
2049 }
2050}
2051
2052fn explicit_style_element_has_only_trivia(raw: &[u8]) -> Result<bool> {
2053 let mut reader = Reader::from_reader(raw);
2054 let mut inside = false;
2055 let mut buf = Vec::new();
2056 loop {
2057 match reader.read_event_into(&mut buf)? {
2058 Event::Start(_) if !inside => inside = true,
2059 Event::End(_) if inside => return Ok(true),
2060 Event::Text(text) if inside && text.as_ref().iter().all(u8::is_ascii_whitespace) => {}
2061 Event::Comment(_) | Event::PI(_) if inside => {}
2062 Event::Eof => return Ok(false),
2063 _ if inside => return Ok(false),
2064 _ => {}
2065 }
2066 buf.clear();
2067 }
2068}
2069
2070#[derive(Default)]
2072struct ConditionalStyleLayers {
2073 paragraph_properties: Option<CT_PPr>,
2074 run_properties: Option<Box<CT_RPr>>,
2075 table_properties: Option<CT_TblPr>,
2076 row_properties: Option<Box<CT_TrPr>>,
2077 cell_properties: Option<CT_TcPr>,
2078}
2079
2080fn parse_conditional_style_properties(
2081 raw: &[u8],
2082 word_prefixes: &[String],
2083) -> Result<ConditionalStyleLayers> {
2084 let mut reader = Reader::from_reader(raw);
2085 let mut layers = ConditionalStyleLayers::default();
2086 let mut buf = Vec::new();
2087 let mut depth = 0usize;
2088 loop {
2089 match reader.read_event_into(&mut buf)? {
2090 Event::Start(ref start) => {
2091 depth += 1;
2092 let prefixes = word_prefixes_at(start, word_prefixes)?;
2093 if depth != 2 {
2094 } else if is_word_element(start.name().as_ref(), b"pPr", &prefixes) {
2097 let captured = capture_element(&mut reader, start)?;
2098 layers.paragraph_properties = Some(parse_scoped_ppr(&captured, &prefixes)?);
2099 depth -= 1;
2100 } else if is_word_element(start.name().as_ref(), b"rPr", &prefixes) {
2101 let captured = capture_element(&mut reader, start)?;
2102 layers.run_properties = Some(Box::new(parse_style_rpr(&captured, &prefixes)?));
2103 depth -= 1;
2104 } else if is_word_element(start.name().as_ref(), b"tblPr", &prefixes) {
2105 layers.table_properties =
2106 Some(CT_TblPr::from_xml_with_prefixes(&mut reader, &prefixes)?);
2107 depth -= 1;
2108 } else if is_word_element(start.name().as_ref(), b"trPr", &prefixes) {
2109 layers.row_properties = Some(Box::new(CT_TrPr::from_xml_with_prefixes(
2110 &mut reader,
2111 &prefixes,
2112 )?));
2113 depth -= 1;
2114 } else if is_word_element(start.name().as_ref(), b"tcPr", &prefixes) {
2115 layers.cell_properties =
2116 Some(CT_TcPr::from_xml_with_prefixes(&mut reader, &prefixes)?);
2117 depth -= 1;
2118 }
2119 }
2120 Event::End(_) => depth = depth.saturating_sub(1),
2121 Event::Eof => break,
2122 _ => {}
2123 }
2124 buf.clear();
2125 }
2126 Ok(layers)
2127}
2128
2129fn serialize_conditional_table_style(conditional: &CT_TblStylePr) -> Result<Vec<u8>> {
2130 let mut reader = Reader::from_reader(conditional.raw_xml.as_slice());
2131 let mut prefixes = vec!["w".to_owned()];
2132 let mut original_region = None;
2133 let mut buf = Vec::new();
2134 loop {
2135 match reader.read_event_into(&mut buf)? {
2136 Event::Start(ref start) => {
2137 prefixes = word_prefixes_at(start, &prefixes)?;
2138 original_region = get_word_attr(start, b"type", &prefixes)?;
2139 break;
2140 }
2141 Event::Eof => break,
2142 _ => {}
2143 }
2144 buf.clear();
2145 }
2146 let Some(region) = conditional.region else {
2147 return Ok(conditional.raw_xml.clone());
2150 };
2151 let original = parse_conditional_style_properties(&conditional.raw_xml, &prefixes)?;
2152 if original_region.as_deref() == Some(region.to_str())
2153 && original.paragraph_properties == conditional.paragraph_properties
2154 && original.run_properties == conditional.run_properties
2155 && original.table_properties == conditional.table_properties
2156 && original.row_properties == conditional.row_properties
2157 && original.cell_properties == conditional.cell_properties
2158 {
2159 return Ok(conditional.raw_xml.clone());
2160 }
2161
2162 let mut extras = preserved_conditional_style_children(&conditional.raw_xml)?;
2163 extras.sort_by_key(|(rank, _)| *rank);
2164 let extra_refs = extras.iter().collect::<Vec<_>>();
2165 let mut extra_index = 0;
2166 let mut writer = Writer::new(Vec::new());
2167 let mut start = BytesStart::new("w:tblStylePr");
2168 start.push_attribute(("w:type", region.to_str()));
2169 push_style_attributes(&mut start, &conditional.extra_attributes);
2170 writer.write_event(Event::Start(start))?;
2171 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 0)?;
2172 if let Some(properties) = &conditional.paragraph_properties {
2173 properties.to_xml(&mut writer)?;
2174 }
2175 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 1)?;
2176 if let Some(properties) = &conditional.run_properties {
2177 properties.to_xml(&mut writer)?;
2178 }
2179 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 2)?;
2180 if let Some(properties) = &conditional.table_properties {
2181 properties.to_xml(&mut writer)?;
2182 }
2183 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 3)?;
2184 if let Some(properties) = &conditional.row_properties {
2185 properties.to_xml(&mut writer)?;
2186 }
2187 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 4)?;
2188 if let Some(properties) = &conditional.cell_properties {
2189 properties.to_xml(&mut writer)?;
2190 }
2191 write_style_extras(&mut writer, &extra_refs, &mut extra_index, u8::MAX)?;
2192 writer.write_event(Event::End(BytesEnd::new("w:tblStylePr")))?;
2193 Ok(writer.into_inner())
2194}
2195
2196fn preserved_conditional_style_children(raw: &[u8]) -> Result<Vec<(u8, Vec<u8>)>> {
2197 let mut reader = Reader::from_reader(raw);
2198 let mut inside = false;
2199 let mut word_prefixes = vec!["w".to_owned()];
2200 let mut extras = Vec::new();
2201 let mut buf = Vec::new();
2202 loop {
2203 match reader.read_event_into(&mut buf)? {
2204 Event::Start(ref start) if !inside => {
2205 word_prefixes = word_prefixes_at(start, &word_prefixes)?;
2206 inside = true;
2207 }
2208 Event::Start(ref start) => {
2209 let prefixes = word_prefixes_at(start, &word_prefixes)?;
2210 let local = start.local_name();
2211 if is_word_element(start.name().as_ref(), local.as_ref(), &prefixes)
2212 && matches!(
2213 local.as_ref(),
2214 b"pPr" | b"rPr" | b"tblPr" | b"trPr" | b"tcPr"
2215 )
2216 {
2217 reader.read_to_end_into(start.name(), &mut Vec::new())?;
2218 } else {
2219 extras.push((5, capture_element(&mut reader, start)?));
2220 }
2221 }
2222 Event::Empty(ref empty) => {
2223 let prefixes = word_prefixes_at(empty, &word_prefixes)?;
2224 let local = empty.local_name();
2225 if !(is_word_element(empty.name().as_ref(), local.as_ref(), &prefixes)
2226 && matches!(
2227 local.as_ref(),
2228 b"pPr" | b"rPr" | b"tblPr" | b"trPr" | b"tcPr"
2229 ))
2230 {
2231 extras.push((5, capture_empty_element(empty)?));
2232 }
2233 }
2234 Event::End(_) | Event::Eof => break,
2235 _ => {}
2236 }
2237 buf.clear();
2238 }
2239 Ok(extras)
2240}
2241
2242fn get_val_attr(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<String>> {
2244 get_word_attr(e, b"val", word_prefixes)
2245}
2246
2247fn get_word_attr(e: &BytesStart, local: &[u8], word_prefixes: &[String]) -> Result<Option<String>> {
2248 for attr in e.attributes() {
2249 let attr = attr?;
2250 if is_word_attribute(attr.key.as_ref(), local, word_prefixes) {
2251 return Ok(Some(std::str::from_utf8(&attr.value)?.to_string()));
2252 }
2253 }
2254 Ok(None)
2255}
2256
2257fn parse_style_toggle(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<bool>> {
2258 let value = get_word_attr(e, b"val", word_prefixes)?;
2259 match value.as_deref() {
2260 None | Some("1" | "true" | "on") => Ok(Some(true)),
2261 Some("0" | "false" | "off") => Ok(Some(false)),
2262 Some(value) => Err(OxmlError::InvalidValue(format!(
2263 "invalid style toggle value: {value}"
2264 ))),
2265 }
2266}
2267
2268fn parse_style_priority(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<u32>> {
2269 get_word_attr(e, b"val", word_prefixes)?
2270 .map(|value| {
2271 value
2272 .parse::<u32>()
2273 .map_err(|_| OxmlError::InvalidValue(format!("invalid style UI priority: {value}")))
2274 })
2275 .transpose()
2276}
2277
2278fn preserved_style_attribute(raw: &[u8], local: &[u8]) -> Result<Option<String>> {
2279 let mut reader = Reader::from_reader(raw);
2280 let mut buf = Vec::new();
2281 loop {
2282 match reader.read_event_into(&mut buf)? {
2283 Event::Start(ref start) | Event::Empty(ref start) => {
2284 let prefixes = word_prefixes_at(start, &["w".to_owned()])?;
2285 return get_word_attr(start, local, &prefixes);
2286 }
2287 Event::Eof => return Ok(None),
2288 _ => {}
2289 }
2290 buf.clear();
2291 }
2292}
2293
2294fn preserved_modeled_xml(modeled_xml: &[(u8, Vec<u8>)], rank: u8) -> Option<&[u8]> {
2295 modeled_xml
2296 .iter()
2297 .find(|(candidate, _)| *candidate == rank)
2298 .map(|(_, raw)| raw.as_slice())
2299}
2300
2301fn rewritten_style_scalar_start(
2302 element: &BytesStart<'_>,
2303 value: &str,
2304) -> Result<BytesStart<'static>> {
2305 let prefixes = word_prefixes_at(element, &["w".to_owned()])?;
2306 let element_name = std::str::from_utf8(element.name().as_ref())?.to_owned();
2307 let mut rewritten = BytesStart::new(element_name.clone());
2308 let mut wrote_value = false;
2309 for attribute in element.attributes() {
2310 let attribute = attribute?;
2311 let key = std::str::from_utf8(attribute.key.as_ref())?.to_owned();
2312 if is_word_attribute(attribute.key.as_ref(), b"val", &prefixes) {
2313 if !wrote_value {
2314 rewritten.push_attribute((key.as_str(), value));
2315 wrote_value = true;
2316 }
2317 } else {
2318 let attribute_value = attribute
2319 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
2320 .into_owned();
2321 rewritten.push_attribute((key.as_str(), attribute_value.as_str()));
2322 }
2323 }
2324 if !wrote_value {
2325 let element_prefix = element_name
2326 .split_once(':')
2327 .map(|(prefix, _)| prefix.to_owned());
2328 let existing_prefix = element_prefix.or_else(|| {
2329 prefixes
2330 .iter()
2331 .find(|prefix| !prefix.is_empty() && !prefix.starts_with('\0'))
2332 .cloned()
2333 });
2334 let prefix = existing_prefix.unwrap_or_else(|| {
2335 let mut candidate = "rdocxStyle".to_owned();
2336 let mut suffix = 1usize;
2337 while element
2338 .attributes()
2339 .filter_map(std::result::Result::ok)
2340 .any(|attribute| attribute.key.as_ref() == format!("xmlns:{candidate}").as_bytes())
2341 {
2342 candidate = format!("rdocxStyle{suffix}");
2343 suffix += 1;
2344 }
2345 let declaration = format!("xmlns:{candidate}");
2346 rewritten.push_attribute((declaration.as_str(), W_NS));
2347 candidate
2348 });
2349 let key = format!("{prefix}:val");
2350 rewritten.push_attribute((key.as_str(), value));
2351 }
2352 Ok(rewritten.into_owned())
2353}
2354
2355fn write_rewritten_style_scalar<W: std::io::Write>(
2356 writer: &mut Writer<W>,
2357 raw: &[u8],
2358 value: &str,
2359) -> Result<()> {
2360 let mut reader = Reader::from_reader(raw);
2361 let mut root_pending = true;
2362 let mut buf = Vec::new();
2363 loop {
2364 match reader.read_event_into(&mut buf)? {
2365 Event::Start(element) if root_pending => {
2366 root_pending = false;
2367 writer.write_event(Event::Start(rewritten_style_scalar_start(&element, value)?))?;
2368 }
2369 Event::Empty(element) if root_pending => {
2370 writer.write_event(Event::Empty(rewritten_style_scalar_start(&element, value)?))?;
2371 break;
2372 }
2373 Event::Eof => break,
2374 event => writer.write_event(event.into_owned())?,
2375 }
2376 buf.clear();
2377 }
2378 Ok(())
2379}
2380
2381fn write_style_string<W: std::io::Write>(
2382 writer: &mut Writer<W>,
2383 name: &str,
2384 rank: u8,
2385 value: Option<&str>,
2386 modeled_xml: &[(u8, Vec<u8>)],
2387) -> Result<()> {
2388 let Some(value) = value else {
2389 return Ok(());
2390 };
2391 if let Some(raw) = preserved_modeled_xml(modeled_xml, rank)
2392 && preserved_style_attribute(raw, b"val")?.as_deref() == Some(value)
2393 {
2394 writer.get_mut().write_all(raw)?;
2395 return Ok(());
2396 }
2397 if let Some(raw) = preserved_modeled_xml(modeled_xml, rank) {
2398 return write_rewritten_style_scalar(writer, raw, value);
2399 }
2400 let mut element = BytesStart::new(name);
2401 element.push_attribute(("w:val", value));
2402 writer.write_event(Event::Empty(element))?;
2403 Ok(())
2404}
2405
2406fn write_style_toggle<W: std::io::Write>(
2407 writer: &mut Writer<W>,
2408 name: &str,
2409 rank: u8,
2410 value: Option<bool>,
2411 modeled_xml: &[(u8, Vec<u8>)],
2412) -> Result<()> {
2413 if let Some(value) = value {
2414 if let Some(raw) = preserved_modeled_xml(modeled_xml, rank) {
2415 let prefixes = vec!["w".to_owned()];
2416 let mut reader = Reader::from_reader(raw);
2417 let mut buf = Vec::new();
2418 let preserved = loop {
2419 match reader.read_event_into(&mut buf)? {
2420 Event::Start(ref start) | Event::Empty(ref start) => {
2421 let prefixes = word_prefixes_at(start, &prefixes)?;
2422 break parse_style_toggle(start, &prefixes)?;
2423 }
2424 Event::Eof => break None,
2425 _ => {}
2426 }
2427 buf.clear();
2428 };
2429 if preserved == Some(value) {
2430 writer.get_mut().write_all(raw)?;
2431 return Ok(());
2432 }
2433 return write_rewritten_style_scalar(writer, raw, if value { "1" } else { "0" });
2434 }
2435 let mut element = BytesStart::new(name);
2436 if !value {
2437 element.push_attribute(("w:val", "0"));
2438 }
2439 writer.write_event(Event::Empty(element))?;
2440 }
2441 Ok(())
2442}
2443
2444fn write_style_priority<W: std::io::Write>(
2445 writer: &mut Writer<W>,
2446 priority: Option<u32>,
2447 modeled_xml: &[(u8, Vec<u8>)],
2448) -> Result<()> {
2449 let Some(priority) = priority else {
2450 return Ok(());
2451 };
2452 if let Some(raw) = preserved_modeled_xml(modeled_xml, 7)
2453 && preserved_style_attribute(raw, b"val")?
2454 .as_deref()
2455 .and_then(|value| value.parse::<u32>().ok())
2456 == Some(priority)
2457 {
2458 writer.get_mut().write_all(raw)?;
2459 return Ok(());
2460 }
2461 let value = priority.to_string();
2462 if let Some(raw) = preserved_modeled_xml(modeled_xml, 7) {
2463 return write_rewritten_style_scalar(writer, raw, &value);
2464 }
2465 let mut element = BytesStart::new("w:uiPriority");
2466 element.push_attribute(("w:val", value.as_str()));
2467 writer.write_event(Event::Empty(element))?;
2468 Ok(())
2469}
2470
2471#[cfg(test)]
2472mod tests {
2473 use super::*;
2474
2475 #[test]
2476 fn round_trip_styles() {
2477 let styles = CT_Styles::new_default();
2478 let xml = styles.to_xml().unwrap();
2479 let parsed = CT_Styles::from_xml(&xml).unwrap();
2480
2481 let original = String::from_utf8(xml.clone()).unwrap();
2483 let (prefix, heading) = original
2484 .split_once(r#"<w:style w:type="paragraph" w:styleId="Heading1">"#)
2485 .unwrap();
2486 assert_eq!(prefix.matches("<w:docDefaults>").count(), 1);
2487 let prefix = prefix.replace(
2488 "<w:docDefaults>",
2489 &format!("<w:docDefaults xmlns:w=\"{W_NS}\">"),
2490 );
2491 let mut heading = heading.to_owned();
2492 for owner in ["pPr", "rPr"] {
2493 let opening = format!("<w:{owner}>");
2494 assert_eq!(heading.matches(&opening).count(), 1);
2495 heading = heading.replace(&opening, &format!("<w:{owner} xmlns:w=\"{W_NS}\">"));
2496 }
2497 let expected =
2498 format!(r#"{prefix}<w:style w:type="paragraph" w:styleId="Heading1">{heading}"#);
2499 let imported = parsed.to_xml().unwrap();
2500 assert_eq!(imported, expected.as_bytes());
2501 let reopened = CT_Styles::from_xml(&imported).unwrap();
2502 assert_eq!(reopened.to_xml().unwrap(), imported);
2503 assert_eq!(reopened, parsed);
2504
2505 assert_eq!(parsed.styles.len(), 2);
2506 assert!(parsed.doc_defaults.is_some());
2507
2508 let normal = parsed.get_by_id("Normal").unwrap();
2509 assert_eq!(normal.name, Some("Normal".to_string()));
2510 assert!(normal.is_default);
2511
2512 let h1 = parsed.get_by_id("Heading1").unwrap();
2513 assert_eq!(h1.based_on, Some("Normal".to_string()));
2514 }
2515
2516 #[test]
2517 fn find_default_style() {
2518 let styles = CT_Styles::new_default();
2519 let default_para = styles.get_default(StyleType::Paragraph).unwrap();
2520 assert_eq!(default_para.style_id, "Normal");
2521 }
2522
2523 #[test]
2524 fn linked_style_and_ui_flags_accept_aliases_and_serialize_in_schema_order() {
2525 let xml = format!(
2526 r#"<q:styles xmlns:q="{W_NS}" xmlns:x="urn:producer"><q:style q:type="paragraph" q:styleId="Corpus" x:root="&"><q:name q:val="Corpus"> <!--name-trivia--> </q:name><q:next q:val="Normal"><?next kept?></q:next><q:link q:val="CorpusChar"> <!--link-trivia--> </q:link><q:autoRedefine q:val="0" x:keep="yes"/><q:hidden> </q:hidden><q:uiPriority q:val="17"></q:uiPriority><q:semiHidden q:val="false"></q:semiHidden><q:unhideWhenUsed></q:unhideWhenUsed><q:qFormat q:val="1"></q:qFormat><q:locked q:val="true"></q:locked><q:pPr><q:spacing q:after="0"/></q:pPr></q:style></q:styles>"#
2527 );
2528 let mut styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2529 let style = styles.get_by_id("Corpus").unwrap();
2530 assert_eq!(style.next_style.as_deref(), Some("Normal"));
2531 assert_eq!(style.linked_style.as_deref(), Some("CorpusChar"));
2532 assert_eq!(style.auto_redefine, Some(false));
2533 assert_eq!(style.hidden, Some(true));
2534 assert_eq!(style.ui_priority, Some(17));
2535 assert_eq!(style.semi_hidden, Some(false));
2536 assert_eq!(style.unhide_when_used, Some(true));
2537 assert_eq!(style.quick_format, Some(true));
2538 assert_eq!(style.locked, Some(true));
2539
2540 let serialized = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2541 let positions = [
2542 "<q:next",
2543 "<q:link",
2544 "<q:autoRedefine",
2545 "<q:hidden",
2546 "<q:uiPriority",
2547 "<q:semiHidden",
2548 "<q:unhideWhenUsed",
2549 "<q:qFormat",
2550 "<q:locked",
2551 "<q:pPr",
2552 ]
2553 .map(|token| serialized.find(token).unwrap());
2554 assert!(positions.windows(2).all(|pair| pair[0] < pair[1]));
2555 assert!(serialized.contains(&format!(
2556 r#"<q:pPr xmlns:q="{W_NS}"><q:spacing q:after="0"/></q:pPr>"#
2557 )));
2558 assert_eq!(serialized.matches("name-trivia").count(), 1);
2559 assert_eq!(serialized.matches("next kept").count(), 1);
2560 assert_eq!(serialized.matches("link-trivia").count(), 1);
2561 assert_eq!(serialized.matches(r#"x:root="&""#).count(), 1);
2562 assert_eq!(serialized.matches(r#"x:keep="yes""#).count(), 1);
2563 let reparsed = CT_Styles::from_xml(serialized.as_bytes()).unwrap();
2564 assert_eq!(reparsed.styles[0], styles.styles[0]);
2565
2566 styles.styles[0].next_style = Some("Other".to_owned());
2567 styles.styles[0].auto_redefine = Some(true);
2568 styles.styles[0].ui_priority = Some(23);
2569 let changed = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2570 assert!(changed.contains("<q:next"));
2571 assert!(changed.contains(r#"q:val="Other""#));
2572 assert_eq!(changed.matches("next kept").count(), 1);
2573 assert!(changed.contains("<q:autoRedefine"));
2574 assert!(changed.contains(r#"q:val="1""#));
2575 assert_eq!(changed.matches(r#"x:keep="yes""#).count(), 1);
2576 assert!(changed.contains("<q:uiPriority"));
2577 assert!(changed.contains(r#"q:val="23""#));
2578 let reparsed = CT_Styles::from_xml(changed.as_bytes()).unwrap();
2579 assert_eq!(reparsed.styles[0].next_style.as_deref(), Some("Other"));
2580 assert_eq!(reparsed.styles[0].auto_redefine, Some(true));
2581 assert_eq!(reparsed.styles[0].ui_priority, Some(23));
2582 }
2583
2584 #[test]
2585 fn style_parser_rejects_prefixes_that_conflict_with_fixed_writer_bindings() {
2586 let xml = format!(
2587 r#"<q:styles xmlns:q="{W_NS}" xmlns:w="urn:not-word"><q:style q:type="paragraph" q:styleId="Corpus"></q:style></q:styles>"#
2588 );
2589 let error = CT_Styles::from_xml(xml.as_bytes()).unwrap_err();
2590 assert!(error.to_string().contains("xmlns:w"));
2591 }
2592
2593 #[test]
2594 fn changed_table_style_properties_keep_one_unknown_child_in_schema_order() {
2595 let xml = format!(
2596 r#"<q:styles xmlns:q="{W_NS}" xmlns:x="urn:producer"><q:style q:type="table" q:styleId="Corpus"><q:tblPr><q:tblStyleRowBandSize q:val="2"/><x:producer x:exact="kept"/><q:shd q:val="clear" q:fill="EEEEEE"/></q:tblPr></q:style></q:styles>"#
2597 );
2598 let mut styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2599 styles.styles[0]
2600 .table_properties
2601 .as_mut()
2602 .unwrap()
2603 .shading
2604 .as_mut()
2605 .unwrap()
2606 .fill = Some("D9EAF7".to_owned());
2607
2608 let serialized = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2609 assert_eq!(serialized.matches("tblStyleRowBandSize").count(), 1);
2610 assert_eq!(serialized.matches("x:producer").count(), 1);
2611 assert!(
2612 serialized.find("tblStyleRowBandSize").unwrap() < serialized.find("w:shd").unwrap()
2613 );
2614 assert_eq!(
2615 CT_Styles::from_xml(serialized.as_bytes()).unwrap().styles[0]
2616 .table_properties
2617 .as_ref()
2618 .and_then(|properties| properties.shading.as_ref())
2619 .and_then(|shading| shading.fill.as_deref()),
2620 Some("D9EAF7")
2621 );
2622 }
2623
2624 #[test]
2625 fn aliased_style_paragraph_properties_use_ancestor_namespace_scope() {
2626 let xml = format!(
2627 r#"<q:styles xmlns:q="{W_NS}" xmlns:ext="urn:producer"><q:style q:type="paragraph" q:styleId="Alias"><q:pPr><ext:jc ext:val="right"/><q:jc q:val="center"/></q:pPr></q:style></q:styles>"#
2628 );
2629 let parsed = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2630 let ppr = parsed.styles[0].ppr.as_ref().unwrap();
2631 assert_eq!(ppr.jc, Some(crate::shared::ST_Jc::Center));
2632 }
2633
2634 #[test]
2635 fn direct_style_parser_uses_supplied_start_ancestor_scope() {
2636 let xml = format!(
2637 r#"<outer xmlns:ext="urn:producer"><q:style xmlns:q="{W_NS}" q:type="paragraph" q:styleId="Direct"><ext:pPr><ext:jc ext:val="right"/></ext:pPr><q:pPr><ext:jc ext:val="right"/><q:jc q:val="center"/></q:pPr></q:style></outer>"#
2638 );
2639 let mut reader = Reader::from_str(&xml);
2640 let mut buf = Vec::new();
2641 let parsed = loop {
2642 match reader.read_event_into(&mut buf) {
2643 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
2644 break CT_Style::from_xml(&mut reader, element).unwrap();
2645 }
2646 Ok(Event::Eof) => panic!("missing style"),
2647 event => {
2648 event.unwrap();
2649 }
2650 }
2651 buf.clear();
2652 };
2653 assert_eq!(
2654 parsed.ppr.as_ref().unwrap().jc,
2655 Some(crate::shared::ST_Jc::Center)
2656 );
2657 }
2658
2659 #[test]
2660 fn direct_style_parser_does_not_promote_foreign_start_prefix() {
2661 let xml = r#"<outer><ext:style xmlns:ext="urn:producer" ext:type="paragraph" ext:styleId="Foreign"><ext:pPr><ext:jc ext:val="right"/></ext:pPr></ext:style></outer>"#;
2662 let mut reader = Reader::from_str(xml);
2663 let mut buf = Vec::new();
2664 let parsed = loop {
2665 match reader.read_event_into(&mut buf) {
2666 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
2667 break CT_Style::from_xml(&mut reader, element).unwrap();
2668 }
2669 Ok(Event::Eof) => panic!("missing style"),
2670 event => {
2671 event.unwrap();
2672 }
2673 }
2674 buf.clear();
2675 };
2676 assert!(parsed.ppr.is_none());
2677 }
2678
2679 #[test]
2680 fn direct_style_parser_accepts_default_word_namespace() {
2681 let xml = format!(
2682 r#"<outer xmlns:ext="urn:producer"><style xmlns="{W_NS}" xmlns:w="{W_NS}" w:type="paragraph" w:styleId="Direct"><ext:pPr><ext:jc ext:val="right"/></ext:pPr><pPr><ext:jc ext:val="right"/><jc w:val="center"/></pPr></style></outer>"#
2683 );
2684 let mut reader = Reader::from_str(&xml);
2685 let mut buf = Vec::new();
2686 let parsed = loop {
2687 match reader.read_event_into(&mut buf) {
2688 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
2689 break CT_Style::from_xml(&mut reader, element).unwrap();
2690 }
2691 Ok(Event::Eof) => panic!("missing style"),
2692 event => {
2693 event.unwrap();
2694 }
2695 }
2696 buf.clear();
2697 };
2698 assert_eq!(
2699 parsed.ppr.as_ref().unwrap().jc,
2700 Some(crate::shared::ST_Jc::Center)
2701 );
2702 }
2703
2704 #[test]
2705 fn table_style_properties_are_namespace_aware_schema_ordered_and_preserved() {
2706 let xml = format!(
2707 r#"<q:styles xmlns:q="{W_NS}" xmlns:bad="urn:not-word" xmlns:x="urn:region"><q:style q:type="table" q:styleId="Dense"><bad:tblPr><bad:tblBorders><bad:top bad:val="double"/></bad:tblBorders></bad:tblPr><q:pPr><q:spacing q:after="40"/></q:pPr><q:rPr><q:b/></q:rPr><q:tblPr><q:tblBorders><q:top q:val="single" q:sz="8" q:color="112233"/></q:tblBorders><ext:keep xmlns:ext="urn:producer" ext:value="byte-identical"/></q:tblPr><q:tblStylePr q:type="firstRow" x:region="&"><q:pPr><q:spacing q:after="0"/></q:pPr><ext:conditional xmlns:ext="urn:producer" ext:value="preserved"/><q:tcPr><q:shd q:val="clear" q:fill="AABBCC"/></q:tcPr></q:tblStylePr></q:style></q:styles>"#
2708 );
2709 let mut styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2710 let style = styles.get_by_id("Dense").unwrap();
2711 assert_eq!(
2712 style
2713 .table_properties
2714 .as_ref()
2715 .and_then(|properties| properties.borders.as_ref())
2716 .and_then(|borders| borders.top.as_ref())
2717 .and_then(|border| border.sz),
2718 Some(8)
2719 );
2720 assert_eq!(style.conditional_table_styles.len(), 1);
2721 assert_eq!(
2722 style.conditional_table_styles[0].region,
2723 Some(TableStyleRegion::FirstRow)
2724 );
2725
2726 let serialized = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2727 assert_eq!(serialized.matches("<q:tblPr").count(), 1);
2728 assert!(
2729 serialized
2730 .contains(r#"<ext:keep xmlns:ext="urn:producer" ext:value="byte-identical"/>"#)
2731 );
2732 let ppr = serialized.find("<q:pPr").unwrap();
2733 let rpr = serialized.find("<q:rPr").unwrap();
2734 let table_properties = serialized.find("<q:tblPr").unwrap();
2735 let conditional = serialized.find("<q:tblStylePr").unwrap();
2736 assert!(ppr < rpr && rpr < table_properties && table_properties < conditional);
2737 assert!(serialized.contains(&format!(
2738 r#"<q:pPr xmlns:q="{W_NS}"><q:spacing q:after="40"/></q:pPr>"#
2739 )));
2740 assert!(serialized.contains(&format!(r#"<q:rPr xmlns:q="{W_NS}"><q:b/></q:rPr>"#)));
2741 assert!(serialized.contains(r#"<bad:tblPr xmlns:bad="urn:not-word">"#));
2742 CT_Styles::from_xml(serialized.as_bytes()).expect("preserved prefixes remain bound");
2743
2744 styles.styles[0]
2745 .table_properties
2746 .as_mut()
2747 .unwrap()
2748 .borders
2749 .as_mut()
2750 .unwrap()
2751 .top
2752 .as_mut()
2753 .unwrap()
2754 .color = Some("445566".to_owned());
2755 let changed = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2756 assert_eq!(changed.matches("<w:tblPr>").count(), 1);
2757 assert_eq!(changed.matches("445566").count(), 1);
2758 assert!(
2759 changed.contains(r#"<ext:keep xmlns:ext="urn:producer" ext:value="byte-identical"/>"#)
2760 );
2761 styles.styles[0].conditional_table_styles[0]
2762 .cell_properties
2763 .as_mut()
2764 .unwrap()
2765 .shading
2766 .as_mut()
2767 .unwrap()
2768 .fill = Some("DDEEFF".to_owned());
2769 styles.styles[0].conditional_table_styles[0].region = Some(TableStyleRegion::LastRow);
2770 let changed = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2771 assert_eq!(changed.matches("DDEEFF").count(), 1);
2772 assert!(!changed.contains("AABBCC"));
2773 assert!(changed.contains(r#"<w:tblStylePr w:type="lastRow" x:region="&""#));
2774 assert_eq!(changed.matches(r#"x:region="&""#).count(), 1);
2775 assert!(!changed.contains(r#"tblStylePr q:type="firstRow""#));
2776 assert!(
2777 changed
2778 .contains(r#"<ext:conditional xmlns:ext="urn:producer" ext:value="preserved"/>"#)
2779 );
2780 let reparsed = CT_Styles::from_xml(changed.as_bytes())
2781 .expect("typed conditional projection remains valid XML");
2782 assert_eq!(
2783 reparsed.styles[0].conditional_table_styles[0].region,
2784 Some(TableStyleRegion::LastRow)
2785 );
2786 assert_eq!(
2787 reparsed.styles[0].conditional_table_styles[0]
2788 .cell_properties
2789 .as_ref()
2790 .and_then(|properties| properties.shading.as_ref())
2791 .and_then(|shading| shading.fill.as_deref()),
2792 Some("DDEEFF")
2793 );
2794 }
2795 #[test]
2796 fn fx200_defaults_source_preserves_unknown_wrappers_and_qualified_owner_after_font_edit() {
2797 let foreign =
2798 r#"<f:rPrDefault xmlns:f="urn:foreign" f:flag='A'><f:rPr/></f:rPrDefault>"#;
2799 let opaque = r#"<z:keep xmlns:z="urn:opaque" z:value='A'><z:child/></z:keep>"#;
2800 let source = format!(
2801 r#"<q:styles xmlns:q="{W_NS}" xmlns:x="urn:attr"><q:docDefaults x:root='A'><!--before-->{foreign}<q:rPrDefault x:wrapper='keep'> {opaque}<q:rPr x:property='A'><q:rFonts q:ascii="Prior" q:hAnsi="Other"/><q:b q:val='true'/><!--inside-->{opaque}</q:rPr><!--after--></q:rPrDefault><q:pPrDefault x:unchanged='B'><q:pPr x:property='B'><q:keepNext/></q:pPr>{opaque}</q:pPrDefault></q:docDefaults></q:styles>"#
2802 );
2803 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2804 let defaults = styles.doc_defaults.as_ref().unwrap();
2805 assert_eq!(
2806 defaults.rpr.as_ref().unwrap().font_ascii.as_deref(),
2807 Some("Prior")
2808 );
2809 assert_eq!(defaults.rpr.as_ref().unwrap().bold, Some(true));
2810 let original_ppr = defaults.ppr.clone();
2811 let original_source = defaults.source_xml.clone().unwrap();
2812 let mut untouched = Writer::new(Vec::new());
2813 defaults.to_xml(&mut untouched).unwrap();
2814 assert_eq!(untouched.into_inner(), original_source);
2815 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2816 properties.font_ascii = Some("Selected".to_owned());
2817 properties.font_hansi = Some("Selected".to_owned());
2818 let saved = styles.to_xml().unwrap();
2819 let text = std::str::from_utf8(&saved).unwrap();
2820 assert!(text.contains(foreign) && text.contains(opaque));
2821 assert!(text.contains("x:root='A'") && text.contains("x:wrapper='keep'"));
2822 assert!(text.contains("<!--before-->") && text.contains("<!--after-->"));
2823 assert!(text.contains("x:property='A'"));
2824 assert!(text.contains("<q:b q:val='true'/><!--inside-->"));
2825 let original_text = std::str::from_utf8(&original_source).unwrap();
2826 let begin = original_text.find("<q:pPrDefault").unwrap();
2827 let end = original_text.find("</q:pPrDefault>").unwrap() + "</q:pPrDefault>".len();
2828 assert!(text.contains(&original_text[begin..end]));
2829 let reopened = CT_Styles::from_xml(&saved).unwrap();
2830 let defaults = reopened.doc_defaults.as_ref().unwrap();
2831 assert_eq!(
2832 defaults.rpr.as_ref().unwrap().font_ascii.as_deref(),
2833 Some("Selected")
2834 );
2835 assert_eq!(defaults.rpr.as_ref().unwrap().bold, Some(true));
2836 assert_eq!(defaults.ppr, original_ppr);
2837 }
2838
2839 #[test]
2840 fn fx200_empty_default_wrapper_and_duplicate_wrapper_last_owner_are_preserved() {
2841 let source = format!(
2842 r#"<w:styles xmlns:w="{W_NS}" xmlns:f="urn:foreign"><w:docDefaults f:keep='yes'/></w:styles>"#
2843 );
2844 let styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2845 let defaults = styles.doc_defaults.as_ref().unwrap();
2846 assert!(defaults.rpr.is_none() && defaults.ppr.is_none());
2847 assert!(
2848 std::str::from_utf8(&styles.to_xml().unwrap())
2849 .unwrap()
2850 .contains("f:keep='yes'")
2851 );
2852 let source = format!(
2853 r#"<w:styles xmlns:w="{W_NS}"><w:docDefaults><w:rPrDefault><w:rPr><w:b/></w:rPr></w:rPrDefault><w:rPrDefault><!--last--></w:rPrDefault></w:docDefaults></w:styles>"#
2854 );
2855 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2856 assert!(styles.doc_defaults.as_ref().unwrap().rpr.is_none());
2857 styles.doc_defaults.as_mut().unwrap().rpr = Some(CT_RPr {
2858 font_ascii: Some("Selected".to_owned()),
2859 ..Default::default()
2860 });
2861 let saved = styles.to_xml().unwrap();
2862 assert!(std::str::from_utf8(&saved).unwrap().contains("<w:b/>"));
2863 let reopened = CT_Styles::from_xml(&saved).unwrap();
2864 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2865 assert_eq!(properties.font_ascii.as_deref(), Some("Selected"));
2866 assert!(properties.bold.is_none());
2867 }
2868 #[test]
2869 fn fx200_same_wrapper_duplicate_default_properties_do_not_resurface_on_font_clear() {
2870 let source = format!(
2871 r#"<w:styles xmlns:w="{W_NS}" xmlns:f="urn:foreign"><w:docDefaults><w:rPrDefault><w:rPr><w:b/></w:rPr><w:rPr f:last='A'><w:rFonts w:ascii="Selected"/></w:rPr></w:rPrDefault></w:docDefaults></w:styles>"#
2872 );
2873 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2874 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2875 assert!(properties.bold.is_none());
2876 assert_eq!(properties.font_ascii.as_deref(), Some("Selected"));
2877 properties.font_ascii = None;
2878 let saved = styles.to_xml().unwrap();
2879 let text = std::str::from_utf8(&saved).unwrap();
2880 assert!(text.contains("<w:b/>") && text.contains("f:last='A'"));
2881 let reopened = CT_Styles::from_xml(&saved).unwrap();
2882 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2883 assert!(properties.bold.is_none() && properties.font_ascii.is_none());
2884 }
2885 #[test]
2886 fn fx200_duplicate_default_font_children_clear_owned_slots_without_reviving_or_erasing_raw() {
2887 for replacement in [None, Some("B")] {
2888 let source = format!(
2889 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:docDefaults><w:rPrDefault><w:rPr><q:rFonts q:ascii='A' f:earlier='A'><f:opaque/><!--font-child--></q:rFonts><!--between--><w:rFonts w:hint='cs' f:last='B'/></w:rPr></w:rPrDefault></w:docDefaults></w:styles>"#
2890 );
2891 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2892 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2893 assert_eq!(properties.font_ascii.as_deref(), Some("A"));
2894 assert_eq!(properties.font_hint.as_deref(), Some("cs"));
2895 properties.font_ascii = replacement.map(str::to_owned);
2896 let saved = styles.to_xml().unwrap();
2897 let text = std::str::from_utf8(&saved).unwrap();
2898 for raw in [
2899 "f:earlier='A'",
2900 "<f:opaque/><!--font-child-->",
2901 "<!--between-->",
2902 "w:hint='cs'",
2903 "f:last='B'",
2904 ] {
2905 assert!(text.contains(raw), "missing {raw}: {text}");
2906 }
2907 assert!(!text.contains("q:ascii='A'"));
2908 let reopened = CT_Styles::from_xml(&saved).unwrap();
2909 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2910 assert_eq!(properties.font_ascii.as_deref(), replacement);
2911 assert_eq!(properties.font_hint.as_deref(), Some("cs"));
2912 }
2913 }
2914
2915 #[test]
2916 fn fx200_duplicate_default_size_children_clear_all_owned_values_preserving_foreign_payload() {
2917 let source = format!(
2918 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:docDefaults><w:rPrDefault><w:rPr><q:sz q:val='24' f:first='A'/><!--size--><w:sz w:val='28' f:last='B'><f:opaque/></w:sz><q:szCs q:val='20' f:first='CS'/><w:szCs w:val='32' f:last='CS2'/><f:sz f:val='999'/></w:rPr></w:rPrDefault></w:docDefaults></w:styles>"#
2919 );
2920 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2921 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2922 assert_eq!(properties.sz, Some(crate::units::HalfPoint(28)));
2923 assert_eq!(properties.sz_cs, Some(crate::units::HalfPoint(32)));
2924 properties.sz = None;
2925 properties.sz_cs = None;
2926 let saved = styles.to_xml().unwrap();
2927 let text = std::str::from_utf8(&saved).unwrap();
2928 for raw in [
2929 "f:first='A'",
2930 "<!--size-->",
2931 "f:last='B'",
2932 "<f:opaque/>",
2933 "f:first='CS'",
2934 "f:last='CS2'",
2935 "<f:sz f:val='999'/>",
2936 ] {
2937 assert!(text.contains(raw), "missing {raw}: {text}");
2938 }
2939 for value in ["q:val='24'", "w:val='28'", "q:val='20'", "w:val='32'"] {
2940 assert!(!text.contains(value));
2941 }
2942 let reopened = CT_Styles::from_xml(&saved).unwrap();
2943 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2944 assert!(properties.sz.is_none() && properties.sz_cs.is_none());
2945 }
2946 #[test]
2947 fn fx200_style_property_root_attributes_and_untouched_owner_bytes_survive_updates() {
2948 let source = format!(
2949 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Imported"><w:name w:val="Imported"/><q:pPr f:keep='A'><q:keepNext q:val='true'/><!--ppr--><f:opaque/></q:pPr><q:rPr f:keep='B'><q:rFonts q:ascii='A' q:hint='cs'/><!--rpr--><f:opaque/></q:rPr></w:style></w:styles>"#
2950 );
2951 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2952 let original = styles.styles[0]
2953 .modeled_xml
2954 .iter()
2955 .find(|(rank, _)| *rank == 20)
2956 .unwrap()
2957 .1
2958 .clone();
2959 styles.styles[0].rpr.as_mut().unwrap().font_ascii = Some("B".to_owned());
2960 let saved = styles.to_xml().unwrap();
2961 assert!(
2962 saved
2963 .windows(original.len())
2964 .any(|window| window == original)
2965 );
2966 let text = std::str::from_utf8(&saved).unwrap();
2967 for raw in [
2968 "f:keep='A'",
2969 "f:keep='B'",
2970 "q:hint='cs'",
2971 "<!--rpr-->",
2972 "<f:opaque/>",
2973 ] {
2974 assert!(text.contains(raw), "{text}");
2975 }
2976 let reopened = CT_Styles::from_xml(&saved).unwrap();
2977 assert_eq!(
2978 reopened.styles[0]
2979 .rpr
2980 .as_ref()
2981 .unwrap()
2982 .font_ascii
2983 .as_deref(),
2984 Some("B")
2985 );
2986 assert_eq!(
2987 reopened.styles[0].ppr.as_ref().unwrap().keep_next,
2988 Some(true)
2989 );
2990 }
2991
2992 #[test]
2993 fn fx200_duplicate_style_property_owners_keep_empty_final_owner_and_foreign_peers() {
2994 let source = format!(
2995 r#"<w:styles xmlns:w="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Imported"><w:name w:val="Imported"/><w:pPr><w:keepNext/></w:pPr><w:pPr f:last='A'/><w:rPr><w:b/></w:rPr><f:rPr f:keep='peer'/><w:rPr f:last='B'><w:rFonts w:ascii='A'/></w:rPr></w:style></w:styles>"#
2996 );
2997 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2998 assert!(styles.styles[0].ppr.as_ref().unwrap().keep_next.is_none());
2999 assert!(styles.styles[0].rpr.as_ref().unwrap().bold.is_none());
3000 styles.styles[0].rpr.as_mut().unwrap().font_ascii = None;
3001 let saved = styles.to_xml().unwrap();
3002 let text = std::str::from_utf8(&saved).unwrap();
3003 for raw in [
3004 "<w:b/>",
3005 "<w:keepNext/>",
3006 "f:last='A'",
3007 "f:last='B'",
3008 "f:keep='peer'",
3009 ] {
3010 assert!(text.contains(raw), "{text}");
3011 }
3012 let first = text.find("<w:b/>").unwrap();
3013 let peer = text.find("f:keep='peer'").unwrap();
3014 let last = text.find("f:last='B'").unwrap();
3015 assert!(first < peer && peer < last, "{text}");
3016 let reopened = CT_Styles::from_xml(&saved).unwrap();
3017 let style = &reopened.styles[0];
3018 assert!(
3019 style.rpr.as_ref().unwrap().bold.is_none()
3020 && style.rpr.as_ref().unwrap().font_ascii.is_none()
3021 );
3022 assert!(style.ppr.as_ref().unwrap().keep_next.is_none());
3023 styles.styles[0].rpr = None;
3024 let cleared = styles.to_xml().unwrap();
3025 let reopened = CT_Styles::from_xml(&cleared).unwrap();
3026 assert!(reopened.styles[0].rpr.is_none());
3027 assert!(
3028 std::str::from_utf8(&cleared)
3029 .unwrap()
3030 .contains("f:keep='peer'")
3031 );
3032 }
3033 #[test]
3034 fn fx200_style_border_container_edit_retains_aliased_root_and_opaque_payload() {
3035 let source = format!(
3036 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Imported"><q:pPr f:outer='A'><q:pBdr f:keep='B'><q:bottom q:val='single' f:edge='C'/><!--border--><f:opaque> keep </f:opaque><f:bottom f:val='foreign'/></q:pBdr><q:keepNext/></q:pPr></w:style></w:styles>"#
3037 );
3038 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3039 let edge = styles.styles[0]
3040 .ppr
3041 .as_mut()
3042 .unwrap()
3043 .borders
3044 .as_mut()
3045 .unwrap()
3046 .bottom
3047 .as_mut()
3048 .unwrap();
3049 edge.val = crate::shared::ST_Border::Double;
3050 edge.sz = Some(8);
3051 edge.color = Some("123456".to_owned());
3052 let saved = styles.to_xml().unwrap();
3053 let text = std::str::from_utf8(&saved).unwrap();
3054 for raw in [
3055 "f:outer='A'",
3056 "f:keep='B'",
3057 "f:edge='C'",
3058 "<!--border-->",
3059 "<f:opaque> keep </f:opaque>",
3060 "<f:bottom f:val='foreign'/>",
3061 ] {
3062 assert!(text.contains(raw), "{text}");
3063 }
3064 let reopened = CT_Styles::from_xml(&saved).unwrap();
3065 let properties = reopened.styles[0].ppr.as_ref().unwrap();
3066 let border = properties
3067 .borders
3068 .as_ref()
3069 .unwrap()
3070 .bottom
3071 .as_ref()
3072 .unwrap();
3073 assert_eq!(border.val, crate::shared::ST_Border::Double);
3074 assert_eq!(border.sz, Some(8));
3075 assert_eq!(border.color.as_deref(), Some("123456"));
3076 assert_eq!(properties.keep_next, Some(true));
3077 assert_eq!(reopened.to_xml().unwrap(), saved);
3078 }
3079
3080 #[test]
3081 fn fx200_duplicate_style_border_clear_keeps_payload_but_suppresses_earlier_edges() {
3082 let source = format!(
3083 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Imported"><w:pPr><q:pBdr f:first='A'><q:top q:val='single'/><f:opaque first='same'/></q:pBdr><f:pBdr f:keep='foreign'/><q:pBdr f:last='B'><q:bottom q:val='double'/><!--last--><f:opaque last='same'/></q:pBdr><w:keepNext/></w:pPr></w:style></w:styles>"#
3084 );
3085 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3086 assert!(
3087 styles.styles[0]
3088 .ppr
3089 .as_ref()
3090 .unwrap()
3091 .borders
3092 .as_ref()
3093 .unwrap()
3094 .bottom
3095 .is_some()
3096 );
3097 styles.styles[0].ppr.as_mut().unwrap().borders = None;
3098 let saved = styles.to_xml().unwrap();
3099 let text = std::str::from_utf8(&saved).unwrap();
3100 for raw in [
3101 "f:first='A'",
3102 "f:last='B'",
3103 "<f:opaque first='same'/>",
3104 "<f:opaque last='same'/>",
3105 "<!--last-->",
3106 "<f:pBdr f:keep='foreign'/>",
3107 ] {
3108 assert!(text.contains(raw), "{text}");
3109 }
3110 assert!(text.find("f:first='A'").unwrap() < text.find("f:keep='foreign'").unwrap());
3111 assert!(text.find("f:keep='foreign'").unwrap() < text.find("f:last='B'").unwrap());
3112 let reopened = CT_Styles::from_xml(&saved).unwrap();
3113 let properties = reopened.styles[0].ppr.as_ref().unwrap();
3114 let border = properties.borders.as_ref().unwrap();
3115 assert!(
3116 border.top.is_none()
3117 && border.bottom.is_none()
3118 && border.left.is_none()
3119 && border.right.is_none()
3120 && border.between.is_none()
3121 && border.bar.is_none()
3122 );
3123 assert_eq!(properties.keep_next, Some(true));
3124 assert_eq!(reopened.to_xml().unwrap(), saved);
3125 }
3126
3127 #[test]
3128 fn fx200_direct_style_reader_restores_both_trim_flags_and_opaque_sibling_whitespace() {
3129 for property in [
3130 "<w:pPr><w:keepNext/></w:pPr>",
3131 "<f:pPr><f:payload> inner </f:payload></f:pPr>",
3132 "<w:rPr><w:b/></w:rPr>",
3133 "<f:rPr><f:payload> inner </f:payload></f:rPr>",
3134 ] {
3135 for (trim_start, trim_end) in [(false, false), (false, true), (true, false)] {
3136 let source = format!(
3137 r#"<w:style xmlns:w="{W_NS}" xmlns:f="urn:foreign" w:type="paragraph" w:styleId="Direct">{property}<f:opaque> keep </f:opaque></w:style>"#
3138 );
3139 let mut reader = Reader::from_reader(source.as_bytes());
3140 reader.config_mut().trim_text_start = trim_start;
3141 reader.config_mut().trim_text_end = trim_end;
3142 let Event::Start(start) = reader.read_event().unwrap() else {
3143 panic!("style root missing")
3144 };
3145 let style = CT_Style::from_xml(&mut reader, &start).unwrap();
3146 assert_eq!(reader.config().trim_text_start, trim_start);
3147 assert_eq!(reader.config().trim_text_end, trim_end);
3148 let expected = match (trim_start, trim_end) {
3149 (false, false) => "> keep </f:opaque>",
3150 (false, true) => "> keep</f:opaque>",
3151 _ => ">keep </f:opaque>",
3152 };
3153 assert!(
3154 style
3155 .extra_xml
3156 .iter()
3157 .any(|(_, raw)| std::str::from_utf8(raw).unwrap().contains(expected)),
3158 "{style:?}"
3159 );
3160 }
3161 let opening = &property[..property.find('>').unwrap() + 1];
3162 let malformed = format!(
3163 r#"<w:style xmlns:w="{W_NS}" xmlns:f="urn:foreign" w:type="paragraph" w:styleId="Direct">{opening}</f:wrong></w:style>"#
3164 );
3165 let mut reader = Reader::from_reader(malformed.as_bytes());
3166 reader.config_mut().trim_text(false);
3167 let Event::Start(start) = reader.read_event().unwrap() else {
3168 panic!("style root missing")
3169 };
3170 assert!(CT_Style::from_xml(&mut reader, &start).is_err());
3171 assert!(!reader.config().trim_text_start && !reader.config().trim_text_end);
3172 }
3173 }
3174 #[test]
3175 fn fx200_started_style_border_edges_match_empty_projection_and_keep_source_on_updates() {
3176 for prefix in ["w", "q"] {
3177 let source = format!(
3178 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Started"><{prefix}:pPr><{prefix}:pBdr><{prefix}:bottom {prefix}:val='single' f:edge='A'><f:opaque> keep </f:opaque><!--edge--></{prefix}:bottom></{prefix}:pBdr></{prefix}:pPr></w:style></w:styles>"#
3179 );
3180 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3181 let original = styles.to_xml().unwrap();
3182 let before = styles.styles[0]
3183 .ppr
3184 .as_ref()
3185 .unwrap()
3186 .borders
3187 .as_ref()
3188 .unwrap()
3189 .bottom
3190 .as_ref()
3191 .unwrap();
3192 assert_eq!(before.val, crate::shared::ST_Border::Single);
3193 assert!(before.source_xml.is_some());
3194 assert_eq!(
3195 CT_Styles::from_xml(&original).unwrap().to_xml().unwrap(),
3196 original
3197 );
3198 let edge = styles.styles[0]
3199 .ppr
3200 .as_mut()
3201 .unwrap()
3202 .borders
3203 .as_mut()
3204 .unwrap()
3205 .bottom
3206 .as_mut()
3207 .unwrap();
3208 edge.val = crate::shared::ST_Border::Double;
3209 edge.sz = Some(8);
3210 edge.color = Some("123456".to_owned());
3211 let saved = styles.to_xml().unwrap();
3212 let text = std::str::from_utf8(&saved).unwrap();
3213 for raw in [
3214 "f:edge='A'",
3215 "<f:opaque> keep </f:opaque>",
3216 "<!--edge-->",
3217 ] {
3218 assert!(text.contains(raw), "{text}");
3219 }
3220 assert!(text.contains(&format!("</{prefix}:bottom>")), "{text}");
3221 let reopened = CT_Styles::from_xml(&saved).unwrap();
3222 let actual = reopened.styles[0]
3223 .ppr
3224 .as_ref()
3225 .unwrap()
3226 .borders
3227 .as_ref()
3228 .unwrap()
3229 .bottom
3230 .as_ref()
3231 .unwrap();
3232 assert_eq!(actual.val, crate::shared::ST_Border::Double);
3233 assert_eq!(actual.sz, Some(8));
3234 assert_eq!(actual.color.as_deref(), Some("123456"));
3235 assert_eq!(reopened.to_xml().unwrap(), saved);
3236 let empty = format!(
3237 r#"<w:pPr xmlns:w="{W_NS}"><w:pBdr><w:bottom w:val="double" w:sz="8" w:color="123456"/></w:pBdr></w:pPr>"#
3238 );
3239 let canonical =
3240 crate::numbering::parse_scoped_ppr(empty.as_bytes(), &["w".to_owned()]).unwrap();
3241 let expected = canonical.borders.as_ref().unwrap().bottom.as_ref().unwrap();
3242 assert_eq!(
3243 (actual.val, actual.sz, actual.color.as_deref()),
3244 (expected.val, expected.sz, expected.color.as_deref())
3245 );
3246 }
3247 }
3248 #[test]
3249 fn fx200_style_border_opaque_descendants_keep_bytes_and_never_become_direct_edges() {
3250 for opaque in [
3251 r#"<f:opaque><q:bottom q:val='double'></q:bottom></f:opaque>"#,
3252 r#"<f:opaque xmlns:q='urn:shadow'><q:bottom q:val='double'></q:bottom><w:left w:val='double'/></f:opaque>"#,
3253 ] {
3254 let source = format!(
3255 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Opaque"><q:pPr><q:pBdr>{opaque}<q:right q:val='single' f:edge='A'><f:inside> real </f:inside></q:right></q:pBdr></q:pPr></w:style></w:styles>"#
3256 );
3257 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3258 let borders = styles.styles[0]
3259 .ppr
3260 .as_ref()
3261 .unwrap()
3262 .borders
3263 .as_ref()
3264 .unwrap();
3265 assert!(borders.bottom.is_none() && borders.left.is_none());
3266 assert_eq!(
3267 borders.right.as_ref().unwrap().val,
3268 crate::shared::ST_Border::Single
3269 );
3270 let noop = styles.to_xml().unwrap();
3271 assert!(std::str::from_utf8(&noop).unwrap().contains(opaque));
3272 styles.styles[0]
3273 .ppr
3274 .as_mut()
3275 .unwrap()
3276 .borders
3277 .as_mut()
3278 .unwrap()
3279 .right
3280 .as_mut()
3281 .unwrap()
3282 .val = crate::shared::ST_Border::Double;
3283 let saved = styles.to_xml().unwrap();
3284 let text = std::str::from_utf8(&saved).unwrap();
3285 assert!(
3286 text.contains(opaque)
3287 && text.contains("<f:inside> real </f:inside>")
3288 && text.contains("f:edge='A'")
3289 );
3290 let reopened = CT_Styles::from_xml(&saved).unwrap();
3291 let actual = reopened.styles[0]
3292 .ppr
3293 .as_ref()
3294 .unwrap()
3295 .borders
3296 .as_ref()
3297 .unwrap();
3298 assert!(actual.bottom.is_none() && actual.left.is_none());
3299 assert_eq!(
3300 actual.right.as_ref().unwrap().val,
3301 crate::shared::ST_Border::Double
3302 );
3303 assert_eq!(reopened.to_xml().unwrap(), saved);
3304 }
3305 }
3306
3307 #[test]
3308 fn fx200_started_logical_style_border_edges_update_once_without_payload_duplication() {
3309 for (physical, logical) in [("start", "left"), ("end", "right")] {
3310 let source = format!(
3311 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Logical"><q:pPr><q:pBdr f:container='A'><q:{physical} q:val='single' f:edge='B'><f:payload> unique </f:payload><!--unique--></q:{physical}></q:pBdr></q:pPr></w:style></w:styles>"#
3312 );
3313 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3314 let initial = styles.to_xml().unwrap();
3315 assert_eq!(
3316 CT_Styles::from_xml(&initial).unwrap().to_xml().unwrap(),
3317 initial
3318 );
3319 for (value, size, color) in [
3320 (crate::shared::ST_Border::Double, 8, "123456"),
3321 (crate::shared::ST_Border::Single, 12, "654321"),
3322 ] {
3323 let borders = styles.styles[0]
3324 .ppr
3325 .as_mut()
3326 .unwrap()
3327 .borders
3328 .as_mut()
3329 .unwrap();
3330 let edge = if logical == "left" {
3331 borders.left.as_mut().unwrap()
3332 } else {
3333 borders.right.as_mut().unwrap()
3334 };
3335 edge.val = value;
3336 edge.sz = Some(size);
3337 edge.color = Some(color.to_owned());
3338 let saved = styles.to_xml().unwrap();
3339 let text = std::str::from_utf8(&saved).unwrap();
3340 assert_eq!(
3341 text.matches(&format!("<q:{physical} ")).count(),
3342 1,
3343 "{text}"
3344 );
3345 assert_eq!(
3346 text.matches(&format!("</q:{physical}>")).count(),
3347 1,
3348 "{text}"
3349 );
3350 assert_eq!(
3351 text.matches("<f:payload> unique </f:payload>").count(),
3352 1,
3353 "{text}"
3354 );
3355 assert_eq!(text.matches("<!--unique-->").count(), 1, "{text}");
3356 assert!(text.contains("f:edge='B'") && text.contains("f:container='A'"));
3357 styles = CT_Styles::from_xml(&saved).unwrap();
3358 let borders = styles.styles[0]
3359 .ppr
3360 .as_ref()
3361 .unwrap()
3362 .borders
3363 .as_ref()
3364 .unwrap();
3365 let actual = if logical == "left" {
3366 borders.left.as_ref().unwrap()
3367 } else {
3368 borders.right.as_ref().unwrap()
3369 };
3370 assert_eq!(
3371 (actual.val, actual.sz, actual.color.as_deref()),
3372 (value, Some(size), Some(color))
3373 );
3374 assert_eq!(styles.to_xml().unwrap(), saved);
3375 }
3376 }
3377 }
3378
3379 #[test]
3380 fn fx200_style_partial_border_clear_removes_all_aliases_without_touching_siblings() {
3381 let source = format!(
3382 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:f"><w:style w:type="paragraph" w:styleId="Partial"><q:pPr><q:pBdr f:root='A'><q:start q:val='single' f:gone='yes'><f:gone>owned</f:gone></q:start><q:left q:val='double'/><!--keep--><q:end q:val='single' f:stay='B'><f:stay> body </f:stay></q:end><f:left f:val='foreign'/></q:pBdr></q:pPr></w:style></w:styles>"#
3383 );
3384 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3385 styles.styles[0]
3386 .ppr
3387 .as_mut()
3388 .unwrap()
3389 .borders
3390 .as_mut()
3391 .unwrap()
3392 .left = None;
3393 let saved = styles.to_xml().unwrap();
3394 let text = std::str::from_utf8(&saved).unwrap();
3395 assert!(!text.contains("<q:start") && !text.contains("<q:left"));
3396 for exact in [
3397 "f:root='A'",
3398 "<!--keep-->",
3399 "f:stay='B'",
3400 "<f:stay> body </f:stay>",
3401 "<f:left f:val='foreign'/>",
3402 ] {
3403 assert!(text.contains(exact), "{text}");
3404 }
3405 let reopened = CT_Styles::from_xml(&saved).unwrap();
3406 let borders = reopened.styles[0]
3407 .ppr
3408 .as_ref()
3409 .unwrap()
3410 .borders
3411 .as_ref()
3412 .unwrap();
3413 assert!(borders.left.is_none());
3414 assert_eq!(
3415 borders.right.as_ref().unwrap().val,
3416 crate::shared::ST_Border::Single
3417 );
3418 assert_eq!(reopened.to_xml().unwrap(), saved);
3419 }
3420 #[test]
3421 fn fx200_changed_style_scalar_keeps_physical_foreign_attributes_and_started_payload() {
3422 for started in [false, true] {
3423 let leaf = if started {
3424 "<q:u q:val='single' f:keep='A'><f:opaque> keep </f:opaque><!--inside--></q:u>"
3425 } else {
3426 "<q:u q:val='single' f:keep='A'/>"
3427 };
3428 let source = format!(
3429 r#"<w:styles xmlns:w="{W_NS}" xmlns:q="{W_NS}" xmlns:f="urn:foreign"><w:style w:type="paragraph" w:styleId="Scalar"><q:rPr f:root='B'>{leaf}<q:b/></q:rPr></w:style></w:styles>"#
3430 );
3431 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3432 styles.styles[0].rpr.as_mut().unwrap().underline =
3433 Some(crate::shared::ST_Underline::Double);
3434 let saved = styles.to_xml().unwrap();
3435 let text = std::str::from_utf8(&saved).unwrap();
3436 assert!(
3437 text.contains("f:keep='A'")
3438 && text.contains("f:root='B'")
3439 && text.contains("<q:b/>"),
3440 "{text}"
3441 );
3442 if started {
3443 assert!(
3444 text.contains("<f:opaque> keep </f:opaque><!--inside-->"),
3445 "{text}"
3446 );
3447 }
3448 let reopened = CT_Styles::from_xml(&saved).unwrap();
3449 assert_eq!(
3450 reopened.styles[0].rpr.as_ref().unwrap().underline,
3451 Some(crate::shared::ST_Underline::Double)
3452 );
3453 assert_eq!(reopened.to_xml().unwrap(), saved);
3454 }
3455 }
3456}