1use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
7use quick_xml::{Reader, Writer, XmlVersion};
8
9use crate::error::{OxmlError, Result};
10use crate::namespace::{MC_NS, W_NS, matches_local_name};
11use crate::properties::{CT_PPr, CT_RPr};
12use crate::raw_xml::{capture_element, capture_empty_element};
13use crate::shared::ST_Jc;
14
15const R_NS: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
16const PRESERVED_PROPERTY_NS: &str = "urn:rdocx:preserved-property";
17const PRESERVED_PROPERTY_LOCAL: &str = "preservedProperty";
18const MAX_PROPERTY_XML_DEPTH: usize = 64;
19const PPR_CHILDREN: &[&[u8]] = &[
20 b"pStyle",
21 b"keepNext",
22 b"keepLines",
23 b"pageBreakBefore",
24 b"framePr",
25 b"widowControl",
26 b"numPr",
27 b"suppressLineNumbers",
28 b"pBdr",
29 b"shd",
30 b"tabs",
31 b"suppressAutoHyphens",
32 b"kinsoku",
33 b"wordWrap",
34 b"overflowPunct",
35 b"topLinePunct",
36 b"autoSpaceDE",
37 b"autoSpaceDN",
38 b"bidi",
39 b"adjustRightInd",
40 b"snapToGrid",
41 b"spacing",
42 b"ind",
43 b"contextualSpacing",
44 b"mirrorIndents",
45 b"suppressOverlap",
46 b"jc",
47 b"textDirection",
48 b"textAlignment",
49 b"textboxTightWrap",
50 b"outlineLvl",
51 b"divId",
52 b"cnfStyle",
53 b"rPr",
54 b"sectPr",
55 b"pPrChange",
56];
57
58const RPR_CHILDREN: &[&[u8]] = &[
59 b"rStyle",
60 b"rFonts",
61 b"b",
62 b"bCs",
63 b"i",
64 b"iCs",
65 b"caps",
66 b"smallCaps",
67 b"strike",
68 b"dstrike",
69 b"outline",
70 b"shadow",
71 b"emboss",
72 b"imprint",
73 b"noProof",
74 b"snapToGrid",
75 b"vanish",
76 b"webHidden",
77 b"color",
78 b"spacing",
79 b"w",
80 b"kern",
81 b"position",
82 b"sz",
83 b"szCs",
84 b"highlight",
85 b"u",
86 b"effect",
87 b"bdr",
88 b"shd",
89 b"fitText",
90 b"vertAlign",
91 b"rtl",
92 b"cs",
93 b"em",
94 b"lang",
95 b"eastAsianLayout",
96 b"specVanish",
97 b"oMath",
98 b"rPrChange",
99];
100
101const PPR_MODELLED_CHILDREN: &[&[u8]] = &[
102 b"pStyle",
103 b"keepNext",
104 b"keepLines",
105 b"pageBreakBefore",
106 b"widowControl",
107 b"numPr",
108 b"pBdr",
109 b"shd",
110 b"tabs",
111 b"suppressAutoHyphens",
112 b"spacing",
113 b"ind",
114 b"jc",
115 b"outlineLvl",
116 b"rPr",
117 b"sectPr",
118];
119
120const RPR_MODELLED_CHILDREN: &[&[u8]] = &[
121 b"rStyle",
122 b"rFonts",
123 b"b",
124 b"bCs",
125 b"i",
126 b"iCs",
127 b"caps",
128 b"smallCaps",
129 b"strike",
130 b"dstrike",
131 b"vanish",
132 b"color",
133 b"spacing",
134 b"w",
135 b"position",
136 b"sz",
137 b"szCs",
138 b"highlight",
139 b"u",
140 b"shd",
141 b"vertAlign",
142];
143
144const NUM_PR_CHILDREN: &[&[u8]] = &[b"ilvl", b"numId", b"numberingChange", b"ins"];
145const P_BDR_CHILDREN: &[&[u8]] = &[
146 b"top", b"left", b"start", b"bottom", b"right", b"end", b"between", b"bar",
147];
148const TABS_CHILDREN: &[&[u8]] = &[b"tab"];
149const SECT_PR_CHILDREN: &[&[u8]] = &[
150 b"headerReference",
151 b"footerReference",
152 b"footnotePr",
153 b"endnotePr",
154 b"type",
155 b"pgSz",
156 b"pgMar",
157 b"paperSrc",
158 b"pgBorders",
159 b"lnNumType",
160 b"pgNumType",
161 b"cols",
162 b"formProt",
163 b"vAlign",
164 b"noEndnote",
165 b"titlePg",
166 b"textDirection",
167 b"bidi",
168 b"rtlGutter",
169 b"docGrid",
170 b"printerSettings",
171 b"sectPrChange",
172];
173
174#[derive(Clone, Copy)]
175enum PropertyKind {
176 Paragraph,
177 Run,
178}
179
180impl PropertyKind {
181 fn children(self) -> &'static [&'static [u8]] {
182 match self {
183 Self::Paragraph => PPR_CHILDREN,
184 Self::Run => RPR_CHILDREN,
185 }
186 }
187
188 fn modelled_children(self) -> &'static [&'static [u8]] {
189 match self {
190 Self::Paragraph => PPR_MODELLED_CHILDREN,
191 Self::Run => RPR_MODELLED_CHILDREN,
192 }
193 }
194
195 fn local_name(self) -> &'static str {
196 match self {
197 Self::Paragraph => "pPr",
198 Self::Run => "rPr",
199 }
200 }
201}
202
203#[derive(Clone, Copy)]
204enum LevelProperty<'a> {
205 Paragraph(&'a CT_PPr),
206 Run(&'a CT_RPr),
207}
208
209impl LevelProperty<'_> {
210 fn kind(&self) -> PropertyKind {
211 match self {
212 Self::Paragraph(_) => PropertyKind::Paragraph,
213 Self::Run(_) => PropertyKind::Run,
214 }
215 }
216
217 fn canonical_xml(&self) -> Result<Vec<u8>> {
218 let mut writer = Writer::new(Vec::new());
219 match self {
220 Self::Paragraph(value) if **value == CT_PPr::default() => {
221 writer.write_event(Event::Empty(BytesStart::new("w:pPr")))?
222 }
223 Self::Paragraph(value) => value.to_xml(&mut writer)?,
224 Self::Run(value) if **value == CT_RPr::default() => {
225 writer.write_event(Event::Empty(BytesStart::new("w:rPr")))?
226 }
227 Self::Run(value) => value.to_xml(&mut writer)?,
228 }
229 Ok(writer.into_inner())
230 }
231
232 fn tab_sources(&self) -> Option<Vec<Option<usize>>> {
233 match self {
234 Self::Paragraph(value) => value
235 .tabs
236 .as_ref()
237 .map(|tabs| tabs.tabs.iter().map(|tab| tab.source_occurrence).collect()),
238 Self::Run(_) => None,
239 }
240 }
241}
242
243fn paragraph_preservation_eq(current: &CT_PPr, original: &CT_PPr) -> bool {
244 current == original
245 && LevelProperty::Paragraph(current).tab_sources()
246 == LevelProperty::Paragraph(original).tab_sources()
247}
248
249fn qualified(prefix: &str, local: &str) -> String {
250 format!("{prefix}:{local}")
251}
252
253fn generated_prefix(declarations: &[(String, String)], preferred: &str, namespace: &str) -> String {
254 if !declarations
255 .iter()
256 .any(|(prefix, value)| prefix == preferred && value != namespace)
257 {
258 return preferred.to_string();
259 }
260
261 for suffix in 1usize.. {
262 let candidate = format!("{preferred}{suffix}");
263 if !declarations
264 .iter()
265 .any(|(prefix, value)| prefix == &candidate && value != namespace)
266 {
267 return candidate;
268 }
269 }
270 unreachable!("the finite root attribute list cannot occupy every prefix")
271}
272
273fn unused_prefix(declarations: &[(String, String)], preferred: &str) -> String {
274 for suffix in 0usize.. {
275 let candidate = if suffix == 0 {
276 preferred.to_string()
277 } else {
278 format!("{preferred}{suffix}")
279 };
280 if !declarations.iter().any(|(prefix, _)| prefix == &candidate) {
281 return candidate;
282 }
283 }
284 unreachable!("the finite preserved namespace set cannot occupy every prefix")
285}
286
287struct PreservationPrefixes {
288 carrier: String,
289 markup_compatibility: String,
290}
291
292impl PreservationPrefixes {
293 fn new(declarations: &[(String, String)]) -> Self {
294 Self {
295 carrier: unused_prefix(declarations, "rdocxPreserve"),
296 markup_compatibility: unused_prefix(declarations, "mc"),
297 }
298 }
299
300 fn namespace_for<'a>(
301 &'a self,
302 prefix: &str,
303 local_declarations: &'a [(String, String)],
304 ancestor_scope: &'a [(String, String)],
305 ) -> Option<&'a str> {
306 local_declarations
307 .iter()
308 .rev()
309 .find(|(candidate, _)| candidate == prefix)
310 .or_else(|| {
311 ancestor_scope
312 .iter()
313 .rev()
314 .find(|(candidate, _)| candidate == prefix)
315 })
316 .map(|(_, namespace)| namespace.as_str())
317 }
318
319 fn mark_ignorable(
320 &self,
321 start: BytesStart<'_>,
322 ancestor_scope: &[(String, String)],
323 ) -> Result<BytesStart<'static>> {
324 let name = std::str::from_utf8(start.name().as_ref())?.to_string();
325 let mut attributes = Vec::new();
326 let mut local_declarations = Vec::new();
327 for attribute in start.attributes() {
328 let attribute = attribute?;
329 let name = std::str::from_utf8(attribute.key.as_ref())?.to_string();
330 let value = attribute
331 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
332 .into_owned();
333 if let Some(prefix) = name.strip_prefix("xmlns:") {
334 local_declarations.push((prefix.to_string(), value.clone()));
335 }
336 attributes.push((name, value));
337 }
338
339 let mut existing_ignorable = None;
340 let mut merged_tokens = Vec::new();
341 for (index, (name, value)) in attributes.iter().enumerate() {
342 let Some((prefix, local)) = name.split_once(':') else {
343 continue;
344 };
345 if local == "Ignorable"
346 && self.namespace_for(prefix, &local_declarations, ancestor_scope) == Some(MC_NS)
347 {
348 existing_ignorable.get_or_insert((index, name.clone()));
349 for token in value.split_whitespace() {
350 if !merged_tokens.iter().any(|existing| existing == token) {
351 merged_tokens.push(token.to_string());
352 }
353 }
354 }
355 }
356 if !merged_tokens.iter().any(|token| token == &self.carrier) {
357 merged_tokens.push(self.carrier.clone());
358 }
359 let merged_ignorable = merged_tokens.join(" ");
360
361 let mut marked = BytesStart::new(name);
362 for (index, (name, value)) in attributes.iter().enumerate() {
363 let expanded_ignorable = name.split_once(':').is_some_and(|(prefix, local)| {
364 local == "Ignorable"
365 && self.namespace_for(prefix, &local_declarations, ancestor_scope)
366 == Some(MC_NS)
367 });
368 if expanded_ignorable {
369 if existing_ignorable
370 .as_ref()
371 .is_some_and(|(first, _)| *first == index)
372 {
373 marked.push_attribute((name.as_str(), merged_ignorable.as_str()));
374 }
375 } else {
376 marked.push_attribute((name.as_str(), value.as_str()));
377 }
378 }
379
380 let carrier_declaration = format!("xmlns:{}", self.carrier);
381 if !attributes
382 .iter()
383 .any(|(name, _)| name == &carrier_declaration)
384 {
385 marked.push_attribute((carrier_declaration.as_str(), PRESERVED_PROPERTY_NS));
386 }
387 if existing_ignorable.is_none() {
388 let mc_declaration = format!("xmlns:{}", self.markup_compatibility);
389 if !attributes.iter().any(|(name, _)| name == &mc_declaration) {
390 marked.push_attribute((mc_declaration.as_str(), MC_NS));
391 }
392 let ignorable = qualified(&self.markup_compatibility, "Ignorable");
393 marked.push_attribute((ignorable.as_str(), merged_ignorable.as_str()));
394 }
395 Ok(marked)
396 }
397}
398
399fn append_namespace_declarations(
400 attributes: &[(String, String)],
401 declarations: &mut Vec<(String, String)>,
402) {
403 declarations.extend(attributes.iter().filter_map(|(name, value)| {
404 name.strip_prefix("xmlns:")
405 .map(|prefix| (prefix.to_string(), value.clone()))
406 }));
407}
408
409fn append_raw_namespace_declarations(
410 raw: &[u8],
411 declarations: &mut Vec<(String, String)>,
412) -> Result<()> {
413 let mut reader = Reader::from_reader(raw);
414 let mut buf = Vec::new();
415 loop {
416 match reader.read_event_into(&mut buf) {
417 Ok(Event::Start(ref element)) | Ok(Event::Empty(ref element)) => {
418 for attribute in element.attributes() {
419 let attribute = attribute?;
420 let name = std::str::from_utf8(attribute.key.as_ref())?;
421 let Some(prefix) = name.strip_prefix("xmlns:") else {
422 continue;
423 };
424 let value = attribute
425 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
426 .into_owned();
427 declarations.push((prefix.to_string(), value));
428 }
429 }
430 Ok(Event::Eof) => break,
431 Err(error) => return Err(error.into()),
432 _ => {}
433 }
434 buf.clear();
435 }
436 Ok(())
437}
438
439fn has_namespace_declaration(
440 root_attributes: &[(String, String)],
441 prefix: &str,
442 namespace: &str,
443) -> bool {
444 let declaration = format!("xmlns:{prefix}");
445 root_attributes
446 .iter()
447 .any(|(name, value)| name == &declaration && value == namespace)
448}
449
450fn prefixed_name(name: &[u8], prefix: &str) -> Result<String> {
451 let name = std::str::from_utf8(name)?;
452 Ok(match name.strip_prefix("w:") {
453 Some(local) => qualified(prefix, local),
454 None => name.to_string(),
455 })
456}
457
458fn prefixed_start(element: &BytesStart<'_>, prefix: &str) -> Result<BytesStart<'static>> {
459 let name = prefixed_name(element.name().as_ref(), prefix)?;
460 let mut rewritten = BytesStart::new(name);
461 for attribute in element.attributes() {
462 let attribute = attribute?;
463 let key = prefixed_name(attribute.key.as_ref(), prefix)?;
464 let value = attribute
465 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
466 .into_owned();
467 rewritten.push_attribute((key.as_str(), value.as_str()));
468 }
469 Ok(rewritten.into_owned())
470}
471
472fn rewrite_generated_prefix(raw: &[u8], prefix: &str) -> Result<Vec<u8>> {
473 if prefix == "w" {
474 return Ok(raw.to_vec());
475 }
476
477 let mut reader = Reader::from_reader(raw);
478 let mut writer = Writer::new(Vec::new());
479 let mut buf = Vec::new();
480 loop {
481 match reader.read_event_into(&mut buf) {
482 Ok(Event::Start(element)) => {
483 writer.write_event(Event::Start(prefixed_start(&element, prefix)?))?;
484 }
485 Ok(Event::Empty(element)) => {
486 writer.write_event(Event::Empty(prefixed_start(&element, prefix)?))?;
487 }
488 Ok(Event::End(element)) => {
489 let name = prefixed_name(element.name().as_ref(), prefix)?;
490 writer.write_event(Event::End(BytesEnd::new(name)))?;
491 }
492 Ok(Event::Eof) => break,
493 Ok(event) => writer.write_event(event.into_owned())?,
494 Err(error) => return Err(error.into()),
495 }
496 buf.clear();
497 }
498 Ok(writer.into_inner())
499}
500
501fn write_xml_events<W: std::io::Write>(writer: &mut Writer<W>, raw: &[u8]) -> Result<()> {
502 let mut reader = Reader::from_reader(raw);
503 let mut buf = Vec::new();
504 loop {
505 match reader.read_event_into(&mut buf) {
506 Ok(Event::Eof) => break,
507 Ok(event) => writer.write_event(event.into_owned())?,
508 Err(error) => return Err(error.into()),
509 }
510 buf.clear();
511 }
512 Ok(())
513}
514
515fn write_extras_at<W: std::io::Write>(
516 writer: &mut Writer<W>,
517 extra_xml: &[(usize, Vec<u8>)],
518 position: usize,
519) -> Result<()> {
520 for (at, raw) in extra_xml {
521 if *at == position {
522 writer.get_mut().write_all(raw)?;
523 }
524 }
525 Ok(())
526}
527
528fn shift_extras_from(extra_xml: &mut [(usize, Vec<u8>)], position: usize) {
529 for (at, _) in extra_xml {
530 if *at >= position {
531 *at += 1;
532 }
533 }
534}
535
536fn capture_root_attributes(start: &BytesStart<'_>) -> Result<Vec<(String, String)>> {
537 let mut attributes = Vec::new();
538 for attribute in start.attributes() {
539 let attribute = attribute?;
540 let name = std::str::from_utf8(attribute.key.as_ref())?.to_string();
541 let value = attribute
542 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
543 .into_owned();
544 if (name == "xmlns:w" && value == W_NS) || (name == "xmlns:r" && value == R_NS) {
545 continue;
546 }
547 attributes.push((name, value));
548 }
549 Ok(attributes)
550}
551
552fn capture_extra_attributes(
553 start: &BytesStart<'_>,
554 modelled: &[&[u8]],
555 word_prefixes: &[String],
556) -> Result<Vec<(String, String)>> {
557 let mut attributes = Vec::new();
558 for attribute in start.attributes() {
559 let attribute = attribute?;
560 if modelled
561 .iter()
562 .any(|local| is_word_attribute(attribute.key.as_ref(), local, word_prefixes))
563 {
564 continue;
565 }
566 let name = std::str::from_utf8(attribute.key.as_ref())?.to_string();
567 let value = attribute
568 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
569 .into_owned();
570 attributes.push((name, value));
571 }
572 Ok(attributes)
573}
574
575fn namespace_binding(prefix: &str, namespace: &str) -> String {
576 format!("\0{prefix}\0{namespace}")
577}
578
579fn split_namespace_binding(value: &str) -> Option<(&str, &str)> {
580 value.strip_prefix('\0')?.split_once('\0')
581}
582
583fn namespace_bindings(scope: &[String]) -> Vec<(String, String)> {
584 scope
585 .iter()
586 .filter_map(|value| split_namespace_binding(value))
587 .map(|(prefix, namespace)| (prefix.to_string(), namespace.to_string()))
588 .collect()
589}
590
591pub(crate) fn word_prefixes_at(
592 start: &BytesStart<'_>,
593 inherited: &[String],
594) -> Result<Vec<String>> {
595 let mut prefixes = inherited.to_vec();
596 for attribute in start.attributes() {
597 let attribute = attribute?;
598 let name = attribute.key.as_ref();
599 let prefix = if name == b"xmlns" {
600 b"".as_slice()
601 } else if let Some(prefix) = name.strip_prefix(b"xmlns:") {
602 prefix
603 } else {
604 continue;
605 };
606 let prefix = std::str::from_utf8(prefix)?.to_string();
607 let value =
608 attribute.decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?;
609 prefixes.retain(|candidate| {
610 candidate != &prefix
611 && split_namespace_binding(candidate)
612 .is_none_or(|(candidate, _)| candidate != prefix)
613 });
614 prefixes.push(namespace_binding(&prefix, &value));
615 if value.as_bytes() == W_NS.as_bytes() {
616 prefixes.push(prefix);
617 }
618 }
619 Ok(prefixes)
620}
621
622fn is_word_attribute(key: &[u8], local: &[u8], word_prefixes: &[String]) -> bool {
623 let Some(separator) = key.iter().position(|byte| *byte == b':') else {
624 return false;
625 };
626 key.get(separator + 1..) == Some(local)
627 && word_prefixes
628 .iter()
629 .any(|prefix| prefix.as_bytes() == &key[..separator])
630}
631
632fn is_word_element(name: &[u8], local: &[u8], word_prefixes: &[String]) -> bool {
633 let qualified_local = name.rsplit(|byte| *byte == b':').next().unwrap_or(name);
634 qualified_local == local && is_word_name(name, word_prefixes)
635}
636
637fn word_attribute_value(
638 element: &BytesStart<'_>,
639 name: &[u8],
640 word_prefixes: &[String],
641) -> Result<Option<String>> {
642 let prefixes = word_prefixes_at(element, word_prefixes)?;
643 for attribute in element.attributes() {
644 let attribute = attribute?;
645 if is_word_attribute(attribute.key.as_ref(), name, &prefixes) {
646 return Ok(Some(
647 attribute
648 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
649 .into_owned(),
650 ));
651 }
652 }
653 Ok(None)
654}
655
656fn abstract_raw_boundary(name: &[u8], current: usize, word_prefixes: &[String]) -> (usize, usize) {
657 if is_word_element(name, b"nsid", word_prefixes) {
658 (0, 1)
659 } else if is_word_element(name, b"tmpl", word_prefixes) {
660 (2, 3)
661 } else if is_word_element(name, b"name", word_prefixes) {
662 (3, 4)
663 } else if is_word_element(name, b"styleLink", word_prefixes) {
664 (4, 5)
665 } else if is_word_element(name, b"numStyleLink", word_prefixes) {
666 (5, 6)
667 } else {
668 (current, current)
669 }
670}
671
672fn root_raw_boundary(name: &[u8], modelled_count: usize, word_prefixes: &[String]) -> usize {
673 if is_word_element(name, b"numPicBullet", word_prefixes) {
674 0
675 } else {
676 1 + modelled_count
677 }
678}
679
680fn push_extra_attributes(start: &mut BytesStart<'_>, attributes: &[(String, String)]) {
681 for (name, value) in attributes {
682 start.push_attribute((name.as_str(), value.as_str()));
683 }
684}
685
686fn level_raw_boundary(name: &[u8], current: usize, word_prefixes: &[String]) -> (usize, usize) {
687 if is_word_element(name, b"lvlRestart", word_prefixes) {
688 (2, 3)
689 } else if is_word_element(name, b"pStyle", word_prefixes) {
690 (3, 4)
691 } else if is_word_element(name, b"isLgl", word_prefixes) {
692 (4, 5)
693 } else if is_word_element(name, b"suff", word_prefixes) {
694 (5, 6)
695 } else if is_word_element(name, b"lvlPicBulletId", word_prefixes) {
696 (7, 8)
697 } else if is_word_element(name, b"legacy", word_prefixes) {
698 (8, 9)
699 } else {
700 (current, current)
701 }
702}
703
704fn u32_attribute(element: &BytesStart<'_>, name: &[u8], word_prefixes: &[String]) -> Result<u32> {
705 Ok(word_attribute_value(element, name, word_prefixes)?
706 .map(|value| value.parse())
707 .transpose()?
708 .unwrap_or(0))
709}
710
711fn is_word_name(name: &[u8], word_prefixes: &[String]) -> bool {
712 let Some(separator) = name.iter().position(|byte| *byte == b':') else {
713 return word_prefixes.iter().any(String::is_empty);
714 };
715 word_prefixes
716 .iter()
717 .any(|prefix| prefix.as_bytes() == &name[..separator])
718}
719
720fn is_word_property_attribute(name: &[u8], word_prefixes: &[String]) -> bool {
721 let Some(separator) = name.iter().position(|byte| *byte == b':') else {
722 return false;
723 };
724 word_prefixes
725 .iter()
726 .any(|prefix| prefix.as_bytes() == &name[..separator])
727}
728
729fn is_relationship_property_attribute(name: &[u8], scope: &[String]) -> bool {
730 let Some(separator) = name.iter().position(|byte| *byte == b':') else {
731 return false;
732 };
733 scope.iter().any(|binding| {
734 split_namespace_binding(binding).is_some_and(|(prefix, namespace)| {
735 prefix.as_bytes() == &name[..separator] && namespace == R_NS
736 })
737 })
738}
739
740fn property_attributes(kind: PropertyKind, local: &[u8]) -> &'static [&'static [u8]] {
741 match local {
742 b"pStyle"
743 | b"keepNext"
744 | b"keepLines"
745 | b"pageBreakBefore"
746 | b"widowControl"
747 | b"suppressAutoHyphens"
748 | b"jc"
749 | b"outlineLvl"
750 | b"rStyle"
751 | b"b"
752 | b"bCs"
753 | b"i"
754 | b"iCs"
755 | b"caps"
756 | b"smallCaps"
757 | b"strike"
758 | b"dstrike"
759 | b"vanish"
760 | b"w"
761 | b"position"
762 | b"sz"
763 | b"szCs"
764 | b"highlight"
765 | b"u"
766 | b"vertAlign"
767 | b"ilvl"
768 | b"numId" => &[b"val"],
769 b"rFonts" => &[
770 b"ascii",
771 b"hAnsi",
772 b"eastAsia",
773 b"cs",
774 b"asciiTheme",
775 b"hAnsiTheme",
776 ],
777 b"spacing" if matches!(kind, PropertyKind::Paragraph) => &[
778 b"before",
779 b"after",
780 b"line",
781 b"lineRule",
782 b"beforeAutospacing",
783 b"afterAutospacing",
784 ],
785 b"spacing" => &[b"val"],
786 b"ind" => &[
787 b"left",
788 b"start",
789 b"right",
790 b"end",
791 b"firstLine",
792 b"hanging",
793 ],
794 b"color" => &[b"val", b"themeColor"],
795 b"shd" => &[b"val", b"color", b"fill"],
796 b"top" | b"left" | b"start" | b"bottom" | b"right" | b"end" | b"between" | b"bar" => {
797 &[b"val", b"sz", b"space", b"color"]
798 }
799 b"tab" => &[b"val", b"pos", b"leader"],
800 b"type" => &[b"val"],
801 b"pgSz" => &[b"w", b"h", b"orient"],
802 b"pgMar" => &[
803 b"top", b"right", b"end", b"bottom", b"left", b"start", b"gutter", b"header", b"footer",
804 ],
805 b"cols" => &[b"num", b"space", b"equalWidth", b"sep"],
806 b"col" => &[b"w", b"space"],
807 b"headerReference" | b"footerReference" => &[b"type", b"id"],
808 _ => &[],
809 }
810}
811
812fn nested_property_children(parent: &[u8]) -> &'static [&'static [u8]] {
813 match parent {
814 b"numPr" => &[b"ilvl", b"numId"],
815 b"pBdr" => &[
816 b"top", b"left", b"start", b"bottom", b"right", b"end", b"between", b"bar",
817 ],
818 b"tabs" => &[b"tab"],
819 b"rPr" => RPR_MODELLED_CHILDREN,
820 b"sectPr" => &[
821 b"headerReference",
822 b"footerReference",
823 b"type",
824 b"pgSz",
825 b"pgMar",
826 b"cols",
827 b"titlePg",
828 ],
829 b"cols" => &[b"col"],
830 _ => &[],
831 }
832}
833
834fn nested_property_schema(parent: &[u8]) -> &'static [&'static [u8]] {
835 match parent {
836 b"numPr" => NUM_PR_CHILDREN,
837 b"pBdr" => P_BDR_CHILDREN,
838 b"tabs" => TABS_CHILDREN,
839 b"rPr" => RPR_CHILDREN,
840 b"sectPr" => SECT_PR_CHILDREN,
841 b"cols" => &[b"col"],
842 _ => &[],
843 }
844}
845
846fn projected_word_start(
847 element: &BytesStart<'_>,
848 word_prefixes: &[String],
849 kind: PropertyKind,
850 root: bool,
851 has_producer: &mut bool,
852) -> Result<BytesStart<'static>> {
853 let local_name = element.local_name();
854 let local = std::str::from_utf8(local_name.as_ref())?;
855 let name = qualified("w", local);
856 let mut projected = BytesStart::new(name);
857 for attribute in element.attributes() {
858 let attribute = attribute?;
859 let key = attribute.key.as_ref();
860 if key.starts_with(b"xmlns") {
861 let value = attribute
862 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?;
863 if value.as_bytes() != W_NS.as_bytes() {
864 *has_producer = true;
865 }
866 continue;
867 }
868 let supported = property_attributes(kind, local_name.as_ref());
869 let attribute_local = key.rsplit(|byte| *byte == b':').next().unwrap_or(key);
870 let modelled_namespace = is_word_property_attribute(key, word_prefixes)
871 || ((local_name.as_ref() == b"headerReference"
872 || local_name.as_ref() == b"footerReference")
873 && attribute_local == b"id"
874 && is_relationship_property_attribute(key, word_prefixes));
875 if root || !modelled_namespace || !supported.contains(&attribute_local) {
876 *has_producer = true;
877 continue;
878 }
879 let attribute_local = std::str::from_utf8(attribute_local)?;
880 let name = qualified("w", attribute_local);
881 let value = attribute
882 .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
883 .into_owned();
884 projected.push_attribute((name.as_str(), value.as_str()));
885 }
886 Ok(projected.into_owned())
887}
888
889fn project_word_element(
890 reader: &mut Reader<&[u8]>,
891 start: &BytesStart<'_>,
892 inherited_prefixes: &[String],
893 writer: &mut Writer<Vec<u8>>,
894 kind: PropertyKind,
895 depth: usize,
896 has_producer: &mut bool,
897) -> Result<()> {
898 if depth >= MAX_PROPERTY_XML_DEPTH {
899 return Err(OxmlError::InvalidValue(format!(
900 "property XML depth exceeds {MAX_PROPERTY_XML_DEPTH}"
901 )));
902 }
903 let prefixes = word_prefixes_at(start, inherited_prefixes)?;
904 writer.write_event(Event::Start(projected_word_start(
905 start,
906 &prefixes,
907 kind,
908 depth == 0,
909 has_producer,
910 )?))?;
911 let parent_local = start.local_name();
912 let mut buf = Vec::new();
913 loop {
914 match reader.read_event_into(&mut buf) {
915 Ok(Event::Start(ref element)) => {
916 let child_prefixes = word_prefixes_at(element, &prefixes)?;
917 let local = element.local_name();
918 let word_name = is_word_name(element.name().as_ref(), &child_prefixes);
919 let modelled = if depth == 0 {
920 kind.modelled_children().contains(&local.as_ref())
921 } else {
922 nested_property_children(parent_local.as_ref()).contains(&local.as_ref())
923 };
924 if word_name {
925 if !modelled {
926 *has_producer = true;
927 }
928 project_word_element(
929 reader,
930 element,
931 &prefixes,
932 writer,
933 kind,
934 depth + 1,
935 has_producer,
936 )?;
937 } else {
938 *has_producer = true;
939 reader.read_to_end_into(element.name(), &mut Vec::new())?;
940 }
941 }
942 Ok(Event::Empty(ref element)) => {
943 let child_prefixes = word_prefixes_at(element, &prefixes)?;
944 let local = element.local_name();
945 let word_name = is_word_name(element.name().as_ref(), &child_prefixes);
946 let modelled = if depth == 0 {
947 kind.modelled_children().contains(&local.as_ref())
948 } else {
949 nested_property_children(parent_local.as_ref()).contains(&local.as_ref())
950 };
951 if word_name {
952 if !modelled {
953 *has_producer = true;
954 }
955 writer.write_event(Event::Empty(projected_word_start(
956 element,
957 &child_prefixes,
958 kind,
959 false,
960 has_producer,
961 )?))?;
962 } else {
963 *has_producer = true;
964 }
965 }
966 Ok(Event::End(_)) => {
967 let local_name = start.local_name();
968 let name = qualified("w", std::str::from_utf8(local_name.as_ref())?);
969 writer.write_event(Event::End(BytesEnd::new(name)))?;
970 return Ok(());
971 }
972 Ok(Event::Text(ref text)) if text.as_ref().iter().all(u8::is_ascii_whitespace) => {}
973 Ok(Event::Eof) => return Ok(()),
974 Ok(_) => *has_producer = true,
975 Err(error) => return Err(error.into()),
976 }
977 buf.clear();
978 }
979}
980
981fn property_projection(
982 raw: &[u8],
983 kind: PropertyKind,
984 word_prefixes: &[String],
985) -> Result<(Vec<u8>, bool)> {
986 let mut reader = Reader::from_reader(raw);
987 let mut writer = Writer::new(Vec::new());
988 let mut buf = Vec::new();
989 let mut has_producer = false;
990 loop {
991 match reader.read_event_into(&mut buf) {
992 Ok(Event::Start(ref element)) => {
993 project_word_element(
994 &mut reader,
995 element,
996 word_prefixes,
997 &mut writer,
998 kind,
999 0,
1000 &mut has_producer,
1001 )?;
1002 return Ok((writer.into_inner(), has_producer));
1003 }
1004 Ok(Event::Empty(ref element)) => {
1005 let prefixes = word_prefixes_at(element, word_prefixes)?;
1006 writer.write_event(Event::Empty(projected_word_start(
1007 element,
1008 &prefixes,
1009 kind,
1010 true,
1011 &mut has_producer,
1012 )?))?;
1013 return Ok((writer.into_inner(), has_producer));
1014 }
1015 Ok(Event::Eof) => return Ok((Vec::new(), false)),
1016 Err(error) => return Err(error.into()),
1017 _ => {}
1018 }
1019 buf.clear();
1020 }
1021}
1022
1023fn ppr_from_raw(raw: &[u8], word_prefixes: &[String]) -> Result<(CT_PPr, bool)> {
1024 let (projected, has_producer) =
1025 property_projection(raw, PropertyKind::Paragraph, word_prefixes)?;
1026 let mut ppr = parse_projected_ppr(&projected)?;
1027 for (index, tab) in ppr
1028 .tabs
1029 .iter_mut()
1030 .flat_map(|tabs| tabs.tabs.iter_mut())
1031 .enumerate()
1032 {
1033 tab.source_occurrence = Some(index);
1034 }
1035 Ok((ppr, has_producer))
1036}
1037
1038fn parse_projected_ppr(projected: &[u8]) -> Result<CT_PPr> {
1039 let mut reader = Reader::from_reader(projected);
1040 let mut buf = Vec::new();
1041 loop {
1042 match reader.read_event_into(&mut buf) {
1043 Ok(Event::Start(_)) => return CT_PPr::from_xml(&mut reader),
1044 Ok(Event::Empty(_)) | Ok(Event::Eof) => return Ok(CT_PPr::default()),
1045 Err(error) => return Err(error.into()),
1046 _ => {}
1047 }
1048 buf.clear();
1049 }
1050}
1051
1052pub(crate) fn parse_scoped_ppr(raw: &[u8], word_prefixes: &[String]) -> Result<CT_PPr> {
1053 let (projected, _) = property_projection(raw, PropertyKind::Paragraph, word_prefixes)?;
1054 parse_projected_ppr(&projected)
1055}
1056
1057fn rpr_from_raw(raw: &[u8], word_prefixes: &[String]) -> Result<(CT_RPr, bool)> {
1058 let (projected, has_producer) = property_projection(raw, PropertyKind::Run, word_prefixes)?;
1059 let mut reader = Reader::from_reader(projected.as_slice());
1060 let mut buf = Vec::new();
1061 loop {
1062 match reader.read_event_into(&mut buf) {
1063 Ok(Event::Start(_)) => return Ok((CT_RPr::from_xml(&mut reader)?, has_producer)),
1064 Ok(Event::Empty(_)) | Ok(Event::Eof) => {
1065 return Ok((CT_RPr::default(), has_producer));
1066 }
1067 Err(error) => return Err(error.into()),
1068 _ => {}
1069 }
1070 buf.clear();
1071 }
1072}
1073
1074fn generated_property_children(raw: &[u8], kind: PropertyKind) -> Result<Vec<(usize, Vec<u8>)>> {
1075 generated_children(raw, kind.children())
1076}
1077
1078fn generated_children(raw: &[u8], schema_children: &[&[u8]]) -> Result<Vec<(usize, Vec<u8>)>> {
1079 let mut reader = Reader::from_reader(raw);
1080 reader.config_mut().trim_text(true);
1081 let mut children = Vec::new();
1082 let mut buf = Vec::new();
1083 let mut inside = false;
1084 loop {
1085 match reader.read_event_into(&mut buf) {
1086 Ok(Event::Start(_)) if !inside => inside = true,
1087 Ok(Event::Empty(_)) if !inside => return Ok(children),
1088 Ok(Event::Start(ref element)) if inside => {
1089 let local = element.local_name();
1090 let position = schema_children
1091 .iter()
1092 .position(|candidate| *candidate == local.as_ref())
1093 .expect("typed property writers emit only modelled children");
1094 children.push((position, capture_element(&mut reader, element)?));
1095 }
1096 Ok(Event::Empty(ref element)) if inside => {
1097 let local = element.local_name();
1098 let position = schema_children
1099 .iter()
1100 .position(|candidate| *candidate == local.as_ref())
1101 .expect("typed property writers emit only modelled children");
1102 children.push((position, capture_empty_element(element)?));
1103 }
1104 Ok(Event::End(_)) | Ok(Event::Eof) => return Ok(children),
1105 Err(error) => return Err(error.into()),
1106 _ => {}
1107 }
1108 buf.clear();
1109 }
1110}
1111
1112struct PropertyOverlay {
1113 start: BytesStart<'static>,
1114 end_name: String,
1115 extras: Vec<(usize, Vec<u8>)>,
1116 modelled: Vec<(usize, Vec<u8>)>,
1117}
1118
1119fn property_overlay(
1120 raw: &[u8],
1121 kind: PropertyKind,
1122 word_prefixes: &[String],
1123) -> Result<PropertyOverlay> {
1124 property_overlay_with_children(
1125 raw,
1126 kind.local_name(),
1127 kind.children(),
1128 kind.modelled_children(),
1129 word_prefixes,
1130 )
1131}
1132
1133fn property_overlay_with_children(
1134 raw: &[u8],
1135 local_name: &str,
1136 schema_children: &[&[u8]],
1137 modelled_children: &[&[u8]],
1138 word_prefixes: &[String],
1139) -> Result<PropertyOverlay> {
1140 let mut reader = Reader::from_reader(raw);
1141 reader.config_mut().trim_text(true);
1142 let mut buf = Vec::new();
1143 let (start, end_name, empty) = loop {
1144 match reader.read_event_into(&mut buf) {
1145 Ok(Event::Start(element)) => {
1146 let end_name = std::str::from_utf8(element.name().as_ref())?.to_string();
1147 break (element.into_owned(), end_name, false);
1148 }
1149 Ok(Event::Empty(element)) => {
1150 let end_name = std::str::from_utf8(element.name().as_ref())?.to_string();
1151 break (element.into_owned(), end_name, true);
1152 }
1153 Ok(Event::Eof) => {
1154 let name = qualified("w", local_name);
1155 break (BytesStart::new(name.clone()), name, true);
1156 }
1157 Err(error) => return Err(error.into()),
1158 _ => {}
1159 }
1160 buf.clear();
1161 };
1162
1163 let root_prefixes = word_prefixes_at(&start, word_prefixes)?;
1164 let mut extras = Vec::new();
1165 let mut modelled = Vec::new();
1166 if !empty {
1167 let mut pending = Vec::new();
1168 let mut last_modelled_position = None;
1169 loop {
1170 buf.clear();
1171 match reader.read_event_into(&mut buf) {
1172 Ok(Event::Start(ref element)) => {
1173 let prefixes = word_prefixes_at(element, &root_prefixes)?;
1174 if is_word_name(element.name().as_ref(), &prefixes)
1175 && let Some(position) = modelled_children.iter().find_map(|local| {
1176 (*local == element.local_name().as_ref()).then(|| {
1177 schema_children
1178 .iter()
1179 .position(|schema| schema == local)
1180 .unwrap()
1181 })
1182 })
1183 {
1184 extras.extend(pending.drain(..).map(|raw| (position, raw)));
1185 modelled.push((position, capture_element(&mut reader, element)?));
1186 last_modelled_position = Some(position);
1187 } else if is_word_name(element.name().as_ref(), &prefixes)
1188 && let Some(position) = schema_children
1189 .iter()
1190 .position(|local| *local == element.local_name().as_ref())
1191 {
1192 extras.extend(pending.drain(..).map(|raw| (position, raw)));
1193 extras.push((position, capture_element(&mut reader, element)?));
1194 last_modelled_position = Some(position);
1195 } else {
1196 pending.push(capture_element(&mut reader, element)?);
1197 }
1198 }
1199 Ok(Event::Empty(ref element)) => {
1200 let prefixes = word_prefixes_at(element, &root_prefixes)?;
1201 if is_word_name(element.name().as_ref(), &prefixes)
1202 && let Some(position) = modelled_children.iter().find_map(|local| {
1203 (*local == element.local_name().as_ref()).then(|| {
1204 schema_children
1205 .iter()
1206 .position(|schema| schema == local)
1207 .unwrap()
1208 })
1209 })
1210 {
1211 extras.extend(pending.drain(..).map(|raw| (position, raw)));
1212 modelled.push((position, capture_empty_element(element)?));
1213 last_modelled_position = Some(position);
1214 } else if is_word_name(element.name().as_ref(), &prefixes)
1215 && let Some(position) = schema_children
1216 .iter()
1217 .position(|local| *local == element.local_name().as_ref())
1218 {
1219 extras.extend(pending.drain(..).map(|raw| (position, raw)));
1220 extras.push((position, capture_empty_element(element)?));
1221 last_modelled_position = Some(position);
1222 } else {
1223 pending.push(capture_empty_element(element)?);
1224 }
1225 }
1226 Ok(Event::End(_)) | Ok(Event::Eof) => {
1227 let position = last_modelled_position.map_or(0, |position| position + 1);
1228 extras.extend(pending.drain(..).map(|raw| (position, raw)));
1229 break;
1230 }
1231 Err(error) => return Err(error.into()),
1232 _ => {}
1233 }
1234 }
1235 }
1236
1237 Ok(PropertyOverlay {
1238 start,
1239 end_name,
1240 extras,
1241 modelled,
1242 })
1243}
1244
1245fn merge_property_child_attributes(
1246 original: &[u8],
1247 generated: &[u8],
1248 word_prefixes: &[String],
1249 kind: PropertyKind,
1250) -> Result<Vec<u8>> {
1251 let mut original_reader = Reader::from_reader(original);
1252 let mut original_buf = Vec::new();
1253 let original_start = loop {
1254 match original_reader.read_event_into(&mut original_buf) {
1255 Ok(Event::Start(element)) | Ok(Event::Empty(element)) => break element.into_owned(),
1256 Ok(Event::Eof) => return Ok(generated.to_vec()),
1257 Err(error) => return Err(error.into()),
1258 _ => {}
1259 }
1260 original_buf.clear();
1261 };
1262 let prefixes = word_prefixes_at(&original_start, word_prefixes)?;
1263 let local = original_start.local_name();
1264 let supported = property_attributes(kind, local.as_ref());
1265 let mut preserved_attributes = Vec::new();
1266 for attribute in original_start.attributes() {
1267 let attribute = attribute?;
1268 let key = attribute.key.as_ref();
1269 let attribute_local = key.rsplit(|byte| *byte == b':').next().unwrap_or(key);
1270 if key.starts_with(b"xmlns")
1271 || !is_word_property_attribute(key, &prefixes)
1272 || !supported.contains(&attribute_local)
1273 {
1274 let name = std::str::from_utf8(key)?.to_string();
1275 let value = attribute
1276 .decoded_and_normalized_value(XmlVersion::Implicit1_0, original_start.decoder())?
1277 .into_owned();
1278 preserved_attributes.push((name, value));
1279 }
1280 }
1281
1282 let mut reader = Reader::from_reader(generated);
1283 let mut writer = Writer::new(Vec::new());
1284 let mut buf = Vec::new();
1285 let mut first = true;
1286 loop {
1287 match reader.read_event_into(&mut buf) {
1288 Ok(Event::Start(mut element)) if first => {
1289 push_extra_attributes(&mut element, &preserved_attributes);
1290 writer.write_event(Event::Start(element.into_owned()))?;
1291 first = false;
1292 }
1293 Ok(Event::Empty(mut element)) if first => {
1294 push_extra_attributes(&mut element, &preserved_attributes);
1295 writer.write_event(Event::Empty(element.into_owned()))?;
1296 first = false;
1297 }
1298 Ok(Event::Eof) => break,
1299 Ok(event) => writer.write_event(event.into_owned())?,
1300 Err(error) => return Err(error.into()),
1301 }
1302 buf.clear();
1303 }
1304 Ok(writer.into_inner())
1305}
1306
1307fn first_element_start(raw: &[u8]) -> Result<(BytesStart<'static>, bool)> {
1308 let mut reader = Reader::from_reader(raw);
1309 let mut buf = Vec::new();
1310 loop {
1311 match reader.read_event_into(&mut buf) {
1312 Ok(Event::Start(element)) => return Ok((element.into_owned(), false)),
1313 Ok(Event::Empty(element)) => return Ok((element.into_owned(), true)),
1314 Ok(Event::Eof) => {
1315 return Err(OxmlError::InvalidValue(
1316 "property child contains no element".to_string(),
1317 ));
1318 }
1319 Err(error) => return Err(error.into()),
1320 _ => {}
1321 }
1322 buf.clear();
1323 }
1324}
1325
1326fn element_prefix(start: &BytesStart<'_>) -> Result<String> {
1327 let name = start.name();
1328 let name = name.as_ref();
1329 let separator = name.iter().position(|byte| *byte == b':').ok_or_else(|| {
1330 OxmlError::InvalidValue("generated property QName has no prefix".to_string())
1331 })?;
1332 Ok(std::str::from_utf8(&name[..separator])?.to_string())
1333}
1334
1335fn producer_leaf_payload(
1336 raw: &[u8],
1337 word_prefixes: &[String],
1338 kind: PropertyKind,
1339 preservation_prefixes: &PreservationPrefixes,
1340) -> Result<Option<Vec<u8>>> {
1341 let (original_start, _) = first_element_start(raw)?;
1342 let prefixes = word_prefixes_at(&original_start, word_prefixes)?;
1343 let local = original_start.local_name();
1344 let local_name = std::str::from_utf8(local.as_ref())?;
1345 let supported = property_attributes(kind, local.as_ref());
1346 let mut declarations = Vec::new();
1347 let mut producer_attributes = Vec::new();
1348 for attribute in original_start.attributes() {
1349 let attribute = attribute?;
1350 let key = attribute.key.as_ref();
1351 let name = std::str::from_utf8(key)?.to_string();
1352 let value = attribute
1353 .decoded_and_normalized_value(XmlVersion::Implicit1_0, original_start.decoder())?
1354 .into_owned();
1355 if key == b"xmlns" || key.starts_with(b"xmlns:") {
1356 declarations.push((name, value));
1357 continue;
1358 }
1359 let attribute_local = key.rsplit(|byte| *byte == b':').next().unwrap_or(key);
1360 if !is_word_property_attribute(key, &prefixes) || !supported.contains(&attribute_local) {
1361 producer_attributes.push((name, value));
1362 }
1363 }
1364 let overlay = property_overlay_with_children(raw, local_name, &[], &[], word_prefixes)?;
1365 let nested = overlay
1366 .extras
1367 .iter()
1368 .map(|(_, child)| child.as_slice())
1369 .collect::<Vec<_>>();
1370 if producer_attributes.is_empty() && nested.is_empty() {
1371 return Ok(None);
1372 }
1373 let carrier_name = qualified(&preservation_prefixes.carrier, PRESERVED_PROPERTY_LOCAL);
1374 let mut start = BytesStart::new(carrier_name.clone());
1375 push_extra_attributes(&mut start, &declarations);
1376 push_extra_attributes(&mut start, &producer_attributes);
1377 let mut writer = Writer::new(Vec::new());
1378 if nested.is_empty() {
1379 writer.write_event(Event::Empty(start))?;
1380 } else {
1381 writer.write_event(Event::Start(start))?;
1382 for child in nested {
1383 writer.get_mut().extend_from_slice(child);
1384 }
1385 writer.write_event(Event::End(BytesEnd::new(carrier_name)))?;
1386 }
1387 Ok(Some(writer.into_inner()))
1388}
1389
1390fn producer_only_property(
1391 raw: &[u8],
1392 word_prefixes: &[String],
1393 kind: PropertyKind,
1394 output_word_prefix: &str,
1395 depth: usize,
1396 preservation_prefixes: &PreservationPrefixes,
1397) -> Result<Option<Vec<u8>>> {
1398 if depth >= MAX_PROPERTY_XML_DEPTH {
1399 return Err(OxmlError::InvalidValue(format!(
1400 "property XML depth exceeds {MAX_PROPERTY_XML_DEPTH}"
1401 )));
1402 }
1403 let (original_start, _) = first_element_start(raw)?;
1404 let prefixes = word_prefixes_at(&original_start, word_prefixes)?;
1405 let local = original_start.local_name();
1406 let local_name = std::str::from_utf8(local.as_ref())?;
1407 if local.as_ref() == b"tabs" {
1408 return producer_only_occurrence_property(
1409 raw,
1410 word_prefixes,
1411 kind,
1412 output_word_prefix,
1413 depth,
1414 preservation_prefixes,
1415 );
1416 }
1417 let supported = property_attributes(kind, local.as_ref());
1418 let mut start = BytesStart::new(qualified(output_word_prefix, local_name));
1419 let mut has_producer = false;
1420 for attribute in original_start.attributes() {
1421 let attribute = attribute?;
1422 let key = attribute.key.as_ref();
1423 let attribute_local = key.rsplit(|byte| *byte == b':').next().unwrap_or(key);
1424 let namespace = key == b"xmlns" || key.starts_with(b"xmlns:");
1425 let producer = !namespace
1426 && (!is_word_property_attribute(key, &prefixes)
1427 || !supported.contains(&attribute_local));
1428 if namespace || producer {
1429 let name = std::str::from_utf8(key)?.to_string();
1430 let value = attribute
1431 .decoded_and_normalized_value(XmlVersion::Implicit1_0, original_start.decoder())?
1432 .into_owned();
1433 start.push_attribute((name.as_str(), value.as_str()));
1434 has_producer |= producer;
1435 }
1436 }
1437
1438 let (schema_children, modelled_children) = if local.as_ref() == kind.local_name().as_bytes() {
1439 (kind.children(), kind.modelled_children())
1440 } else {
1441 (
1442 nested_property_schema(local.as_ref()),
1443 nested_property_children(local.as_ref()),
1444 )
1445 };
1446 if schema_children.is_empty() && local.as_ref() != kind.local_name().as_bytes() {
1447 return producer_leaf_payload(raw, word_prefixes, kind, preservation_prefixes);
1448 }
1449 let overlay = property_overlay_with_children(
1450 raw,
1451 local_name,
1452 schema_children,
1453 modelled_children,
1454 word_prefixes,
1455 )?;
1456 let mut projected_children = Vec::new();
1457 for (position, child) in &overlay.modelled {
1458 if let Some(projected) = producer_only_property(
1459 child,
1460 &prefixes,
1461 kind,
1462 output_word_prefix,
1463 depth + 1,
1464 preservation_prefixes,
1465 )? {
1466 projected_children.push((*position, projected));
1467 }
1468 }
1469 has_producer |= !overlay.extras.is_empty() || !projected_children.is_empty();
1470 if !has_producer {
1471 return Ok(None);
1472 }
1473
1474 let end_name = qualified(output_word_prefix, local_name);
1475 let mut writer = Writer::new(Vec::new());
1476 if !projected_children.is_empty() {
1477 start = preservation_prefixes.mark_ignorable(start, &namespace_bindings(word_prefixes))?;
1478 }
1479 if overlay.extras.is_empty() && projected_children.is_empty() {
1480 writer.write_event(Event::Empty(start))?;
1481 return Ok(Some(writer.into_inner()));
1482 }
1483 writer.write_event(Event::Start(start))?;
1484 for boundary in 0..=schema_children.len() {
1485 for (_, extra) in overlay
1486 .extras
1487 .iter()
1488 .filter(|(position, _)| *position == boundary)
1489 {
1490 writer.get_mut().extend_from_slice(extra);
1491 }
1492 for (_, projected) in projected_children
1493 .iter()
1494 .filter(|(position, _)| *position == boundary)
1495 {
1496 writer.get_mut().extend_from_slice(projected);
1497 }
1498 }
1499 writer.write_event(Event::End(BytesEnd::new(end_name)))?;
1500 Ok(Some(writer.into_inner()))
1501}
1502
1503struct OccurrenceOverlay {
1504 word_prefixes: Vec<String>,
1505 modelled: Vec<Vec<u8>>,
1506 extras: Vec<(usize, Vec<u8>)>,
1507}
1508
1509fn occurrence_overlay(raw: &[u8], word_prefixes: &[String]) -> Result<OccurrenceOverlay> {
1510 let mut reader = Reader::from_reader(raw);
1511 reader.config_mut().trim_text(true);
1512 let mut buf = Vec::new();
1513 let start = loop {
1514 match reader.read_event_into(&mut buf) {
1515 Ok(Event::Start(element)) | Ok(Event::Empty(element)) => break element.into_owned(),
1516 Ok(Event::Eof) => {
1517 return Err(OxmlError::InvalidValue(
1518 "repeated property contains no element".to_string(),
1519 ));
1520 }
1521 Err(error) => return Err(error.into()),
1522 _ => {}
1523 }
1524 buf.clear();
1525 };
1526 let prefixes = word_prefixes_at(&start, word_prefixes)?;
1527 let mut modelled = Vec::new();
1528 let mut extras = Vec::new();
1529 let mut pending = Vec::new();
1530 loop {
1531 buf.clear();
1532 match reader.read_event_into(&mut buf) {
1533 Ok(Event::Start(ref element)) => {
1534 let child_prefixes = word_prefixes_at(element, &prefixes)?;
1535 if is_word_element(element.name().as_ref(), b"tab", &child_prefixes) {
1536 extras.extend(pending.drain(..).map(|raw| (modelled.len(), raw)));
1537 modelled.push(capture_element(&mut reader, element)?);
1538 } else {
1539 pending.push(capture_element(&mut reader, element)?);
1540 }
1541 }
1542 Ok(Event::Empty(ref element)) => {
1543 let child_prefixes = word_prefixes_at(element, &prefixes)?;
1544 if is_word_element(element.name().as_ref(), b"tab", &child_prefixes) {
1545 extras.extend(pending.drain(..).map(|raw| (modelled.len(), raw)));
1546 modelled.push(capture_empty_element(element)?);
1547 } else {
1548 pending.push(capture_empty_element(element)?);
1549 }
1550 }
1551 Ok(Event::End(_)) | Ok(Event::Eof) => {
1552 extras.extend(pending.drain(..).map(|raw| (modelled.len(), raw)));
1553 break;
1554 }
1555 Err(error) => return Err(error.into()),
1556 _ => {}
1557 }
1558 }
1559 Ok(OccurrenceOverlay {
1560 word_prefixes: prefixes,
1561 modelled,
1562 extras,
1563 })
1564}
1565
1566fn producer_only_occurrence_property(
1567 raw: &[u8],
1568 word_prefixes: &[String],
1569 kind: PropertyKind,
1570 output_word_prefix: &str,
1571 depth: usize,
1572 preservation_prefixes: &PreservationPrefixes,
1573) -> Result<Option<Vec<u8>>> {
1574 if depth >= MAX_PROPERTY_XML_DEPTH {
1575 return Err(OxmlError::InvalidValue(format!(
1576 "property XML depth exceeds {MAX_PROPERTY_XML_DEPTH}"
1577 )));
1578 }
1579 let (original_start, _) = first_element_start(raw)?;
1580 let local = original_start.local_name();
1581 let local_name = std::str::from_utf8(local.as_ref())?;
1582 let prefixes = word_prefixes_at(&original_start, word_prefixes)?;
1583 let supported = property_attributes(kind, local.as_ref());
1584 let mut start = BytesStart::new(qualified(output_word_prefix, local_name));
1585 let mut has_producer = false;
1586 for attribute in original_start.attributes() {
1587 let attribute = attribute?;
1588 let key = attribute.key.as_ref();
1589 let attribute_local = key.rsplit(|byte| *byte == b':').next().unwrap_or(key);
1590 let namespace = key == b"xmlns" || key.starts_with(b"xmlns:");
1591 let producer = !namespace
1592 && (!is_word_property_attribute(key, &prefixes)
1593 || !supported.contains(&attribute_local));
1594 if namespace || producer {
1595 let name = std::str::from_utf8(key)?.to_string();
1596 let value = attribute
1597 .decoded_and_normalized_value(XmlVersion::Implicit1_0, original_start.decoder())?
1598 .into_owned();
1599 start.push_attribute((name.as_str(), value.as_str()));
1600 has_producer |= producer;
1601 }
1602 }
1603 let overlay = occurrence_overlay(raw, word_prefixes)?;
1604 let mut projected = Vec::with_capacity(overlay.modelled.len());
1605 for child in &overlay.modelled {
1606 projected.push(producer_leaf_payload(
1607 child,
1608 &overlay.word_prefixes,
1609 kind,
1610 preservation_prefixes,
1611 )?);
1612 }
1613 has_producer |= !overlay.extras.is_empty() || projected.iter().any(Option::is_some);
1614 if !has_producer {
1615 return Ok(None);
1616 }
1617 let end_name = qualified(output_word_prefix, local_name);
1618 let mut writer = Writer::new(Vec::new());
1619 if projected.iter().any(Option::is_some) {
1620 start = preservation_prefixes.mark_ignorable(start, &namespace_bindings(word_prefixes))?;
1621 }
1622 writer.write_event(Event::Start(start))?;
1623 let mut extras_by_boundary = vec![Vec::new(); overlay.modelled.len() + 1];
1624 for (boundary, raw) in &overlay.extras {
1625 extras_by_boundary[*boundary].push(raw.as_slice());
1626 }
1627 for (boundary, extras) in extras_by_boundary.iter().enumerate() {
1628 for raw in extras {
1629 writer.get_mut().extend_from_slice(raw);
1630 }
1631 if let Some(Some(payload)) = projected.get(boundary) {
1632 writer.get_mut().extend_from_slice(payload);
1633 }
1634 }
1635 writer.write_event(Event::End(BytesEnd::new(end_name)))?;
1636 Ok(Some(writer.into_inner()))
1637}
1638
1639fn match_occurrences(
1640 original_len: usize,
1641 generated_sources: &[Option<usize>],
1642) -> (Vec<Option<usize>>, usize) {
1643 let mut used = vec![false; original_len];
1644 let matches = generated_sources
1645 .iter()
1646 .map(|source| {
1647 let source = source.filter(|index| *index < original_len && !used[*index]);
1648 if let Some(index) = source {
1649 used[index] = true;
1650 }
1651 source
1652 })
1653 .collect();
1654 let work = original_len + generated_sources.len();
1655 (matches, work)
1656}
1657
1658fn merge_repeated_property(
1659 original: &[u8],
1660 generated: &[u8],
1661 word_prefixes: &[String],
1662 kind: PropertyKind,
1663 depth: usize,
1664 generated_sources: Option<&[Option<usize>]>,
1665 preservation_prefixes: &PreservationPrefixes,
1666) -> Result<Vec<u8>> {
1667 Ok(merge_repeated_property_with_work(
1668 original,
1669 generated,
1670 word_prefixes,
1671 kind,
1672 depth,
1673 generated_sources,
1674 preservation_prefixes,
1675 )?
1676 .0)
1677}
1678
1679fn merge_repeated_property_with_work(
1680 original: &[u8],
1681 generated: &[u8],
1682 word_prefixes: &[String],
1683 kind: PropertyKind,
1684 depth: usize,
1685 generated_sources: Option<&[Option<usize>]>,
1686 preservation_prefixes: &PreservationPrefixes,
1687) -> Result<(Vec<u8>, usize)> {
1688 let overlay = occurrence_overlay(original, word_prefixes)?;
1689 let merged_generated =
1690 merge_property_child_attributes(original, generated, word_prefixes, kind)?;
1691 let (merged_start, _) = first_element_start(&merged_generated)?;
1692 let generated_tabs = generated_children(generated, TABS_CHILDREN)?;
1693 let default_sources = vec![None; generated_tabs.len()];
1694 let generated_sources = generated_sources.unwrap_or(&default_sources);
1695 let (matches, mut work) = match_occurrences(overlay.modelled.len(), generated_sources);
1696 let mut extras_by_boundary = vec![Vec::new(); overlay.modelled.len() + 1];
1697 for (boundary, raw) in &overlay.extras {
1698 extras_by_boundary[*boundary].push(raw.as_slice());
1699 work += 1;
1700 }
1701 let mut writer = Writer::new(Vec::new());
1702 let end_name = std::str::from_utf8(merged_start.name().as_ref())?.to_string();
1703 writer.write_event(Event::Start(merged_start))?;
1704 let mut next_boundary = 0;
1705 for ((_, generated_tab), original_index) in generated_tabs.iter().zip(matches) {
1706 work += 1;
1707 if let Some(index) = original_index {
1708 while next_boundary <= index {
1709 for raw in &extras_by_boundary[next_boundary] {
1710 writer.get_mut().extend_from_slice(raw);
1711 work += 1;
1712 }
1713 next_boundary += 1;
1714 work += 1;
1715 }
1716 let merged = merge_property_child(
1717 &overlay.modelled[index],
1718 generated_tab,
1719 &overlay.word_prefixes,
1720 kind,
1721 depth + 1,
1722 None,
1723 preservation_prefixes,
1724 )?;
1725 write_xml_events(&mut writer, &merged)?;
1726 } else {
1727 write_xml_events(&mut writer, generated_tab)?;
1728 }
1729 }
1730 while next_boundary < extras_by_boundary.len() {
1731 for raw in &extras_by_boundary[next_boundary] {
1732 writer.get_mut().extend_from_slice(raw);
1733 work += 1;
1734 }
1735 next_boundary += 1;
1736 work += 1;
1737 }
1738 writer.write_event(Event::End(BytesEnd::new(end_name)))?;
1739 debug_assert!(
1740 work <= 4 * (overlay.modelled.len() + generated_tabs.len() + overlay.extras.len() + 1)
1741 );
1742 Ok((writer.into_inner(), work))
1743}
1744
1745fn merge_property_child(
1746 original: &[u8],
1747 generated: &[u8],
1748 word_prefixes: &[String],
1749 kind: PropertyKind,
1750 depth: usize,
1751 tab_sources: Option<&[Option<usize>]>,
1752 preservation_prefixes: &PreservationPrefixes,
1753) -> Result<Vec<u8>> {
1754 if depth >= MAX_PROPERTY_XML_DEPTH {
1755 return Err(OxmlError::InvalidValue(format!(
1756 "property XML depth exceeds {MAX_PROPERTY_XML_DEPTH}"
1757 )));
1758 }
1759
1760 let (original_start, _) = first_element_start(original)?;
1761 let original_prefixes = word_prefixes_at(&original_start, word_prefixes)?;
1762 let local = original_start.local_name();
1763 if local.as_ref() == b"tabs" {
1764 return merge_repeated_property(
1765 original,
1766 generated,
1767 word_prefixes,
1768 kind,
1769 depth,
1770 tab_sources,
1771 preservation_prefixes,
1772 );
1773 }
1774 let schema_children = nested_property_schema(local.as_ref());
1775 if schema_children.is_empty() {
1776 return merge_property_child_attributes(original, generated, word_prefixes, kind);
1777 }
1778
1779 let modelled_children = nested_property_children(local.as_ref());
1780 let local_name = std::str::from_utf8(local.as_ref())?;
1781 let overlay = property_overlay_with_children(
1782 original,
1783 local_name,
1784 schema_children,
1785 modelled_children,
1786 word_prefixes,
1787 )?;
1788 let merged_generated =
1789 merge_property_child_attributes(original, generated, word_prefixes, kind)?;
1790 let (merged_start, _) = first_element_start(&merged_generated)?;
1791 let output_word_prefix = element_prefix(&merged_start)?;
1792 let end_name = std::str::from_utf8(merged_start.name().as_ref())?.to_string();
1793 let children = generated_children(generated, schema_children)?;
1794 let mut used_original = vec![false; overlay.modelled.len()];
1795 let matches = children
1796 .iter()
1797 .map(|(position, _)| {
1798 let index = overlay
1799 .modelled
1800 .iter()
1801 .enumerate()
1802 .find(|(index, (original_position, _))| {
1803 !used_original[*index] && original_position == position
1804 })
1805 .map(|(index, _)| index);
1806 if let Some(index) = index {
1807 used_original[index] = true;
1808 }
1809 index
1810 })
1811 .collect::<Vec<_>>();
1812 let mut retained = Vec::new();
1813 for (index, (position, raw)) in overlay.modelled.iter().enumerate() {
1814 if !used_original[index]
1815 && let Some(projected) = producer_only_property(
1816 raw,
1817 &original_prefixes,
1818 kind,
1819 &output_word_prefix,
1820 depth + 1,
1821 preservation_prefixes,
1822 )?
1823 {
1824 retained.push((*position, projected));
1825 }
1826 }
1827 let mut writer = Writer::new(Vec::new());
1828 writer.write_event(Event::Start(merged_start))?;
1829 let mut next_boundary = 0;
1830 for ((position, child), original_index) in children.iter().zip(matches) {
1831 for boundary in next_boundary..=*position {
1832 write_extras_at(&mut writer, &overlay.extras, boundary)?;
1833 for (_, projected) in retained
1834 .iter()
1835 .filter(|(retained_position, _)| *retained_position == boundary)
1836 {
1837 writer.get_mut().extend_from_slice(projected);
1838 }
1839 }
1840 if let Some(index) = original_index {
1841 let merged = merge_property_child(
1842 &overlay.modelled[index].1,
1843 child,
1844 &original_prefixes,
1845 kind,
1846 depth + 1,
1847 tab_sources,
1848 preservation_prefixes,
1849 )?;
1850 write_xml_events(&mut writer, &merged)?;
1851 } else {
1852 write_xml_events(&mut writer, child)?;
1853 }
1854 next_boundary = position + 1;
1855 }
1856 for boundary in next_boundary..=schema_children.len() {
1857 write_extras_at(&mut writer, &overlay.extras, boundary)?;
1858 for (_, projected) in retained
1859 .iter()
1860 .filter(|(retained_position, _)| *retained_position == boundary)
1861 {
1862 writer.get_mut().extend_from_slice(projected);
1863 }
1864 }
1865 writer.write_event(Event::End(BytesEnd::new(end_name)))?;
1866 Ok(writer.into_inner())
1867}
1868
1869fn write_level_property<W: std::io::Write>(
1870 writer: &mut Writer<W>,
1871 property: LevelProperty<'_>,
1872 preservation: Option<(LevelProperty<'_>, &[u8], &[String])>,
1873 word_prefix: &str,
1874 preservation_prefixes: &PreservationPrefixes,
1875) -> Result<()> {
1876 let tab_sources = property.tab_sources();
1877 let Some((original, raw, original_prefixes)) = preservation else {
1878 let generated = rewrite_generated_prefix(&property.canonical_xml()?, word_prefix)?;
1879 write_xml_events(writer, &generated)?;
1880 return Ok(());
1881 };
1882
1883 let overlay = property_overlay(raw, property.kind(), original_prefixes)?;
1884 let generated = rewrite_generated_prefix(&property.canonical_xml()?, word_prefix)?;
1885 let children = generated_property_children(&generated, property.kind())?;
1886 let original_generated = rewrite_generated_prefix(&original.canonical_xml()?, word_prefix)?;
1887 let original_children = generated_property_children(&original_generated, property.kind())?;
1888 let tab_provenance_unchanged = tab_sources == original.tab_sources();
1889 let mut used_original = vec![false; overlay.modelled.len()];
1890 let matches = children
1891 .iter()
1892 .map(|(position, _)| {
1893 let index = overlay
1894 .modelled
1895 .iter()
1896 .enumerate()
1897 .find(|(index, (original_position, _))| {
1898 !used_original[*index] && original_position == position
1899 })
1900 .map(|(index, _)| index);
1901 if let Some(index) = index {
1902 used_original[index] = true;
1903 }
1904 index
1905 })
1906 .collect::<Vec<_>>();
1907 let mut retained = Vec::new();
1908 for (index, (position, raw)) in overlay.modelled.iter().enumerate() {
1909 if !used_original[index]
1910 && let Some(projected) = producer_only_property(
1911 raw,
1912 original_prefixes,
1913 property.kind(),
1914 word_prefix,
1915 0,
1916 preservation_prefixes,
1917 )?
1918 {
1919 retained.push((*position, projected));
1920 }
1921 }
1922 writer.write_event(Event::Start(overlay.start))?;
1923 let mut next_boundary = 0;
1924 for ((position, child), original_index) in children.iter().zip(matches) {
1925 for boundary in next_boundary..=*position {
1926 write_extras_at(writer, &overlay.extras, boundary)?;
1927 for (_, projected) in retained
1928 .iter()
1929 .filter(|(retained_position, _)| *retained_position == boundary)
1930 {
1931 write_xml_events(writer, projected)?;
1932 }
1933 }
1934 let original_raw = original_index.map(|index| overlay.modelled[index].1.as_slice());
1935 let original_canonical = original_children
1936 .iter()
1937 .find(|(original_position, _)| original_position == position)
1938 .map(|(_, raw)| raw.as_slice());
1939 let child_is_tabs = first_element_start(child)?.0.local_name().as_ref() == b"tabs";
1940 if let Some(original_raw) = original_raw
1941 && original_canonical == Some(child.as_slice())
1942 && (!child_is_tabs || tab_provenance_unchanged)
1943 {
1944 writer.get_mut().write_all(original_raw)?;
1945 } else if let Some(original_raw) = original_raw {
1946 let merged = merge_property_child(
1947 original_raw,
1948 child,
1949 original_prefixes,
1950 property.kind(),
1951 0,
1952 tab_sources.as_deref(),
1953 preservation_prefixes,
1954 )?;
1955 write_xml_events(writer, &merged)?;
1956 } else {
1957 write_xml_events(writer, child)?;
1958 }
1959 next_boundary = position + 1;
1960 }
1961 for boundary in next_boundary..=property.kind().children().len() {
1962 write_extras_at(writer, &overlay.extras, boundary)?;
1963 for (_, projected) in retained
1964 .iter()
1965 .filter(|(retained_position, _)| *retained_position == boundary)
1966 {
1967 write_xml_events(writer, projected)?;
1968 }
1969 }
1970 writer.write_event(Event::End(BytesEnd::new(overlay.end_name)))?;
1971 Ok(())
1972}
1973
1974#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1976pub enum ST_NumberFormat {
1977 Decimal,
1978 UpperRoman,
1979 LowerRoman,
1980 UpperLetter,
1981 LowerLetter,
1982 Ordinal,
1983 Bullet,
1984 None,
1985}
1986
1987impl ST_NumberFormat {
1988 pub fn from_str(s: &str) -> Self {
1989 match s {
1990 "decimal" => Self::Decimal,
1991 "upperRoman" => Self::UpperRoman,
1992 "lowerRoman" => Self::LowerRoman,
1993 "upperLetter" => Self::UpperLetter,
1994 "lowerLetter" => Self::LowerLetter,
1995 "ordinal" => Self::Ordinal,
1996 "bullet" => Self::Bullet,
1997 "none" => Self::None,
1998 _ => Self::Decimal,
1999 }
2000 }
2001
2002 pub fn to_str(self) -> &'static str {
2003 match self {
2004 Self::Decimal => "decimal",
2005 Self::UpperRoman => "upperRoman",
2006 Self::LowerRoman => "lowerRoman",
2007 Self::UpperLetter => "upperLetter",
2008 Self::LowerLetter => "lowerLetter",
2009 Self::Ordinal => "ordinal",
2010 Self::Bullet => "bullet",
2011 Self::None => "none",
2012 }
2013 }
2014}
2015
2016#[derive(Debug, Clone, PartialEq)]
2018pub struct CT_Lvl {
2019 pub ilvl: u32,
2021 pub start: Option<u32>,
2023 pub num_fmt: Option<ST_NumberFormat>,
2025 pub lvl_text: Option<String>,
2027 pub lvl_jc: Option<ST_Jc>,
2029 pub ppr: Option<CT_PPr>,
2031 pub rpr: Option<CT_RPr>,
2033 pub extra_xml: Vec<(usize, Vec<u8>)>,
2035 pub extra_attributes: Vec<(String, String)>,
2037 pub ppr_raw: Option<(CT_PPr, Vec<u8>, Vec<String>)>,
2039 pub rpr_raw: Option<(CT_RPr, Vec<u8>, Vec<String>)>,
2041}
2042
2043#[allow(non_snake_case)]
2044impl CT_Lvl {
2045 pub fn new(ilvl: u32) -> Self {
2046 CT_Lvl {
2047 ilvl,
2048 start: None,
2049 num_fmt: None,
2050 lvl_text: None,
2051 lvl_jc: None,
2052 ppr: None,
2053 rpr: None,
2054 extra_xml: Vec::new(),
2055 extra_attributes: Vec::new(),
2056 ppr_raw: None,
2057 rpr_raw: None,
2058 }
2059 }
2060
2061 pub fn from_xml(reader: &mut Reader<&[u8]>, ilvl: u32) -> Result<Self> {
2062 Self::from_xml_with_prefixes(reader, ilvl, &["w".to_string()])
2063 }
2064
2065 fn from_xml_with_prefixes(
2066 reader: &mut Reader<&[u8]>,
2067 ilvl: u32,
2068 word_prefixes: &[String],
2069 ) -> Result<Self> {
2070 let mut lvl = CT_Lvl::new(ilvl);
2071 let mut buf = Vec::new();
2072 let mut boundary = 0;
2073
2074 loop {
2075 match reader.read_event_into(&mut buf) {
2076 Ok(Event::Start(ref e)) => {
2077 let name = e.name();
2078 let prefixes = word_prefixes_at(e, word_prefixes)?;
2079 if is_word_element(name.as_ref(), b"start", &prefixes) {
2080 if let Some(value) = word_attribute_value(e, b"val", &prefixes)? {
2081 lvl.start = Some(value.parse()?);
2082 }
2083 reader.read_to_end_into(name, &mut Vec::new())?;
2084 boundary = 1;
2085 } else if is_word_element(name.as_ref(), b"numFmt", &prefixes) {
2086 if let Some(value) = word_attribute_value(e, b"val", &prefixes)? {
2087 lvl.num_fmt = Some(ST_NumberFormat::from_str(&value));
2088 }
2089 reader.read_to_end_into(name, &mut Vec::new())?;
2090 boundary = 2;
2091 } else if is_word_element(name.as_ref(), b"lvlText", &prefixes) {
2092 lvl.lvl_text = word_attribute_value(e, b"val", &prefixes)?;
2093 reader.read_to_end_into(name, &mut Vec::new())?;
2094 boundary = 7;
2095 } else if is_word_element(name.as_ref(), b"lvlJc", &prefixes) {
2096 if let Some(value) = word_attribute_value(e, b"val", &prefixes)? {
2097 lvl.lvl_jc = Some(ST_Jc::from_str(&value)?);
2098 }
2099 reader.read_to_end_into(name, &mut Vec::new())?;
2100 boundary = 10;
2101 } else if is_word_element(name.as_ref(), b"pPr", &prefixes) {
2102 let raw = capture_element(reader, e)?;
2103 let (ppr, has_producer) = ppr_from_raw(&raw, &prefixes)?;
2104 lvl.ppr = Some(ppr.clone());
2105 if has_producer {
2106 lvl.ppr_raw = Some((ppr, raw, prefixes));
2107 }
2108 boundary = 11;
2109 } else if is_word_element(name.as_ref(), b"rPr", &prefixes) {
2110 let raw = capture_element(reader, e)?;
2111 let (rpr, has_producer) = rpr_from_raw(&raw, &prefixes)?;
2112 lvl.rpr = Some(rpr.clone());
2113 if has_producer {
2114 lvl.rpr_raw = Some((rpr, raw, prefixes));
2115 }
2116 boundary = 12;
2117 } else {
2118 let (at, next) = level_raw_boundary(name.as_ref(), boundary, &prefixes);
2119 lvl.extra_xml.push((at, capture_element(reader, e)?));
2120 boundary = next;
2121 }
2122 }
2123 Ok(Event::Empty(ref e)) => {
2124 let name = e.name();
2125 let prefixes = word_prefixes_at(e, word_prefixes)?;
2126 if is_word_element(name.as_ref(), b"start", &prefixes) {
2127 if let Some(val) = word_attribute_value(e, b"val", &prefixes)? {
2128 lvl.start = Some(val.parse()?);
2129 }
2130 boundary = 1;
2131 } else if is_word_element(name.as_ref(), b"numFmt", &prefixes) {
2132 if let Some(val) = word_attribute_value(e, b"val", &prefixes)? {
2133 lvl.num_fmt = Some(ST_NumberFormat::from_str(&val));
2134 }
2135 boundary = 2;
2136 } else if is_word_element(name.as_ref(), b"lvlText", &prefixes) {
2137 lvl.lvl_text = word_attribute_value(e, b"val", &prefixes)?;
2138 boundary = 7;
2139 } else if is_word_element(name.as_ref(), b"lvlJc", &prefixes)
2140 && let Some(val) = word_attribute_value(e, b"val", &prefixes)?
2141 {
2142 lvl.lvl_jc = Some(ST_Jc::from_str(&val)?);
2143 boundary = 10;
2144 } else if is_word_element(name.as_ref(), b"pPr", &prefixes) {
2145 let raw = capture_empty_element(e)?;
2146 let (ppr, has_producer) = ppr_from_raw(&raw, &prefixes)?;
2147 lvl.ppr = Some(ppr.clone());
2148 if has_producer {
2149 lvl.ppr_raw = Some((ppr, raw, prefixes));
2150 }
2151 boundary = 11;
2152 } else if is_word_element(name.as_ref(), b"rPr", &prefixes) {
2153 let raw = capture_empty_element(e)?;
2154 let (rpr, has_producer) = rpr_from_raw(&raw, &prefixes)?;
2155 lvl.rpr = Some(rpr.clone());
2156 if has_producer {
2157 lvl.rpr_raw = Some((rpr, raw, prefixes));
2158 }
2159 boundary = 12;
2160 } else {
2161 let (at, next) = level_raw_boundary(name.as_ref(), boundary, &prefixes);
2162 lvl.extra_xml.push((at, capture_empty_element(e)?));
2163 boundary = next;
2164 }
2165 }
2166 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"lvl") => {
2167 break;
2168 }
2169 Ok(Event::Eof) => break,
2170 Err(e) => return Err(e.into()),
2171 _ => {}
2172 }
2173 buf.clear();
2174 }
2175
2176 Ok(lvl)
2177 }
2178
2179 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2180 let preservation_prefixes = PreservationPrefixes::new(&[]);
2181 self.to_xml_with_prefix(writer, "w", &preservation_prefixes)
2182 }
2183
2184 fn to_xml_with_prefix<W: std::io::Write>(
2185 &self,
2186 writer: &mut Writer<W>,
2187 word_prefix: &str,
2188 preservation_prefixes: &PreservationPrefixes,
2189 ) -> Result<()> {
2190 let mut buf = itoa::Buffer::new();
2191 let level_name = qualified(word_prefix, "lvl");
2192 let ilvl_name = qualified(word_prefix, "ilvl");
2193 let mut start = BytesStart::new(level_name.as_str());
2194 start.push_attribute((ilvl_name.as_str(), buf.format(self.ilvl)));
2195 push_extra_attributes(&mut start, &self.extra_attributes);
2196 writer.write_event(Event::Start(start))?;
2197
2198 write_extras_at(writer, &self.extra_xml, 0)?;
2199 if let Some(s) = self.start {
2200 let name = qualified(word_prefix, "start");
2201 let val_name = qualified(word_prefix, "val");
2202 let mut e = BytesStart::new(name.as_str());
2203 e.push_attribute((val_name.as_str(), buf.format(s)));
2204 writer.write_event(Event::Empty(e))?;
2205 }
2206
2207 write_extras_at(writer, &self.extra_xml, 1)?;
2208 if let Some(fmt) = self.num_fmt {
2209 let name = qualified(word_prefix, "numFmt");
2210 let val_name = qualified(word_prefix, "val");
2211 let mut e = BytesStart::new(name.as_str());
2212 e.push_attribute((val_name.as_str(), fmt.to_str()));
2213 writer.write_event(Event::Empty(e))?;
2214 }
2215
2216 for boundary in 2..=6 {
2217 write_extras_at(writer, &self.extra_xml, boundary)?;
2218 }
2219 if let Some(ref text) = self.lvl_text {
2220 let name = qualified(word_prefix, "lvlText");
2221 let val_name = qualified(word_prefix, "val");
2222 let mut e = BytesStart::new(name.as_str());
2223 e.push_attribute((val_name.as_str(), text.as_str()));
2224 writer.write_event(Event::Empty(e))?;
2225 }
2226
2227 for boundary in 7..=9 {
2228 write_extras_at(writer, &self.extra_xml, boundary)?;
2229 }
2230 if let Some(jc) = self.lvl_jc {
2231 let name = qualified(word_prefix, "lvlJc");
2232 let val_name = qualified(word_prefix, "val");
2233 let mut e = BytesStart::new(name.as_str());
2234 e.push_attribute((val_name.as_str(), jc.to_str()));
2235 writer.write_event(Event::Empty(e))?;
2236 }
2237
2238 write_extras_at(writer, &self.extra_xml, 10)?;
2239 if let Some(ref ppr) = self.ppr {
2240 if let Some((original, raw, _)) = &self.ppr_raw
2241 && paragraph_preservation_eq(ppr, original)
2242 {
2243 writer.get_mut().write_all(raw)?;
2244 } else {
2245 write_level_property(
2246 writer,
2247 LevelProperty::Paragraph(ppr),
2248 self.ppr_raw.as_ref().map(|(original, raw, prefixes)| {
2249 (
2250 LevelProperty::Paragraph(original),
2251 raw.as_slice(),
2252 prefixes.as_slice(),
2253 )
2254 }),
2255 word_prefix,
2256 preservation_prefixes,
2257 )?;
2258 }
2259 } else if let Some((_, raw, prefixes)) = &self.ppr_raw
2260 && let Some(projected) = producer_only_property(
2261 raw,
2262 prefixes,
2263 PropertyKind::Paragraph,
2264 word_prefix,
2265 0,
2266 preservation_prefixes,
2267 )?
2268 {
2269 write_xml_events(writer, &projected)?;
2270 }
2271
2272 write_extras_at(writer, &self.extra_xml, 11)?;
2273 if let Some(ref rpr) = self.rpr {
2274 if let Some((original, raw, _)) = &self.rpr_raw
2275 && rpr == original
2276 {
2277 writer.get_mut().write_all(raw)?;
2278 } else {
2279 write_level_property(
2280 writer,
2281 LevelProperty::Run(rpr),
2282 self.rpr_raw.as_ref().map(|(original, raw, prefixes)| {
2283 (
2284 LevelProperty::Run(original),
2285 raw.as_slice(),
2286 prefixes.as_slice(),
2287 )
2288 }),
2289 word_prefix,
2290 preservation_prefixes,
2291 )?;
2292 }
2293 } else if let Some((_, raw, prefixes)) = &self.rpr_raw
2294 && let Some(projected) = producer_only_property(
2295 raw,
2296 prefixes,
2297 PropertyKind::Run,
2298 word_prefix,
2299 0,
2300 preservation_prefixes,
2301 )?
2302 {
2303 write_xml_events(writer, &projected)?;
2304 }
2305
2306 write_extras_at(writer, &self.extra_xml, 12)?;
2307
2308 writer.write_event(Event::End(BytesEnd::new(level_name)))?;
2309 Ok(())
2310 }
2311}
2312
2313#[derive(Debug, Clone, PartialEq)]
2315pub struct CT_AbstractNum {
2316 pub abstract_num_id: u32,
2317 pub levels: Vec<CT_Lvl>,
2318 pub multi_level_type: Option<String>,
2320 pub extra_xml: Vec<(usize, Vec<u8>)>,
2322 pub extra_attributes: Vec<(String, String)>,
2324}
2325
2326#[allow(non_snake_case)]
2327impl CT_AbstractNum {
2328 pub fn new(id: u32) -> Self {
2329 CT_AbstractNum {
2330 abstract_num_id: id,
2331 levels: Vec::new(),
2332 multi_level_type: None,
2333 extra_xml: Vec::new(),
2334 extra_attributes: Vec::new(),
2335 }
2336 }
2337
2338 pub fn from_xml(reader: &mut Reader<&[u8]>, abstract_num_id: u32) -> Result<Self> {
2339 Self::from_xml_with_prefixes(reader, abstract_num_id, &["w".to_string()])
2340 }
2341
2342 fn from_xml_with_prefixes(
2343 reader: &mut Reader<&[u8]>,
2344 abstract_num_id: u32,
2345 word_prefixes: &[String],
2346 ) -> Result<Self> {
2347 let mut abs = CT_AbstractNum::new(abstract_num_id);
2348 let mut buf = Vec::new();
2349 let mut boundary = 0;
2350
2351 loop {
2352 match reader.read_event_into(&mut buf) {
2353 Ok(Event::Start(ref e)) => {
2354 let name = e.name();
2355 let prefixes = word_prefixes_at(e, word_prefixes)?;
2356 if is_word_element(name.as_ref(), b"multiLevelType", &prefixes) {
2357 abs.multi_level_type = word_attribute_value(e, b"val", &prefixes)?;
2358 reader.read_to_end_into(name, &mut Vec::new())?;
2359 boundary = 2;
2360 } else if is_word_element(name.as_ref(), b"lvl", &prefixes) {
2361 let ilvl = u32_attribute(e, b"ilvl", &prefixes)?;
2362 let mut level = CT_Lvl::from_xml_with_prefixes(reader, ilvl, &prefixes)?;
2363 level.extra_attributes =
2364 capture_extra_attributes(e, &[b"ilvl"], &prefixes)?;
2365 abs.levels.push(level);
2366 boundary = 7 + abs.levels.len();
2367 } else {
2368 let (at, next) = abstract_raw_boundary(name.as_ref(), boundary, &prefixes);
2369 abs.extra_xml.push((at, capture_element(reader, e)?));
2370 boundary = next;
2371 }
2372 }
2373 Ok(Event::Empty(ref e)) => {
2374 let name = e.name();
2375 let prefixes = word_prefixes_at(e, word_prefixes)?;
2376 if is_word_element(name.as_ref(), b"multiLevelType", &prefixes) {
2377 abs.multi_level_type = word_attribute_value(e, b"val", &prefixes)?;
2378 boundary = 2;
2379 } else if is_word_element(name.as_ref(), b"lvl", &prefixes) {
2380 let mut level = CT_Lvl::new(u32_attribute(e, b"ilvl", &prefixes)?);
2381 level.extra_attributes =
2382 capture_extra_attributes(e, &[b"ilvl"], &prefixes)?;
2383 abs.levels.push(level);
2384 boundary = 7 + abs.levels.len();
2385 } else {
2386 let (at, next) = abstract_raw_boundary(name.as_ref(), boundary, &prefixes);
2387 abs.extra_xml.push((at, capture_empty_element(e)?));
2388 boundary = next;
2389 }
2390 }
2391 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"abstractNum") => {
2392 break;
2393 }
2394 Ok(Event::Eof) => break,
2395 Err(e) => return Err(e.into()),
2396 _ => {}
2397 }
2398 buf.clear();
2399 }
2400
2401 Ok(abs)
2402 }
2403
2404 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2405 let preservation_prefixes = PreservationPrefixes::new(&[]);
2406 self.to_xml_with_prefix(writer, "w", &preservation_prefixes)
2407 }
2408
2409 fn to_xml_with_prefix<W: std::io::Write>(
2410 &self,
2411 writer: &mut Writer<W>,
2412 word_prefix: &str,
2413 preservation_prefixes: &PreservationPrefixes,
2414 ) -> Result<()> {
2415 let mut buf = itoa::Buffer::new();
2416 let abstract_name = qualified(word_prefix, "abstractNum");
2417 let id_name = qualified(word_prefix, "abstractNumId");
2418 let mut start = BytesStart::new(abstract_name.as_str());
2419 start.push_attribute((id_name.as_str(), buf.format(self.abstract_num_id)));
2420 push_extra_attributes(&mut start, &self.extra_attributes);
2421 writer.write_event(Event::Start(start))?;
2422
2423 write_extras_at(writer, &self.extra_xml, 0)?;
2424 write_extras_at(writer, &self.extra_xml, 1)?;
2425 if let Some(ref mlt) = self.multi_level_type {
2426 let name = qualified(word_prefix, "multiLevelType");
2427 let val_name = qualified(word_prefix, "val");
2428 let mut e = BytesStart::new(name.as_str());
2429 e.push_attribute((val_name.as_str(), mlt.as_str()));
2430 writer.write_event(Event::Empty(e))?;
2431 }
2432
2433 for boundary in 2..=6 {
2434 write_extras_at(writer, &self.extra_xml, boundary)?;
2435 }
2436 for (index, lvl) in self.levels.iter().enumerate() {
2437 write_extras_at(writer, &self.extra_xml, 7 + index)?;
2438 lvl.to_xml_with_prefix(writer, word_prefix, preservation_prefixes)?;
2439 }
2440 write_extras_at(writer, &self.extra_xml, 7 + self.levels.len())?;
2441
2442 writer.write_event(Event::End(BytesEnd::new(abstract_name)))?;
2443 Ok(())
2444 }
2445}
2446
2447#[derive(Debug, Clone, PartialEq)]
2449pub struct CT_Num {
2450 pub num_id: u32,
2451 pub abstract_num_id: u32,
2452 pub extra_xml: Vec<(usize, Vec<u8>)>,
2454 pub extra_attributes: Vec<(String, String)>,
2456}
2457
2458#[allow(non_snake_case)]
2459impl CT_Num {
2460 pub fn from_xml(reader: &mut Reader<&[u8]>, num_id: u32) -> Result<Self> {
2461 Self::from_xml_with_prefixes(reader, num_id, &["w".to_string()])
2462 }
2463
2464 fn from_xml_with_prefixes(
2465 reader: &mut Reader<&[u8]>,
2466 num_id: u32,
2467 word_prefixes: &[String],
2468 ) -> Result<Self> {
2469 let mut abstract_num_id = 0;
2470 let mut extra_xml = Vec::new();
2471 let mut buf = Vec::new();
2472 let mut position = 0;
2473
2474 loop {
2475 match reader.read_event_into(&mut buf) {
2476 Ok(Event::Empty(ref e)) => {
2477 let prefixes = word_prefixes_at(e, word_prefixes)?;
2478 if is_word_element(e.name().as_ref(), b"abstractNumId", &prefixes)
2479 && let Some(val) = word_attribute_value(e, b"val", &prefixes)?
2480 {
2481 abstract_num_id = val.parse()?;
2482 position += 1;
2483 } else {
2484 extra_xml.push((position, capture_empty_element(e)?));
2485 }
2486 }
2487 Ok(Event::Start(ref e)) => {
2488 let prefixes = word_prefixes_at(e, word_prefixes)?;
2489 if is_word_element(e.name().as_ref(), b"abstractNumId", &prefixes) {
2490 if let Some(value) = word_attribute_value(e, b"val", &prefixes)? {
2491 abstract_num_id = value.parse()?;
2492 }
2493 reader.read_to_end_into(e.name(), &mut Vec::new())?;
2494 position = 1;
2495 } else {
2496 let raw = capture_element(reader, e)?;
2497 extra_xml.push((position, raw));
2498 }
2499 }
2500 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"num") => {
2501 break;
2502 }
2503 Ok(Event::Eof) => break,
2504 Err(e) => return Err(e.into()),
2505 _ => {}
2506 }
2507 buf.clear();
2508 }
2509
2510 Ok(CT_Num {
2511 num_id,
2512 abstract_num_id,
2513 extra_xml,
2514 extra_attributes: Vec::new(),
2515 })
2516 }
2517
2518 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2519 self.to_xml_with_prefix(writer, "w")
2520 }
2521
2522 fn to_xml_with_prefix<W: std::io::Write>(
2523 &self,
2524 writer: &mut Writer<W>,
2525 word_prefix: &str,
2526 ) -> Result<()> {
2527 let mut buf = itoa::Buffer::new();
2528 let num_name = qualified(word_prefix, "num");
2529 let num_id_name = qualified(word_prefix, "numId");
2530 let mut start = BytesStart::new(num_name.as_str());
2531 start.push_attribute((num_id_name.as_str(), buf.format(self.num_id)));
2532 push_extra_attributes(&mut start, &self.extra_attributes);
2533 writer.write_event(Event::Start(start))?;
2534
2535 write_extras_at(writer, &self.extra_xml, 0)?;
2536 let abs_name = qualified(word_prefix, "abstractNumId");
2537 let val_name = qualified(word_prefix, "val");
2538 let mut abs_ref = BytesStart::new(abs_name.as_str());
2539 abs_ref.push_attribute((val_name.as_str(), buf.format(self.abstract_num_id)));
2540 writer.write_event(Event::Empty(abs_ref))?;
2541 write_extras_at(writer, &self.extra_xml, 1)?;
2542
2543 writer.write_event(Event::End(BytesEnd::new(num_name)))?;
2544 Ok(())
2545 }
2546}
2547
2548#[derive(Debug, Clone, PartialEq)]
2550pub struct CT_Numbering {
2551 pub abstract_nums: Vec<CT_AbstractNum>,
2552 pub nums: Vec<CT_Num>,
2553 pub root_attributes: Vec<(String, String)>,
2555 pub extra_xml: Vec<(usize, Vec<u8>)>,
2557}
2558
2559#[allow(non_snake_case)]
2560impl CT_Numbering {
2561 pub fn new() -> Self {
2562 CT_Numbering {
2563 abstract_nums: Vec::new(),
2564 nums: Vec::new(),
2565 root_attributes: Vec::new(),
2566 extra_xml: Vec::new(),
2567 }
2568 }
2569
2570 fn preserved_namespace_declarations(&self) -> Result<Vec<(String, String)>> {
2571 let mut declarations = Vec::new();
2572 append_namespace_declarations(&self.root_attributes, &mut declarations);
2573 for (_, raw) in &self.extra_xml {
2574 append_raw_namespace_declarations(raw, &mut declarations)?;
2575 }
2576 for abstract_num in &self.abstract_nums {
2577 append_namespace_declarations(&abstract_num.extra_attributes, &mut declarations);
2578 for (_, raw) in &abstract_num.extra_xml {
2579 append_raw_namespace_declarations(raw, &mut declarations)?;
2580 }
2581 for level in &abstract_num.levels {
2582 append_namespace_declarations(&level.extra_attributes, &mut declarations);
2583 for (_, raw) in &level.extra_xml {
2584 append_raw_namespace_declarations(raw, &mut declarations)?;
2585 }
2586 if let Some((_, raw, _)) = &level.ppr_raw {
2587 append_raw_namespace_declarations(raw, &mut declarations)?;
2588 }
2589 if let Some((_, raw, _)) = &level.rpr_raw {
2590 append_raw_namespace_declarations(raw, &mut declarations)?;
2591 }
2592 }
2593 }
2594 for num in &self.nums {
2595 append_namespace_declarations(&num.extra_attributes, &mut declarations);
2596 for (_, raw) in &num.extra_xml {
2597 append_raw_namespace_declarations(raw, &mut declarations)?;
2598 }
2599 }
2600 Ok(declarations)
2601 }
2602
2603 pub fn from_xml(xml: &[u8]) -> Result<Self> {
2605 let mut reader = Reader::from_reader(xml);
2606 reader.config_mut().trim_text(true);
2607
2608 let mut abstract_nums = Vec::new();
2609 let mut nums = Vec::new();
2610 let mut root_attributes = Vec::new();
2611 let mut extra_xml = Vec::new();
2612 let mut buf = Vec::new();
2613 let mut position = 0;
2614 let mut word_prefixes = Vec::new();
2615
2616 loop {
2617 match reader.read_event_into(&mut buf) {
2618 Ok(Event::Start(ref e)) => {
2619 let name = e.name();
2620 let prefixes = word_prefixes_at(e, &word_prefixes)?;
2621 if is_word_element(name.as_ref(), b"abstractNum", &prefixes) {
2622 let id = u32_attribute(e, b"abstractNumId", &prefixes)?;
2623 let mut abstract_num =
2624 CT_AbstractNum::from_xml_with_prefixes(&mut reader, id, &prefixes)?;
2625 abstract_num.extra_attributes =
2626 capture_extra_attributes(e, &[b"abstractNumId"], &prefixes)?;
2627 abstract_nums.push(abstract_num);
2628 position += 1;
2629 } else if is_word_element(name.as_ref(), b"num", &prefixes) {
2630 let id = u32_attribute(e, b"numId", &prefixes)?;
2631 let mut num = CT_Num::from_xml_with_prefixes(&mut reader, id, &prefixes)?;
2632 num.extra_attributes = capture_extra_attributes(e, &[b"numId"], &prefixes)?;
2633 nums.push(num);
2634 position += 1;
2635 } else if is_word_element(name.as_ref(), b"numbering", &prefixes) {
2636 root_attributes = capture_root_attributes(e)?;
2637 word_prefixes = prefixes;
2638 } else {
2639 extra_xml.push((
2640 root_raw_boundary(name.as_ref(), position, &prefixes),
2641 capture_element(&mut reader, e)?,
2642 ));
2643 }
2644 }
2645 Ok(Event::Empty(ref e)) => {
2646 let name = e.name();
2647 let prefixes = word_prefixes_at(e, &word_prefixes)?;
2648 if is_word_element(name.as_ref(), b"numbering", &prefixes) {
2649 root_attributes = capture_root_attributes(e)?;
2650 word_prefixes = prefixes;
2651 } else if is_word_element(name.as_ref(), b"abstractNum", &prefixes) {
2652 let mut abstract_num =
2653 CT_AbstractNum::new(u32_attribute(e, b"abstractNumId", &prefixes)?);
2654 abstract_num.extra_attributes =
2655 capture_extra_attributes(e, &[b"abstractNumId"], &prefixes)?;
2656 abstract_nums.push(abstract_num);
2657 position += 1;
2658 } else if is_word_element(name.as_ref(), b"num", &prefixes) {
2659 nums.push(CT_Num {
2660 num_id: u32_attribute(e, b"numId", &prefixes)?,
2661 abstract_num_id: 0,
2662 extra_xml: Vec::new(),
2663 extra_attributes: capture_extra_attributes(e, &[b"numId"], &prefixes)?,
2664 });
2665 position += 1;
2666 } else {
2667 extra_xml.push((
2668 root_raw_boundary(name.as_ref(), position, &prefixes),
2669 capture_empty_element(e)?,
2670 ));
2671 }
2672 }
2673 Ok(Event::Eof) => break,
2674 Err(e) => return Err(e.into()),
2675 _ => {}
2676 }
2677 buf.clear();
2678 }
2679
2680 Ok(CT_Numbering {
2681 abstract_nums,
2682 nums,
2683 root_attributes,
2684 extra_xml,
2685 })
2686 }
2687
2688 pub fn to_xml(&self) -> Result<Vec<u8>> {
2690 let mut writer = Writer::new_with_indent(Vec::new(), b' ', 2);
2691 let declarations = self.preserved_namespace_declarations()?;
2692 let word_prefix = generated_prefix(&declarations, "w", W_NS);
2693 let relationship_prefix = generated_prefix(&declarations, "r", R_NS);
2694 let preservation_prefixes = PreservationPrefixes::new(&declarations);
2695
2696 writer.write_event(Event::Decl(BytesDecl::new(
2697 "1.0",
2698 Some("UTF-8"),
2699 Some("yes"),
2700 )))?;
2701
2702 let root_name = qualified(&word_prefix, "numbering");
2703 let mut start = BytesStart::new(root_name.as_str());
2704 let word_declaration = format!("xmlns:{word_prefix}");
2705 if !has_namespace_declaration(&self.root_attributes, &word_prefix, W_NS) {
2706 start.push_attribute((word_declaration.as_str(), W_NS));
2707 }
2708 let relationship_declaration = format!("xmlns:{relationship_prefix}");
2709 if !has_namespace_declaration(&self.root_attributes, &relationship_prefix, R_NS) {
2710 start.push_attribute((relationship_declaration.as_str(), R_NS));
2711 }
2712 for (name, value) in &self.root_attributes {
2713 start.push_attribute((name.as_str(), value.as_str()));
2714 }
2715 writer.write_event(Event::Start(start))?;
2716
2717 write_extras_at(&mut writer, &self.extra_xml, 0)?;
2718 let mut position = 0;
2719 for abs in &self.abstract_nums {
2720 write_extras_at(&mut writer, &self.extra_xml, 1 + position)?;
2721 abs.to_xml_with_prefix(&mut writer, &word_prefix, &preservation_prefixes)?;
2722 position += 1;
2723 }
2724
2725 for num in &self.nums {
2726 write_extras_at(&mut writer, &self.extra_xml, 1 + position)?;
2727 num.to_xml_with_prefix(&mut writer, &word_prefix)?;
2728 position += 1;
2729 }
2730
2731 write_extras_at(&mut writer, &self.extra_xml, 1 + position)?;
2732
2733 writer.write_event(Event::End(BytesEnd::new(root_name)))?;
2734
2735 Ok(writer.into_inner())
2736 }
2737
2738 pub fn next_abstract_num_id(&self) -> u32 {
2740 let maximum = self
2741 .abstract_nums
2742 .iter()
2743 .map(|a| a.abstract_num_id)
2744 .max()
2745 .unwrap_or(0);
2746 if self.abstract_nums.is_empty() {
2747 return 0;
2748 }
2749 if let Some(next) = maximum.checked_add(1) {
2750 return next;
2751 }
2752 let used: std::collections::HashSet<_> = self
2753 .abstract_nums
2754 .iter()
2755 .map(|item| item.abstract_num_id)
2756 .collect();
2757 (0..=u32::MAX)
2758 .find(|candidate| !used.contains(candidate))
2759 .expect("an in-memory numbering collection cannot occupy every u32 identifier")
2760 }
2761
2762 pub fn next_num_id(&self) -> u32 {
2764 let maximum = self.nums.iter().map(|n| n.num_id).max().unwrap_or(0);
2765 if self.nums.is_empty() {
2766 return 1;
2767 }
2768 if let Some(next) = maximum.checked_add(1) {
2769 return next;
2770 }
2771 let used: std::collections::HashSet<_> = self.nums.iter().map(|item| item.num_id).collect();
2772 (1..=u32::MAX)
2773 .find(|candidate| !used.contains(candidate))
2774 .expect("an in-memory numbering collection cannot occupy every nonzero u32 identifier")
2775 }
2776
2777 pub fn add_bullet_list(&mut self) -> u32 {
2779 self.add_list(&[(ST_NumberFormat::Bullet, Some(1))])
2780 }
2781
2782 pub fn add_numbered_list(&mut self) -> u32 {
2784 self.add_list(&[(ST_NumberFormat::Decimal, Some(1))])
2785 }
2786
2787 pub fn add_list(&mut self, levels: &[(ST_NumberFormat, Option<u32>)]) -> u32 {
2801 let abs_id = self.next_abstract_num_id();
2802 let num_id = self.next_num_id();
2803
2804 let mut abs = CT_AbstractNum::new(abs_id);
2805 abs.multi_level_type = Some("hybridMultilevel".to_string());
2806
2807 let mut last_specified = ST_NumberFormat::Decimal;
2808 for i in 0..9u32 {
2809 let (num_fmt, start) = match levels.get(i as usize) {
2810 Some((fmt, start)) => {
2811 last_specified = *fmt;
2812 (*fmt, start.unwrap_or(1))
2813 }
2814 None => (level_fill_format(last_specified, i), 1),
2815 };
2816
2817 abs.levels.push(build_level(i, num_fmt, start));
2818 }
2819
2820 let abstract_position = 1 + self.abstract_nums.len();
2821 shift_extras_from(&mut self.extra_xml, abstract_position);
2822 self.abstract_nums.push(abs);
2823 let num_position = 1 + self.abstract_nums.len() + self.nums.len();
2824 shift_extras_from(&mut self.extra_xml, num_position);
2825 self.nums.push(CT_Num {
2826 num_id,
2827 abstract_num_id: abs_id,
2828 extra_xml: Vec::new(),
2829 extra_attributes: Vec::new(),
2830 });
2831
2832 num_id
2833 }
2834
2835 pub fn set_list_level(
2841 &mut self,
2842 num_id: u32,
2843 ilvl: u32,
2844 num_fmt: ST_NumberFormat,
2845 start: Option<u32>,
2846 ) -> bool {
2847 if ilvl > 8 {
2848 return false;
2849 }
2850
2851 let Some(num) = self.nums.iter().find(|n| n.num_id == num_id) else {
2852 return false;
2853 };
2854 let abstract_num_id = num.abstract_num_id;
2855 let Some(abs) = self
2856 .abstract_nums
2857 .iter_mut()
2858 .find(|a| a.abstract_num_id == abstract_num_id)
2859 else {
2860 return false;
2861 };
2862
2863 let level = build_level(ilvl, num_fmt, start.unwrap_or(1));
2864 match abs.levels.iter_mut().find(|l| l.ilvl == ilvl) {
2865 Some(existing) => {
2866 existing.start = level.start;
2867 existing.num_fmt = level.num_fmt;
2868 existing.lvl_text = level.lvl_text;
2869 }
2870 None => {
2871 let index = abs
2872 .levels
2873 .iter()
2874 .position(|existing| existing.ilvl > ilvl)
2875 .unwrap_or(abs.levels.len());
2876 let boundary = 7 + index;
2877 shift_extras_from(&mut abs.extra_xml, boundary);
2878 abs.levels.insert(index, level);
2879 }
2880 }
2881
2882 true
2883 }
2884
2885 pub fn get_abstract_num_for(&self, num_id: u32) -> Option<&CT_AbstractNum> {
2887 let num = self.nums.iter().find(|n| n.num_id == num_id)?;
2888 self.abstract_nums
2889 .iter()
2890 .find(|a| a.abstract_num_id == num.abstract_num_id)
2891 }
2892}
2893
2894impl Default for CT_Numbering {
2895 fn default() -> Self {
2896 Self::new()
2897 }
2898}
2899
2900const BULLET_CHARS: [&str; 9] = [
2902 "\u{2022}", "\u{25E6}", "\u{25AA}", "\u{2022}", "\u{25E6}", "\u{25AA}", "\u{2022}", "\u{25E6}", "\u{25AA}",
2907];
2908
2909const NUMBERED_FORMATS: [ST_NumberFormat; 9] = [
2912 ST_NumberFormat::Decimal,
2913 ST_NumberFormat::LowerLetter,
2914 ST_NumberFormat::LowerRoman,
2915 ST_NumberFormat::Decimal,
2916 ST_NumberFormat::LowerLetter,
2917 ST_NumberFormat::LowerRoman,
2918 ST_NumberFormat::Decimal,
2919 ST_NumberFormat::LowerLetter,
2920 ST_NumberFormat::LowerRoman,
2921];
2922
2923fn level_fill_format(last_specified: ST_NumberFormat, ilvl: u32) -> ST_NumberFormat {
2927 match last_specified {
2928 ST_NumberFormat::Bullet => ST_NumberFormat::Bullet,
2929 _ => NUMBERED_FORMATS[ilvl as usize % NUMBERED_FORMATS.len()],
2930 }
2931}
2932
2933fn build_level(ilvl: u32, num_fmt: ST_NumberFormat, start: u32) -> CT_Lvl {
2936 let mut lvl = CT_Lvl::new(ilvl);
2937 lvl.start = Some(start);
2938 lvl.num_fmt = Some(num_fmt);
2939 lvl.lvl_text = Some(match num_fmt {
2940 ST_NumberFormat::Bullet => BULLET_CHARS[ilvl as usize % BULLET_CHARS.len()].to_string(),
2941 _ => format!("%{}.", ilvl + 1),
2942 });
2943 lvl.lvl_jc = Some(ST_Jc::Left);
2944
2945 let indent = (ilvl + 1) as i32 * 720;
2947 lvl.ppr = Some(CT_PPr {
2948 ind_left: Some(crate::units::Twips(indent)),
2949 ind_hanging: Some(crate::units::Twips(360)),
2950 ..Default::default()
2951 });
2952
2953 lvl
2954}
2955
2956#[cfg(test)]
2957mod tests {
2958 use super::*;
2959 use crate::borders::CT_TabStop;
2960 use crate::shared::ST_TabJc;
2961 use crate::units::Twips;
2962
2963 #[test]
2964 fn round_trip_numbering() {
2965 let mut numbering = CT_Numbering::new();
2966 let num_id = numbering.add_bullet_list();
2967 assert_eq!(num_id, 1);
2968
2969 let xml = numbering.to_xml().unwrap();
2970 let parsed = CT_Numbering::from_xml(&xml).unwrap();
2971
2972 assert_eq!(parsed.abstract_nums.len(), 1);
2973 assert_eq!(parsed.nums.len(), 1);
2974 assert_eq!(parsed.nums[0].num_id, 1);
2975 assert_eq!(parsed.nums[0].abstract_num_id, 0);
2976
2977 let abs = &parsed.abstract_nums[0];
2978 assert_eq!(abs.levels.len(), 9);
2979 assert_eq!(abs.levels[0].num_fmt, Some(ST_NumberFormat::Bullet));
2980 assert_eq!(abs.levels[0].lvl_text, Some("\u{2022}".to_string()));
2981 }
2982
2983 #[test]
2984 fn round_trip_numbered_list() {
2985 let mut numbering = CT_Numbering::new();
2986 let num_id = numbering.add_numbered_list();
2987 assert_eq!(num_id, 1);
2988
2989 let xml = numbering.to_xml().unwrap();
2990 let parsed = CT_Numbering::from_xml(&xml).unwrap();
2991
2992 let abs = &parsed.abstract_nums[0];
2993 assert_eq!(abs.levels[0].num_fmt, Some(ST_NumberFormat::Decimal));
2994 assert_eq!(abs.levels[0].lvl_text, Some("%1.".to_string()));
2995 assert_eq!(abs.levels[1].num_fmt, Some(ST_NumberFormat::LowerLetter));
2996 }
2997
2998 #[test]
2999 fn multiple_lists() {
3000 let mut numbering = CT_Numbering::new();
3001 let bullet_id = numbering.add_bullet_list();
3002 let num_id = numbering.add_numbered_list();
3003
3004 assert_eq!(bullet_id, 1);
3005 assert_eq!(num_id, 2);
3006
3007 let xml = numbering.to_xml().unwrap();
3008 let parsed = CT_Numbering::from_xml(&xml).unwrap();
3009
3010 assert_eq!(parsed.abstract_nums.len(), 2);
3011 assert_eq!(parsed.nums.len(), 2);
3012 }
3013
3014 #[test]
3015 fn level_indentation() {
3016 let mut numbering = CT_Numbering::new();
3017 numbering.add_bullet_list();
3018
3019 let abs = &numbering.abstract_nums[0];
3020 assert_eq!(
3022 abs.levels[0].ppr.as_ref().unwrap().ind_left,
3023 Some(Twips(720))
3024 );
3025 assert_eq!(
3026 abs.levels[0].ppr.as_ref().unwrap().ind_hanging,
3027 Some(Twips(360))
3028 );
3029 assert_eq!(
3031 abs.levels[2].ppr.as_ref().unwrap().ind_left,
3032 Some(Twips(2160))
3033 );
3034 }
3035
3036 #[test]
3037 fn parse_numbering_xml() {
3038 let xml = br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3039<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3040 <w:abstractNum w:abstractNumId="0">
3041 <w:multiLevelType w:val="hybridMultilevel"/>
3042 <w:lvl w:ilvl="0">
3043 <w:start w:val="1"/>
3044 <w:numFmt w:val="decimal"/>
3045 <w:lvlText w:val="%1."/>
3046 <w:lvlJc w:val="left"/>
3047 <w:pPr>
3048 <w:ind w:left="720" w:hanging="360"/>
3049 </w:pPr>
3050 </w:lvl>
3051 <w:lvl w:ilvl="1">
3052 <w:start w:val="1"/>
3053 <w:numFmt w:val="lowerLetter"/>
3054 <w:lvlText w:val="%2."/>
3055 <w:lvlJc w:val="left"/>
3056 </w:lvl>
3057 </w:abstractNum>
3058 <w:num w:numId="1">
3059 <w:abstractNumId w:val="0"/>
3060 </w:num>
3061</w:numbering>"#;
3062
3063 let numbering = CT_Numbering::from_xml(xml).unwrap();
3064 assert_eq!(numbering.abstract_nums.len(), 1);
3065 assert_eq!(numbering.nums.len(), 1);
3066
3067 let abs = &numbering.abstract_nums[0];
3068 assert_eq!(abs.abstract_num_id, 0);
3069 assert_eq!(abs.multi_level_type, Some("hybridMultilevel".to_string()));
3070 assert_eq!(abs.levels.len(), 2);
3071 assert_eq!(abs.levels[0].start, Some(1));
3072 assert_eq!(abs.levels[0].num_fmt, Some(ST_NumberFormat::Decimal));
3073 assert_eq!(abs.levels[0].lvl_text, Some("%1.".to_string()));
3074 assert_eq!(
3075 abs.levels[0].ppr.as_ref().unwrap().ind_left,
3076 Some(Twips(720))
3077 );
3078 assert_eq!(abs.levels[1].num_fmt, Some(ST_NumberFormat::LowerLetter));
3079
3080 let num = &numbering.nums[0];
3081 assert_eq!(num.num_id, 1);
3082 assert_eq!(num.abstract_num_id, 0);
3083 }
3084
3085 #[test]
3086 fn get_abstract_num_for_lookup() {
3087 let mut numbering = CT_Numbering::new();
3088 numbering.add_bullet_list();
3089 numbering.add_numbered_list();
3090
3091 let abs = numbering.get_abstract_num_for(2).unwrap();
3092 assert_eq!(abs.levels[0].num_fmt, Some(ST_NumberFormat::Decimal));
3093
3094 assert!(numbering.get_abstract_num_for(99).is_none());
3095 }
3096
3097 #[test]
3098 fn add_list_mixed_levels_round_trip() {
3099 let mut numbering = CT_Numbering::new();
3100 let num_id = numbering.add_list(&[
3101 (ST_NumberFormat::Bullet, None),
3102 (ST_NumberFormat::Decimal, Some(3)),
3103 ]);
3104 assert_eq!(num_id, 1);
3105
3106 let xml = numbering.to_xml().unwrap();
3107 let parsed = CT_Numbering::from_xml(&xml).unwrap();
3108
3109 let abs = parsed.get_abstract_num_for(num_id).unwrap();
3110 assert_eq!(abs.levels.len(), 9);
3111 assert_eq!(abs.levels[0].num_fmt, Some(ST_NumberFormat::Bullet));
3112 assert_eq!(abs.levels[0].lvl_text, Some("\u{2022}".to_string()));
3113 assert_eq!(abs.levels[1].num_fmt, Some(ST_NumberFormat::Decimal));
3114 assert_eq!(abs.levels[1].lvl_text, Some("%2.".to_string()));
3115 assert_eq!(abs.levels[1].start, Some(3));
3116 assert_eq!(abs.levels[2].num_fmt, Some(ST_NumberFormat::LowerRoman));
3118 assert_eq!(abs.levels[2].start, Some(1));
3119 }
3120
3121 #[test]
3122 fn add_list_fill_keeps_bullets_for_bullet_lists() {
3123 let mut numbering = CT_Numbering::new();
3124 let num_id = numbering.add_list(&[(ST_NumberFormat::Bullet, None)]);
3125
3126 let abs = numbering.get_abstract_num_for(num_id).unwrap();
3127 for level in &abs.levels {
3128 assert_eq!(level.num_fmt, Some(ST_NumberFormat::Bullet));
3129 }
3130 }
3131
3132 #[test]
3133 fn add_list_delegation_matches_legacy_templates() {
3134 let mut via_helpers = CT_Numbering::new();
3135 via_helpers.add_bullet_list();
3136 via_helpers.add_numbered_list();
3137
3138 let mut via_add_list = CT_Numbering::new();
3139 via_add_list.add_list(&[(ST_NumberFormat::Bullet, Some(1))]);
3140 via_add_list.add_list(&[(ST_NumberFormat::Decimal, Some(1))]);
3141
3142 assert_eq!(via_helpers.abstract_nums, via_add_list.abstract_nums);
3143 assert_eq!(via_helpers.nums, via_add_list.nums);
3144 }
3145
3146 #[test]
3147 fn set_list_level_redefines_one_level() {
3148 let mut numbering = CT_Numbering::new();
3149 let num_id = numbering.add_list(&[(ST_NumberFormat::Bullet, None)]);
3150
3151 assert!(numbering.set_list_level(num_id, 1, ST_NumberFormat::Decimal, Some(3)));
3152
3153 let abs = numbering.get_abstract_num_for(num_id).unwrap();
3154 assert_eq!(abs.levels[1].num_fmt, Some(ST_NumberFormat::Decimal));
3155 assert_eq!(abs.levels[1].lvl_text, Some("%2.".to_string()));
3156 assert_eq!(abs.levels[1].start, Some(3));
3157 assert_eq!(abs.levels[0].num_fmt, Some(ST_NumberFormat::Bullet));
3159 assert_eq!(abs.levels[2].num_fmt, Some(ST_NumberFormat::Bullet));
3160 }
3161
3162 #[test]
3163 fn set_list_level_rejects_unknown_targets() {
3164 let mut numbering = CT_Numbering::new();
3165 let num_id = numbering.add_list(&[(ST_NumberFormat::Bullet, None)]);
3166
3167 assert!(!numbering.set_list_level(99, 0, ST_NumberFormat::Decimal, None));
3168 assert!(!numbering.set_list_level(num_id, 9, ST_NumberFormat::Decimal, None));
3169 }
3170
3171 #[test]
3172 fn list_mutations_preserve_unmodelled_numbering_xml() {
3173 let xml = br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3174<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3175 xmlns:q="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3176 xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
3177 xmlns:ext="urn:producer:extension"
3178 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
3179 mc:Ignorable="w15">
3180 <w:abstractNum ext:abstractNumId="producer-abstract" q:abstractNumId="0">
3181 <w:nsid w:val="12345678"/>
3182 <w:tmpl w:val="87654321"/>
3183 <w:lvl xmlns:q="urn:producer:shadow" q:ilvl="shadow-level"
3184 ext:ilvl="producer-level" w:ilvl="0"
3185 xmlns:ilvl="urn:producer:level"
3186 xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml">
3187 <w:start w:val="1"/>
3188 <w:numFmt w:val="decimal"/>
3189 <w:lvlRestart w:val="0"/>
3190 <w:lvlText w:val="%1."/>
3191 <ilvl:extension/>
3192 <w15:extension w15:value="bound-prefix"/>
3193 <w16:extension w16:value="level-bound-prefix"/>
3194 </w:lvl>
3195 <ext:tmpl ext:value="after-level"/>
3196 </w:abstractNum>
3197 <w:num ext:numId="producer-instance" q:numId="1">
3198 <w:abstractNumId w:val="0"/>
3199 <w:lvlOverride w:ilvl="0"><w:startOverride w:val="4"/></w:lvlOverride>
3200 </w:num>
3201 <ext:numPicBullet ext:value="after-instance"/>
3202 <w:extLst><w:ext w:uri="preserve-me"/></w:extLst>
3203</w:numbering>"#;
3204
3205 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3206 assert!(numbering.set_list_level(1, 0, ST_NumberFormat::UpperRoman, Some(3)));
3207 numbering.add_list(&[(ST_NumberFormat::Bullet, None)]);
3208
3209 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3210 for raw in [
3211 r#"<w:nsid w:val="12345678"/>"#,
3212 r#"<w:tmpl w:val="87654321"/>"#,
3213 r#"<w:lvlRestart w:val="0"/>"#,
3214 r#"<ilvl:extension/>"#,
3215 r#"<w15:extension w15:value="bound-prefix"/>"#,
3216 r#"<w16:extension w16:value="level-bound-prefix"/>"#,
3217 r#"<w:lvlOverride w:ilvl="0"><w:startOverride w:val="4"/></w:lvlOverride>"#,
3218 r#"<ext:tmpl ext:value="after-level"/>"#,
3219 r#"<ext:numPicBullet ext:value="after-instance"/>"#,
3220 r#"<w:extLst><w:ext w:uri="preserve-me"/></w:extLst>"#,
3221 ] {
3222 assert!(output.contains(raw), "missing preserved XML: {raw}");
3223 }
3224 assert!(
3225 output.contains(r#"xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml""#),
3226 "{output}"
3227 );
3228 assert!(
3229 output.contains(
3230 r#"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006""#
3231 )
3232 );
3233 assert!(output.contains(r#"mc:Ignorable="w15""#));
3234 assert!(
3235 output.contains(r#"xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml""#)
3236 );
3237 for attribute in [
3238 r#"ext:abstractNumId="producer-abstract""#,
3239 r#"ext:ilvl="producer-level""#,
3240 r#"xmlns:ilvl="urn:producer:level""#,
3241 r#"ext:numId="producer-instance""#,
3242 ] {
3243 assert!(output.contains(attribute), "missing attribute: {attribute}");
3244 }
3245 for rewritten_attribute in ["q:abstractNumId=", "q:numId="] {
3246 assert!(
3247 !output.contains(rewritten_attribute),
3248 "aliased modelled attribute was emitted twice: {rewritten_attribute}"
3249 );
3250 }
3251 assert!(output.contains(r#"q:ilvl="shadow-level""#));
3252 let first_level = output.find(r#"<w:lvl w:ilvl="0""#).unwrap();
3253 let foreign_template = output.find("<ext:tmpl ").unwrap();
3254 let instance = output.find("<w:num w:numId=").unwrap();
3255 let foreign_picture = output.find("<ext:numPicBullet ").unwrap();
3256 let final_extension = output.find("<w:extLst>").unwrap();
3257 assert!(first_level < foreign_template && foreign_template < instance);
3258 assert!(instance < foreign_picture && foreign_picture < final_extension);
3259 }
3260
3261 #[test]
3262 fn sparse_level_materialization_keeps_raw_children_in_schema_order() {
3263 let xml = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3264 <w:abstractNum w:abstractNumId="0">
3265 <w:lvl w:ilvl="0">
3266 <w:start w:val="1"/>
3267 <w:lvlRestart w:val="0"/>
3268 <w:lvlText w:val="%1."/>
3269 </w:lvl>
3270 </w:abstractNum>
3271 <w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num>
3272</w:numbering>"#;
3273 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3274 assert!(numbering.set_list_level(1, 0, ST_NumberFormat::UpperRoman, Some(2)));
3275
3276 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3277 let start = output.find("<w:start ").unwrap();
3278 let format = output.find("<w:numFmt ").unwrap();
3279 let restart = output.find("<w:lvlRestart ").unwrap();
3280 let text = output.find("<w:lvlText ").unwrap();
3281 assert!(start < format && format < restart && restart < text);
3282 }
3283
3284 #[test]
3285 fn inserting_a_missing_level_keeps_abstract_predecessors_in_schema_order() {
3286 let xml = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3287 <w:abstractNum w:abstractNumId="0">
3288 <w:nsid w:val="12345678"/>
3289 <w:tmpl w:val="87654321"/>
3290 <w:lvl w:ilvl="0"><w:numFmt w:val="decimal"/></w:lvl>
3291 <w:lvl w:ilvl="2"><w:numFmt w:val="lowerRoman"/></w:lvl>
3292 </w:abstractNum>
3293 <w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num>
3294</w:numbering>"#;
3295 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3296 assert!(numbering.set_list_level(1, 1, ST_NumberFormat::UpperLetter, Some(1)));
3297
3298 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3299 let nsid = output.find("<w:nsid ").unwrap();
3300 let template = output.find("<w:tmpl ").unwrap();
3301 let level_zero = output.find(r#"<w:lvl w:ilvl="0""#).unwrap();
3302 let level_one = output.find(r#"<w:lvl w:ilvl="1""#).unwrap();
3303 let level_two = output.find(r#"<w:lvl w:ilvl="2""#).unwrap();
3304 assert!(nsid < template && template < level_zero);
3305 assert!(level_zero < level_one && level_one < level_two);
3306 }
3307
3308 #[test]
3309 fn self_closing_numbering_elements_are_not_captured_as_unknown_children() {
3310 let empty = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>"#;
3311 let parsed = CT_Numbering::from_xml(empty).unwrap();
3312 let output = String::from_utf8(parsed.to_xml().unwrap()).unwrap();
3313 assert_eq!(output.matches("<w:numbering").count(), 1);
3314 assert!(parsed.extra_xml.is_empty());
3315
3316 let with_abstract = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:abstractNum w:abstractNumId="7"/></w:numbering>"#;
3317 let parsed = CT_Numbering::from_xml(with_abstract).unwrap();
3318 assert_eq!(parsed.abstract_nums[0].abstract_num_id, 7);
3319 assert_eq!(parsed.next_abstract_num_id(), 8);
3320
3321 let nested =
3322 br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3323 xmlns:ext="urn:producer:extension">
3324 <w:abstractNum w:abstractNumId="1">
3325 <w:lvl w:ilvl="0"/>
3326 <w:lvl w:ilvl="1">
3327 <w:pPr ext:marker="p"><ext:pPrData/></w:pPr>
3328 <w:rPr ext:marker="r"><ext:rPrData/></w:rPr>
3329 </w:lvl>
3330 </w:abstractNum>
3331</w:numbering>"#;
3332 let parsed = CT_Numbering::from_xml(nested).unwrap();
3333 assert!(parsed.abstract_nums[0].levels[0].extra_xml.is_empty());
3334 assert!(parsed.abstract_nums[0].levels[1].extra_xml.is_empty());
3335 assert!(parsed.abstract_nums[0].levels[1].ppr_raw.is_some());
3336 assert!(parsed.abstract_nums[0].levels[1].rpr_raw.is_some());
3337 let output = String::from_utf8(parsed.to_xml().unwrap()).unwrap();
3338 assert_eq!(output.matches(r#"<w:lvl w:ilvl="0""#).count(), 1);
3339 assert_eq!(output.matches("<w:pPr").count(), 1);
3340 assert_eq!(output.matches("<w:rPr").count(), 1);
3341 assert!(output.contains(r#"<w:pPr ext:marker="p"><ext:pPrData/></w:pPr>"#));
3342 assert!(output.contains(r#"<w:rPr ext:marker="r"><ext:rPrData/></w:rPr>"#));
3343 }
3344
3345 #[test]
3346 fn expanded_scalar_numbering_elements_are_modelled_once() {
3347 let xml = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3348 <w:abstractNum w:abstractNumId="0">
3349 <w:multiLevelType w:val="hybridMultilevel"></w:multiLevelType>
3350 <w:lvl w:ilvl="0">
3351 <w:start w:val="1"></w:start>
3352 <w:numFmt w:val="decimal"></w:numFmt>
3353 <w:lvlText w:val="%1."></w:lvlText>
3354 <w:lvlJc w:val="left"></w:lvlJc>
3355 </w:lvl>
3356 </w:abstractNum>
3357 <w:num w:numId="1"><w:abstractNumId w:val="0"></w:abstractNumId></w:num>
3358</w:numbering>"#;
3359 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3360 assert_eq!(numbering.nums[0].abstract_num_id, 0);
3361 assert!(numbering.set_list_level(1, 0, ST_NumberFormat::UpperRoman, Some(4)));
3362
3363 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3364 for name in ["multiLevelType", "start", "numFmt", "lvlText", "lvlJc"] {
3365 assert_eq!(
3366 output.matches(&format!("<w:{name}")).count(),
3367 1,
3368 "expanded scalar duplicated: {name}"
3369 );
3370 }
3371 assert_eq!(output.matches("<w:abstractNumId ").count(), 1);
3372 assert!(output.contains(r#"<w:start w:val="4"/>"#));
3373 assert!(output.contains(r#"<w:numFmt w:val="upperRoman"/>"#));
3374 }
3375
3376 #[test]
3377 fn adding_the_first_list_keeps_schema_final_root_content_last() {
3378 let xml = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3379 <w:numIdMac w:val="7"/>
3380</w:numbering>"#;
3381 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3382 numbering.add_list(&[(ST_NumberFormat::Decimal, None)]);
3383
3384 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3385 let abstract_num = output.find("<w:abstractNum ").unwrap();
3386 let num = output.find("<w:num w:numId=").unwrap();
3387 let final_content = output.find("<w:numIdMac ").unwrap();
3388 assert!(abstract_num < num && num < final_content);
3389 }
3390
3391 #[test]
3392 fn list_identifier_allocation_uses_a_gap_after_the_maximum_value() {
3393 let mut numbering = CT_Numbering {
3394 abstract_nums: vec![CT_AbstractNum::new(u32::MAX)],
3395 nums: vec![CT_Num {
3396 num_id: u32::MAX,
3397 abstract_num_id: u32::MAX,
3398 extra_xml: Vec::new(),
3399 extra_attributes: Vec::new(),
3400 }],
3401 root_attributes: Vec::new(),
3402 extra_xml: Vec::new(),
3403 };
3404
3405 let num_id = numbering.add_list(&[(ST_NumberFormat::Decimal, None)]);
3406 assert_ne!(num_id, u32::MAX);
3407 assert_eq!(numbering.abstract_nums.last().unwrap().abstract_num_id, 0);
3408 assert_eq!(num_id, 1);
3409 }
3410
3411 #[test]
3412 fn list_definition_accepts_only_the_first_nine_supplied_levels() {
3413 let mut numbering = CT_Numbering::new();
3414 let levels = vec![(ST_NumberFormat::Decimal, None); 10];
3415 let num_id = numbering.add_list(&levels);
3416
3417 assert_eq!(
3418 numbering.get_abstract_num_for(num_id).unwrap().levels.len(),
3419 9
3420 );
3421 }
3422
3423 #[test]
3424 fn foreign_canonical_prefix_spellings_keep_their_expanded_names() {
3425 let xml = br#"<q:numbering
3426 xmlns:q="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3427 xmlns:w="urn:producer:word"
3428 xmlns:r="urn:producer:relationships"
3429 xmlns:w1="urn:occupied:word"
3430 xmlns:r1="urn:occupied:relationships">
3431 <q:abstractNum q:abstractNumId="0"><q:lvl q:ilvl="0"/></q:abstractNum>
3432 <w:extension r:token="producer"/>
3433</q:numbering>"#;
3434
3435 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3436 numbering.abstract_nums[0].levels[0].start = Some(2);
3437 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3438
3439 assert!(output.contains(r#"xmlns:w="urn:producer:word""#));
3440 assert!(output.contains(r#"xmlns:r="urn:producer:relationships""#));
3441 assert!(output.contains(
3442 r#"xmlns:w2="http://schemas.openxmlformats.org/wordprocessingml/2006/main""#
3443 ));
3444 assert!(output.contains(
3445 r#"xmlns:r2="http://schemas.openxmlformats.org/officeDocument/2006/relationships""#
3446 ));
3447 assert!(output.contains(r#"<w:extension r:token="producer"/>"#));
3448 assert!(output.contains(r#"<w2:start w2:val="2"/>"#));
3449
3450 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
3451 assert_eq!(reparsed.abstract_nums[0].levels[0].start, Some(2));
3452 }
3453
3454 #[test]
3455 fn descendant_declarations_cannot_shadow_generated_prefixes() {
3456 let xml = br#"<q:numbering
3457 xmlns:q="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3458 xmlns:w="urn:producer:w" xmlns:w1="urn:producer:w1"
3459 xmlns:r="urn:producer:r">
3460 <q:abstractNum xmlns:w2="urn:producer:w2" xmlns:r1="urn:producer:r1"
3461 q:abstractNumId="0">
3462 <q:lvl xmlns:w3="urn:producer:w3" q:ilvl="0">
3463 <q:pPr xmlns:w4="urn:producer:w4" xmlns:ext="urn:producer:extension"
3464 ext:marker="keep"><q:ind q:left="720"/></q:pPr>
3465 </q:lvl>
3466 </q:abstractNum>
3467 <q:num xmlns:w5="urn:producer:w5" q:numId="1">
3468 <q:abstractNumId q:val="0"/>
3469 </q:num>
3470</q:numbering>"#;
3471
3472 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3473 numbering.abstract_nums[0].levels[0]
3474 .ppr
3475 .as_mut()
3476 .unwrap()
3477 .ind_left = Some(Twips(1440));
3478 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3479
3480 assert!(output.contains("<w6:numbering "), "{output}");
3481 assert!(output.contains(
3482 r#"xmlns:w6="http://schemas.openxmlformats.org/wordprocessingml/2006/main""#
3483 ));
3484 assert!(output.contains(
3485 r#"xmlns:r2="http://schemas.openxmlformats.org/officeDocument/2006/relationships""#
3486 ));
3487 assert!(output.contains("<w6:abstractNum w6:abstractNumId="));
3488 assert!(output.contains(r#"xmlns:w2="urn:producer:w2""#));
3489 assert!(output.contains("<w6:lvl w6:ilvl="));
3490 assert!(output.contains(r#"xmlns:w3="urn:producer:w3""#));
3491 assert!(output.contains(r#"<q:pPr xmlns:w4="urn:producer:w4""#));
3492 assert!(output.contains("<w6:num w6:numId="));
3493 assert!(output.contains(r#"xmlns:w5="urn:producer:w5""#));
3494
3495 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
3496 assert_eq!(
3497 reparsed.abstract_nums[0].levels[0]
3498 .ppr
3499 .as_ref()
3500 .unwrap()
3501 .ind_left,
3502 Some(Twips(1440))
3503 );
3504 }
3505
3506 #[test]
3507 fn typed_level_property_edits_preserve_producer_xml() {
3508 let xml = br#"<w:numbering
3509 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3510 xmlns:ext="urn:producer:extension">
3511 <w:abstractNum w:abstractNumId="0">
3512 <w:lvl w:ilvl="0">
3513 <w:pPr ext:marker="p"><ext:pPrData/><w:ind w:left="720"/></w:pPr>
3514 <w:rPr ext:marker="r"><ext:rPrData/><w:b/></w:rPr>
3515 </w:lvl>
3516 </w:abstractNum>
3517</w:numbering>"#;
3518
3519 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3520 let level = &mut numbering.abstract_nums[0].levels[0];
3521 level.ppr.as_mut().unwrap().ind_left = Some(Twips(1440));
3522 level.rpr.as_mut().unwrap().bold = Some(false);
3523 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3524
3525 for preserved in [
3526 r#"ext:marker="p""#,
3527 r#"<ext:pPrData/>"#,
3528 r#"ext:marker="r""#,
3529 r#"<ext:rPrData/>"#,
3530 ] {
3531 assert_eq!(output.matches(preserved).count(), 1, "{output}");
3532 }
3533 assert!(output.contains(r#"<w:ind w:left="1440"/>"#), "{output}");
3534 assert!(output.contains(r#"<w:b w:val="false"/>"#), "{output}");
3535
3536 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
3537 let level = &reparsed.abstract_nums[0].levels[0];
3538 assert_eq!(level.ppr.as_ref().unwrap().ind_left, Some(Twips(1440)));
3539 assert_eq!(level.rpr.as_ref().unwrap().bold, Some(false));
3540 }
3541
3542 #[test]
3543 fn property_local_name_collisions_remain_foreign_during_typed_edits() {
3544 let xml = br#"<w:numbering
3545 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3546 xmlns:ext="urn:producer:extension">
3547 <w:abstractNum w:abstractNumId="0">
3548 <w:lvl w:ilvl="0">
3549 <w:pPr ext:marker="p"><ext:ind ext:left="999"/><w:ind w:left="720"/></w:pPr>
3550 <w:rPr ext:marker="r"><ext:b/><w:b/></w:rPr>
3551 </w:lvl>
3552 </w:abstractNum>
3553</w:numbering>"#;
3554
3555 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3556 let level = &mut numbering.abstract_nums[0].levels[0];
3557 assert_eq!(level.ppr.as_ref().unwrap().ind_left, Some(Twips(720)));
3558 assert_eq!(level.rpr.as_ref().unwrap().bold, Some(true));
3559 level.ppr.as_mut().unwrap().ind_left = Some(Twips(1440));
3560 level.rpr.as_mut().unwrap().bold = Some(false);
3561 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3562
3563 assert_eq!(output.matches(r#"<ext:ind ext:left="999"/>"#).count(), 1);
3564 assert_eq!(output.matches("<ext:b/>").count(), 1);
3565 assert_eq!(output.matches(r#"<w:ind w:left="1440"/>"#).count(), 1);
3566 assert_eq!(output.matches(r#"<w:b w:val="false"/>"#).count(), 1);
3567 }
3568
3569 #[test]
3570 fn unsupported_word_property_xml_survives_typed_edits() {
3571 let xml = br#"<w:numbering
3572 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3573 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3574 <w:pPr>
3575 <w:numPr><w:ilvl w:val="0"/><w:producer w:token="nested"/></w:numPr>
3576 <w:ind w:left="720" w:producer="attribute"/>
3577 </w:pPr>
3578 </w:lvl></w:abstractNum>
3579</w:numbering>"#;
3580
3581 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3582 let ppr = numbering.abstract_nums[0].levels[0].ppr.as_mut().unwrap();
3583 ppr.ind_left = Some(Twips(1440));
3584 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3585
3586 assert!(
3587 output.contains(r#"<w:producer w:token="nested"/>"#),
3588 "{output}"
3589 );
3590 assert!(output.contains(r#"w:producer="attribute""#), "{output}");
3591 assert!(output.contains(r#"<w:ind w:left="1440""#), "{output}");
3592 }
3593
3594 #[test]
3595 fn default_foreign_elements_and_no_namespace_attributes_remain_unmodelled() {
3596 let xml = br#"<w:numbering
3597 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3598 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3599 <w:pPr>
3600 <ind xmlns="urn:producer" left="999"/>
3601 <w:ind w:left="720" producer="keep"/>
3602 </w:pPr>
3603 </w:lvl></w:abstractNum>
3604</w:numbering>"#;
3605
3606 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3607 let ppr = numbering.abstract_nums[0].levels[0].ppr.as_mut().unwrap();
3608 assert_eq!(ppr.ind_left, Some(Twips(720)));
3609 ppr.ind_left = Some(Twips(1440));
3610 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3611
3612 assert!(output.contains(r#"<ind xmlns="urn:producer" left="999"/>"#));
3613 assert!(output.contains(r#"producer="keep""#));
3614 assert!(output.contains(r#"<w:ind w:left="1440""#));
3615 }
3616
3617 #[test]
3618 fn property_overlay_uses_wordprocessingml_schema_order() {
3619 let xml = br#"<w:numbering
3620 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3621 xmlns:ext="urn:producer:extension">
3622 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3623 <w:pPr ext:marker="p">
3624 <w:numPr><w:numId w:val="1"/></w:numPr><ext:p/><w:pBdr><w:top w:val="single"/></w:pBdr>
3625 <w:tabs><w:tab w:val="left" w:pos="720"/></w:tabs><w:suppressAutoHyphens/>
3626 </w:pPr>
3627 <w:rPr ext:marker="r">
3628 <w:strike/><w:dstrike/><ext:r/><w:vanish/>
3629 <w:highlight w:val="yellow"/><ext:u/><w:u w:val="single"/>
3630 </w:rPr>
3631 </w:lvl></w:abstractNum>
3632</w:numbering>"#;
3633
3634 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3635 let level = &mut numbering.abstract_nums[0].levels[0];
3636 level.ppr.as_mut().unwrap().num_id = Some(2);
3637 level.rpr.as_mut().unwrap().strike = Some(false);
3638 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3639
3640 let num_pr = output.find("<w:numPr>").unwrap();
3641 let p_extra = output.find("<ext:p/>").unwrap();
3642 let borders = output.find("<w:pBdr>").unwrap();
3643 let tabs = output.find("<w:tabs>").unwrap();
3644 let hyphens = output.find("<w:suppressAutoHyphens").unwrap();
3645 assert!(num_pr < p_extra && p_extra < borders && borders < tabs && tabs < hyphens);
3646 let strike = output.find("<w:strike ").unwrap();
3647 let dstrike = output.find("<w:dstrike").unwrap();
3648 let r_extra = output.find("<ext:r/>").unwrap();
3649 let vanish = output.find("<w:vanish").unwrap();
3650 let highlight = output.find("<w:highlight").unwrap();
3651 let u_extra = output.find("<ext:u/>").unwrap();
3652 let underline = output.find("<w:u ").unwrap();
3653 assert!(strike < dstrike && dstrike < r_extra && r_extra < vanish);
3654 assert!(highlight < u_extra && u_extra < underline);
3655 }
3656
3657 #[test]
3658 fn property_projection_rejects_excessive_depth_normally() {
3659 let mut nested = String::new();
3660 for _ in 0..70 {
3661 nested.push_str("<w:producer>");
3662 }
3663 for _ in 0..70 {
3664 nested.push_str("</w:producer>");
3665 }
3666 let xml = format!(
3667 r#"<w:numbering xmlns:w="{W_NS}"><w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0"><w:pPr><w:numPr>{nested}</w:numPr></w:pPr></w:lvl></w:abstractNum></w:numbering>"#
3668 );
3669
3670 let error = CT_Numbering::from_xml(xml.as_bytes()).unwrap_err();
3671 assert!(
3672 matches!(error, crate::OxmlError::InvalidValue(ref message) if message.contains("property XML depth")),
3673 "{error}"
3674 );
3675 }
3676
3677 #[test]
3678 fn changed_composite_properties_keep_nested_producer_xml() {
3679 let xml = br#"<w:numbering
3680 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3681 xmlns:ext="urn:producer:extension">
3682 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3683 <w:pPr><w:numPr ext:marker="keep"><w:numId w:val="1"/><w:producer w:token="word"><ext:deep/></w:producer><ext:data/></w:numPr></w:pPr>
3684 </w:lvl></w:abstractNum>
3685</w:numbering>"#;
3686
3687 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3688 numbering.abstract_nums[0].levels[0]
3689 .ppr
3690 .as_mut()
3691 .unwrap()
3692 .num_id = Some(2);
3693 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3694
3695 assert!(output.contains(r#"<w:numId w:val="2"/>"#), "{output}");
3696 assert!(output.contains(r#"ext:marker="keep""#), "{output}");
3697 let producer = output.find(r#"<w:producer w:token="word">"#).unwrap();
3698 let deep = output.find("<ext:deep/>").unwrap();
3699 let producer_end = output.find("</w:producer>").unwrap();
3700 assert!(producer < deep && deep < producer_end, "{output}");
3701 assert!(output.contains("<ext:data/>"), "{output}");
3702 }
3703
3704 #[test]
3705 fn run_property_change_remains_schema_final_after_typed_addition() {
3706 let xml = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3707 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3708 <w:rPr><w:rPrChange w:id="7"><w:rPr><w:i/></w:rPr></w:rPrChange></w:rPr>
3709 </w:lvl></w:abstractNum>
3710</w:numbering>"#;
3711
3712 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3713 numbering.abstract_nums[0].levels[0]
3714 .rpr
3715 .as_mut()
3716 .unwrap()
3717 .bold = Some(true);
3718 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3719
3720 let bold = output.find("<w:b").unwrap();
3721 let change = output.find("<w:rPrChange").unwrap();
3722 assert!(bold < change, "{output}");
3723 assert!(output.contains(r#"<w:rPr><w:i/></w:rPr>"#), "{output}");
3724 }
3725
3726 #[test]
3727 fn numbering_model_identity_uses_element_and_attribute_namespaces() {
3728 let xml = br#"<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
3729 <abstractNum xmlns="urn:producer" abstractNumId="99"/>
3730 <w:abstractNum abstractNumId="8" w:abstractNumId="0">
3731 <lvl xmlns="urn:producer" ilvl="7"/>
3732 <w:lvl ilvl="6" w:ilvl="0">
3733 <start xmlns="urn:producer" val="99"/>
3734 <w:start val="88" w:val="1"/>
3735 <w:numFmt val="upperRoman" w:val="decimal"/>
3736 </w:lvl>
3737 </w:abstractNum>
3738 <num xmlns="urn:producer" numId="77"/>
3739 <w:num numId="5" w:numId="1"><w:abstractNumId val="9" w:val="0"/></w:num>
3740</w:numbering>"#;
3741
3742 let numbering = CT_Numbering::from_xml(xml).unwrap();
3743 assert_eq!(numbering.abstract_nums.len(), 1);
3744 assert_eq!(numbering.abstract_nums[0].abstract_num_id, 0);
3745 assert_eq!(numbering.abstract_nums[0].levels.len(), 1);
3746 assert_eq!(numbering.abstract_nums[0].levels[0].ilvl, 0);
3747 assert_eq!(numbering.abstract_nums[0].levels[0].start, Some(1));
3748 assert_eq!(
3749 numbering.abstract_nums[0].levels[0].num_fmt,
3750 Some(ST_NumberFormat::Decimal)
3751 );
3752 assert_eq!(numbering.nums.len(), 1);
3753 assert_eq!(numbering.nums[0].num_id, 1);
3754 assert_eq!(numbering.nums[0].abstract_num_id, 0);
3755
3756 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3757 assert!(
3758 output.contains(r#"<abstractNum xmlns="urn:producer" abstractNumId="99"/>"#),
3759 "{output}"
3760 );
3761 assert!(output.contains(r#"abstractNumId="8""#), "{output}");
3762 assert!(output.contains(r#"ilvl="6""#), "{output}");
3763 assert!(
3764 output.contains(r#"<start xmlns="urn:producer" val="99"/>"#),
3765 "{output}"
3766 );
3767 assert!(
3768 output.contains(r#"<num xmlns="urn:producer" numId="77"/>"#),
3769 "{output}"
3770 );
3771 assert!(output.contains(r#"numId="5""#), "{output}");
3772 }
3773
3774 #[test]
3775 fn producer_only_tabs_survive_unrelated_edit_and_explicit_clear() {
3776 let xml = br#"<w:numbering
3777 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3778 xmlns:ext="urn:producer:extension">
3779 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3780 <w:pPr><w:tabs><ext:data/></w:tabs><w:ind w:left="720"/></w:pPr>
3781 </w:lvl></w:abstractNum>
3782</w:numbering>"#;
3783
3784 let mut unrelated = CT_Numbering::from_xml(xml).unwrap();
3785 unrelated.abstract_nums[0].levels[0]
3786 .ppr
3787 .as_mut()
3788 .unwrap()
3789 .ind_left = Some(Twips(1440));
3790 let output = String::from_utf8(unrelated.to_xml().unwrap()).unwrap();
3791 let tabs = output.find("<w:tabs>").unwrap();
3792 let producer = output.find("<ext:data/>").unwrap();
3793 let tabs_end = output.find("</w:tabs>").unwrap();
3794 assert!(tabs < producer && producer < tabs_end, "{output}");
3795 assert!(output.contains(r#"w:left="1440""#), "{output}");
3796
3797 let mut cleared = CT_Numbering::from_xml(xml).unwrap();
3798 cleared.abstract_nums[0].levels[0]
3799 .ppr
3800 .as_mut()
3801 .unwrap()
3802 .tabs = None;
3803 let output = String::from_utf8(cleared.to_xml().unwrap()).unwrap();
3804 let tabs = output.find("<w:tabs>").unwrap();
3805 let producer = output.find("<ext:data/>").unwrap();
3806 let tabs_end = output.find("</w:tabs>").unwrap();
3807 assert!(tabs < producer && producer < tabs_end, "{output}");
3808 }
3809
3810 #[test]
3811 fn repeated_tabs_keep_occurrence_identity_and_between_nodes() {
3812 let xml = br#"<w:numbering
3813 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3814 xmlns:ext="urn:producer:extension">
3815 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3816 <w:pPr><w:tabs>
3817 <w:tab w:val="left" w:pos="720" ext:id="a"/><ext:between/>
3818 <w:tab w:val="left" w:pos="1440" ext:id="b"/>
3819 </w:tabs></w:pPr>
3820 </w:lvl></w:abstractNum>
3821</w:numbering>"#;
3822
3823 let mut removed = CT_Numbering::from_xml(xml).unwrap();
3824 removed.abstract_nums[0].levels[0]
3825 .ppr
3826 .as_mut()
3827 .unwrap()
3828 .tabs
3829 .as_mut()
3830 .unwrap()
3831 .tabs
3832 .remove(0);
3833 let output = String::from_utf8(removed.to_xml().unwrap()).unwrap();
3834 assert!(!output.contains(r#"ext:id="a""#), "{output}");
3835 let between = output.find("<ext:between/>").unwrap();
3836 let surviving = output.find(r#"w:pos="1440" ext:id="b""#).unwrap();
3837 assert!(between < surviving, "{output}");
3838
3839 let mut inserted = CT_Numbering::from_xml(xml).unwrap();
3840 inserted.abstract_nums[0].levels[0]
3841 .ppr
3842 .as_mut()
3843 .unwrap()
3844 .tabs
3845 .as_mut()
3846 .unwrap()
3847 .tabs
3848 .insert(0, CT_TabStop::new(ST_TabJc::Left, Twips(360)));
3849 let output = String::from_utf8(inserted.to_xml().unwrap()).unwrap();
3850 let inserted_tab = output.find(r#"w:pos="360""#).unwrap();
3851 let first = output.find(r#"w:pos="720" ext:id="a""#).unwrap();
3852 let between = output.find("<ext:between/>").unwrap();
3853 let second = output.find(r#"w:pos="1440" ext:id="b""#).unwrap();
3854 assert!(
3855 inserted_tab < first && first < between && between < second,
3856 "{output}"
3857 );
3858 }
3859
3860 #[test]
3861 fn explicit_property_clears_keep_only_producer_projection() {
3862 let xml = br#"<w:numbering
3863 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3864 xmlns:ext="urn:producer:extension">
3865 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3866 <w:pPr ext:container="keep"><w:tabs><w:tab w:val="left" w:pos="720" ext:id="a"/><ext:data/></w:tabs><w:ind w:left="1440"/></w:pPr>
3867 </w:lvl></w:abstractNum>
3868</w:numbering>"#;
3869
3870 let mut tabs_cleared = CT_Numbering::from_xml(xml).unwrap();
3871 tabs_cleared.abstract_nums[0].levels[0]
3872 .ppr
3873 .as_mut()
3874 .unwrap()
3875 .tabs = None;
3876 let output = String::from_utf8(tabs_cleared.to_xml().unwrap()).unwrap();
3877 assert!(
3878 output.contains(r#"<rdocxPreserve:preservedProperty ext:id="a"/>"#),
3879 "{output}"
3880 );
3881 assert!(output.contains("<ext:data/>"), "{output}");
3882 assert!(!output.contains(r#"w:pos="720""#), "{output}");
3883 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
3884 assert!(
3885 reparsed.abstract_nums[0].levels[0]
3886 .ppr
3887 .as_ref()
3888 .unwrap()
3889 .tabs
3890 .as_ref()
3891 .unwrap()
3892 .tabs
3893 .is_empty()
3894 );
3895
3896 let mut container_cleared = CT_Numbering::from_xml(xml).unwrap();
3897 container_cleared.abstract_nums[0].levels[0].ppr = None;
3898 let output = String::from_utf8(container_cleared.to_xml().unwrap()).unwrap();
3899 assert!(
3900 output.contains(r#"<w:pPr ext:container="keep""#),
3901 "{output}"
3902 );
3903 assert!(
3904 output.contains(r#"<rdocxPreserve:preservedProperty ext:id="a"/>"#),
3905 "{output}"
3906 );
3907 assert!(output.contains("<ext:data/>"), "{output}");
3908 assert!(!output.contains(r#"w:left="1440""#), "{output}");
3909 assert!(!output.contains(r#"w:pos="720""#), "{output}");
3910 }
3911
3912 #[test]
3913 fn cleared_tabs_keep_word_and_no_namespace_producer_attributes() {
3914 let xml = br#"<w:numbering
3915 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3916 xmlns:rdocxPreserve="urn:producer:occupied"
3917 xmlns:mc="urn:producer:mc">
3918 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3919 <w:pPr><w:tabs><w:tab w:val="left" w:pos="720" producer="plain" w:producer="word" token="rdocxPreserve:value"/></w:tabs></w:pPr>
3920 </w:lvl></w:abstractNum>
3921</w:numbering>"#;
3922
3923 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3924 numbering.abstract_nums[0].levels[0]
3925 .ppr
3926 .as_mut()
3927 .unwrap()
3928 .tabs = None;
3929 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3930 assert!(
3931 output.contains(r#"xmlns:rdocxPreserve="urn:producer:occupied""#),
3932 "{output}"
3933 );
3934 assert!(
3935 output.contains(r#"<w:tabs xmlns:rdocxPreserve1="urn:rdocx:preserved-property" xmlns:mc1="http://schemas.openxmlformats.org/markup-compatibility/2006" mc1:Ignorable="rdocxPreserve1">"#),
3936 "{output}"
3937 );
3938 assert!(
3939 output.contains(r#"<rdocxPreserve1:preservedProperty producer="plain" w:producer="word" token="rdocxPreserve:value"/>"#),
3940 "{output}"
3941 );
3942 assert!(output.contains(r#"producer="plain""#), "{output}");
3943 assert!(output.contains(r#"w:producer="word""#), "{output}");
3944 assert!(!output.contains("<w:tab "), "{output}");
3945 let mut reader = Reader::from_str(&output);
3946 let mut buf = Vec::new();
3947 loop {
3948 match reader.read_event_into(&mut buf) {
3949 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"pPr" => {
3950 break;
3951 }
3952 Ok(Event::Eof) => panic!("missing pPr"),
3953 _ => {}
3954 }
3955 buf.clear();
3956 }
3957 let direct = CT_PPr::from_xml(&mut reader).unwrap();
3958 assert!(direct.tabs.unwrap().tabs.is_empty());
3959 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
3960 assert!(
3961 reparsed.abstract_nums[0].levels[0]
3962 .ppr
3963 .as_ref()
3964 .unwrap()
3965 .tabs
3966 .as_ref()
3967 .unwrap()
3968 .tabs
3969 .is_empty()
3970 );
3971 }
3972
3973 #[test]
3974 fn provenance_only_tab_replacement_does_not_reuse_raw_ppr() {
3975 let xml = br#"<w:numbering
3976 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
3977 xmlns:ext="urn:producer">
3978 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
3979 <w:pPr><w:tabs><w:tab w:val="left" w:pos="720" ext:id="source"/></w:tabs></w:pPr>
3980 </w:lvl></w:abstractNum>
3981</w:numbering>"#;
3982
3983 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
3984 let parsed = &mut numbering.abstract_nums[0].levels[0]
3985 .ppr
3986 .as_mut()
3987 .unwrap()
3988 .tabs
3989 .as_mut()
3990 .unwrap()
3991 .tabs[0];
3992 assert_eq!(parsed.source_occurrence, Some(0));
3993 *parsed = CT_TabStop::new(ST_TabJc::Left, Twips(720));
3994 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
3995 assert!(output.contains(r#"w:pos="720""#), "{output}");
3996 assert!(!output.contains(r#"ext:id="source""#), "{output}");
3997 }
3998
3999 #[test]
4000 fn carrier_extends_existing_expanded_ignorable_attribute_once() {
4001 let xml = br#"<w:numbering
4002 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4003 xmlns:mc="urn:producer:occupied"
4004 xmlns:compat="http://schemas.openxmlformats.org/markup-compatibility/2006"
4005 xmlns:w15="urn:word:fifteen"
4006 xmlns:ext="urn:producer">
4007 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4008 <w:pPr><w:tabs compat:Ignorable="w15"><w:tab w:val="left" w:pos="720" ext:id="source"/></w:tabs></w:pPr>
4009 </w:lvl></w:abstractNum>
4010</w:numbering>"#;
4011
4012 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4013 numbering.abstract_nums[0].levels[0]
4014 .ppr
4015 .as_mut()
4016 .unwrap()
4017 .tabs = None;
4018 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4019 assert_eq!(output.matches(":Ignorable=").count(), 1, "{output}");
4020 assert!(
4021 output.contains(r#"compat:Ignorable="w15 rdocxPreserve""#),
4022 "{output}"
4023 );
4024 assert!(
4025 output.contains(r#"xmlns:rdocxPreserve="urn:rdocx:preserved-property""#),
4026 "{output}"
4027 );
4028 }
4029
4030 #[test]
4031 fn carrier_resolves_ignorable_against_property_ancestor_scope() {
4032 let xml = br#"<w:numbering
4033 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4034 xmlns:compat="http://schemas.openxmlformats.org/markup-compatibility/2006"
4035 xmlns:ext="urn:producer">
4036 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0" xmlns:compat="urn:producer:near">
4037 <w:pPr><w:tabs compat:Ignorable="producer"><w:tab w:val="left" w:pos="720" ext:id="source"/></w:tabs></w:pPr>
4038 </w:lvl></w:abstractNum>
4039</w:numbering>"#;
4040
4041 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4042 numbering.abstract_nums[0].levels[0]
4043 .ppr
4044 .as_mut()
4045 .unwrap()
4046 .tabs = None;
4047 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4048 assert!(
4049 output.contains(r#"compat:Ignorable="producer""#),
4050 "{output}"
4051 );
4052 assert!(output.contains(r#":Ignorable="rdocxPreserve""#), "{output}");
4053 assert_eq!(output.matches(":Ignorable=").count(), 2, "{output}");
4054 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
4055 assert!(
4056 reparsed.abstract_nums[0].levels[0]
4057 .ppr
4058 .as_ref()
4059 .unwrap()
4060 .tabs
4061 .as_ref()
4062 .unwrap()
4063 .tabs
4064 .is_empty()
4065 );
4066 }
4067
4068 #[test]
4069 fn numbering_projection_ignores_foreign_nested_run_properties() {
4070 let xml = br#"<w:numbering
4071 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4072 xmlns:ext="urn:producer">
4073 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4074 <w:pPr><ext:rPr><ext:b/></ext:rPr><w:rPr><ext:b/></w:rPr><w:ind w:left="720"/></w:pPr>
4075 </w:lvl></w:abstractNum>
4076</w:numbering>"#;
4077
4078 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4079 let ppr = numbering.abstract_nums[0].levels[0].ppr.as_mut().unwrap();
4080 assert_eq!(ppr.rpr.as_ref().and_then(|rpr| rpr.bold), None);
4081 ppr.ind_left = Some(Twips(1440));
4082 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4083 assert!(output.contains("<ext:rPr><ext:b/></ext:rPr>"), "{output}");
4084 assert!(output.contains("<w:rPr>"), "{output}");
4085 assert_eq!(output.matches("<ext:b/>").count(), 2, "{output}");
4086 assert!(output.contains(r#"w:left="1440""#), "{output}");
4087 }
4088
4089 #[test]
4090 fn repeated_producer_payload_keeps_clear_boundaries() {
4091 let xml = br#"<w:numbering
4092 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4093 xmlns:ext="urn:producer:extension">
4094 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4095 <w:pPr><w:tabs>
4096 <w:tab w:val="left" w:pos="720" ext:id="a"/><ext:between/>
4097 <w:tab w:val="left" w:pos="1440" ext:id="b"/>
4098 </w:tabs></w:pPr>
4099 </w:lvl></w:abstractNum>
4100</w:numbering>"#;
4101
4102 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4103 numbering.abstract_nums[0].levels[0]
4104 .ppr
4105 .as_mut()
4106 .unwrap()
4107 .tabs = None;
4108 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4109 let first = output
4110 .find(r#"<rdocxPreserve:preservedProperty ext:id="a"/>"#)
4111 .unwrap();
4112 let between = output.find("<ext:between/>").unwrap();
4113 let second = output
4114 .find(r#"<rdocxPreserve:preservedProperty ext:id="b"/>"#)
4115 .unwrap();
4116 assert!(first < between && between < second, "{output}");
4117 let reparsed = CT_Numbering::from_xml(output.as_bytes()).unwrap();
4118 assert!(
4119 reparsed.abstract_nums[0].levels[0]
4120 .ppr
4121 .as_ref()
4122 .unwrap()
4123 .tabs
4124 .as_ref()
4125 .unwrap()
4126 .tabs
4127 .is_empty()
4128 );
4129 }
4130
4131 #[test]
4132 fn typed_tab_edit_keeps_occurrence_producer_attributes() {
4133 let xml = br#"<w:numbering
4134 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4135 xmlns:ext="urn:producer:extension">
4136 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4137 <w:pPr><w:tabs><w:tab w:val="left" w:pos="720" ext:id="a"/></w:tabs></w:pPr>
4138 </w:lvl></w:abstractNum>
4139</w:numbering>"#;
4140
4141 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4142 numbering.abstract_nums[0].levels[0]
4143 .ppr
4144 .as_mut()
4145 .unwrap()
4146 .tabs
4147 .as_mut()
4148 .unwrap()
4149 .tabs[0]
4150 .pos = Twips(1440);
4151 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4152 assert!(output.contains(r#"w:pos="1440" ext:id="a""#), "{output}");
4153 }
4154
4155 #[test]
4156 fn tab_identity_collisions_and_inserted_edits_keep_occurrence_ownership() {
4157 let xml = br#"<w:numbering
4158 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4159 xmlns:ext="urn:producer:extension">
4160 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4161 <w:pPr><w:tabs>
4162 <w:tab w:val="left" w:pos="720" ext:id="a"/><ext:between/>
4163 <w:tab w:val="left" w:pos="1440" ext:id="b"/>
4164 </w:tabs></w:pPr>
4165 </w:lvl></w:abstractNum>
4166</w:numbering>"#;
4167
4168 let mut collision = CT_Numbering::from_xml(xml).unwrap();
4169 collision.abstract_nums[0].levels[0]
4170 .ppr
4171 .as_mut()
4172 .unwrap()
4173 .tabs
4174 .as_mut()
4175 .unwrap()
4176 .tabs[0]
4177 .pos = Twips(1440);
4178 let output = String::from_utf8(collision.to_xml().unwrap()).unwrap();
4179 let first = output.find(r#"w:pos="1440" ext:id="a""#).unwrap();
4180 let between = output.find("<ext:between/>").unwrap();
4181 let second = output[first + 1..]
4182 .find(r#"w:pos="1440" ext:id="b""#)
4183 .map(|offset| first + 1 + offset)
4184 .unwrap();
4185 assert!(first < between && between < second, "{output}");
4186
4187 let mut inserted_and_edited = CT_Numbering::from_xml(xml).unwrap();
4188 let tabs = &mut inserted_and_edited.abstract_nums[0].levels[0]
4189 .ppr
4190 .as_mut()
4191 .unwrap()
4192 .tabs
4193 .as_mut()
4194 .unwrap()
4195 .tabs;
4196 tabs[0].pos = Twips(1080);
4197 tabs.insert(0, CT_TabStop::new(ST_TabJc::Left, Twips(360)));
4198 let output = String::from_utf8(inserted_and_edited.to_xml().unwrap()).unwrap();
4199 let inserted = output.find(r#"w:pos="360""#).unwrap();
4200 let first = output.find(r#"w:pos="1080" ext:id="a""#).unwrap();
4201 let between = output.find("<ext:between/>").unwrap();
4202 let second = output.find(r#"w:pos="1440" ext:id="b""#).unwrap();
4203 assert!(
4204 inserted < first && first < between && between < second,
4205 "{output}"
4206 );
4207 }
4208
4209 #[test]
4210 fn later_insertions_and_removals_anchor_earlier_tab_edits() {
4211 let xml = br#"<w:numbering
4212 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4213 xmlns:ext="urn:producer:extension">
4214 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4215 <w:pPr><w:tabs>
4216 <w:tab w:val="left" w:pos="720" ext:id="a"/><ext:between-a-b/>
4217 <w:tab w:val="left" w:pos="1440" ext:id="b"/><ext:between-b-c/>
4218 <w:tab w:val="left" w:pos="2160" ext:id="c"/>
4219 </w:tabs></w:pPr>
4220 </w:lvl></w:abstractNum>
4221</w:numbering>"#;
4222
4223 let mut inserted = CT_Numbering::from_xml(xml).unwrap();
4224 let tabs = &mut inserted.abstract_nums[0].levels[0]
4225 .ppr
4226 .as_mut()
4227 .unwrap()
4228 .tabs
4229 .as_mut()
4230 .unwrap()
4231 .tabs;
4232 tabs[0].pos = Twips(1080);
4233 tabs.push(CT_TabStop::new(ST_TabJc::Left, Twips(2880)));
4234 let output = String::from_utf8(inserted.to_xml().unwrap()).unwrap();
4235 let first = output.find(r#"w:pos="1080" ext:id="a""#).unwrap();
4236 let first_boundary = output.find("<ext:between-a-b/>").unwrap();
4237 let second = output.find(r#"w:pos="1440" ext:id="b""#).unwrap();
4238 let second_boundary = output.find("<ext:between-b-c/>").unwrap();
4239 let third = output.find(r#"w:pos="2160" ext:id="c""#).unwrap();
4240 let inserted = output.find(r#"w:pos="2880""#).unwrap();
4241 assert!(
4242 first < first_boundary
4243 && first_boundary < second
4244 && second < second_boundary
4245 && second_boundary < third
4246 && third < inserted,
4247 "{output}"
4248 );
4249
4250 let mut removed = CT_Numbering::from_xml(xml).unwrap();
4251 let tabs = &mut removed.abstract_nums[0].levels[0]
4252 .ppr
4253 .as_mut()
4254 .unwrap()
4255 .tabs
4256 .as_mut()
4257 .unwrap()
4258 .tabs;
4259 tabs[0].pos = Twips(1080);
4260 tabs.pop();
4261 let output = String::from_utf8(removed.to_xml().unwrap()).unwrap();
4262 let first = output.find(r#"w:pos="1080" ext:id="a""#).unwrap();
4263 let first_boundary = output.find("<ext:between-a-b/>").unwrap();
4264 let second = output.find(r#"w:pos="1440" ext:id="b""#).unwrap();
4265 assert!(
4266 first < first_boundary && first_boundary < second,
4267 "{output}"
4268 );
4269 assert!(!output.contains(r#"ext:id="c""#), "{output}");
4270 }
4271
4272 #[test]
4273 fn provenance_keeps_same_segment_edits_and_first_duplicate_claim() {
4274 let inserted_xml = br#"<w:numbering
4275 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4276 xmlns:ext="urn:producer:extension">
4277 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4278 <w:pPr><w:tabs><w:tab w:val="left" w:pos="720" ext:id="a"/><ext:between/><w:tab w:val="left" w:pos="1440" ext:id="b"/></w:tabs></w:pPr>
4279 </w:lvl></w:abstractNum>
4280</w:numbering>"#;
4281 let mut inserted = CT_Numbering::from_xml(inserted_xml).unwrap();
4282 let tabs = &mut inserted.abstract_nums[0].levels[0]
4283 .ppr
4284 .as_mut()
4285 .unwrap()
4286 .tabs
4287 .as_mut()
4288 .unwrap()
4289 .tabs;
4290 tabs[0].pos = Twips(1080);
4291 tabs.insert(1, CT_TabStop::new(ST_TabJc::Left, Twips(1200)));
4292 let output = String::from_utf8(inserted.to_xml().unwrap()).unwrap();
4293 assert!(output.contains(r#"w:pos="1080" ext:id="a""#), "{output}");
4294 assert!(output.contains(r#"w:pos="1200"/>"#), "{output}");
4295 assert!(output.contains(r#"w:pos="1440" ext:id="b""#), "{output}");
4296
4297 let removed_xml = br#"<w:numbering
4298 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4299 xmlns:ext="urn:producer:extension">
4300 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4301 <w:pPr><w:tabs><w:tab w:val="left" w:pos="720" ext:id="a"/><ext:first/><w:tab w:val="left" w:pos="1080" ext:id="x"/><ext:second/><w:tab w:val="left" w:pos="1440" ext:id="b"/></w:tabs></w:pPr>
4302 </w:lvl></w:abstractNum>
4303</w:numbering>"#;
4304 let mut removed = CT_Numbering::from_xml(removed_xml).unwrap();
4305 let tabs = &mut removed.abstract_nums[0].levels[0]
4306 .ppr
4307 .as_mut()
4308 .unwrap()
4309 .tabs
4310 .as_mut()
4311 .unwrap()
4312 .tabs;
4313 tabs[0].pos = Twips(900);
4314 tabs.remove(1);
4315 let output = String::from_utf8(removed.to_xml().unwrap()).unwrap();
4316 let first = output.find(r#"w:pos="900" ext:id="a""#).unwrap();
4317 let first_extra = output.find("<ext:first/>").unwrap();
4318 let second_extra = output.find("<ext:second/>").unwrap();
4319 let second = output.find(r#"w:pos="1440" ext:id="b""#).unwrap();
4320 assert!(
4321 first < first_extra && first_extra < second_extra && second_extra < second,
4322 "{output}"
4323 );
4324 assert!(!output.contains(r#"ext:id="x""#), "{output}");
4325
4326 let mut duplicated = CT_Numbering::from_xml(inserted_xml).unwrap();
4327 let tabs = &mut duplicated.abstract_nums[0].levels[0]
4328 .ppr
4329 .as_mut()
4330 .unwrap()
4331 .tabs
4332 .as_mut()
4333 .unwrap()
4334 .tabs;
4335 let mut duplicate = tabs[0].clone();
4336 duplicate.pos = Twips(1200);
4337 tabs.insert(1, duplicate);
4338 let output = String::from_utf8(duplicated.to_xml().unwrap()).unwrap();
4339 assert!(output.contains(r#"w:pos="720" ext:id="a""#), "{output}");
4340 assert!(output.contains(r#"w:pos="1200"/>"#), "{output}");
4341 assert_eq!(output.matches(r#"ext:id="a""#).count(), 1, "{output}");
4342 }
4343
4344 #[test]
4345 fn aliased_tabs_use_generated_container_qname_and_local_child_alias() {
4346 let xml = br#"<w:numbering
4347 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4348 xmlns:ext="urn:producer:extension">
4349 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4350 <w:pPr><q:tabs xmlns:q="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><q:tab q:val="left" q:pos="720" ext:id="a"/></q:tabs></w:pPr>
4351 </w:lvl></w:abstractNum>
4352</w:numbering>"#;
4353
4354 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4355 numbering.abstract_nums[0].levels[0]
4356 .ppr
4357 .as_mut()
4358 .unwrap()
4359 .tabs
4360 .as_mut()
4361 .unwrap()
4362 .tabs
4363 .insert(0, CT_TabStop::new(ST_TabJc::Left, Twips(360)));
4364 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4365 assert!(
4366 output.contains(
4367 r#"<w:tabs xmlns:q="http://schemas.openxmlformats.org/wordprocessingml/2006/main">"#
4368 ),
4369 "{output}"
4370 );
4371 assert!(output.contains(r#"w:pos="720" ext:id="a""#), "{output}");
4372 assert!(output.contains("</w:tabs>"), "{output}");
4373 assert!(!output.contains("</q:tabs>"), "{output}");
4374 }
4375
4376 #[test]
4377 fn high_count_tab_overlay_uses_bounded_occurrence_queues() {
4378 const TAB_COUNT: usize = 10_000;
4379 let sources = (0..TAB_COUNT).map(Some).collect::<Vec<_>>();
4380 let (_, work) = match_occurrences(TAB_COUNT, &sources);
4381 assert!(work <= TAB_COUNT + sources.len());
4382
4383 let mut original_tabs = format!(r#"<w:tabs xmlns:w="{W_NS}" xmlns:ext="urn:producer">"#);
4384 let mut generated_tabs = format!(r#"<w:tabs xmlns:w="{W_NS}">"#);
4385 for index in 0..TAB_COUNT {
4386 original_tabs.push_str(&format!(
4387 r#"<w:tab w:val="left" w:pos="{}" ext:id="{}"/>"#,
4388 index + 1,
4389 index
4390 ));
4391 generated_tabs.push_str(&format!(
4392 r#"<w:tab w:val="left" w:pos="{}"/>"#,
4393 if index == 0 { 20_000 } else { index + 1 }
4394 ));
4395 if index + 1 < TAB_COUNT {
4396 original_tabs.push_str("<ext:between/>");
4397 }
4398 }
4399 original_tabs.push_str("</w:tabs>");
4400 generated_tabs.push_str("</w:tabs>");
4401 let preservation_prefixes = PreservationPrefixes::new(&[]);
4402 let (_, writer_work) = merge_repeated_property_with_work(
4403 original_tabs.as_bytes(),
4404 generated_tabs.as_bytes(),
4405 &["w".to_string()],
4406 PropertyKind::Paragraph,
4407 0,
4408 Some(&sources),
4409 &preservation_prefixes,
4410 )
4411 .unwrap();
4412 assert!(writer_work <= 4 * (TAB_COUNT * 3 + 1));
4413
4414 let mut xml = format!(
4415 r#"<w:numbering xmlns:w="{W_NS}" xmlns:ext="urn:producer"><w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0"><w:pPr><w:tabs>"#
4416 );
4417 xml.push_str(
4418 original_tabs
4419 .strip_prefix(&format!(
4420 r#"<w:tabs xmlns:w="{W_NS}" xmlns:ext="urn:producer">"#
4421 ))
4422 .unwrap()
4423 .strip_suffix("</w:tabs>")
4424 .unwrap(),
4425 );
4426 xml.push_str("</w:tabs></w:pPr></w:lvl></w:abstractNum></w:numbering>");
4427
4428 let mut numbering = CT_Numbering::from_xml(xml.as_bytes()).unwrap();
4429 numbering.abstract_nums[0].levels[0]
4430 .ppr
4431 .as_mut()
4432 .unwrap()
4433 .tabs
4434 .as_mut()
4435 .unwrap()
4436 .tabs[0]
4437 .pos = Twips(20_000);
4438 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4439 assert!(output.contains(r#"ext:id="9999""#));
4440 assert!(output.contains("<ext:between/>"));
4441 }
4442
4443 #[test]
4444 fn property_extensions_keep_schema_slots_when_typed_children_change() {
4445 let xml = br#"<w:numbering
4446 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4447 xmlns:ext="urn:producer:extension">
4448 <w:abstractNum w:abstractNumId="0">
4449 <w:lvl w:ilvl="0">
4450 <w:pPr ext:marker="p"><w:pStyle w:val="List"/><ext:data/><w:keepNext/></w:pPr>
4451 </w:lvl>
4452 </w:abstractNum>
4453</w:numbering>"#;
4454
4455 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4456 let ppr = numbering.abstract_nums[0].levels[0].ppr.as_mut().unwrap();
4457 ppr.style_id = None;
4458 ppr.ind_left = Some(Twips(720));
4459 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4460
4461 let extension = output.find("<ext:data/>").unwrap();
4462 let keep_next = output.find("<w:keepNext").unwrap();
4463 let indentation = output.find("<w:ind ").unwrap();
4464 assert!(extension < keep_next && keep_next < indentation, "{output}");
4465
4466 let xml = br#"<w:numbering
4467 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
4468 xmlns:ext="urn:producer:extension">
4469 <w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0">
4470 <w:pPr ext:marker="p"><ext:data/><w:keepNext/></w:pPr>
4471 </w:lvl></w:abstractNum>
4472</w:numbering>"#;
4473 let mut numbering = CT_Numbering::from_xml(xml).unwrap();
4474 numbering.abstract_nums[0].levels[0]
4475 .ppr
4476 .as_mut()
4477 .unwrap()
4478 .style_id = Some("List".to_string());
4479 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4480 let style = output.find("<w:pStyle ").unwrap();
4481 let extension = output.find("<ext:data/>").unwrap();
4482 let keep_next = output.find("<w:keepNext").unwrap();
4483 assert!(style < extension && extension < keep_next, "{output}");
4484 }
4485
4486 #[test]
4487 fn canonical_properties_use_parent_writer_indentation() {
4488 let mut numbering = CT_Numbering::new();
4489 numbering.add_numbered_list();
4490 let output = String::from_utf8(numbering.to_xml().unwrap()).unwrap();
4491
4492 assert!(
4493 output.contains("\n <w:pPr>\n <w:ind "),
4494 "{output}"
4495 );
4496 assert!(!output.contains("</w:lvlJc><w:pPr>"), "{output}");
4497 }
4498
4499 #[test]
4500 fn level_public_preservation_fields_and_canonical_equality_stay_stable() {
4501 let level = CT_Lvl {
4502 ilvl: 0,
4503 start: Some(1),
4504 num_fmt: Some(ST_NumberFormat::Decimal),
4505 lvl_text: Some("%1.".to_string()),
4506 lvl_jc: Some(ST_Jc::Left),
4507 ppr: Some(CT_PPr {
4508 ind_left: Some(Twips(720)),
4509 ..Default::default()
4510 }),
4511 rpr: Some(CT_RPr {
4512 bold: Some(true),
4513 ..Default::default()
4514 }),
4515 extra_xml: Vec::new(),
4516 extra_attributes: Vec::new(),
4517 ppr_raw: None,
4518 rpr_raw: None,
4519 };
4520 let numbering = CT_Numbering {
4521 abstract_nums: vec![CT_AbstractNum {
4522 abstract_num_id: 0,
4523 levels: vec![level.clone()],
4524 multi_level_type: None,
4525 extra_xml: Vec::new(),
4526 extra_attributes: Vec::new(),
4527 }],
4528 nums: Vec::new(),
4529 root_attributes: Vec::new(),
4530 extra_xml: Vec::new(),
4531 };
4532
4533 let reparsed = CT_Numbering::from_xml(&numbering.to_xml().unwrap()).unwrap();
4534 assert_eq!(reparsed.abstract_nums[0].levels[0], level);
4535
4536 let mut with_extra = level.clone();
4537 with_extra.extra_xml.push((0, b"<ext:data/>".to_vec()));
4538 assert_ne!(with_extra, level);
4539 }
4540}