Skip to main content

rdocx_oxml/
numbering.rs

1//! Numbering definitions: `CT_Numbering`, `CT_AbstractNum`, `CT_Num`, `CT_Lvl`.
2//!
3//! These types represent the content of `numbering.xml`, which defines
4//! abstract numbering formats and numbering instances that paragraphs reference.
5
6use 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/// `ST_NumberFormat` — Numbering format type.
1975#[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/// `CT_Lvl` — A single level (0–8) in an abstract numbering definition.
2017#[derive(Debug, Clone, PartialEq)]
2018pub struct CT_Lvl {
2019    /// Level index (0–8)
2020    pub ilvl: u32,
2021    /// Starting number
2022    pub start: Option<u32>,
2023    /// Number format
2024    pub num_fmt: Option<ST_NumberFormat>,
2025    /// Level text (e.g., "%1.", "%1.%2.", bullet char)
2026    pub lvl_text: Option<String>,
2027    /// Level justification
2028    pub lvl_jc: Option<ST_Jc>,
2029    /// Paragraph properties for this level (typically indentation)
2030    pub ppr: Option<CT_PPr>,
2031    /// Run properties for the numbering symbol
2032    pub rpr: Option<CT_RPr>,
2033    /// Unmodelled children retained at their modelled-child boundaries.
2034    pub extra_xml: Vec<(usize, Vec<u8>)>,
2035    /// Unmodelled attributes and namespace declarations from `w:lvl`.
2036    pub extra_attributes: Vec<(String, String)>,
2037    /// Original typed value, raw XML, and namespace scope for an extended `w:pPr`.
2038    pub ppr_raw: Option<(CT_PPr, Vec<u8>, Vec<String>)>,
2039    /// Original typed value, raw XML, and namespace scope for an extended `w:rPr`.
2040    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/// `CT_AbstractNum` — An abstract numbering definition with up to 9 levels.
2314#[derive(Debug, Clone, PartialEq)]
2315pub struct CT_AbstractNum {
2316    pub abstract_num_id: u32,
2317    pub levels: Vec<CT_Lvl>,
2318    /// Optional multi-level type hint
2319    pub multi_level_type: Option<String>,
2320    /// Unmodelled children retained at their modelled-child boundaries.
2321    pub extra_xml: Vec<(usize, Vec<u8>)>,
2322    /// Unmodelled attributes and namespace declarations from `w:abstractNum`.
2323    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/// `CT_Num` — A numbering instance that references an abstract numbering definition.
2448#[derive(Debug, Clone, PartialEq)]
2449pub struct CT_Num {
2450    pub num_id: u32,
2451    pub abstract_num_id: u32,
2452    /// Unmodelled children such as level overrides.
2453    pub extra_xml: Vec<(usize, Vec<u8>)>,
2454    /// Unmodelled attributes and namespace declarations from `w:num`.
2455    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/// `CT_Numbering` — Root element of the numbering definitions part.
2549#[derive(Debug, Clone, PartialEq)]
2550pub struct CT_Numbering {
2551    pub abstract_nums: Vec<CT_AbstractNum>,
2552    pub nums: Vec<CT_Num>,
2553    /// Namespace declarations and compatibility attributes from the root.
2554    pub root_attributes: Vec<(String, String)>,
2555    /// Unmodelled root children retained at their modelled-child boundaries.
2556    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    /// Parse from XML bytes (the content of numbering.xml).
2604    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    /// Serialize to XML bytes.
2689    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    /// Get the next available abstract numbering ID.
2739    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    /// Get the next available numbering instance ID.
2763    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    /// Create a bullet list definition and return its numId.
2778    pub fn add_bullet_list(&mut self) -> u32 {
2779        self.add_list(&[(ST_NumberFormat::Bullet, Some(1))])
2780    }
2781
2782    /// Create a numbered (decimal) list definition and return its numId.
2783    pub fn add_numbered_list(&mut self) -> u32 {
2784        self.add_list(&[(ST_NumberFormat::Decimal, Some(1))])
2785    }
2786
2787    /// Create a list definition with explicit per-level formats and return
2788    /// its numId.
2789    ///
2790    /// `levels[i]` specifies level `i` as `(format, start)`; a `start` of
2791    /// `None` defaults to 1 (`start` has no meaning for bullet levels). All
2792    /// nine levels are always defined so a paragraph referencing a deeper
2793    /// level than was specified still renders: unspecified levels continue
2794    /// from the last specified format's family — the bullet-glyph rotation
2795    /// for bullets, the decimal/letter/roman rotation otherwise — matching
2796    /// the [`Self::add_bullet_list`] / [`Self::add_numbered_list`] templates.
2797    ///
2798    /// An empty `levels` behaves like [`Self::add_numbered_list`]. Only the
2799    /// first nine entries are representable and any later entries are ignored.
2800    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    /// Redefine one level of an existing list definition, for callers that
2836    /// only learn a deeper level's format when content first reaches it.
2837    ///
2838    /// Returns `false` when `num_id` is unknown or `ilvl` is out of range
2839    /// (levels are 0–8).
2840    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    /// Look up the abstract numbering definition for a given numId.
2886    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
2900/// Bullet glyph rotation shared by the list templates: • ◦ ▪ repeating.
2901const BULLET_CHARS: [&str; 9] = [
2902    "\u{2022}", // bullet •
2903    "\u{25E6}", // white bullet â—¦
2904    "\u{25AA}", // black small square â–ª
2905    "\u{2022}", // repeat pattern
2906    "\u{25E6}", "\u{25AA}", "\u{2022}", "\u{25E6}", "\u{25AA}",
2907];
2908
2909/// Numeric format rotation shared by the list templates:
2910/// decimal, lowerLetter, lowerRoman repeating.
2911const 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
2923/// Template format for an unspecified level, keyed on the last format the
2924/// caller did specify: bullets stay bullets; anything numeric continues the
2925/// numbered rotation.
2926fn 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
2933/// One level in the shared template shape: bullet glyph or `%N.` text,
2934/// left-justified, indented 720tw per depth with a 360tw hanging indent.
2935fn 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    // Standard indentation: 720tw per level
2946    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        // Level 0: 720tw indent, 360tw hanging
3021        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        // Level 2: 2160tw indent
3030        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        // Unspecified levels continue the last specified family (numeric).
3117        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        // Neighbors untouched.
3158        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}