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 pub extra_xml: Vec<Vec<u8>>,
1566}
1567
1568#[allow(non_snake_case)]
1569impl CT_Styles {
1570 pub fn new() -> Self {
1571 CT_Styles {
1572 doc_defaults: None,
1573 styles: Vec::new(),
1574 extra_xml: Vec::new(),
1575 }
1576 }
1577
1578 pub fn from_xml(xml: &[u8]) -> Result<Self> {
1580 let mut reader = Reader::from_reader(xml);
1581 reader.config_mut().trim_text(true);
1582
1583 let mut doc_defaults = None;
1584 let mut styles = Vec::new();
1585 let mut extra_xml = Vec::new();
1586 let mut buf = Vec::new();
1587 let mut word_prefixes = Vec::new();
1588 let mut namespace_bindings = Vec::new();
1589
1590 loop {
1591 match reader.read_event_into(&mut buf) {
1592 Ok(Event::Start(ref e)) => {
1593 let name = e.name();
1594 let prefixes = word_prefixes_at(e, &word_prefixes)?;
1595 if is_word_element(name.as_ref(), b"docDefaults", &prefixes) {
1596 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1597 reader.config_mut().trim_text(false);
1598 let captured = capture_element(&mut reader, e)?;
1599 reader.config_mut().trim_text(true);
1600 let raw = crate::text::raw_with_external_bindings(&captured, &bindings)?;
1601 doc_defaults = Some(CT_DocDefaults::from_source_xml(&raw, &prefixes)?);
1602 } else if is_word_element(name.as_ref(), b"style", &prefixes) {
1603 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1604 styles.push(CT_Style::from_xml_with_prefixes(
1605 &mut reader,
1606 e,
1607 &prefixes,
1608 &bindings,
1609 )?);
1610 } else if is_word_element(name.as_ref(), b"styles", &prefixes) {
1611 word_prefixes = prefixes;
1613 namespace_bindings = namespace_bindings_at(e, &namespace_bindings)?;
1614 } else {
1615 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1616 reader.config_mut().trim_text(false);
1617 let captured = capture_element(&mut reader, e)?;
1618 reader.config_mut().trim_text(true);
1619 extra_xml.push(make_style_raw_self_contained(&captured, &bindings)?);
1620 }
1621 }
1622 Ok(Event::Empty(ref e)) => {
1623 let prefixes = word_prefixes_at(e, &word_prefixes)?;
1624 let bindings = namespace_bindings_at(e, &namespace_bindings)?;
1625 if is_word_element(e.name().as_ref(), b"docDefaults", &prefixes) {
1626 let raw = crate::text::raw_with_external_bindings(
1627 &capture_empty_element(e)?,
1628 &bindings,
1629 )?;
1630 doc_defaults = Some(CT_DocDefaults::from_source_xml(&raw, &prefixes)?);
1631 } else if is_word_element(e.name().as_ref(), b"style", &prefixes) {
1632 let owned = e.to_owned();
1633 let mut empty = Reader::from_reader(&[] as &[u8]);
1634 styles.push(CT_Style::from_xml_with_prefixes(
1635 &mut empty, &owned, &prefixes, &bindings,
1636 )?);
1637 } else if !is_word_element(e.name().as_ref(), b"styles", &prefixes) {
1638 extra_xml.push(make_style_raw_self_contained(
1639 &capture_empty_element(e)?,
1640 &bindings,
1641 )?);
1642 }
1643 }
1644 Ok(Event::Eof) => break,
1645 Err(e) => return Err(e.into()),
1646 _ => {}
1647 }
1648 buf.clear();
1649 }
1650
1651 Ok(CT_Styles {
1652 doc_defaults,
1653 styles,
1654 extra_xml,
1655 })
1656 }
1657
1658 pub fn to_xml(&self) -> Result<Vec<u8>> {
1660 let mut writer = Writer::new(Vec::new());
1661
1662 writer.write_event(Event::Decl(BytesDecl::new(
1663 "1.0",
1664 Some("UTF-8"),
1665 Some("yes"),
1666 )))?;
1667
1668 let mut styles_start = BytesStart::new("w:styles");
1669 styles_start.push_attribute(("xmlns:w", W_NS));
1670 styles_start.push_attribute((
1671 "xmlns:r",
1672 "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
1673 ));
1674 writer.write_event(Event::Start(styles_start))?;
1675
1676 if let Some(ref defaults) = self.doc_defaults {
1677 defaults.to_xml(&mut writer)?;
1678 }
1679
1680 for raw in &self.extra_xml {
1681 writer.get_mut().extend_from_slice(raw);
1682 }
1683
1684 for style in &self.styles {
1685 style.to_xml(&mut writer)?;
1686 }
1687
1688 writer.write_event(Event::End(BytesEnd::new("w:styles")))?;
1689
1690 Ok(writer.into_inner())
1691 }
1692
1693 pub fn get_by_id(&self, style_id: &str) -> Option<&CT_Style> {
1695 self.styles.iter().find(|s| s.style_id == style_id)
1696 }
1697
1698 pub fn get_default(&self, style_type: StyleType) -> Option<&CT_Style> {
1700 self.styles
1701 .iter()
1702 .find(|s| s.style_type == style_type && s.is_default)
1703 }
1704
1705 pub fn new_default() -> Self {
1707 use crate::units::HalfPoint;
1708
1709 let normal = CT_Style {
1710 style_id: "Normal".to_string(),
1711 style_type: StyleType::Paragraph,
1712 name: Some("Normal".to_string()),
1713 based_on: None,
1714 next_style: None,
1715 linked_style: None,
1716 auto_redefine: None,
1717 hidden: None,
1718 ui_priority: None,
1719 semi_hidden: None,
1720 unhide_when_used: None,
1721 quick_format: None,
1722 locked: None,
1723 is_default: true,
1724 ppr: None,
1725 rpr: None,
1726 table_properties: None,
1727 table_properties_original: None,
1728 table_properties_xml: None,
1729 table_row_properties: None,
1730 table_cell_properties: None,
1731 conditional_table_styles: Vec::new(),
1732 extra_attributes: Vec::new(),
1733 modeled_xml: Vec::new(),
1734 extra_xml: Vec::new(),
1735 };
1736
1737 let heading1 = CT_Style {
1738 style_id: "Heading1".to_string(),
1739 style_type: StyleType::Paragraph,
1740 name: Some("heading 1".to_string()),
1741 based_on: Some("Normal".to_string()),
1742 next_style: Some("Normal".to_string()),
1743 linked_style: None,
1744 auto_redefine: None,
1745 hidden: None,
1746 ui_priority: None,
1747 semi_hidden: None,
1748 unhide_when_used: None,
1749 quick_format: None,
1750 locked: None,
1751 is_default: false,
1752 ppr: Some(CT_PPr {
1753 keep_next: Some(true),
1754 keep_lines: Some(true),
1755 space_before: Some(crate::units::Twips(240)),
1756 space_after: Some(crate::units::Twips(0)),
1757 ..Default::default()
1758 }),
1759 rpr: Some(CT_RPr {
1760 sz: Some(HalfPoint(32)),
1761 sz_cs: Some(HalfPoint(32)),
1762 bold: Some(true),
1763 bold_cs: Some(true),
1764 color: Some("2F5496".to_string()),
1765 ..Default::default()
1766 }),
1767 table_properties: None,
1768 table_properties_original: None,
1769 table_properties_xml: None,
1770 table_row_properties: None,
1771 table_cell_properties: None,
1772 conditional_table_styles: Vec::new(),
1773 extra_attributes: Vec::new(),
1774 modeled_xml: Vec::new(),
1775 extra_xml: Vec::new(),
1776 };
1777
1778 let doc_defaults = CT_DocDefaults {
1779 source_xml: None,
1780 rpr: Some(CT_RPr {
1781 font_ascii: Some("Calibri".to_string()),
1782 font_hansi: Some("Calibri".to_string()),
1783 font_east_asia: Some("Calibri".to_string()),
1784 font_cs: Some("Times New Roman".to_string()),
1785 sz: Some(HalfPoint(22)),
1786 sz_cs: Some(HalfPoint(22)),
1787 ..Default::default()
1788 }),
1789 ppr: Some(CT_PPr {
1790 space_after: Some(crate::units::Twips(160)),
1791 line_spacing: Some(crate::units::Twips(259)),
1792 line_rule: Some("auto".to_string()),
1793 ..Default::default()
1794 }),
1795 };
1796
1797 CT_Styles {
1798 doc_defaults: Some(doc_defaults),
1799 styles: vec![normal, heading1],
1800 extra_xml: Vec::new(),
1801 }
1802 }
1803}
1804
1805impl Default for CT_Styles {
1806 fn default() -> Self {
1807 Self::new()
1808 }
1809}
1810
1811fn style_child_rank(name: &[u8], word_prefixes: &[String]) -> u8 {
1812 let local = name.rsplit(|byte| *byte == b':').next().unwrap_or(name);
1813 if !is_word_element(name, local, word_prefixes) {
1814 return 26;
1815 }
1816 match local {
1817 b"name" => 0,
1818 b"aliases" => 1,
1819 b"basedOn" => 2,
1820 b"next" => 3,
1821 b"link" => 4,
1822 b"autoRedefine" => 5,
1823 b"hidden" => 6,
1824 b"uiPriority" => 7,
1825 b"semiHidden" => 8,
1826 b"unhideWhenUsed" => 9,
1827 b"qFormat" => 10,
1828 b"locked" => 11,
1829 b"personal" => 12,
1830 b"personalCompose" => 13,
1831 b"personalReply" => 14,
1832 b"rsid" => 19,
1833 b"pPr" => 20,
1834 b"rPr" => 21,
1835 b"tblPr" => 22,
1836 b"trPr" => 23,
1837 b"tcPr" => 24,
1838 b"tblStylePr" => 25,
1839 _ => 26,
1840 }
1841}
1842
1843fn style_scalar_rank(name: &[u8], word_prefixes: &[String]) -> Option<u8> {
1844 let rank = style_child_rank(name, word_prefixes);
1845 matches!(rank, 0 | 2..=11).then_some(rank)
1846}
1847
1848fn style_scalar_empty_requires_snapshot(element: &BytesStart<'_>, rank: u8) -> Result<bool> {
1849 let canonical_name: &[u8] = match rank {
1850 0 => b"w:name",
1851 2 => b"w:basedOn",
1852 3 => b"w:next",
1853 4 => b"w:link",
1854 5 => b"w:autoRedefine",
1855 6 => b"w:hidden",
1856 7 => b"w:uiPriority",
1857 8 => b"w:semiHidden",
1858 9 => b"w:unhideWhenUsed",
1859 10 => b"w:qFormat",
1860 11 => b"w:locked",
1861 _ => unreachable!("style scalar rank"),
1862 };
1863 if element.name().as_ref() != canonical_name {
1864 return Ok(true);
1865 }
1866
1867 for attribute in element.attributes() {
1868 if attribute?.key.as_ref() != b"w:val" {
1869 return Ok(true);
1870 }
1871 }
1872 Ok(false)
1873}
1874
1875#[allow(clippy::too_many_arguments)]
1876fn apply_style_scalar(
1877 rank: u8,
1878 string_value: Option<String>,
1879 toggle_value: Option<bool>,
1880 priority_value: Option<u32>,
1881 name: &mut Option<String>,
1882 based_on: &mut Option<String>,
1883 next_style: &mut Option<String>,
1884 linked_style: &mut Option<String>,
1885 auto_redefine: &mut Option<bool>,
1886 hidden: &mut Option<bool>,
1887 ui_priority: &mut Option<u32>,
1888 semi_hidden: &mut Option<bool>,
1889 unhide_when_used: &mut Option<bool>,
1890 quick_format: &mut Option<bool>,
1891 locked: &mut Option<bool>,
1892) {
1893 match rank {
1894 0 => *name = string_value,
1895 2 => *based_on = string_value,
1896 3 => *next_style = string_value,
1897 4 => *linked_style = string_value,
1898 5 => *auto_redefine = toggle_value,
1899 6 => *hidden = toggle_value,
1900 7 => *ui_priority = priority_value,
1901 8 => *semi_hidden = toggle_value,
1902 9 => *unhide_when_used = toggle_value,
1903 10 => *quick_format = toggle_value,
1904 11 => *locked = toggle_value,
1905 _ => unreachable!("style scalar rank"),
1906 }
1907}
1908
1909fn write_style_extras<W: std::io::Write>(
1910 writer: &mut Writer<W>,
1911 extras: &[&(u8, Vec<u8>)],
1912 index: &mut usize,
1913 before_rank: u8,
1914) -> Result<()> {
1915 while let Some((rank, raw)) = extras.get(*index).copied() {
1916 if *rank >= before_rank {
1917 break;
1918 }
1919 writer.get_mut().write_all(raw)?;
1920 *index += 1;
1921 }
1922 Ok(())
1923}
1924
1925fn namespace_bindings_at(
1926 element: &BytesStart,
1927 inherited: &[(String, String)],
1928) -> Result<Vec<(String, String)>> {
1929 let mut bindings = inherited.to_vec();
1930 for attribute in element.attributes() {
1931 let attribute = attribute?;
1932 let key = attribute.key.as_ref();
1933 let prefix = if key == b"xmlns" {
1934 Some(String::new())
1935 } else {
1936 key.strip_prefix(b"xmlns:")
1937 .map(|prefix| String::from_utf8_lossy(prefix).into_owned())
1938 };
1939 let Some(prefix) = prefix else {
1940 continue;
1941 };
1942 let uri = std::str::from_utf8(&attribute.value)?.to_owned();
1943 if let Some(binding) = bindings
1944 .iter_mut()
1945 .find(|(candidate, _)| candidate == &prefix)
1946 {
1947 binding.1 = uri;
1948 } else {
1949 bindings.push((prefix, uri));
1950 }
1951 }
1952 Ok(bindings)
1953}
1954
1955fn reject_conflicting_style_prefixes(element: &BytesStart<'_>) -> Result<()> {
1956 for attribute in element.attributes() {
1957 let attribute = attribute?;
1958 let expected = match attribute.key.as_ref() {
1959 b"xmlns:w" => Some(W_NS),
1960 b"xmlns:r" => {
1961 Some("http://schemas.openxmlformats.org/officeDocument/2006/relationships")
1962 }
1963 _ => None,
1964 };
1965 let Some(expected) = expected else {
1966 continue;
1967 };
1968 let value = std::str::from_utf8(&attribute.value)?;
1969 if value != expected {
1970 return Err(OxmlError::InvalidValue(format!(
1971 "{} conflicts with the fixed style writer namespace",
1972 String::from_utf8_lossy(attribute.key.as_ref())
1973 )));
1974 }
1975 }
1976 Ok(())
1977}
1978
1979fn raw_style_attributes(
1980 element: &BytesStart<'_>,
1981 modeled: &[&[u8]],
1982 word_prefixes: &[String],
1983 namespace_bindings: &[(String, String)],
1984) -> Result<Vec<(String, String)>> {
1985 let mut attributes = Vec::new();
1986 for attribute in element.attributes() {
1987 let attribute = attribute?;
1988 let key = attribute.key.as_ref();
1989 let value = attribute
1990 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
1991 .into_owned();
1992 let canonical_namespace = (key == b"xmlns:w" && value == W_NS)
1993 || (key == b"xmlns:r"
1994 && value == "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
1995 if !canonical_namespace
1996 && !modeled
1997 .iter()
1998 .any(|local| is_word_attribute(key, local, word_prefixes))
1999 {
2000 attributes.push((std::str::from_utf8(key)?.to_owned(), value));
2001 }
2002 }
2003 if !attributes.is_empty() {
2004 for (prefix, namespace) in namespace_bindings {
2005 if prefix.is_empty()
2006 || (prefix == "w" && namespace == W_NS)
2007 || (prefix == "r"
2008 && namespace
2009 == "http://schemas.openxmlformats.org/officeDocument/2006/relationships")
2010 {
2011 continue;
2012 }
2013 let declaration = format!("xmlns:{prefix}");
2014 if !attributes.iter().any(|(name, _)| name == &declaration) {
2015 attributes.push((declaration, namespace.clone()));
2016 }
2017 }
2018 }
2019 Ok(attributes)
2020}
2021
2022fn reject_conflicting_style_bindings(bindings: &[(String, String)]) -> Result<()> {
2023 for (prefix, namespace) in bindings {
2024 let expected = match prefix.as_str() {
2025 "w" => Some(W_NS),
2026 "r" => Some("http://schemas.openxmlformats.org/officeDocument/2006/relationships"),
2027 _ => None,
2028 };
2029 if expected.is_some_and(|expected| namespace != expected) {
2030 return Err(OxmlError::InvalidValue(format!(
2031 "xmlns:{prefix} conflicts with the fixed style writer namespace"
2032 )));
2033 }
2034 }
2035 Ok(())
2036}
2037
2038fn push_style_attributes(element: &mut BytesStart<'_>, attributes: &[(String, String)]) {
2039 for (name, value) in attributes {
2040 element.push_attribute((name.as_str(), value.as_str()));
2041 }
2042}
2043
2044fn make_style_raw_self_contained(
2045 raw: &[u8],
2046 namespace_bindings: &[(String, String)],
2047) -> Result<Vec<u8>> {
2048 let external_bindings = namespace_bindings
2049 .iter()
2050 .filter(|(prefix, namespace)| {
2051 !((prefix == "w" && namespace == W_NS)
2052 || (prefix == "r"
2053 && namespace
2054 == "http://schemas.openxmlformats.org/officeDocument/2006/relationships"))
2055 })
2056 .cloned()
2057 .collect::<Vec<_>>();
2058 crate::text::raw_with_external_bindings(raw, &external_bindings)
2059}
2060
2061fn parse_style_table_properties(raw: &[u8], word_prefixes: &[String]) -> Result<CT_TblPr> {
2062 let mut reader = Reader::from_reader(raw);
2063 let mut buf = Vec::new();
2064 loop {
2065 match reader.read_event_into(&mut buf)? {
2066 Event::Start(ref start)
2067 if is_word_element(start.name().as_ref(), b"tblPr", word_prefixes) =>
2068 {
2069 let prefixes = word_prefixes_at(start, word_prefixes)?;
2070 return CT_TblPr::from_xml_with_prefixes(&mut reader, &prefixes);
2071 }
2072 Event::Eof => return Ok(CT_TblPr::default()),
2073 _ => {}
2074 }
2075 buf.clear();
2076 }
2077}
2078
2079fn explicit_style_element_has_only_trivia(raw: &[u8]) -> Result<bool> {
2080 let mut reader = Reader::from_reader(raw);
2081 let mut inside = false;
2082 let mut buf = Vec::new();
2083 loop {
2084 match reader.read_event_into(&mut buf)? {
2085 Event::Start(_) if !inside => inside = true,
2086 Event::End(_) if inside => return Ok(true),
2087 Event::Text(text) if inside && text.as_ref().iter().all(u8::is_ascii_whitespace) => {}
2088 Event::Comment(_) | Event::PI(_) if inside => {}
2089 Event::Eof => return Ok(false),
2090 _ if inside => return Ok(false),
2091 _ => {}
2092 }
2093 buf.clear();
2094 }
2095}
2096
2097#[derive(Default)]
2099struct ConditionalStyleLayers {
2100 paragraph_properties: Option<CT_PPr>,
2101 run_properties: Option<Box<CT_RPr>>,
2102 table_properties: Option<CT_TblPr>,
2103 row_properties: Option<Box<CT_TrPr>>,
2104 cell_properties: Option<CT_TcPr>,
2105}
2106
2107fn parse_conditional_style_properties(
2108 raw: &[u8],
2109 word_prefixes: &[String],
2110) -> Result<ConditionalStyleLayers> {
2111 let mut reader = Reader::from_reader(raw);
2112 let mut layers = ConditionalStyleLayers::default();
2113 let mut buf = Vec::new();
2114 let mut depth = 0usize;
2115 loop {
2116 match reader.read_event_into(&mut buf)? {
2117 Event::Start(ref start) => {
2118 depth += 1;
2119 let prefixes = word_prefixes_at(start, word_prefixes)?;
2120 if depth != 2 {
2121 } else if is_word_element(start.name().as_ref(), b"pPr", &prefixes) {
2124 let captured = capture_element(&mut reader, start)?;
2125 layers.paragraph_properties = Some(parse_scoped_ppr(&captured, &prefixes)?);
2126 depth -= 1;
2127 } else if is_word_element(start.name().as_ref(), b"rPr", &prefixes) {
2128 let captured = capture_element(&mut reader, start)?;
2129 layers.run_properties = Some(Box::new(parse_style_rpr(&captured, &prefixes)?));
2130 depth -= 1;
2131 } else if is_word_element(start.name().as_ref(), b"tblPr", &prefixes) {
2132 layers.table_properties =
2133 Some(CT_TblPr::from_xml_with_prefixes(&mut reader, &prefixes)?);
2134 depth -= 1;
2135 } else if is_word_element(start.name().as_ref(), b"trPr", &prefixes) {
2136 layers.row_properties = Some(Box::new(CT_TrPr::from_xml_with_prefixes(
2137 &mut reader,
2138 &prefixes,
2139 )?));
2140 depth -= 1;
2141 } else if is_word_element(start.name().as_ref(), b"tcPr", &prefixes) {
2142 layers.cell_properties =
2143 Some(CT_TcPr::from_xml_with_prefixes(&mut reader, &prefixes)?);
2144 depth -= 1;
2145 }
2146 }
2147 Event::End(_) => depth = depth.saturating_sub(1),
2148 Event::Eof => break,
2149 _ => {}
2150 }
2151 buf.clear();
2152 }
2153 Ok(layers)
2154}
2155
2156fn serialize_conditional_table_style(conditional: &CT_TblStylePr) -> Result<Vec<u8>> {
2157 let mut reader = Reader::from_reader(conditional.raw_xml.as_slice());
2158 let mut prefixes = vec!["w".to_owned()];
2159 let mut original_region = None;
2160 let mut buf = Vec::new();
2161 loop {
2162 match reader.read_event_into(&mut buf)? {
2163 Event::Start(ref start) => {
2164 prefixes = word_prefixes_at(start, &prefixes)?;
2165 original_region = get_word_attr(start, b"type", &prefixes)?;
2166 break;
2167 }
2168 Event::Eof => break,
2169 _ => {}
2170 }
2171 buf.clear();
2172 }
2173 let Some(region) = conditional.region else {
2174 return Ok(conditional.raw_xml.clone());
2177 };
2178 let original = parse_conditional_style_properties(&conditional.raw_xml, &prefixes)?;
2179 if original_region.as_deref() == Some(region.to_str())
2180 && original.paragraph_properties == conditional.paragraph_properties
2181 && original.run_properties == conditional.run_properties
2182 && original.table_properties == conditional.table_properties
2183 && original.row_properties == conditional.row_properties
2184 && original.cell_properties == conditional.cell_properties
2185 {
2186 return Ok(conditional.raw_xml.clone());
2187 }
2188
2189 let mut extras = preserved_conditional_style_children(&conditional.raw_xml)?;
2190 extras.sort_by_key(|(rank, _)| *rank);
2191 let extra_refs = extras.iter().collect::<Vec<_>>();
2192 let mut extra_index = 0;
2193 let mut writer = Writer::new(Vec::new());
2194 let mut start = BytesStart::new("w:tblStylePr");
2195 start.push_attribute(("w:type", region.to_str()));
2196 push_style_attributes(&mut start, &conditional.extra_attributes);
2197 writer.write_event(Event::Start(start))?;
2198 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 0)?;
2199 if let Some(properties) = &conditional.paragraph_properties {
2200 properties.to_xml(&mut writer)?;
2201 }
2202 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 1)?;
2203 if let Some(properties) = &conditional.run_properties {
2204 properties.to_xml(&mut writer)?;
2205 }
2206 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 2)?;
2207 if let Some(properties) = &conditional.table_properties {
2208 properties.to_xml(&mut writer)?;
2209 }
2210 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 3)?;
2211 if let Some(properties) = &conditional.row_properties {
2212 properties.to_xml(&mut writer)?;
2213 }
2214 write_style_extras(&mut writer, &extra_refs, &mut extra_index, 4)?;
2215 if let Some(properties) = &conditional.cell_properties {
2216 properties.to_xml(&mut writer)?;
2217 }
2218 write_style_extras(&mut writer, &extra_refs, &mut extra_index, u8::MAX)?;
2219 writer.write_event(Event::End(BytesEnd::new("w:tblStylePr")))?;
2220 Ok(writer.into_inner())
2221}
2222
2223fn preserved_conditional_style_children(raw: &[u8]) -> Result<Vec<(u8, Vec<u8>)>> {
2224 let mut reader = Reader::from_reader(raw);
2225 let mut inside = false;
2226 let mut word_prefixes = vec!["w".to_owned()];
2227 let mut extras = Vec::new();
2228 let mut buf = Vec::new();
2229 loop {
2230 match reader.read_event_into(&mut buf)? {
2231 Event::Start(ref start) if !inside => {
2232 word_prefixes = word_prefixes_at(start, &word_prefixes)?;
2233 inside = true;
2234 }
2235 Event::Start(ref start) => {
2236 let prefixes = word_prefixes_at(start, &word_prefixes)?;
2237 let local = start.local_name();
2238 if is_word_element(start.name().as_ref(), local.as_ref(), &prefixes)
2239 && matches!(
2240 local.as_ref(),
2241 b"pPr" | b"rPr" | b"tblPr" | b"trPr" | b"tcPr"
2242 )
2243 {
2244 reader.read_to_end_into(start.name(), &mut Vec::new())?;
2245 } else {
2246 extras.push((5, capture_element(&mut reader, start)?));
2247 }
2248 }
2249 Event::Empty(ref empty) => {
2250 let prefixes = word_prefixes_at(empty, &word_prefixes)?;
2251 let local = empty.local_name();
2252 if !(is_word_element(empty.name().as_ref(), local.as_ref(), &prefixes)
2253 && matches!(
2254 local.as_ref(),
2255 b"pPr" | b"rPr" | b"tblPr" | b"trPr" | b"tcPr"
2256 ))
2257 {
2258 extras.push((5, capture_empty_element(empty)?));
2259 }
2260 }
2261 Event::End(_) | Event::Eof => break,
2262 _ => {}
2263 }
2264 buf.clear();
2265 }
2266 Ok(extras)
2267}
2268
2269fn get_val_attr(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<String>> {
2271 get_word_attr(e, b"val", word_prefixes)
2272}
2273
2274fn get_word_attr(e: &BytesStart, local: &[u8], word_prefixes: &[String]) -> Result<Option<String>> {
2275 for attr in e.attributes() {
2276 let attr = attr?;
2277 if is_word_attribute(attr.key.as_ref(), local, word_prefixes) {
2278 return Ok(Some(std::str::from_utf8(&attr.value)?.to_string()));
2279 }
2280 }
2281 Ok(None)
2282}
2283
2284fn parse_style_toggle(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<bool>> {
2285 let value = get_word_attr(e, b"val", word_prefixes)?;
2286 match value.as_deref() {
2287 None | Some("1" | "true" | "on") => Ok(Some(true)),
2288 Some("0" | "false" | "off") => Ok(Some(false)),
2289 Some(value) => Err(OxmlError::InvalidValue(format!(
2290 "invalid style toggle value: {value}"
2291 ))),
2292 }
2293}
2294
2295fn parse_style_priority(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<u32>> {
2296 get_word_attr(e, b"val", word_prefixes)?
2297 .map(|value| {
2298 value
2299 .parse::<u32>()
2300 .map_err(|_| OxmlError::InvalidValue(format!("invalid style UI priority: {value}")))
2301 })
2302 .transpose()
2303}
2304
2305fn preserved_style_attribute(raw: &[u8], local: &[u8]) -> Result<Option<String>> {
2306 let mut reader = Reader::from_reader(raw);
2307 let mut buf = Vec::new();
2308 loop {
2309 match reader.read_event_into(&mut buf)? {
2310 Event::Start(ref start) | Event::Empty(ref start) => {
2311 let prefixes = word_prefixes_at(start, &["w".to_owned()])?;
2312 return get_word_attr(start, local, &prefixes);
2313 }
2314 Event::Eof => return Ok(None),
2315 _ => {}
2316 }
2317 buf.clear();
2318 }
2319}
2320
2321fn preserved_modeled_xml(modeled_xml: &[(u8, Vec<u8>)], rank: u8) -> Option<&[u8]> {
2322 modeled_xml
2323 .iter()
2324 .find(|(candidate, _)| *candidate == rank)
2325 .map(|(_, raw)| raw.as_slice())
2326}
2327
2328fn rewritten_style_scalar_start(
2329 element: &BytesStart<'_>,
2330 value: &str,
2331) -> Result<BytesStart<'static>> {
2332 let prefixes = word_prefixes_at(element, &["w".to_owned()])?;
2333 let element_name = std::str::from_utf8(element.name().as_ref())?.to_owned();
2334 let mut rewritten = BytesStart::new(element_name.clone());
2335 let mut wrote_value = false;
2336 for attribute in element.attributes() {
2337 let attribute = attribute?;
2338 let key = std::str::from_utf8(attribute.key.as_ref())?.to_owned();
2339 if is_word_attribute(attribute.key.as_ref(), b"val", &prefixes) {
2340 if !wrote_value {
2341 rewritten.push_attribute((key.as_str(), value));
2342 wrote_value = true;
2343 }
2344 } else {
2345 let attribute_value = attribute
2346 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
2347 .into_owned();
2348 rewritten.push_attribute((key.as_str(), attribute_value.as_str()));
2349 }
2350 }
2351 if !wrote_value {
2352 let element_prefix = element_name
2353 .split_once(':')
2354 .map(|(prefix, _)| prefix.to_owned());
2355 let existing_prefix = element_prefix.or_else(|| {
2356 prefixes
2357 .iter()
2358 .find(|prefix| !prefix.is_empty() && !prefix.starts_with('\0'))
2359 .cloned()
2360 });
2361 let prefix = existing_prefix.unwrap_or_else(|| {
2362 let mut candidate = "rdocxStyle".to_owned();
2363 let mut suffix = 1usize;
2364 while element
2365 .attributes()
2366 .filter_map(std::result::Result::ok)
2367 .any(|attribute| attribute.key.as_ref() == format!("xmlns:{candidate}").as_bytes())
2368 {
2369 candidate = format!("rdocxStyle{suffix}");
2370 suffix += 1;
2371 }
2372 let declaration = format!("xmlns:{candidate}");
2373 rewritten.push_attribute((declaration.as_str(), W_NS));
2374 candidate
2375 });
2376 let key = format!("{prefix}:val");
2377 rewritten.push_attribute((key.as_str(), value));
2378 }
2379 Ok(rewritten.into_owned())
2380}
2381
2382fn write_rewritten_style_scalar<W: std::io::Write>(
2383 writer: &mut Writer<W>,
2384 raw: &[u8],
2385 value: &str,
2386) -> Result<()> {
2387 let mut reader = Reader::from_reader(raw);
2388 let mut root_pending = true;
2389 let mut buf = Vec::new();
2390 loop {
2391 match reader.read_event_into(&mut buf)? {
2392 Event::Start(element) if root_pending => {
2393 root_pending = false;
2394 writer.write_event(Event::Start(rewritten_style_scalar_start(&element, value)?))?;
2395 }
2396 Event::Empty(element) if root_pending => {
2397 writer.write_event(Event::Empty(rewritten_style_scalar_start(&element, value)?))?;
2398 break;
2399 }
2400 Event::Eof => break,
2401 event => writer.write_event(event.into_owned())?,
2402 }
2403 buf.clear();
2404 }
2405 Ok(())
2406}
2407
2408fn write_style_string<W: std::io::Write>(
2409 writer: &mut Writer<W>,
2410 name: &str,
2411 rank: u8,
2412 value: Option<&str>,
2413 modeled_xml: &[(u8, Vec<u8>)],
2414) -> Result<()> {
2415 let Some(value) = value else {
2416 return Ok(());
2417 };
2418 if let Some(raw) = preserved_modeled_xml(modeled_xml, rank)
2419 && preserved_style_attribute(raw, b"val")?.as_deref() == Some(value)
2420 {
2421 writer.get_mut().write_all(raw)?;
2422 return Ok(());
2423 }
2424 if let Some(raw) = preserved_modeled_xml(modeled_xml, rank) {
2425 return write_rewritten_style_scalar(writer, raw, value);
2426 }
2427 let mut element = BytesStart::new(name);
2428 element.push_attribute(("w:val", value));
2429 writer.write_event(Event::Empty(element))?;
2430 Ok(())
2431}
2432
2433fn write_style_toggle<W: std::io::Write>(
2434 writer: &mut Writer<W>,
2435 name: &str,
2436 rank: u8,
2437 value: Option<bool>,
2438 modeled_xml: &[(u8, Vec<u8>)],
2439) -> Result<()> {
2440 if let Some(value) = value {
2441 if let Some(raw) = preserved_modeled_xml(modeled_xml, rank) {
2442 let prefixes = vec!["w".to_owned()];
2443 let mut reader = Reader::from_reader(raw);
2444 let mut buf = Vec::new();
2445 let preserved = loop {
2446 match reader.read_event_into(&mut buf)? {
2447 Event::Start(ref start) | Event::Empty(ref start) => {
2448 let prefixes = word_prefixes_at(start, &prefixes)?;
2449 break parse_style_toggle(start, &prefixes)?;
2450 }
2451 Event::Eof => break None,
2452 _ => {}
2453 }
2454 buf.clear();
2455 };
2456 if preserved == Some(value) {
2457 writer.get_mut().write_all(raw)?;
2458 return Ok(());
2459 }
2460 return write_rewritten_style_scalar(writer, raw, if value { "1" } else { "0" });
2461 }
2462 let mut element = BytesStart::new(name);
2463 if !value {
2464 element.push_attribute(("w:val", "0"));
2465 }
2466 writer.write_event(Event::Empty(element))?;
2467 }
2468 Ok(())
2469}
2470
2471fn write_style_priority<W: std::io::Write>(
2472 writer: &mut Writer<W>,
2473 priority: Option<u32>,
2474 modeled_xml: &[(u8, Vec<u8>)],
2475) -> Result<()> {
2476 let Some(priority) = priority else {
2477 return Ok(());
2478 };
2479 if let Some(raw) = preserved_modeled_xml(modeled_xml, 7)
2480 && preserved_style_attribute(raw, b"val")?
2481 .as_deref()
2482 .and_then(|value| value.parse::<u32>().ok())
2483 == Some(priority)
2484 {
2485 writer.get_mut().write_all(raw)?;
2486 return Ok(());
2487 }
2488 let value = priority.to_string();
2489 if let Some(raw) = preserved_modeled_xml(modeled_xml, 7) {
2490 return write_rewritten_style_scalar(writer, raw, &value);
2491 }
2492 let mut element = BytesStart::new("w:uiPriority");
2493 element.push_attribute(("w:val", value.as_str()));
2494 writer.write_event(Event::Empty(element))?;
2495 Ok(())
2496}
2497
2498#[cfg(test)]
2499mod tests {
2500 use super::*;
2501
2502 #[test]
2503 fn unmodelled_root_children_round_trip_before_the_styles() {
2504 let xml = format!(
2505 concat!(
2506 r#"<w:styles xmlns:w="{}" xmlns:x="urn:x">"#,
2507 r#"<w:docDefaults/>"#,
2508 r#"<w:latentStyles w:count="1"><w:lsdException w:name="Normal" x:a="1"/></w:latentStyles>"#,
2509 r#"<x:empty/>"#,
2510 r#"<w:style w:type="paragraph" w:styleId="Empty"/>"#,
2511 r#"<w:style w:type="paragraph" w:styleId="Normal"><w:name w:val="Normal"/></w:style>"#,
2512 r#"</w:styles>"#,
2513 ),
2514 W_NS
2515 );
2516 let styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2517 assert_eq!(styles.extra_xml.len(), 2);
2518 assert_eq!(styles.styles.len(), 2);
2519 assert_eq!(styles.styles[0].style_id, "Empty");
2520 let written = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2521 let latent = written
2522 .find(concat!(
2523 r#"<w:latentStyles w:count="1" xmlns:x="urn:x">"#,
2524 r#"<w:lsdException w:name="Normal" x:a="1"/></w:latentStyles>"#,
2525 ))
2526 .unwrap_or_else(|| panic!("{written}"));
2527 let empty = written.find(r#"<x:empty xmlns:x="urn:x"/>"#).unwrap();
2528 let empty_style = written
2529 .find(r#"<w:style w:type="paragraph" w:styleId="Empty">"#)
2530 .unwrap();
2531 assert!(written.find("<w:docDefaults").unwrap() < latent);
2532 assert!(latent < empty);
2533 assert!(empty < empty_style);
2534 assert_eq!(CT_Styles::from_xml(written.as_bytes()).unwrap(), styles);
2535 }
2536
2537 #[test]
2538 fn round_trip_styles() {
2539 let styles = CT_Styles::new_default();
2540 let xml = styles.to_xml().unwrap();
2541 let parsed = CT_Styles::from_xml(&xml).unwrap();
2542
2543 let original = String::from_utf8(xml.clone()).unwrap();
2545 let (prefix, heading) = original
2546 .split_once(r#"<w:style w:type="paragraph" w:styleId="Heading1">"#)
2547 .unwrap();
2548 assert_eq!(prefix.matches("<w:docDefaults>").count(), 1);
2549 let prefix = prefix.replace(
2550 "<w:docDefaults>",
2551 &format!("<w:docDefaults xmlns:w=\"{W_NS}\">"),
2552 );
2553 let mut heading = heading.to_owned();
2554 for owner in ["pPr", "rPr"] {
2555 let opening = format!("<w:{owner}>");
2556 assert_eq!(heading.matches(&opening).count(), 1);
2557 heading = heading.replace(&opening, &format!("<w:{owner} xmlns:w=\"{W_NS}\">"));
2558 }
2559 let expected =
2560 format!(r#"{prefix}<w:style w:type="paragraph" w:styleId="Heading1">{heading}"#);
2561 let imported = parsed.to_xml().unwrap();
2562 assert_eq!(imported, expected.as_bytes());
2563 let reopened = CT_Styles::from_xml(&imported).unwrap();
2564 assert_eq!(reopened.to_xml().unwrap(), imported);
2565 assert_eq!(reopened, parsed);
2566
2567 assert_eq!(parsed.styles.len(), 2);
2568 assert!(parsed.doc_defaults.is_some());
2569
2570 let normal = parsed.get_by_id("Normal").unwrap();
2571 assert_eq!(normal.name, Some("Normal".to_string()));
2572 assert!(normal.is_default);
2573
2574 let h1 = parsed.get_by_id("Heading1").unwrap();
2575 assert_eq!(h1.based_on, Some("Normal".to_string()));
2576 }
2577
2578 #[test]
2579 fn find_default_style() {
2580 let styles = CT_Styles::new_default();
2581 let default_para = styles.get_default(StyleType::Paragraph).unwrap();
2582 assert_eq!(default_para.style_id, "Normal");
2583 }
2584
2585 #[test]
2586 fn linked_style_and_ui_flags_accept_aliases_and_serialize_in_schema_order() {
2587 let xml = format!(
2588 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>"#
2589 );
2590 let mut styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2591 let style = styles.get_by_id("Corpus").unwrap();
2592 assert_eq!(style.next_style.as_deref(), Some("Normal"));
2593 assert_eq!(style.linked_style.as_deref(), Some("CorpusChar"));
2594 assert_eq!(style.auto_redefine, Some(false));
2595 assert_eq!(style.hidden, Some(true));
2596 assert_eq!(style.ui_priority, Some(17));
2597 assert_eq!(style.semi_hidden, Some(false));
2598 assert_eq!(style.unhide_when_used, Some(true));
2599 assert_eq!(style.quick_format, Some(true));
2600 assert_eq!(style.locked, Some(true));
2601
2602 let serialized = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2603 let positions = [
2604 "<q:next",
2605 "<q:link",
2606 "<q:autoRedefine",
2607 "<q:hidden",
2608 "<q:uiPriority",
2609 "<q:semiHidden",
2610 "<q:unhideWhenUsed",
2611 "<q:qFormat",
2612 "<q:locked",
2613 "<q:pPr",
2614 ]
2615 .map(|token| serialized.find(token).unwrap());
2616 assert!(positions.windows(2).all(|pair| pair[0] < pair[1]));
2617 assert!(serialized.contains(&format!(
2618 r#"<q:pPr xmlns:q="{W_NS}"><q:spacing q:after="0"/></q:pPr>"#
2619 )));
2620 assert_eq!(serialized.matches("name-trivia").count(), 1);
2621 assert_eq!(serialized.matches("next kept").count(), 1);
2622 assert_eq!(serialized.matches("link-trivia").count(), 1);
2623 assert_eq!(serialized.matches(r#"x:root="&""#).count(), 1);
2624 assert_eq!(serialized.matches(r#"x:keep="yes""#).count(), 1);
2625 let reparsed = CT_Styles::from_xml(serialized.as_bytes()).unwrap();
2626 assert_eq!(reparsed.styles[0], styles.styles[0]);
2627
2628 styles.styles[0].next_style = Some("Other".to_owned());
2629 styles.styles[0].auto_redefine = Some(true);
2630 styles.styles[0].ui_priority = Some(23);
2631 let changed = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2632 assert!(changed.contains("<q:next"));
2633 assert!(changed.contains(r#"q:val="Other""#));
2634 assert_eq!(changed.matches("next kept").count(), 1);
2635 assert!(changed.contains("<q:autoRedefine"));
2636 assert!(changed.contains(r#"q:val="1""#));
2637 assert_eq!(changed.matches(r#"x:keep="yes""#).count(), 1);
2638 assert!(changed.contains("<q:uiPriority"));
2639 assert!(changed.contains(r#"q:val="23""#));
2640 let reparsed = CT_Styles::from_xml(changed.as_bytes()).unwrap();
2641 assert_eq!(reparsed.styles[0].next_style.as_deref(), Some("Other"));
2642 assert_eq!(reparsed.styles[0].auto_redefine, Some(true));
2643 assert_eq!(reparsed.styles[0].ui_priority, Some(23));
2644 }
2645
2646 #[test]
2647 fn style_parser_rejects_prefixes_that_conflict_with_fixed_writer_bindings() {
2648 let xml = format!(
2649 r#"<q:styles xmlns:q="{W_NS}" xmlns:w="urn:not-word"><q:style q:type="paragraph" q:styleId="Corpus"></q:style></q:styles>"#
2650 );
2651 let error = CT_Styles::from_xml(xml.as_bytes()).unwrap_err();
2652 assert!(error.to_string().contains("xmlns:w"));
2653 }
2654
2655 #[test]
2656 fn changed_table_style_properties_keep_one_unknown_child_in_schema_order() {
2657 let xml = format!(
2658 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>"#
2659 );
2660 let mut styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2661 styles.styles[0]
2662 .table_properties
2663 .as_mut()
2664 .unwrap()
2665 .shading
2666 .as_mut()
2667 .unwrap()
2668 .fill = Some("D9EAF7".to_owned());
2669
2670 let serialized = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2671 assert_eq!(serialized.matches("tblStyleRowBandSize").count(), 1);
2672 assert_eq!(serialized.matches("x:producer").count(), 1);
2673 assert!(
2674 serialized.find("tblStyleRowBandSize").unwrap() < serialized.find("w:shd").unwrap()
2675 );
2676 assert_eq!(
2677 CT_Styles::from_xml(serialized.as_bytes()).unwrap().styles[0]
2678 .table_properties
2679 .as_ref()
2680 .and_then(|properties| properties.shading.as_ref())
2681 .and_then(|shading| shading.fill.as_deref()),
2682 Some("D9EAF7")
2683 );
2684 }
2685
2686 #[test]
2687 fn aliased_style_paragraph_properties_use_ancestor_namespace_scope() {
2688 let xml = format!(
2689 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>"#
2690 );
2691 let parsed = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2692 let ppr = parsed.styles[0].ppr.as_ref().unwrap();
2693 assert_eq!(ppr.jc, Some(crate::shared::ST_Jc::Center));
2694 }
2695
2696 #[test]
2697 fn direct_style_parser_uses_supplied_start_ancestor_scope() {
2698 let xml = format!(
2699 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>"#
2700 );
2701 let mut reader = Reader::from_str(&xml);
2702 let mut buf = Vec::new();
2703 let parsed = loop {
2704 match reader.read_event_into(&mut buf) {
2705 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
2706 break CT_Style::from_xml(&mut reader, element).unwrap();
2707 }
2708 Ok(Event::Eof) => panic!("missing style"),
2709 event => {
2710 event.unwrap();
2711 }
2712 }
2713 buf.clear();
2714 };
2715 assert_eq!(
2716 parsed.ppr.as_ref().unwrap().jc,
2717 Some(crate::shared::ST_Jc::Center)
2718 );
2719 }
2720
2721 #[test]
2722 fn direct_style_parser_does_not_promote_foreign_start_prefix() {
2723 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>"#;
2724 let mut reader = Reader::from_str(xml);
2725 let mut buf = Vec::new();
2726 let parsed = loop {
2727 match reader.read_event_into(&mut buf) {
2728 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
2729 break CT_Style::from_xml(&mut reader, element).unwrap();
2730 }
2731 Ok(Event::Eof) => panic!("missing style"),
2732 event => {
2733 event.unwrap();
2734 }
2735 }
2736 buf.clear();
2737 };
2738 assert!(parsed.ppr.is_none());
2739 }
2740
2741 #[test]
2742 fn direct_style_parser_accepts_default_word_namespace() {
2743 let xml = format!(
2744 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>"#
2745 );
2746 let mut reader = Reader::from_str(&xml);
2747 let mut buf = Vec::new();
2748 let parsed = loop {
2749 match reader.read_event_into(&mut buf) {
2750 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
2751 break CT_Style::from_xml(&mut reader, element).unwrap();
2752 }
2753 Ok(Event::Eof) => panic!("missing style"),
2754 event => {
2755 event.unwrap();
2756 }
2757 }
2758 buf.clear();
2759 };
2760 assert_eq!(
2761 parsed.ppr.as_ref().unwrap().jc,
2762 Some(crate::shared::ST_Jc::Center)
2763 );
2764 }
2765
2766 #[test]
2767 fn table_style_properties_are_namespace_aware_schema_ordered_and_preserved() {
2768 let xml = format!(
2769 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>"#
2770 );
2771 let mut styles = CT_Styles::from_xml(xml.as_bytes()).unwrap();
2772 let style = styles.get_by_id("Dense").unwrap();
2773 assert_eq!(
2774 style
2775 .table_properties
2776 .as_ref()
2777 .and_then(|properties| properties.borders.as_ref())
2778 .and_then(|borders| borders.top.as_ref())
2779 .and_then(|border| border.sz),
2780 Some(8)
2781 );
2782 assert_eq!(style.conditional_table_styles.len(), 1);
2783 assert_eq!(
2784 style.conditional_table_styles[0].region,
2785 Some(TableStyleRegion::FirstRow)
2786 );
2787
2788 let serialized = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2789 assert_eq!(serialized.matches("<q:tblPr").count(), 1);
2790 assert!(
2791 serialized
2792 .contains(r#"<ext:keep xmlns:ext="urn:producer" ext:value="byte-identical"/>"#)
2793 );
2794 let ppr = serialized.find("<q:pPr").unwrap();
2795 let rpr = serialized.find("<q:rPr").unwrap();
2796 let table_properties = serialized.find("<q:tblPr").unwrap();
2797 let conditional = serialized.find("<q:tblStylePr").unwrap();
2798 assert!(ppr < rpr && rpr < table_properties && table_properties < conditional);
2799 assert!(serialized.contains(&format!(
2800 r#"<q:pPr xmlns:q="{W_NS}"><q:spacing q:after="40"/></q:pPr>"#
2801 )));
2802 assert!(serialized.contains(&format!(r#"<q:rPr xmlns:q="{W_NS}"><q:b/></q:rPr>"#)));
2803 assert!(serialized.contains(r#"<bad:tblPr xmlns:bad="urn:not-word">"#));
2804 CT_Styles::from_xml(serialized.as_bytes()).expect("preserved prefixes remain bound");
2805
2806 styles.styles[0]
2807 .table_properties
2808 .as_mut()
2809 .unwrap()
2810 .borders
2811 .as_mut()
2812 .unwrap()
2813 .top
2814 .as_mut()
2815 .unwrap()
2816 .color = Some("445566".to_owned());
2817 let changed = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2818 assert_eq!(changed.matches("<w:tblPr>").count(), 1);
2819 assert_eq!(changed.matches("445566").count(), 1);
2820 assert!(
2821 changed.contains(r#"<ext:keep xmlns:ext="urn:producer" ext:value="byte-identical"/>"#)
2822 );
2823 styles.styles[0].conditional_table_styles[0]
2824 .cell_properties
2825 .as_mut()
2826 .unwrap()
2827 .shading
2828 .as_mut()
2829 .unwrap()
2830 .fill = Some("DDEEFF".to_owned());
2831 styles.styles[0].conditional_table_styles[0].region = Some(TableStyleRegion::LastRow);
2832 let changed = String::from_utf8(styles.to_xml().unwrap()).unwrap();
2833 assert_eq!(changed.matches("DDEEFF").count(), 1);
2834 assert!(!changed.contains("AABBCC"));
2835 assert!(changed.contains(r#"<w:tblStylePr w:type="lastRow" x:region="&""#));
2836 assert_eq!(changed.matches(r#"x:region="&""#).count(), 1);
2837 assert!(!changed.contains(r#"tblStylePr q:type="firstRow""#));
2838 assert!(
2839 changed
2840 .contains(r#"<ext:conditional xmlns:ext="urn:producer" ext:value="preserved"/>"#)
2841 );
2842 let reparsed = CT_Styles::from_xml(changed.as_bytes())
2843 .expect("typed conditional projection remains valid XML");
2844 assert_eq!(
2845 reparsed.styles[0].conditional_table_styles[0].region,
2846 Some(TableStyleRegion::LastRow)
2847 );
2848 assert_eq!(
2849 reparsed.styles[0].conditional_table_styles[0]
2850 .cell_properties
2851 .as_ref()
2852 .and_then(|properties| properties.shading.as_ref())
2853 .and_then(|shading| shading.fill.as_deref()),
2854 Some("DDEEFF")
2855 );
2856 }
2857 #[test]
2858 fn fx200_defaults_source_preserves_unknown_wrappers_and_qualified_owner_after_font_edit() {
2859 let foreign =
2860 r#"<f:rPrDefault xmlns:f="urn:foreign" f:flag='A'><f:rPr/></f:rPrDefault>"#;
2861 let opaque = r#"<z:keep xmlns:z="urn:opaque" z:value='A'><z:child/></z:keep>"#;
2862 let source = format!(
2863 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>"#
2864 );
2865 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2866 let defaults = styles.doc_defaults.as_ref().unwrap();
2867 assert_eq!(
2868 defaults.rpr.as_ref().unwrap().font_ascii.as_deref(),
2869 Some("Prior")
2870 );
2871 assert_eq!(defaults.rpr.as_ref().unwrap().bold, Some(true));
2872 let original_ppr = defaults.ppr.clone();
2873 let original_source = defaults.source_xml.clone().unwrap();
2874 let mut untouched = Writer::new(Vec::new());
2875 defaults.to_xml(&mut untouched).unwrap();
2876 assert_eq!(untouched.into_inner(), original_source);
2877 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2878 properties.font_ascii = Some("Selected".to_owned());
2879 properties.font_hansi = Some("Selected".to_owned());
2880 let saved = styles.to_xml().unwrap();
2881 let text = std::str::from_utf8(&saved).unwrap();
2882 assert!(text.contains(foreign) && text.contains(opaque));
2883 assert!(text.contains("x:root='A'") && text.contains("x:wrapper='keep'"));
2884 assert!(text.contains("<!--before-->") && text.contains("<!--after-->"));
2885 assert!(text.contains("x:property='A'"));
2886 assert!(text.contains("<q:b q:val='true'/><!--inside-->"));
2887 let original_text = std::str::from_utf8(&original_source).unwrap();
2888 let begin = original_text.find("<q:pPrDefault").unwrap();
2889 let end = original_text.find("</q:pPrDefault>").unwrap() + "</q:pPrDefault>".len();
2890 assert!(text.contains(&original_text[begin..end]));
2891 let reopened = CT_Styles::from_xml(&saved).unwrap();
2892 let defaults = reopened.doc_defaults.as_ref().unwrap();
2893 assert_eq!(
2894 defaults.rpr.as_ref().unwrap().font_ascii.as_deref(),
2895 Some("Selected")
2896 );
2897 assert_eq!(defaults.rpr.as_ref().unwrap().bold, Some(true));
2898 assert_eq!(defaults.ppr, original_ppr);
2899 }
2900
2901 #[test]
2902 fn fx200_empty_default_wrapper_and_duplicate_wrapper_last_owner_are_preserved() {
2903 let source = format!(
2904 r#"<w:styles xmlns:w="{W_NS}" xmlns:f="urn:foreign"><w:docDefaults f:keep='yes'/></w:styles>"#
2905 );
2906 let styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2907 let defaults = styles.doc_defaults.as_ref().unwrap();
2908 assert!(defaults.rpr.is_none() && defaults.ppr.is_none());
2909 assert!(
2910 std::str::from_utf8(&styles.to_xml().unwrap())
2911 .unwrap()
2912 .contains("f:keep='yes'")
2913 );
2914 let source = format!(
2915 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>"#
2916 );
2917 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2918 assert!(styles.doc_defaults.as_ref().unwrap().rpr.is_none());
2919 styles.doc_defaults.as_mut().unwrap().rpr = Some(CT_RPr {
2920 font_ascii: Some("Selected".to_owned()),
2921 ..Default::default()
2922 });
2923 let saved = styles.to_xml().unwrap();
2924 assert!(std::str::from_utf8(&saved).unwrap().contains("<w:b/>"));
2925 let reopened = CT_Styles::from_xml(&saved).unwrap();
2926 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2927 assert_eq!(properties.font_ascii.as_deref(), Some("Selected"));
2928 assert!(properties.bold.is_none());
2929 }
2930 #[test]
2931 fn fx200_same_wrapper_duplicate_default_properties_do_not_resurface_on_font_clear() {
2932 let source = format!(
2933 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>"#
2934 );
2935 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2936 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2937 assert!(properties.bold.is_none());
2938 assert_eq!(properties.font_ascii.as_deref(), Some("Selected"));
2939 properties.font_ascii = None;
2940 let saved = styles.to_xml().unwrap();
2941 let text = std::str::from_utf8(&saved).unwrap();
2942 assert!(text.contains("<w:b/>") && text.contains("f:last='A'"));
2943 let reopened = CT_Styles::from_xml(&saved).unwrap();
2944 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2945 assert!(properties.bold.is_none() && properties.font_ascii.is_none());
2946 }
2947 #[test]
2948 fn fx200_duplicate_default_font_children_clear_owned_slots_without_reviving_or_erasing_raw() {
2949 for replacement in [None, Some("B")] {
2950 let source = format!(
2951 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>"#
2952 );
2953 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2954 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2955 assert_eq!(properties.font_ascii.as_deref(), Some("A"));
2956 assert_eq!(properties.font_hint.as_deref(), Some("cs"));
2957 properties.font_ascii = replacement.map(str::to_owned);
2958 let saved = styles.to_xml().unwrap();
2959 let text = std::str::from_utf8(&saved).unwrap();
2960 for raw in [
2961 "f:earlier='A'",
2962 "<f:opaque/><!--font-child-->",
2963 "<!--between-->",
2964 "w:hint='cs'",
2965 "f:last='B'",
2966 ] {
2967 assert!(text.contains(raw), "missing {raw}: {text}");
2968 }
2969 assert!(!text.contains("q:ascii='A'"));
2970 let reopened = CT_Styles::from_xml(&saved).unwrap();
2971 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
2972 assert_eq!(properties.font_ascii.as_deref(), replacement);
2973 assert_eq!(properties.font_hint.as_deref(), Some("cs"));
2974 }
2975 }
2976
2977 #[test]
2978 fn fx200_duplicate_default_size_children_clear_all_owned_values_preserving_foreign_payload() {
2979 let source = format!(
2980 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>"#
2981 );
2982 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
2983 let properties = styles.doc_defaults.as_mut().unwrap().rpr.as_mut().unwrap();
2984 assert_eq!(properties.sz, Some(crate::units::HalfPoint(28)));
2985 assert_eq!(properties.sz_cs, Some(crate::units::HalfPoint(32)));
2986 properties.sz = None;
2987 properties.sz_cs = None;
2988 let saved = styles.to_xml().unwrap();
2989 let text = std::str::from_utf8(&saved).unwrap();
2990 for raw in [
2991 "f:first='A'",
2992 "<!--size-->",
2993 "f:last='B'",
2994 "<f:opaque/>",
2995 "f:first='CS'",
2996 "f:last='CS2'",
2997 "<f:sz f:val='999'/>",
2998 ] {
2999 assert!(text.contains(raw), "missing {raw}: {text}");
3000 }
3001 for value in ["q:val='24'", "w:val='28'", "q:val='20'", "w:val='32'"] {
3002 assert!(!text.contains(value));
3003 }
3004 let reopened = CT_Styles::from_xml(&saved).unwrap();
3005 let properties = reopened.doc_defaults.unwrap().rpr.unwrap();
3006 assert!(properties.sz.is_none() && properties.sz_cs.is_none());
3007 }
3008 #[test]
3009 fn fx200_style_property_root_attributes_and_untouched_owner_bytes_survive_updates() {
3010 let source = format!(
3011 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>"#
3012 );
3013 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3014 let original = styles.styles[0]
3015 .modeled_xml
3016 .iter()
3017 .find(|(rank, _)| *rank == 20)
3018 .unwrap()
3019 .1
3020 .clone();
3021 styles.styles[0].rpr.as_mut().unwrap().font_ascii = Some("B".to_owned());
3022 let saved = styles.to_xml().unwrap();
3023 assert!(
3024 saved
3025 .windows(original.len())
3026 .any(|window| window == original)
3027 );
3028 let text = std::str::from_utf8(&saved).unwrap();
3029 for raw in [
3030 "f:keep='A'",
3031 "f:keep='B'",
3032 "q:hint='cs'",
3033 "<!--rpr-->",
3034 "<f:opaque/>",
3035 ] {
3036 assert!(text.contains(raw), "{text}");
3037 }
3038 let reopened = CT_Styles::from_xml(&saved).unwrap();
3039 assert_eq!(
3040 reopened.styles[0]
3041 .rpr
3042 .as_ref()
3043 .unwrap()
3044 .font_ascii
3045 .as_deref(),
3046 Some("B")
3047 );
3048 assert_eq!(
3049 reopened.styles[0].ppr.as_ref().unwrap().keep_next,
3050 Some(true)
3051 );
3052 }
3053
3054 #[test]
3055 fn fx200_duplicate_style_property_owners_keep_empty_final_owner_and_foreign_peers() {
3056 let source = format!(
3057 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>"#
3058 );
3059 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3060 assert!(styles.styles[0].ppr.as_ref().unwrap().keep_next.is_none());
3061 assert!(styles.styles[0].rpr.as_ref().unwrap().bold.is_none());
3062 styles.styles[0].rpr.as_mut().unwrap().font_ascii = None;
3063 let saved = styles.to_xml().unwrap();
3064 let text = std::str::from_utf8(&saved).unwrap();
3065 for raw in [
3066 "<w:b/>",
3067 "<w:keepNext/>",
3068 "f:last='A'",
3069 "f:last='B'",
3070 "f:keep='peer'",
3071 ] {
3072 assert!(text.contains(raw), "{text}");
3073 }
3074 let first = text.find("<w:b/>").unwrap();
3075 let peer = text.find("f:keep='peer'").unwrap();
3076 let last = text.find("f:last='B'").unwrap();
3077 assert!(first < peer && peer < last, "{text}");
3078 let reopened = CT_Styles::from_xml(&saved).unwrap();
3079 let style = &reopened.styles[0];
3080 assert!(
3081 style.rpr.as_ref().unwrap().bold.is_none()
3082 && style.rpr.as_ref().unwrap().font_ascii.is_none()
3083 );
3084 assert!(style.ppr.as_ref().unwrap().keep_next.is_none());
3085 styles.styles[0].rpr = None;
3086 let cleared = styles.to_xml().unwrap();
3087 let reopened = CT_Styles::from_xml(&cleared).unwrap();
3088 assert!(reopened.styles[0].rpr.is_none());
3089 assert!(
3090 std::str::from_utf8(&cleared)
3091 .unwrap()
3092 .contains("f:keep='peer'")
3093 );
3094 }
3095 #[test]
3096 fn fx200_style_border_container_edit_retains_aliased_root_and_opaque_payload() {
3097 let source = format!(
3098 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>"#
3099 );
3100 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3101 let edge = styles.styles[0]
3102 .ppr
3103 .as_mut()
3104 .unwrap()
3105 .borders
3106 .as_mut()
3107 .unwrap()
3108 .bottom
3109 .as_mut()
3110 .unwrap();
3111 edge.val = crate::shared::ST_Border::Double;
3112 edge.sz = Some(8);
3113 edge.color = Some("123456".to_owned());
3114 let saved = styles.to_xml().unwrap();
3115 let text = std::str::from_utf8(&saved).unwrap();
3116 for raw in [
3117 "f:outer='A'",
3118 "f:keep='B'",
3119 "f:edge='C'",
3120 "<!--border-->",
3121 "<f:opaque> keep </f:opaque>",
3122 "<f:bottom f:val='foreign'/>",
3123 ] {
3124 assert!(text.contains(raw), "{text}");
3125 }
3126 let reopened = CT_Styles::from_xml(&saved).unwrap();
3127 let properties = reopened.styles[0].ppr.as_ref().unwrap();
3128 let border = properties
3129 .borders
3130 .as_ref()
3131 .unwrap()
3132 .bottom
3133 .as_ref()
3134 .unwrap();
3135 assert_eq!(border.val, crate::shared::ST_Border::Double);
3136 assert_eq!(border.sz, Some(8));
3137 assert_eq!(border.color.as_deref(), Some("123456"));
3138 assert_eq!(properties.keep_next, Some(true));
3139 assert_eq!(reopened.to_xml().unwrap(), saved);
3140 }
3141
3142 #[test]
3143 fn fx200_duplicate_style_border_clear_keeps_payload_but_suppresses_earlier_edges() {
3144 let source = format!(
3145 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>"#
3146 );
3147 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3148 assert!(
3149 styles.styles[0]
3150 .ppr
3151 .as_ref()
3152 .unwrap()
3153 .borders
3154 .as_ref()
3155 .unwrap()
3156 .bottom
3157 .is_some()
3158 );
3159 styles.styles[0].ppr.as_mut().unwrap().borders = None;
3160 let saved = styles.to_xml().unwrap();
3161 let text = std::str::from_utf8(&saved).unwrap();
3162 for raw in [
3163 "f:first='A'",
3164 "f:last='B'",
3165 "<f:opaque first='same'/>",
3166 "<f:opaque last='same'/>",
3167 "<!--last-->",
3168 "<f:pBdr f:keep='foreign'/>",
3169 ] {
3170 assert!(text.contains(raw), "{text}");
3171 }
3172 assert!(text.find("f:first='A'").unwrap() < text.find("f:keep='foreign'").unwrap());
3173 assert!(text.find("f:keep='foreign'").unwrap() < text.find("f:last='B'").unwrap());
3174 let reopened = CT_Styles::from_xml(&saved).unwrap();
3175 let properties = reopened.styles[0].ppr.as_ref().unwrap();
3176 let border = properties.borders.as_ref().unwrap();
3177 assert!(
3178 border.top.is_none()
3179 && border.bottom.is_none()
3180 && border.left.is_none()
3181 && border.right.is_none()
3182 && border.between.is_none()
3183 && border.bar.is_none()
3184 );
3185 assert_eq!(properties.keep_next, Some(true));
3186 assert_eq!(reopened.to_xml().unwrap(), saved);
3187 }
3188
3189 #[test]
3190 fn fx200_direct_style_reader_restores_both_trim_flags_and_opaque_sibling_whitespace() {
3191 for property in [
3192 "<w:pPr><w:keepNext/></w:pPr>",
3193 "<f:pPr><f:payload> inner </f:payload></f:pPr>",
3194 "<w:rPr><w:b/></w:rPr>",
3195 "<f:rPr><f:payload> inner </f:payload></f:rPr>",
3196 ] {
3197 for (trim_start, trim_end) in [(false, false), (false, true), (true, false)] {
3198 let source = format!(
3199 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>"#
3200 );
3201 let mut reader = Reader::from_reader(source.as_bytes());
3202 reader.config_mut().trim_text_start = trim_start;
3203 reader.config_mut().trim_text_end = trim_end;
3204 let Event::Start(start) = reader.read_event().unwrap() else {
3205 panic!("style root missing")
3206 };
3207 let style = CT_Style::from_xml(&mut reader, &start).unwrap();
3208 assert_eq!(reader.config().trim_text_start, trim_start);
3209 assert_eq!(reader.config().trim_text_end, trim_end);
3210 let expected = match (trim_start, trim_end) {
3211 (false, false) => "> keep </f:opaque>",
3212 (false, true) => "> keep</f:opaque>",
3213 _ => ">keep </f:opaque>",
3214 };
3215 assert!(
3216 style
3217 .extra_xml
3218 .iter()
3219 .any(|(_, raw)| std::str::from_utf8(raw).unwrap().contains(expected)),
3220 "{style:?}"
3221 );
3222 }
3223 let opening = &property[..property.find('>').unwrap() + 1];
3224 let malformed = format!(
3225 r#"<w:style xmlns:w="{W_NS}" xmlns:f="urn:foreign" w:type="paragraph" w:styleId="Direct">{opening}</f:wrong></w:style>"#
3226 );
3227 let mut reader = Reader::from_reader(malformed.as_bytes());
3228 reader.config_mut().trim_text(false);
3229 let Event::Start(start) = reader.read_event().unwrap() else {
3230 panic!("style root missing")
3231 };
3232 assert!(CT_Style::from_xml(&mut reader, &start).is_err());
3233 assert!(!reader.config().trim_text_start && !reader.config().trim_text_end);
3234 }
3235 }
3236 #[test]
3237 fn fx200_started_style_border_edges_match_empty_projection_and_keep_source_on_updates() {
3238 for prefix in ["w", "q"] {
3239 let source = format!(
3240 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>"#
3241 );
3242 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3243 let original = styles.to_xml().unwrap();
3244 let before = styles.styles[0]
3245 .ppr
3246 .as_ref()
3247 .unwrap()
3248 .borders
3249 .as_ref()
3250 .unwrap()
3251 .bottom
3252 .as_ref()
3253 .unwrap();
3254 assert_eq!(before.val, crate::shared::ST_Border::Single);
3255 assert!(before.source_xml.is_some());
3256 assert_eq!(
3257 CT_Styles::from_xml(&original).unwrap().to_xml().unwrap(),
3258 original
3259 );
3260 let edge = styles.styles[0]
3261 .ppr
3262 .as_mut()
3263 .unwrap()
3264 .borders
3265 .as_mut()
3266 .unwrap()
3267 .bottom
3268 .as_mut()
3269 .unwrap();
3270 edge.val = crate::shared::ST_Border::Double;
3271 edge.sz = Some(8);
3272 edge.color = Some("123456".to_owned());
3273 let saved = styles.to_xml().unwrap();
3274 let text = std::str::from_utf8(&saved).unwrap();
3275 for raw in [
3276 "f:edge='A'",
3277 "<f:opaque> keep </f:opaque>",
3278 "<!--edge-->",
3279 ] {
3280 assert!(text.contains(raw), "{text}");
3281 }
3282 assert!(text.contains(&format!("</{prefix}:bottom>")), "{text}");
3283 let reopened = CT_Styles::from_xml(&saved).unwrap();
3284 let actual = reopened.styles[0]
3285 .ppr
3286 .as_ref()
3287 .unwrap()
3288 .borders
3289 .as_ref()
3290 .unwrap()
3291 .bottom
3292 .as_ref()
3293 .unwrap();
3294 assert_eq!(actual.val, crate::shared::ST_Border::Double);
3295 assert_eq!(actual.sz, Some(8));
3296 assert_eq!(actual.color.as_deref(), Some("123456"));
3297 assert_eq!(reopened.to_xml().unwrap(), saved);
3298 let empty = format!(
3299 r#"<w:pPr xmlns:w="{W_NS}"><w:pBdr><w:bottom w:val="double" w:sz="8" w:color="123456"/></w:pBdr></w:pPr>"#
3300 );
3301 let canonical =
3302 crate::numbering::parse_scoped_ppr(empty.as_bytes(), &["w".to_owned()]).unwrap();
3303 let expected = canonical.borders.as_ref().unwrap().bottom.as_ref().unwrap();
3304 assert_eq!(
3305 (actual.val, actual.sz, actual.color.as_deref()),
3306 (expected.val, expected.sz, expected.color.as_deref())
3307 );
3308 }
3309 }
3310 #[test]
3311 fn fx200_style_border_opaque_descendants_keep_bytes_and_never_become_direct_edges() {
3312 for opaque in [
3313 r#"<f:opaque><q:bottom q:val='double'></q:bottom></f:opaque>"#,
3314 r#"<f:opaque xmlns:q='urn:shadow'><q:bottom q:val='double'></q:bottom><w:left w:val='double'/></f:opaque>"#,
3315 ] {
3316 let source = format!(
3317 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>"#
3318 );
3319 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3320 let borders = styles.styles[0]
3321 .ppr
3322 .as_ref()
3323 .unwrap()
3324 .borders
3325 .as_ref()
3326 .unwrap();
3327 assert!(borders.bottom.is_none() && borders.left.is_none());
3328 assert_eq!(
3329 borders.right.as_ref().unwrap().val,
3330 crate::shared::ST_Border::Single
3331 );
3332 let noop = styles.to_xml().unwrap();
3333 assert!(std::str::from_utf8(&noop).unwrap().contains(opaque));
3334 styles.styles[0]
3335 .ppr
3336 .as_mut()
3337 .unwrap()
3338 .borders
3339 .as_mut()
3340 .unwrap()
3341 .right
3342 .as_mut()
3343 .unwrap()
3344 .val = crate::shared::ST_Border::Double;
3345 let saved = styles.to_xml().unwrap();
3346 let text = std::str::from_utf8(&saved).unwrap();
3347 assert!(
3348 text.contains(opaque)
3349 && text.contains("<f:inside> real </f:inside>")
3350 && text.contains("f:edge='A'")
3351 );
3352 let reopened = CT_Styles::from_xml(&saved).unwrap();
3353 let actual = reopened.styles[0]
3354 .ppr
3355 .as_ref()
3356 .unwrap()
3357 .borders
3358 .as_ref()
3359 .unwrap();
3360 assert!(actual.bottom.is_none() && actual.left.is_none());
3361 assert_eq!(
3362 actual.right.as_ref().unwrap().val,
3363 crate::shared::ST_Border::Double
3364 );
3365 assert_eq!(reopened.to_xml().unwrap(), saved);
3366 }
3367 }
3368
3369 #[test]
3370 fn fx200_started_logical_style_border_edges_update_once_without_payload_duplication() {
3371 for (physical, logical) in [("start", "left"), ("end", "right")] {
3372 let source = format!(
3373 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>"#
3374 );
3375 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3376 let initial = styles.to_xml().unwrap();
3377 assert_eq!(
3378 CT_Styles::from_xml(&initial).unwrap().to_xml().unwrap(),
3379 initial
3380 );
3381 for (value, size, color) in [
3382 (crate::shared::ST_Border::Double, 8, "123456"),
3383 (crate::shared::ST_Border::Single, 12, "654321"),
3384 ] {
3385 let borders = styles.styles[0]
3386 .ppr
3387 .as_mut()
3388 .unwrap()
3389 .borders
3390 .as_mut()
3391 .unwrap();
3392 let edge = if logical == "left" {
3393 borders.left.as_mut().unwrap()
3394 } else {
3395 borders.right.as_mut().unwrap()
3396 };
3397 edge.val = value;
3398 edge.sz = Some(size);
3399 edge.color = Some(color.to_owned());
3400 let saved = styles.to_xml().unwrap();
3401 let text = std::str::from_utf8(&saved).unwrap();
3402 assert_eq!(
3403 text.matches(&format!("<q:{physical} ")).count(),
3404 1,
3405 "{text}"
3406 );
3407 assert_eq!(
3408 text.matches(&format!("</q:{physical}>")).count(),
3409 1,
3410 "{text}"
3411 );
3412 assert_eq!(
3413 text.matches("<f:payload> unique </f:payload>").count(),
3414 1,
3415 "{text}"
3416 );
3417 assert_eq!(text.matches("<!--unique-->").count(), 1, "{text}");
3418 assert!(text.contains("f:edge='B'") && text.contains("f:container='A'"));
3419 styles = CT_Styles::from_xml(&saved).unwrap();
3420 let borders = styles.styles[0]
3421 .ppr
3422 .as_ref()
3423 .unwrap()
3424 .borders
3425 .as_ref()
3426 .unwrap();
3427 let actual = if logical == "left" {
3428 borders.left.as_ref().unwrap()
3429 } else {
3430 borders.right.as_ref().unwrap()
3431 };
3432 assert_eq!(
3433 (actual.val, actual.sz, actual.color.as_deref()),
3434 (value, Some(size), Some(color))
3435 );
3436 assert_eq!(styles.to_xml().unwrap(), saved);
3437 }
3438 }
3439 }
3440
3441 #[test]
3442 fn fx200_style_partial_border_clear_removes_all_aliases_without_touching_siblings() {
3443 let source = format!(
3444 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>"#
3445 );
3446 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3447 styles.styles[0]
3448 .ppr
3449 .as_mut()
3450 .unwrap()
3451 .borders
3452 .as_mut()
3453 .unwrap()
3454 .left = None;
3455 let saved = styles.to_xml().unwrap();
3456 let text = std::str::from_utf8(&saved).unwrap();
3457 assert!(!text.contains("<q:start") && !text.contains("<q:left"));
3458 for exact in [
3459 "f:root='A'",
3460 "<!--keep-->",
3461 "f:stay='B'",
3462 "<f:stay> body </f:stay>",
3463 "<f:left f:val='foreign'/>",
3464 ] {
3465 assert!(text.contains(exact), "{text}");
3466 }
3467 let reopened = CT_Styles::from_xml(&saved).unwrap();
3468 let borders = reopened.styles[0]
3469 .ppr
3470 .as_ref()
3471 .unwrap()
3472 .borders
3473 .as_ref()
3474 .unwrap();
3475 assert!(borders.left.is_none());
3476 assert_eq!(
3477 borders.right.as_ref().unwrap().val,
3478 crate::shared::ST_Border::Single
3479 );
3480 assert_eq!(reopened.to_xml().unwrap(), saved);
3481 }
3482 #[test]
3483 fn fx200_changed_style_scalar_keeps_physical_foreign_attributes_and_started_payload() {
3484 for started in [false, true] {
3485 let leaf = if started {
3486 "<q:u q:val='single' f:keep='A'><f:opaque> keep </f:opaque><!--inside--></q:u>"
3487 } else {
3488 "<q:u q:val='single' f:keep='A'/>"
3489 };
3490 let source = format!(
3491 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>"#
3492 );
3493 let mut styles = CT_Styles::from_xml(source.as_bytes()).unwrap();
3494 styles.styles[0].rpr.as_mut().unwrap().underline =
3495 Some(crate::shared::ST_Underline::Double);
3496 let saved = styles.to_xml().unwrap();
3497 let text = std::str::from_utf8(&saved).unwrap();
3498 assert!(
3499 text.contains("f:keep='A'")
3500 && text.contains("f:root='B'")
3501 && text.contains("<q:b/>"),
3502 "{text}"
3503 );
3504 if started {
3505 assert!(
3506 text.contains("<f:opaque> keep </f:opaque><!--inside-->"),
3507 "{text}"
3508 );
3509 }
3510 let reopened = CT_Styles::from_xml(&saved).unwrap();
3511 assert_eq!(
3512 reopened.styles[0].rpr.as_ref().unwrap().underline,
3513 Some(crate::shared::ST_Underline::Double)
3514 );
3515 assert_eq!(reopened.to_xml().unwrap(), saved);
3516 }
3517 }
3518}