Skip to main content

rdocx_oxml/
settings.rs

1//! Word document settings and document-protection metadata.
2
3use oxml_core::Twips;
4use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
5use quick_xml::{Reader, Writer, XmlVersion};
6
7use crate::document::{CT_NoteProperties, CT_SectPr};
8use crate::error::{OxmlError, Result};
9use crate::math::{MathProperties, fixed_math_prefix_is_safe, is_math_element};
10use crate::namespace::W_NS;
11use crate::numbering::{namespace_bindings, word_prefixes_at};
12use crate::properties::{is_word_attribute, is_word_element, parse_integer_measurement};
13use crate::raw_xml::capture_element;
14
15/// The editing operation permitted by `w:documentProtection`.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ProtectionMode {
18    ReadOnly,
19    Comments,
20    TrackedChanges,
21    Forms,
22}
23
24impl ProtectionMode {
25    fn parse(value: &str) -> Option<Self> {
26        match value {
27            "readOnly" => Some(Self::ReadOnly),
28            "comments" => Some(Self::Comments),
29            "trackedChanges" => Some(Self::TrackedChanges),
30            "forms" => Some(Self::Forms),
31            _ => None,
32        }
33    }
34
35    fn as_str(self) -> &'static str {
36        match self {
37            Self::ReadOnly => "readOnly",
38            Self::Comments => "comments",
39            Self::TrackedChanges => "trackedChanges",
40            Self::Forms => "forms",
41        }
42    }
43}
44
45/// The cryptographic provider category recorded by Word.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum CryptProviderType {
48    RsaAes,
49    RsaFull,
50    Custom,
51}
52
53impl CryptProviderType {
54    fn parse(value: &str) -> Option<Self> {
55        match value {
56            "rsaAES" => Some(Self::RsaAes),
57            "rsaFull" => Some(Self::RsaFull),
58            "custom" => Some(Self::Custom),
59            _ => None,
60        }
61    }
62
63    fn as_str(self) -> &'static str {
64        match self {
65            Self::RsaAes => "rsaAES",
66            Self::RsaFull => "rsaFull",
67            Self::Custom => "custom",
68        }
69    }
70}
71
72/// The algorithm class recorded by Word.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum CryptAlgorithmClass {
75    Hash,
76    Custom,
77}
78
79impl CryptAlgorithmClass {
80    fn parse(value: &str) -> Option<Self> {
81        match value {
82            "hash" => Some(Self::Hash),
83            "custom" => Some(Self::Custom),
84            _ => None,
85        }
86    }
87
88    fn as_str(self) -> &'static str {
89        match self {
90            Self::Hash => "hash",
91            Self::Custom => "custom",
92        }
93    }
94}
95
96/// The algorithm type recorded by Word.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum CryptAlgorithmType {
99    Any,
100    Custom,
101}
102
103impl CryptAlgorithmType {
104    fn parse(value: &str) -> Option<Self> {
105        match value {
106            "typeAny" => Some(Self::Any),
107            "custom" => Some(Self::Custom),
108            _ => None,
109        }
110    }
111
112    fn as_str(self) -> &'static str {
113        match self {
114            Self::Any => "typeAny",
115            Self::Custom => "custom",
116        }
117    }
118}
119
120/// One valid `w:documentProtection` element.
121///
122/// Authoring records caller-supplied protection metadata verbatim. Deriving a
123/// hash from a password, choosing a salt and picking a spin count are explicit
124/// non-goals, so nothing here computes a credential.
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct DocumentProtection {
127    pub mode: ProtectionMode,
128    pub enforcement: Option<bool>,
129    pub formatting: Option<bool>,
130    pub provider_type: Option<CryptProviderType>,
131    pub algorithm_class: Option<CryptAlgorithmClass>,
132    pub algorithm_type: Option<CryptAlgorithmType>,
133    pub algorithm_sid: Option<u32>,
134    pub spin_count: Option<u32>,
135    pub hash: Option<String>,
136    pub salt: Option<String>,
137}
138
139/// One valid document variable from `w:settings/w:docVars`.
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct DocumentVariable {
142    pub name: String,
143    pub value: String,
144}
145
146/// One `w:compatSetting` entry from the document compatibility settings.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct CompatibilitySetting {
149    pub name: String,
150    pub uri: String,
151    pub value: String,
152}
153
154/// Document-wide character spacing compression behavior.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum CharacterSpacingControl {
157    DoNotCompress,
158    CompressPunctuation,
159    CompressPunctuationAndJapaneseKana,
160}
161
162impl CharacterSpacingControl {
163    fn parse(value: &str) -> Option<Self> {
164        match value {
165            "doNotCompress" => Some(Self::DoNotCompress),
166            "compressPunctuation" => Some(Self::CompressPunctuation),
167            "compressPunctuationAndJapaneseKana" => Some(Self::CompressPunctuationAndJapaneseKana),
168            _ => None,
169        }
170    }
171
172    fn as_str(self) -> &'static str {
173        match self {
174            Self::DoNotCompress => "doNotCompress",
175            Self::CompressPunctuation => "compressPunctuation",
176            Self::CompressPunctuationAndJapaneseKana => "compressPunctuationAndJapaneseKana",
177        }
178    }
179}
180
181/// Default document theme languages from `w:themeFontLang`.
182#[derive(Debug, Clone, PartialEq, Eq, Default)]
183pub struct ThemeFontLanguage {
184    pub latin: Option<String>,
185    pub east_asia: Option<String>,
186    pub bidi: Option<String>,
187}
188
189/// The document view Word opens with, from `w:view`.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum DocumentView {
192    None,
193    Print,
194    Outline,
195    MasterPages,
196    Normal,
197    Web,
198}
199
200impl DocumentView {
201    fn parse(value: &str) -> Option<Self> {
202        match value {
203            "none" => Some(Self::None),
204            "print" => Some(Self::Print),
205            "outline" => Some(Self::Outline),
206            "masterPages" => Some(Self::MasterPages),
207            "normal" => Some(Self::Normal),
208            "web" => Some(Self::Web),
209            _ => None,
210        }
211    }
212
213    fn as_str(self) -> &'static str {
214        match self {
215            Self::None => "none",
216            Self::Print => "print",
217            Self::Outline => "outline",
218            Self::MasterPages => "masterPages",
219            Self::Normal => "normal",
220            Self::Web => "web",
221        }
222    }
223}
224
225/// The magnification preset recorded by `w:zoom`.
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub enum ZoomKind {
228    None,
229    FullPage,
230    BestFit,
231    TextFit,
232}
233
234impl ZoomKind {
235    fn parse(value: &str) -> Option<Self> {
236        match value {
237            "none" => Some(Self::None),
238            "fullPage" => Some(Self::FullPage),
239            "bestFit" => Some(Self::BestFit),
240            "textFit" => Some(Self::TextFit),
241            _ => None,
242        }
243    }
244
245    fn as_str(self) -> &'static str {
246        match self {
247            Self::None => "none",
248            Self::FullPage => "fullPage",
249            Self::BestFit => "bestFit",
250            Self::TextFit => "textFit",
251        }
252    }
253}
254
255/// The `w:zoom` magnification preset and its percentage.
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
257pub struct DocumentZoom {
258    pub kind: Option<ZoomKind>,
259    pub percent: Option<u32>,
260}
261
262/// Whether Word considers a proofing pass current, from `w:proofState`.
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum ProofState {
265    Clean,
266    Dirty,
267}
268
269impl ProofState {
270    fn parse(value: &str) -> Option<Self> {
271        match value {
272            "clean" => Some(Self::Clean),
273            "dirty" => Some(Self::Dirty),
274            _ => None,
275        }
276    }
277
278    fn as_str(self) -> &'static str {
279        match self {
280            Self::Clean => "clean",
281            Self::Dirty => "dirty",
282        }
283    }
284}
285
286/// The spelling and grammar proofing state recorded by `w:proofState`.
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
288pub struct DocumentProofState {
289    pub spelling: Option<ProofState>,
290    pub grammar: Option<ProofState>,
291}
292
293/// The mail-merge output category from `w:mainDocumentType`.
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum MailMergeDocumentType {
296    Catalog,
297    Envelopes,
298    MailingLabels,
299    FormLetters,
300    Email,
301    Fax,
302}
303
304impl MailMergeDocumentType {
305    fn parse(value: &str) -> Option<Self> {
306        match value {
307            "catalog" => Some(Self::Catalog),
308            "envelopes" => Some(Self::Envelopes),
309            "mailingLabels" => Some(Self::MailingLabels),
310            "formLetters" => Some(Self::FormLetters),
311            "email" => Some(Self::Email),
312            "fax" => Some(Self::Fax),
313            _ => None,
314        }
315    }
316
317    fn as_str(self) -> &'static str {
318        match self {
319            Self::Catalog => "catalog",
320            Self::Envelopes => "envelopes",
321            Self::MailingLabels => "mailingLabels",
322            Self::FormLetters => "formLetters",
323            Self::Email => "email",
324            Self::Fax => "fax",
325        }
326    }
327}
328
329/// The mail-merge destination from `w:destination`.
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum MailMergeDestination {
332    NewDocument,
333    Printer,
334    Email,
335    Fax,
336}
337
338impl MailMergeDestination {
339    fn parse(value: &str) -> Option<Self> {
340        match value {
341            "newDocument" => Some(Self::NewDocument),
342            "printer" => Some(Self::Printer),
343            "email" => Some(Self::Email),
344            "fax" => Some(Self::Fax),
345            _ => None,
346        }
347    }
348
349    fn as_str(self) -> &'static str {
350        match self {
351            Self::NewDocument => "newDocument",
352            Self::Printer => "printer",
353            Self::Email => "email",
354            Self::Fax => "fax",
355        }
356    }
357}
358
359/// The bounded `w:mailMerge` subset this model authors.
360///
361/// `w:dataSource`, `w:headerSource` and `w:odso` stay preservation-only. The
362/// first two carry `r:id` values whose rebasing belongs to the relationship
363/// layer, and `w:odso` is a nested tree no story asked for.
364#[derive(Debug, Clone, PartialEq, Eq, Default)]
365pub struct MailMerge {
366    pub main_document_type: Option<MailMergeDocumentType>,
367    pub link_to_query: Option<bool>,
368    pub data_type: Option<String>,
369    pub connect_string: Option<String>,
370    pub query: Option<String>,
371    pub do_not_suppress_blank_lines: Option<bool>,
372    pub destination: Option<MailMergeDestination>,
373    pub address_field_name: Option<String>,
374    pub mail_subject: Option<String>,
375    pub mail_as_attachment: Option<bool>,
376    pub view_merged_data: Option<bool>,
377    pub active_record: Option<i32>,
378    pub check_errors: Option<i32>,
379}
380
381/// Why a supported settings child produced no typed value.
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum SettingsDiagnosticReason {
384    /// More than one occurrence, so no single occurrence owns the value.
385    Duplicated,
386    /// One occurrence whose attributes did not parse to a typed value.
387    Malformed,
388}
389
390/// One supported settings child the typed model could not own.
391///
392/// A name outside the closed supported set is never a diagnostic, which is what
393/// makes "no unmodeled supported children" decidable rather than open-ended.
394#[derive(Debug, Clone, PartialEq, Eq)]
395pub struct SettingsDiagnostic {
396    /// The path from the part root, such as `["compat", "compatSetting"]`.
397    pub path: Vec<&'static str>,
398    pub occurrences: usize,
399    pub reason: SettingsDiagnosticReason,
400}
401
402/// Occurrence counts for the closed supported set.
403///
404/// Parsing fills it, every setter and remover keeps it current, and both the
405/// diagnostics and the removal ambiguity guard read it. That is one place to
406/// look instead of one counter field per modeled child.
407#[derive(Debug, Clone, PartialEq, Eq, Default)]
408pub(crate) struct Tally {
409    entries: Vec<TallyEntry>,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
413struct TallyEntry {
414    path: Vec<&'static str>,
415    total: usize,
416    typed: usize,
417}
418
419impl Tally {
420    /// Count one occurrence and report the running total for that path.
421    pub(crate) fn record(&mut self, path: &[&'static str], typed: bool) -> usize {
422        let index = match self.entries.iter().position(|entry| entry.path == path) {
423            Some(index) => index,
424            None => {
425                self.entries.push(TallyEntry {
426                    path: path.to_vec(),
427                    total: 0,
428                    typed: 0,
429                });
430                self.entries.len() - 1
431            }
432        };
433        let entry = &mut self.entries[index];
434        entry.total += 1;
435        entry.typed += usize::from(typed);
436        entry.total
437    }
438
439    /// Replace the tally for one path with a single well-formed occurrence, or
440    /// with no occurrence at all.
441    pub(crate) fn set(&mut self, path: &[&'static str], present: bool) {
442        let count = usize::from(present);
443        match self.entries.iter_mut().find(|entry| entry.path == path) {
444            Some(entry) => {
445                entry.total = count;
446                entry.typed = count;
447            }
448            None => {
449                if present {
450                    self.entries.push(TallyEntry {
451                        path: path.to_vec(),
452                        total: 1,
453                        typed: 1,
454                    });
455                }
456            }
457        }
458    }
459
460    pub(crate) fn total(&self, path: &[&str]) -> usize {
461        self.entries
462            .iter()
463            .find(|entry| entry.path == path)
464            .map_or(0, |entry| entry.total)
465    }
466
467    pub(crate) fn typed(&self, path: &[&str]) -> usize {
468        self.entries
469            .iter()
470            .find(|entry| entry.path == path)
471            .map_or(0, |entry| entry.typed)
472    }
473
474    /// Report every supported child the typed model could not own.
475    ///
476    /// `repeatable` names the paths whose schema allows more than one
477    /// occurrence, so repetition there is not a defect.
478    pub(crate) fn diagnostics(&self, repeatable: &[&[&str]]) -> Vec<SettingsDiagnostic> {
479        let mut diagnostics = Vec::new();
480        for entry in &self.entries {
481            let repeats = repeatable.contains(&entry.path.as_slice());
482            if !repeats && entry.total > 1 {
483                diagnostics.push(SettingsDiagnostic {
484                    path: entry.path.clone(),
485                    occurrences: entry.total,
486                    reason: SettingsDiagnosticReason::Duplicated,
487                });
488            } else if entry.typed < entry.total {
489                diagnostics.push(SettingsDiagnostic {
490                    path: entry.path.clone(),
491                    occurrences: entry.total - entry.typed,
492                    reason: SettingsDiagnosticReason::Malformed,
493                });
494            }
495        }
496        diagnostics
497    }
498}
499
500/// Assign a parsed member only while exactly one occurrence owns it.
501///
502/// A second occurrence clears the slot and keeps it clear, because no single
503/// occurrence can then be called the value.
504pub(crate) fn assign<T>(slot: &mut Option<T>, value: Option<T>, total: usize) {
505    *slot = if total == 1 { value } else { None };
506}
507
508/// The typed contents of a Word settings part.
509#[derive(Debug, Clone, PartialEq, Eq, Default)]
510pub struct CT_Settings {
511    view: Option<DocumentView>,
512    zoom: Option<DocumentZoom>,
513    remove_personal_information: Option<bool>,
514    remove_date_and_time: Option<bool>,
515    mirror_margins: Option<bool>,
516    gutter_at_top: Option<bool>,
517    proof_state: Option<DocumentProofState>,
518    link_styles: Option<bool>,
519    mail_merge: Option<MailMerge>,
520    track_revisions: Option<bool>,
521    do_not_track_moves: Option<bool>,
522    do_not_track_formatting: Option<bool>,
523    document_protection: Option<DocumentProtection>,
524    footnote_properties: Option<CT_NoteProperties>,
525    endnote_properties: Option<CT_NoteProperties>,
526    default_tab_stop: Option<Twips>,
527    automatic_hyphenation: Option<bool>,
528    consecutive_hyphen_limit: Option<i32>,
529    hyphenation_zone: Option<Twips>,
530    do_not_hyphenate_caps: Option<bool>,
531    default_table_style: Option<String>,
532    even_and_odd_headers: Option<bool>,
533    book_fold_rev_printing: Option<bool>,
534    book_fold_printing: Option<bool>,
535    book_fold_printing_sheets: Option<i32>,
536    character_spacing_control: Option<CharacterSpacingControl>,
537    update_fields: Option<bool>,
538    compatibility_options: Vec<(CompatibilityOption, bool)>,
539    compatibility_settings: Vec<CompatibilitySetting>,
540    document_variables: Vec<DocumentVariable>,
541    math_properties: Option<MathProperties>,
542    theme_font_language: Option<ThemeFontLanguage>,
543    decimal_symbol: Option<String>,
544    list_separator: Option<String>,
545    tally: Tally,
546    diagnostics: Vec<SettingsDiagnostic>,
547    /// Parsed parts keep their complete producer bytes as the serialization
548    /// source. This retains root attributes, child order, whitespace, and all
549    /// unmodelled content without interpreting it.
550    source_xml: Option<Vec<u8>>,
551}
552
553impl CT_Settings {
554    /// Return the presence-sensitive embedded TrueType font setting.
555    pub fn embed_true_type_fonts(&self) -> Result<Option<bool>> {
556        self.font_flag(b"embedTrueTypeFonts")
557    }
558
559    /// Set or remove only the owned embedded TrueType font setting.
560    pub fn set_embed_true_type_fonts(&mut self, value: Option<bool>) -> Result<()> {
561        self.set_font_flag(b"embedTrueTypeFonts", value)
562    }
563
564    /// Return the presence-sensitive embedded system font setting.
565    pub fn embed_system_fonts(&self) -> Result<Option<bool>> {
566        self.font_flag(b"embedSystemFonts")
567    }
568
569    /// Set or remove only the owned embedded system font setting.
570    pub fn set_embed_system_fonts(&mut self, value: Option<bool>) -> Result<()> {
571        self.set_font_flag(b"embedSystemFonts", value)
572    }
573
574    /// Return the presence-sensitive font subsetting setting.
575    pub fn save_subset_fonts(&self) -> Result<Option<bool>> {
576        self.font_flag(b"saveSubsetFonts")
577    }
578
579    /// Set or remove only the owned font subsetting setting.
580    pub fn set_save_subset_fonts(&mut self, value: Option<bool>) -> Result<()> {
581        self.set_font_flag(b"saveSubsetFonts", value)
582    }
583
584    fn font_flag(&self, local: &[u8]) -> Result<Option<bool>> {
585        let source = self.to_xml()?;
586        let occurrences = find_top_level_children(&source, local)?;
587        if occurrences.len() > 1 {
588            return Err(OxmlError::InvalidValue("ambiguous font setting".to_owned()));
589        }
590        let Some((raw, inherited)) = occurrences.first() else {
591            return Ok(None);
592        };
593        let mut reader = Reader::from_reader(raw.as_slice());
594        match reader.read_event()? {
595            Event::Start(element) | Event::Empty(element) => {
596                let prefixes = word_prefixes_at(&element, inherited)?;
597                let mut values = 0;
598                for attribute in element.attributes() {
599                    if is_word_attribute(attribute?.key.as_ref(), b"val", &prefixes) {
600                        values += 1;
601                    }
602                }
603                if values > 1 {
604                    return Err(OxmlError::InvalidValue(
605                        "ambiguous expanded font setting value".to_owned(),
606                    ));
607                }
608                parse_toggle(&element, &prefixes)?
609                    .map(Some)
610                    .ok_or_else(|| OxmlError::InvalidValue("invalid font setting value".to_owned()))
611            }
612            _ => Err(OxmlError::InvalidValue(
613                "missing font setting root".to_owned(),
614            )),
615        }
616    }
617
618    fn set_font_flag(&mut self, local: &[u8], value: Option<bool>) -> Result<()> {
619        if self.font_flag(local)? == value {
620            return Ok(());
621        }
622        let source = self.to_xml()?;
623        let occurrences = find_top_level_children(&source, local)?;
624        let replacement = match (occurrences.first(), value) {
625            (Some((raw, inherited)), Some(enabled)) => rewrite_word_root_attribute(
626                raw,
627                inherited,
628                b"val",
629                if enabled { "1" } else { "0" },
630            )?,
631            (Some((raw, inherited)), None) => {
632                let mut reader = Reader::from_reader(raw.as_slice());
633                let mut root = true;
634                loop {
635                    match reader.read_event()? {
636                        Event::Start(element) | Event::Empty(element) if root => {
637                            root = false;
638                            let prefixes = word_prefixes_at(&element, inherited)?;
639                            for attribute in element.attributes() {
640                                let attribute = attribute?;
641                                if attribute.key.as_ref() != b"xmlns"
642                                    && !attribute.key.as_ref().starts_with(b"xmlns:")
643                                    && !is_word_attribute(attribute.key.as_ref(), b"val", &prefixes)
644                                {
645                                    return Err(OxmlError::InvalidValue(
646                                        "cannot delete opaque font setting attributes".to_owned(),
647                                    ));
648                                }
649                            }
650                        }
651                        Event::End(_) => {}
652                        Event::Text(text) if text.as_ref().iter().all(u8::is_ascii_whitespace) => {}
653                        Event::Eof => break,
654                        _ => {
655                            return Err(OxmlError::InvalidValue(
656                                "cannot delete opaque font setting payload".to_owned(),
657                            ));
658                        }
659                    }
660                }
661                Vec::new()
662            }
663            (None, Some(enabled)) => {
664                write_toggle_setting(&format!("w:{}", std::str::from_utf8(local)?), enabled)?
665            }
666            (None, None) => return Ok(()),
667        };
668        self.source_xml = Some(rewrite_ordered_child(
669            &source,
670            SETTINGS_ORDER,
671            local,
672            &replacement,
673            is_word_element,
674        )?);
675        Ok(())
676    }
677
678    /// Change only the owned background-display toggle, retaining its opaque payload.
679    pub fn set_display_background_shape(&mut self, enabled: bool) -> Result<()> {
680        let source = self.to_xml()?;
681        let occurrences = find_top_level_children(&source, b"displayBackgroundShape")?;
682        if occurrences.len() > 1 {
683            return Err(OxmlError::InvalidValue(
684                "cannot rewrite ambiguous w:displayBackgroundShape".to_owned(),
685            ));
686        }
687        let replacement = match occurrences.first() {
688            Some((raw, inherited)) => rewrite_word_root_attribute(
689                raw,
690                inherited,
691                b"val",
692                if enabled { "1" } else { "0" },
693            )?,
694            None => write_toggle_setting("w:displayBackgroundShape", enabled)?,
695        };
696        self.source_xml = Some(rewrite_ordered_child(
697            &source,
698            SETTINGS_ORDER,
699            b"displayBackgroundShape",
700            &replacement,
701            is_word_element,
702        )?);
703        Ok(())
704    }
705
706    pub fn new() -> Self {
707        Self::default()
708    }
709
710    /// Parse a complete Word settings part.
711    pub fn from_xml(xml: &[u8]) -> Result<Self> {
712        let mut reader = Reader::from_reader(xml);
713        reader.config_mut().trim_text(false);
714        let mut model = Self::default();
715        let mut root_prefixes = Vec::new();
716        let mut group: Option<(&'static str, usize, Vec<String>)> = None;
717        let mut mail_merge = MailMerge::default();
718        let mut saw_root = false;
719        let mut depth = 0usize;
720        let mut buffer = Vec::new();
721
722        loop {
723            let event = reader.read_event_into(&mut buffer)?;
724            match event {
725                Event::Start(ref element) | Event::Empty(ref element) => {
726                    let started = matches!(event, Event::Start(_));
727                    let inherited = match &group {
728                        Some((_, _, prefixes)) => prefixes,
729                        None => &root_prefixes,
730                    };
731                    let prefixes = word_prefixes_at(element, inherited)?;
732                    if !saw_root {
733                        if !is_word_element(element.name().as_ref(), b"settings", &prefixes) {
734                            return Err(OxmlError::MissingElement("settings root".to_owned()));
735                        }
736                        root_prefixes = prefixes;
737                        saw_root = true;
738                        depth = usize::from(started);
739                        buffer.clear();
740                        continue;
741                    }
742                    if depth == 1 && is_math_element(element.name().as_ref(), b"mathPr", &prefixes)
743                    {
744                        let raw = if started {
745                            capture_element(&mut reader, element)?
746                        } else {
747                            capture_empty_element(element)?
748                        };
749                        let bindings = namespace_bindings(&prefixes);
750                        let parsed = if fixed_math_prefix_is_safe(&raw, &bindings)? {
751                            Some(MathProperties::from_raw(&raw, &bindings)?)
752                        } else {
753                            None
754                        };
755                        let total = model.tally.record(&["mathPr"], parsed.is_some());
756                        assign(&mut model.math_properties, parsed, total);
757                        buffer.clear();
758                        continue;
759                    }
760                    if depth == 1
761                        && (is_word_element(element.name().as_ref(), b"footnotePr", &prefixes)
762                            || is_word_element(element.name().as_ref(), b"endnotePr", &prefixes))
763                    {
764                        let footnote =
765                            is_word_element(element.name().as_ref(), b"footnotePr", &prefixes);
766                        let name = if footnote { "footnotePr" } else { "endnotePr" };
767                        let properties = if started {
768                            CT_SectPr::parse_note_properties(
769                                &mut reader,
770                                &prefixes,
771                                &[],
772                                name.as_bytes(),
773                            )?
774                        } else {
775                            CT_NoteProperties::default()
776                        };
777                        let total = model.tally.record(&[name], true);
778                        let slot = if footnote {
779                            &mut model.footnote_properties
780                        } else {
781                            &mut model.endnote_properties
782                        };
783                        assign(slot, Some(properties), total);
784                        buffer.clear();
785                        continue;
786                    }
787                    if depth == 1 {
788                        model.absorb_top_level(element, &prefixes, &mut group, depth)?;
789                        if !started {
790                            // A self-closing group has no children, so its
791                            // namespace scope must not leak onto its siblings.
792                            group = None;
793                        }
794                    } else if let Some((name, child_depth, _)) = &group
795                        && *child_depth == depth
796                    {
797                        let name = *name;
798                        model.absorb_group_child(name, element, &prefixes, &mut mail_merge)?;
799                    }
800                    if started {
801                        depth += 1;
802                    }
803                }
804                Event::End(_) if depth > 0 => {
805                    if let Some((_, child_depth, _)) = &group
806                        && *child_depth == depth
807                    {
808                        group = None;
809                    }
810                    depth -= 1;
811                }
812                Event::Eof => break,
813                _ => {}
814            }
815            buffer.clear();
816        }
817
818        if !saw_root {
819            return Err(OxmlError::MissingElement("settings root".to_owned()));
820        }
821        if model.tally.total(&["mailMerge"]) == 1 {
822            model.mail_merge = Some(mail_merge);
823        }
824        model.compatibility_options.sort_by_key(|entry| entry.0);
825        model.diagnostics = model.tally.diagnostics(SETTINGS_REPEATABLE);
826        model.source_xml = Some(xml.to_vec());
827        Ok(model)
828    }
829
830    /// Project one top-level settings child into its typed member.
831    fn absorb_top_level(
832        &mut self,
833        element: &BytesStart<'_>,
834        prefixes: &[String],
835        group: &mut Option<(&'static str, usize, Vec<String>)>,
836        depth: usize,
837    ) -> Result<()> {
838        let Some(name) = supported_name(SUPPORTED_SETTINGS, element, prefixes) else {
839            return Ok(());
840        };
841        match name {
842            "view" => {
843                let value = word_value(element, prefixes).and_then(|v| DocumentView::parse(&v));
844                let total = self.tally.record(&[name], value.is_some());
845                assign(&mut self.view, value, total);
846            }
847            "zoom" => {
848                let value = parse_zoom(element, prefixes);
849                let total = self.tally.record(&[name], value.is_some());
850                assign(&mut self.zoom, value, total);
851            }
852            "removePersonalInformation" => {
853                let value = parse_toggle(element, prefixes)?;
854                let total = self.tally.record(&[name], value.is_some());
855                assign(&mut self.remove_personal_information, value, total);
856            }
857            "removeDateAndTime" => {
858                let value = parse_toggle(element, prefixes)?;
859                let total = self.tally.record(&[name], value.is_some());
860                assign(&mut self.remove_date_and_time, value, total);
861            }
862            "mirrorMargins" => {
863                let value = parse_toggle(element, prefixes)?;
864                let total = self.tally.record(&[name], value.is_some());
865                assign(&mut self.mirror_margins, value, total);
866            }
867            "gutterAtTop" => {
868                let value = parse_toggle(element, prefixes)?;
869                let total = self.tally.record(&[name], value.is_some());
870                assign(&mut self.gutter_at_top, value, total);
871            }
872            "proofState" => {
873                let value = parse_proof_state(element, prefixes);
874                let total = self.tally.record(&[name], value.is_some());
875                assign(&mut self.proof_state, value, total);
876            }
877            "linkStyles" => {
878                let value = parse_toggle(element, prefixes)?;
879                let total = self.tally.record(&[name], value.is_some());
880                assign(&mut self.link_styles, value, total);
881            }
882            "mailMerge" => {
883                self.tally.record(&[name], true);
884                *group = Some(("mailMerge", depth + 1, prefixes.to_vec()));
885            }
886            "trackRevisions" => {
887                let value = parse_toggle(element, prefixes)?;
888                let total = self.tally.record(&[name], value.is_some());
889                assign(&mut self.track_revisions, value, total);
890            }
891            "doNotTrackMoves" => {
892                let value = parse_toggle(element, prefixes)?;
893                let total = self.tally.record(&[name], value.is_some());
894                assign(&mut self.do_not_track_moves, value, total);
895            }
896            "doNotTrackFormatting" => {
897                let value = parse_toggle(element, prefixes)?;
898                let total = self.tally.record(&[name], value.is_some());
899                assign(&mut self.do_not_track_formatting, value, total);
900            }
901            "documentProtection" => {
902                let value = parse_document_protection(element, prefixes);
903                let total = self.tally.record(&[name], value.is_some());
904                assign(&mut self.document_protection, value, total);
905            }
906            "defaultTabStop" => {
907                let value = parse_twips(element, prefixes);
908                let total = self.tally.record(&[name], value.is_some());
909                assign(&mut self.default_tab_stop, value, total);
910            }
911            "autoHyphenation" => {
912                let value = parse_toggle(element, prefixes)?;
913                let total = self.tally.record(&[name], value.is_some());
914                assign(&mut self.automatic_hyphenation, value, total);
915            }
916            "consecutiveHyphenLimit" => {
917                let value = parse_decimal(element, prefixes);
918                let total = self.tally.record(&[name], value.is_some());
919                assign(&mut self.consecutive_hyphen_limit, value, total);
920            }
921            "hyphenationZone" => {
922                let value = parse_twips(element, prefixes);
923                let total = self.tally.record(&[name], value.is_some());
924                assign(&mut self.hyphenation_zone, value, total);
925            }
926            "doNotHyphenateCaps" => {
927                let value = parse_toggle(element, prefixes)?;
928                let total = self.tally.record(&[name], value.is_some());
929                assign(&mut self.do_not_hyphenate_caps, value, total);
930            }
931            "defaultTableStyle" => {
932                let value = word_value(element, prefixes);
933                let total = self.tally.record(&[name], value.is_some());
934                assign(&mut self.default_table_style, value, total);
935            }
936            "evenAndOddHeaders" => {
937                let value = parse_toggle(element, prefixes)?;
938                let total = self.tally.record(&[name], value.is_some());
939                assign(&mut self.even_and_odd_headers, value, total);
940            }
941            "bookFoldRevPrinting" => {
942                let value = parse_toggle(element, prefixes)?;
943                let total = self.tally.record(&[name], value.is_some());
944                assign(&mut self.book_fold_rev_printing, value, total);
945            }
946            "bookFoldPrinting" => {
947                let value = parse_toggle(element, prefixes)?;
948                let total = self.tally.record(&[name], value.is_some());
949                assign(&mut self.book_fold_printing, value, total);
950            }
951            "bookFoldPrintingSheets" => {
952                let value = parse_decimal(element, prefixes);
953                let total = self.tally.record(&[name], value.is_some());
954                assign(&mut self.book_fold_printing_sheets, value, total);
955            }
956            "characterSpacingControl" => {
957                let value =
958                    word_value(element, prefixes).and_then(|v| CharacterSpacingControl::parse(&v));
959                let total = self.tally.record(&[name], value.is_some());
960                assign(&mut self.character_spacing_control, value, total);
961            }
962            "updateFields" => {
963                let value = parse_toggle(element, prefixes)?;
964                let total = self.tally.record(&[name], value.is_some());
965                assign(&mut self.update_fields, value, total);
966            }
967            "compat" => {
968                self.tally.record(&[name], true);
969                *group = Some(("compat", depth + 1, prefixes.to_vec()));
970            }
971            "docVars" => {
972                self.tally.record(&[name], true);
973                *group = Some(("docVars", depth + 1, prefixes.to_vec()));
974            }
975            "themeFontLang" => {
976                let value = Some(parse_theme_font_language(element, prefixes)?);
977                let total = self.tally.record(&[name], value.is_some());
978                assign(&mut self.theme_font_language, value, total);
979            }
980            "decimalSymbol" => {
981                let value = word_value(element, prefixes);
982                let total = self.tally.record(&[name], value.is_some());
983                assign(&mut self.decimal_symbol, value, total);
984            }
985            "listSeparator" => {
986                let value = word_value(element, prefixes);
987                let total = self.tally.record(&[name], value.is_some());
988                assign(&mut self.list_separator, value, total);
989            }
990            // `mathPr` lives in the OfficeMath namespace and is matched before
991            // the supported-name lookup runs, so it never reaches this match.
992            _ => {}
993        }
994        Ok(())
995    }
996
997    /// Project one child of `w:compat`, `w:docVars` or `w:mailMerge`.
998    fn absorb_group_child(
999        &mut self,
1000        group: &str,
1001        element: &BytesStart<'_>,
1002        prefixes: &[String],
1003        mail_merge: &mut MailMerge,
1004    ) -> Result<()> {
1005        match group {
1006            "docVars" => {
1007                if is_word_element(element.name().as_ref(), b"docVar", prefixes) {
1008                    let variable = parse_document_variable(element, prefixes);
1009                    self.tally
1010                        .record(&["docVars", "docVar"], variable.is_some());
1011                    if let Some(variable) = variable {
1012                        self.document_variables.push(variable);
1013                    }
1014                }
1015            }
1016            "compat" => {
1017                if is_word_element(element.name().as_ref(), b"compatSetting", prefixes) {
1018                    let setting = parse_compatibility_setting(element, prefixes);
1019                    self.tally
1020                        .record(&["compat", "compatSetting"], setting.is_some());
1021                    if let Some(setting) = setting {
1022                        self.compatibility_settings.push(setting);
1023                    }
1024                } else if let Some(option) = CompatibilityOption::find(element, prefixes) {
1025                    let value = parse_toggle(element, prefixes)?;
1026                    let total = self
1027                        .tally
1028                        .record(&["compat", option.local_name()], value.is_some());
1029                    self.compatibility_options
1030                        .retain(|(existing, _)| *existing != option);
1031                    if total == 1
1032                        && let Some(value) = value
1033                    {
1034                        self.compatibility_options.push((option, value));
1035                    }
1036                }
1037            }
1038            "mailMerge" => {
1039                let Some(name) = supported_name(MAIL_MERGE_MEMBERS, element, prefixes) else {
1040                    return Ok(());
1041                };
1042                match name {
1043                    "mainDocumentType" => {
1044                        let value = word_value(element, prefixes)
1045                            .and_then(|v| MailMergeDocumentType::parse(&v));
1046                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1047                        assign(&mut mail_merge.main_document_type, value, total);
1048                    }
1049                    "linkToQuery" => {
1050                        let value = parse_toggle(element, prefixes)?;
1051                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1052                        assign(&mut mail_merge.link_to_query, value, total);
1053                    }
1054                    "dataType" => {
1055                        let value = word_value(element, prefixes);
1056                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1057                        assign(&mut mail_merge.data_type, value, total);
1058                    }
1059                    "connectString" => {
1060                        let value = word_value(element, prefixes);
1061                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1062                        assign(&mut mail_merge.connect_string, value, total);
1063                    }
1064                    "query" => {
1065                        let value = word_value(element, prefixes);
1066                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1067                        assign(&mut mail_merge.query, value, total);
1068                    }
1069                    "doNotSuppressBlankLines" => {
1070                        let value = parse_toggle(element, prefixes)?;
1071                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1072                        assign(&mut mail_merge.do_not_suppress_blank_lines, value, total);
1073                    }
1074                    "destination" => {
1075                        let value = word_value(element, prefixes)
1076                            .and_then(|v| MailMergeDestination::parse(&v));
1077                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1078                        assign(&mut mail_merge.destination, value, total);
1079                    }
1080                    "addressFieldName" => {
1081                        let value = word_value(element, prefixes);
1082                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1083                        assign(&mut mail_merge.address_field_name, value, total);
1084                    }
1085                    "mailSubject" => {
1086                        let value = word_value(element, prefixes);
1087                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1088                        assign(&mut mail_merge.mail_subject, value, total);
1089                    }
1090                    "mailAsAttachment" => {
1091                        let value = parse_toggle(element, prefixes)?;
1092                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1093                        assign(&mut mail_merge.mail_as_attachment, value, total);
1094                    }
1095                    "viewMergedData" => {
1096                        let value = parse_toggle(element, prefixes)?;
1097                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1098                        assign(&mut mail_merge.view_merged_data, value, total);
1099                    }
1100                    "activeRecord" => {
1101                        let value = parse_decimal(element, prefixes);
1102                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1103                        assign(&mut mail_merge.active_record, value, total);
1104                    }
1105                    "checkErrors" => {
1106                        let value = parse_decimal(element, prefixes);
1107                        let total = self.tally.record(&["mailMerge", name], value.is_some());
1108                        assign(&mut mail_merge.check_errors, value, total);
1109                    }
1110                    _ => {}
1111                }
1112            }
1113            _ => {}
1114        }
1115        Ok(())
1116    }
1117}
1118
1119impl CT_Settings {
1120    /// Document-wide footnote policy, before a section override.
1121    pub fn footnote_properties(&self) -> Option<&CT_NoteProperties> {
1122        self.footnote_properties.as_ref()
1123    }
1124
1125    /// Document-wide endnote policy, before a section override.
1126    pub fn endnote_properties(&self) -> Option<&CT_NoteProperties> {
1127        self.endnote_properties.as_ref()
1128    }
1129
1130    /// Replace document-wide footnote policy in schema order.
1131    pub fn set_footnote_properties(&mut self, properties: CT_NoteProperties) -> Result<()> {
1132        let mut writer = Writer::new(Vec::new());
1133        properties.to_xml(&mut writer, "w:footnotePr")?;
1134        self.finish_set("footnotePr", writer.into_inner())?;
1135        self.footnote_properties = Some(properties);
1136        Ok(())
1137    }
1138
1139    /// Replace document-wide endnote policy in schema order.
1140    pub fn set_endnote_properties(&mut self, properties: CT_NoteProperties) -> Result<()> {
1141        let mut writer = Writer::new(Vec::new());
1142        properties.to_xml(&mut writer, "w:endnotePr")?;
1143        self.finish_set("endnotePr", writer.into_inner())?;
1144        self.endnote_properties = Some(properties);
1145        Ok(())
1146    }
1147
1148    /// Remove document-wide footnote policy.
1149    pub fn remove_footnote_properties(&mut self) -> Result<Option<CT_NoteProperties>> {
1150        if !self.begin_removal("footnotePr")? {
1151            return Ok(None);
1152        }
1153        self.finish_removal("footnotePr")?;
1154        Ok(self.footnote_properties.take())
1155    }
1156
1157    /// Remove document-wide endnote policy.
1158    pub fn remove_endnote_properties(&mut self) -> Result<Option<CT_NoteProperties>> {
1159        if !self.begin_removal("endnotePr")? {
1160            return Ok(None);
1161        }
1162        self.finish_removal("endnotePr")?;
1163        Ok(self.endnote_properties.take())
1164    }
1165
1166    /// Report every supported child the typed model could not own.
1167    ///
1168    /// A package authored entirely through the public API can only produce
1169    /// well-formed single occurrences, so its list is empty by construction.
1170    pub fn diagnostics(&self) -> &[SettingsDiagnostic] {
1171        &self.diagnostics
1172    }
1173
1174    /// Replace one modeled top-level child at its schema position.
1175    fn rewrite_scalar(&mut self, local: &[u8], replacement: Vec<u8>) -> Result<()> {
1176        if let Some(source) = &self.source_xml {
1177            self.source_xml = Some(rewrite_ordered_child(
1178                source,
1179                SETTINGS_ORDER,
1180                local,
1181                &replacement,
1182                is_word_element,
1183            )?);
1184        }
1185        Ok(())
1186    }
1187
1188    /// Record one well-formed occurrence and write it at its schema position.
1189    fn finish_set(&mut self, name: &'static str, replacement: Vec<u8>) -> Result<()> {
1190        self.rewrite_scalar(name.as_bytes(), replacement)?;
1191        self.tally.set(&[name], true);
1192        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1193        Ok(())
1194    }
1195
1196    /// Report whether one modeled top-level child can be removed.
1197    ///
1198    /// Removal is never allowed to reinterpret an unowned occurrence, so a
1199    /// duplicated or malformed member is an error rather than a silent rewrite.
1200    fn begin_removal(&mut self, name: &'static str) -> Result<bool> {
1201        let total = self.tally.total(&[name]);
1202        if total == 0 {
1203            return Ok(false);
1204        }
1205        if total > 1 || self.tally.typed(&[name]) != total {
1206            return Err(OxmlError::InvalidValue(format!(
1207                "cannot rewrite ambiguous or malformed w:{name}"
1208            )));
1209        }
1210        Ok(true)
1211    }
1212
1213    /// Delete the single modeled occurrence and leave every neighbour alone.
1214    fn finish_removal(&mut self, name: &'static str) -> Result<()> {
1215        self.rewrite_scalar(name.as_bytes(), Vec::new())?;
1216        self.tally.set(&[name], false);
1217        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1218        Ok(())
1219    }
1220
1221    /// Return the document view Word opens with.
1222    pub fn view(&self) -> Option<DocumentView> {
1223        self.view
1224    }
1225
1226    pub fn set_view(&mut self, value: DocumentView) -> Result<()> {
1227        self.view = Some(value);
1228        self.finish_set("view", write_valued_setting("w:view", value.as_str())?)
1229    }
1230
1231    pub fn remove_view(&mut self) -> Result<Option<DocumentView>> {
1232        if !self.begin_removal("view")? {
1233            return Ok(None);
1234        }
1235        let removed = self.view.take();
1236        self.finish_removal("view")?;
1237        Ok(removed)
1238    }
1239
1240    /// Return the magnification preset and percentage.
1241    pub fn zoom(&self) -> Option<DocumentZoom> {
1242        self.zoom
1243    }
1244
1245    pub fn set_zoom(&mut self, value: DocumentZoom) -> Result<()> {
1246        let replacement = write_zoom(&value)?;
1247        self.zoom = Some(value);
1248        self.finish_set("zoom", replacement)
1249    }
1250
1251    pub fn remove_zoom(&mut self) -> Result<Option<DocumentZoom>> {
1252        if !self.begin_removal("zoom")? {
1253            return Ok(None);
1254        }
1255        let removed = self.zoom.take();
1256        self.finish_removal("zoom")?;
1257        Ok(removed)
1258    }
1259
1260    /// Return the `w:removePersonalInformation` toggle.
1261    pub fn remove_personal_information(&self) -> Option<bool> {
1262        self.remove_personal_information
1263    }
1264
1265    pub fn set_remove_personal_information(&mut self, enabled: bool) -> Result<()> {
1266        self.remove_personal_information = Some(enabled);
1267        self.finish_set(
1268            "removePersonalInformation",
1269            write_toggle_setting("w:removePersonalInformation", enabled)?,
1270        )
1271    }
1272
1273    /// Remove the `w:removePersonalInformation` toggle itself.
1274    pub fn remove_remove_personal_information(&mut self) -> Result<Option<bool>> {
1275        if !self.begin_removal("removePersonalInformation")? {
1276            return Ok(None);
1277        }
1278        let removed = self.remove_personal_information.take();
1279        self.finish_removal("removePersonalInformation")?;
1280        Ok(removed)
1281    }
1282
1283    /// Return the `w:removeDateAndTime` toggle.
1284    pub fn remove_date_and_time(&self) -> Option<bool> {
1285        self.remove_date_and_time
1286    }
1287
1288    pub fn set_remove_date_and_time(&mut self, enabled: bool) -> Result<()> {
1289        self.remove_date_and_time = Some(enabled);
1290        self.finish_set(
1291            "removeDateAndTime",
1292            write_toggle_setting("w:removeDateAndTime", enabled)?,
1293        )
1294    }
1295
1296    /// Remove the `w:removeDateAndTime` toggle itself.
1297    pub fn remove_remove_date_and_time(&mut self) -> Result<Option<bool>> {
1298        if !self.begin_removal("removeDateAndTime")? {
1299            return Ok(None);
1300        }
1301        let removed = self.remove_date_and_time.take();
1302        self.finish_removal("removeDateAndTime")?;
1303        Ok(removed)
1304    }
1305
1306    /// Return the `w:mirrorMargins` toggle.
1307    pub fn mirror_margins(&self) -> Option<bool> {
1308        self.mirror_margins
1309    }
1310
1311    pub fn set_mirror_margins(&mut self, enabled: bool) -> Result<()> {
1312        self.mirror_margins = Some(enabled);
1313        self.finish_set(
1314            "mirrorMargins",
1315            write_toggle_setting("w:mirrorMargins", enabled)?,
1316        )
1317    }
1318
1319    pub fn remove_mirror_margins(&mut self) -> Result<Option<bool>> {
1320        if !self.begin_removal("mirrorMargins")? {
1321            return Ok(None);
1322        }
1323        let removed = self.mirror_margins.take();
1324        self.finish_removal("mirrorMargins")?;
1325        Ok(removed)
1326    }
1327
1328    /// Return the `w:gutterAtTop` toggle.
1329    pub fn gutter_at_top(&self) -> Option<bool> {
1330        self.gutter_at_top
1331    }
1332
1333    pub fn set_gutter_at_top(&mut self, enabled: bool) -> Result<()> {
1334        self.gutter_at_top = Some(enabled);
1335        self.finish_set(
1336            "gutterAtTop",
1337            write_toggle_setting("w:gutterAtTop", enabled)?,
1338        )
1339    }
1340
1341    pub fn remove_gutter_at_top(&mut self) -> Result<Option<bool>> {
1342        if !self.begin_removal("gutterAtTop")? {
1343            return Ok(None);
1344        }
1345        let removed = self.gutter_at_top.take();
1346        self.finish_removal("gutterAtTop")?;
1347        Ok(removed)
1348    }
1349
1350    /// Return the spelling and grammar proofing state.
1351    pub fn proof_state(&self) -> Option<DocumentProofState> {
1352        self.proof_state
1353    }
1354
1355    pub fn set_proof_state(&mut self, value: DocumentProofState) -> Result<()> {
1356        let replacement = write_proof_state(&value)?;
1357        self.proof_state = Some(value);
1358        self.finish_set("proofState", replacement)
1359    }
1360
1361    pub fn remove_proof_state(&mut self) -> Result<Option<DocumentProofState>> {
1362        if !self.begin_removal("proofState")? {
1363            return Ok(None);
1364        }
1365        let removed = self.proof_state.take();
1366        self.finish_removal("proofState")?;
1367        Ok(removed)
1368    }
1369
1370    /// Return the `w:linkStyles` toggle.
1371    pub fn link_styles(&self) -> Option<bool> {
1372        self.link_styles
1373    }
1374
1375    pub fn set_link_styles(&mut self, enabled: bool) -> Result<()> {
1376        self.link_styles = Some(enabled);
1377        self.finish_set("linkStyles", write_toggle_setting("w:linkStyles", enabled)?)
1378    }
1379
1380    pub fn remove_link_styles(&mut self) -> Result<Option<bool>> {
1381        if !self.begin_removal("linkStyles")? {
1382            return Ok(None);
1383        }
1384        let removed = self.link_styles.take();
1385        self.finish_removal("linkStyles")?;
1386        Ok(removed)
1387    }
1388
1389    /// Return the bounded mail-merge configuration.
1390    pub fn mail_merge(&self) -> Option<&MailMerge> {
1391        self.mail_merge.as_ref()
1392    }
1393
1394    /// Replace the modeled mail-merge members at their schema positions.
1395    ///
1396    /// `w:dataSource`, `w:headerSource` and `w:odso` keep their producer bytes,
1397    /// which is why each modeled member is written in place rather than the
1398    /// group being replaced whole.
1399    pub fn set_mail_merge(&mut self, value: MailMerge) -> Result<()> {
1400        let children = mail_merge_children(&value)?;
1401        if let Some(source) = &self.source_xml {
1402            let mut rewritten = ensure_top_level_group(source, b"mailMerge")?;
1403            for (local, replacement) in &children {
1404                rewritten = rewrite_group_child(
1405                    &rewritten,
1406                    b"mailMerge",
1407                    local.as_bytes(),
1408                    replacement,
1409                    MAIL_MERGE_ORDER,
1410                )?;
1411            }
1412            self.source_xml = Some(rewritten);
1413        }
1414        self.tally.set(&["mailMerge"], true);
1415        for (local, replacement) in &children {
1416            self.tally
1417                .set(&["mailMerge", local], !replacement.is_empty());
1418        }
1419        self.mail_merge = Some(value);
1420        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1421        Ok(())
1422    }
1423
1424    /// Remove every modeled mail-merge member.
1425    ///
1426    /// The group itself goes only when nothing preserved is left inside it,
1427    /// because `CT_MailMerge` requires members this model does not author.
1428    pub fn remove_mail_merge(&mut self) -> Result<Option<MailMerge>> {
1429        let total = self.tally.total(&["mailMerge"]);
1430        if total == 0 {
1431            return Ok(None);
1432        }
1433        if total > 1 {
1434            return Err(OxmlError::InvalidValue(
1435                "cannot rewrite ambiguous or malformed w:mailMerge".to_owned(),
1436            ));
1437        }
1438        let removed = self.mail_merge.take();
1439        let mut group_remains = false;
1440        if let Some(source) = &self.source_xml {
1441            let mut rewritten = source.clone();
1442            for local in MAIL_MERGE_MEMBERS {
1443                rewritten = rewrite_group_child(
1444                    &rewritten,
1445                    b"mailMerge",
1446                    local.as_bytes(),
1447                    &[],
1448                    MAIL_MERGE_ORDER,
1449                )?;
1450            }
1451            if top_level_group_is_childless(&rewritten, b"mailMerge")? {
1452                rewritten = rewrite_ordered_child(
1453                    &rewritten,
1454                    SETTINGS_ORDER,
1455                    b"mailMerge",
1456                    &[],
1457                    is_word_element,
1458                )?;
1459            } else {
1460                group_remains = true;
1461            }
1462            self.source_xml = Some(rewritten);
1463        }
1464        for local in MAIL_MERGE_MEMBERS {
1465            self.tally.set(&["mailMerge", local], false);
1466        }
1467        self.tally.set(&["mailMerge"], group_remains);
1468        if group_remains {
1469            self.mail_merge = Some(MailMerge::default());
1470        }
1471        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1472        Ok(removed)
1473    }
1474
1475    /// Return the `w:trackRevisions` toggle.
1476    pub fn track_revisions(&self) -> Option<bool> {
1477        self.track_revisions
1478    }
1479
1480    pub fn set_track_revisions(&mut self, enabled: bool) -> Result<()> {
1481        self.track_revisions = Some(enabled);
1482        self.finish_set(
1483            "trackRevisions",
1484            write_toggle_setting("w:trackRevisions", enabled)?,
1485        )
1486    }
1487
1488    pub fn remove_track_revisions(&mut self) -> Result<Option<bool>> {
1489        if !self.begin_removal("trackRevisions")? {
1490            return Ok(None);
1491        }
1492        let removed = self.track_revisions.take();
1493        self.finish_removal("trackRevisions")?;
1494        Ok(removed)
1495    }
1496
1497    /// Return the `w:doNotTrackMoves` toggle.
1498    pub fn do_not_track_moves(&self) -> Option<bool> {
1499        self.do_not_track_moves
1500    }
1501
1502    pub fn set_do_not_track_moves(&mut self, enabled: bool) -> Result<()> {
1503        self.do_not_track_moves = Some(enabled);
1504        self.finish_set(
1505            "doNotTrackMoves",
1506            write_toggle_setting("w:doNotTrackMoves", enabled)?,
1507        )
1508    }
1509
1510    pub fn remove_do_not_track_moves(&mut self) -> Result<Option<bool>> {
1511        if !self.begin_removal("doNotTrackMoves")? {
1512            return Ok(None);
1513        }
1514        let removed = self.do_not_track_moves.take();
1515        self.finish_removal("doNotTrackMoves")?;
1516        Ok(removed)
1517    }
1518
1519    /// Return the `w:doNotTrackFormatting` toggle.
1520    pub fn do_not_track_formatting(&self) -> Option<bool> {
1521        self.do_not_track_formatting
1522    }
1523
1524    pub fn set_do_not_track_formatting(&mut self, enabled: bool) -> Result<()> {
1525        self.do_not_track_formatting = Some(enabled);
1526        self.finish_set(
1527            "doNotTrackFormatting",
1528            write_toggle_setting("w:doNotTrackFormatting", enabled)?,
1529        )
1530    }
1531
1532    pub fn remove_do_not_track_formatting(&mut self) -> Result<Option<bool>> {
1533        if !self.begin_removal("doNotTrackFormatting")? {
1534            return Ok(None);
1535        }
1536        let removed = self.do_not_track_formatting.take();
1537        self.finish_removal("doNotTrackFormatting")?;
1538        Ok(removed)
1539    }
1540
1541    /// Return valid document-protection metadata, when the part records it.
1542    pub fn document_protection(&self) -> Option<&DocumentProtection> {
1543        self.document_protection.as_ref()
1544    }
1545
1546    /// Record caller-supplied document-protection metadata verbatim.
1547    ///
1548    /// Nothing here derives a hash, chooses a salt or picks a spin count.
1549    pub fn set_document_protection(&mut self, value: DocumentProtection) -> Result<()> {
1550        let replacement = write_document_protection(&value)?;
1551        self.document_protection = Some(value);
1552        self.finish_set("documentProtection", replacement)
1553    }
1554
1555    pub fn remove_document_protection(&mut self) -> Result<Option<DocumentProtection>> {
1556        if !self.begin_removal("documentProtection")? {
1557            return Ok(None);
1558        }
1559        let removed = self.document_protection.take();
1560        self.finish_removal("documentProtection")?;
1561        Ok(removed)
1562    }
1563
1564    /// Return every valid document variable in package order.
1565    pub fn document_variables(&self) -> &[DocumentVariable] {
1566        &self.document_variables
1567    }
1568
1569    pub fn document_variable(&self, name: &str) -> Option<&str> {
1570        self.document_variables
1571            .iter()
1572            .find(|variable| variable.name == name)
1573            .map(|variable| variable.value.as_str())
1574    }
1575
1576    pub fn set_document_variable(&mut self, name: String, value: String) -> Result<()> {
1577        if let Some(index) = self
1578            .document_variables
1579            .iter()
1580            .position(|variable| variable.name == name)
1581        {
1582            self.document_variables[index].value = value;
1583            let mut occurrence = 0usize;
1584            self.document_variables.retain(|variable| {
1585                if variable.name != name {
1586                    return true;
1587                }
1588                occurrence += 1;
1589                occurrence == 1
1590            });
1591        } else {
1592            self.document_variables
1593                .push(DocumentVariable { name, value });
1594        }
1595        self.rewrite_group_list(b"docVars", b"docVar")?;
1596        self.tally.set(&["docVars"], true);
1597        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1598        Ok(())
1599    }
1600
1601    pub fn remove_document_variable(&mut self, name: &str) -> Result<Option<DocumentVariable>> {
1602        let Some(removed) = self
1603            .document_variables
1604            .iter()
1605            .find(|variable| variable.name == name)
1606            .cloned()
1607        else {
1608            return Ok(None);
1609        };
1610        self.document_variables
1611            .retain(|variable| variable.name != name);
1612        self.rewrite_group_list(b"docVars", b"docVar")?;
1613        Ok(Some(removed))
1614    }
1615
1616    pub fn compatibility_settings(&self) -> &[CompatibilitySetting] {
1617        &self.compatibility_settings
1618    }
1619
1620    pub fn set_compatibility_setting(&mut self, setting: CompatibilitySetting) -> Result<()> {
1621        if let Some(index) = self
1622            .compatibility_settings
1623            .iter()
1624            .position(|existing| existing.name == setting.name && existing.uri == setting.uri)
1625        {
1626            self.compatibility_settings[index] = setting.clone();
1627            let mut occurrence = 0usize;
1628            self.compatibility_settings.retain(|existing| {
1629                if existing.name != setting.name || existing.uri != setting.uri {
1630                    return true;
1631                }
1632                occurrence += 1;
1633                occurrence == 1
1634            });
1635        } else {
1636            self.compatibility_settings.push(setting);
1637        }
1638        self.rewrite_group_list(b"compat", b"compatSetting")?;
1639        self.tally.set(&["compat"], true);
1640        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1641        Ok(())
1642    }
1643
1644    pub fn remove_compatibility_setting(
1645        &mut self,
1646        name: &str,
1647        uri: &str,
1648    ) -> Result<Option<CompatibilitySetting>> {
1649        let Some(removed) = self
1650            .compatibility_settings
1651            .iter()
1652            .find(|setting| setting.name == name && setting.uri == uri)
1653            .cloned()
1654        else {
1655            return Ok(None);
1656        };
1657        self.compatibility_settings
1658            .retain(|setting| setting.name != name || setting.uri != uri);
1659        self.rewrite_group_list(b"compat", b"compatSetting")?;
1660        Ok(Some(removed))
1661    }
1662
1663    /// Replace every modeled occurrence of one repeated group child.
1664    fn rewrite_group_list(&mut self, group_local: &[u8], child_local: &[u8]) -> Result<()> {
1665        let Some(source) = &self.source_xml else {
1666            return Ok(());
1667        };
1668        let modeled = if child_local == b"docVar" {
1669            write_document_variables(&self.document_variables)?
1670        } else {
1671            write_compatibility_settings(&self.compatibility_settings)?
1672        };
1673        self.source_xml = Some(rewrite_group_setting(
1674            source,
1675            group_local,
1676            child_local,
1677            modeled,
1678        )?);
1679        Ok(())
1680    }
1681
1682    /// Return every modeled `w:compat` toggle option in schema order.
1683    pub fn compatibility_options(&self) -> &[(CompatibilityOption, bool)] {
1684        &self.compatibility_options
1685    }
1686
1687    pub fn compatibility_option(&self, option: CompatibilityOption) -> Option<bool> {
1688        self.compatibility_options
1689            .iter()
1690            .find(|(existing, _)| *existing == option)
1691            .map(|(_, value)| *value)
1692    }
1693
1694    pub fn set_compatibility_option(
1695        &mut self,
1696        option: CompatibilityOption,
1697        enabled: bool,
1698    ) -> Result<()> {
1699        let local = option.local_name();
1700        let replacement = write_toggle_setting(&format!("w:{local}"), enabled)?;
1701        if let Some(source) = &self.source_xml {
1702            let ensured = ensure_top_level_group(source, b"compat")?;
1703            self.source_xml = Some(rewrite_group_child(
1704                &ensured,
1705                b"compat",
1706                local.as_bytes(),
1707                &replacement,
1708                COMPAT_ORDER,
1709            )?);
1710        }
1711        self.compatibility_options
1712            .retain(|(existing, _)| *existing != option);
1713        self.compatibility_options.push((option, enabled));
1714        self.compatibility_options.sort_by_key(|entry| entry.0);
1715        self.tally.set(&["compat"], true);
1716        self.tally.set(&["compat", local], true);
1717        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1718        Ok(())
1719    }
1720
1721    pub fn remove_compatibility_option(
1722        &mut self,
1723        option: CompatibilityOption,
1724    ) -> Result<Option<bool>> {
1725        let local = option.local_name();
1726        let total = self.tally.total(&["compat", local]);
1727        if total == 0 {
1728            return Ok(None);
1729        }
1730        if total > 1 || self.tally.typed(&["compat", local]) != total {
1731            return Err(OxmlError::InvalidValue(format!(
1732                "cannot rewrite ambiguous or malformed w:{local}"
1733            )));
1734        }
1735        let removed = self.compatibility_option(option);
1736        self.compatibility_options
1737            .retain(|(existing, _)| *existing != option);
1738        if let Some(source) = &self.source_xml {
1739            self.source_xml = Some(rewrite_group_child(
1740                source,
1741                b"compat",
1742                local.as_bytes(),
1743                &[],
1744                COMPAT_ORDER,
1745            )?);
1746        }
1747        self.tally.set(&["compat", local], false);
1748        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
1749        Ok(removed)
1750    }
1751
1752    pub fn default_tab_stop(&self) -> Option<Twips> {
1753        self.default_tab_stop
1754    }
1755
1756    pub fn set_default_tab_stop(&mut self, value: Twips) -> Result<()> {
1757        if value.0 < 0 {
1758            return Err(OxmlError::InvalidValue(
1759                "default tab stop must be non-negative twips".to_owned(),
1760            ));
1761        }
1762        let replacement = write_valued_setting("w:defaultTabStop", &value.0.to_string())?;
1763        self.default_tab_stop = Some(value);
1764        self.finish_set("defaultTabStop", replacement)
1765    }
1766
1767    pub fn remove_default_tab_stop(&mut self) -> Result<Option<Twips>> {
1768        if !self.begin_removal("defaultTabStop")? {
1769            return Ok(None);
1770        }
1771        let removed = self.default_tab_stop.take();
1772        self.finish_removal("defaultTabStop")?;
1773        Ok(removed)
1774    }
1775
1776    /// Return whether Word automatic hyphenation is enabled.
1777    ///
1778    /// OOXML defines omission as disabled.
1779    pub fn automatic_hyphenation(&self) -> bool {
1780        self.automatic_hyphenation.unwrap_or(false)
1781    }
1782
1783    /// Set the document automatic-hyphenation toggle.
1784    ///
1785    /// Parsed settings retain every unrelated producer byte. The one modeled
1786    /// toggle is rewritten with the fixed `w:` prefix at its schema position.
1787    pub fn set_automatic_hyphenation(&mut self, enabled: bool) -> Result<()> {
1788        self.automatic_hyphenation = Some(enabled);
1789        self.finish_set(
1790            "autoHyphenation",
1791            write_toggle_setting("w:autoHyphenation", enabled)?,
1792        )
1793    }
1794
1795    pub fn remove_automatic_hyphenation(&mut self) -> Result<Option<bool>> {
1796        if !self.begin_removal("autoHyphenation")? {
1797            return Ok(None);
1798        }
1799        let removed = self.automatic_hyphenation.take();
1800        self.finish_removal("autoHyphenation")?;
1801        Ok(removed)
1802    }
1803
1804    /// Return the maximum count of consecutive hyphenated lines.
1805    pub fn consecutive_hyphen_limit(&self) -> Option<i32> {
1806        self.consecutive_hyphen_limit
1807    }
1808
1809    pub fn set_consecutive_hyphen_limit(&mut self, value: i32) -> Result<()> {
1810        let replacement = write_valued_setting("w:consecutiveHyphenLimit", &value.to_string())?;
1811        self.consecutive_hyphen_limit = Some(value);
1812        self.finish_set("consecutiveHyphenLimit", replacement)
1813    }
1814
1815    pub fn remove_consecutive_hyphen_limit(&mut self) -> Result<Option<i32>> {
1816        if !self.begin_removal("consecutiveHyphenLimit")? {
1817            return Ok(None);
1818        }
1819        let removed = self.consecutive_hyphen_limit.take();
1820        self.finish_removal("consecutiveHyphenLimit")?;
1821        Ok(removed)
1822    }
1823
1824    /// Return the hyphenation zone width.
1825    pub fn hyphenation_zone(&self) -> Option<Twips> {
1826        self.hyphenation_zone
1827    }
1828
1829    pub fn set_hyphenation_zone(&mut self, value: Twips) -> Result<()> {
1830        if value.0 < 0 {
1831            return Err(OxmlError::InvalidValue(
1832                "hyphenation zone must be non-negative twips".to_owned(),
1833            ));
1834        }
1835        let replacement = write_valued_setting("w:hyphenationZone", &value.0.to_string())?;
1836        self.hyphenation_zone = Some(value);
1837        self.finish_set("hyphenationZone", replacement)
1838    }
1839
1840    pub fn remove_hyphenation_zone(&mut self) -> Result<Option<Twips>> {
1841        if !self.begin_removal("hyphenationZone")? {
1842            return Ok(None);
1843        }
1844        let removed = self.hyphenation_zone.take();
1845        self.finish_removal("hyphenationZone")?;
1846        Ok(removed)
1847    }
1848
1849    /// Return the `w:doNotHyphenateCaps` toggle.
1850    pub fn do_not_hyphenate_caps(&self) -> Option<bool> {
1851        self.do_not_hyphenate_caps
1852    }
1853
1854    pub fn set_do_not_hyphenate_caps(&mut self, enabled: bool) -> Result<()> {
1855        self.do_not_hyphenate_caps = Some(enabled);
1856        self.finish_set(
1857            "doNotHyphenateCaps",
1858            write_toggle_setting("w:doNotHyphenateCaps", enabled)?,
1859        )
1860    }
1861
1862    pub fn remove_do_not_hyphenate_caps(&mut self) -> Result<Option<bool>> {
1863        if !self.begin_removal("doNotHyphenateCaps")? {
1864            return Ok(None);
1865        }
1866        let removed = self.do_not_hyphenate_caps.take();
1867        self.finish_removal("doNotHyphenateCaps")?;
1868        Ok(removed)
1869    }
1870
1871    /// Return the default table style identifier.
1872    pub fn default_table_style(&self) -> Option<&str> {
1873        self.default_table_style.as_deref()
1874    }
1875
1876    pub fn set_default_table_style(&mut self, value: String) -> Result<()> {
1877        let replacement = write_valued_setting("w:defaultTableStyle", &value)?;
1878        self.default_table_style = Some(value);
1879        self.finish_set("defaultTableStyle", replacement)
1880    }
1881
1882    pub fn remove_default_table_style(&mut self) -> Result<Option<String>> {
1883        if !self.begin_removal("defaultTableStyle")? {
1884            return Ok(None);
1885        }
1886        let removed = self.default_table_style.take();
1887        self.finish_removal("defaultTableStyle")?;
1888        Ok(removed)
1889    }
1890
1891    /// Return whether Word selects distinct even-page header and footer stories.
1892    ///
1893    /// OOXML defines omission as disabled.
1894    pub fn even_and_odd_headers(&self) -> bool {
1895        self.even_and_odd_headers.unwrap_or(false)
1896    }
1897
1898    /// Set distinct even-page header and footer selection.
1899    pub fn set_even_and_odd_headers(&mut self, enabled: bool) -> Result<()> {
1900        self.even_and_odd_headers = Some(enabled);
1901        self.finish_set(
1902            "evenAndOddHeaders",
1903            write_toggle_setting("w:evenAndOddHeaders", enabled)?,
1904        )
1905    }
1906
1907    pub fn remove_even_and_odd_headers(&mut self) -> Result<Option<bool>> {
1908        if !self.begin_removal("evenAndOddHeaders")? {
1909            return Ok(None);
1910        }
1911        let removed = self.even_and_odd_headers.take();
1912        self.finish_removal("evenAndOddHeaders")?;
1913        Ok(removed)
1914    }
1915
1916    /// Return the `w:bookFoldRevPrinting` toggle.
1917    pub fn book_fold_rev_printing(&self) -> Option<bool> {
1918        self.book_fold_rev_printing
1919    }
1920
1921    pub fn set_book_fold_rev_printing(&mut self, enabled: bool) -> Result<()> {
1922        self.book_fold_rev_printing = Some(enabled);
1923        self.finish_set(
1924            "bookFoldRevPrinting",
1925            write_toggle_setting("w:bookFoldRevPrinting", enabled)?,
1926        )
1927    }
1928
1929    pub fn remove_book_fold_rev_printing(&mut self) -> Result<Option<bool>> {
1930        if !self.begin_removal("bookFoldRevPrinting")? {
1931            return Ok(None);
1932        }
1933        let removed = self.book_fold_rev_printing.take();
1934        self.finish_removal("bookFoldRevPrinting")?;
1935        Ok(removed)
1936    }
1937
1938    /// Return the `w:bookFoldPrinting` toggle.
1939    pub fn book_fold_printing(&self) -> Option<bool> {
1940        self.book_fold_printing
1941    }
1942
1943    pub fn set_book_fold_printing(&mut self, enabled: bool) -> Result<()> {
1944        self.book_fold_printing = Some(enabled);
1945        self.finish_set(
1946            "bookFoldPrinting",
1947            write_toggle_setting("w:bookFoldPrinting", enabled)?,
1948        )
1949    }
1950
1951    pub fn remove_book_fold_printing(&mut self) -> Result<Option<bool>> {
1952        if !self.begin_removal("bookFoldPrinting")? {
1953            return Ok(None);
1954        }
1955        let removed = self.book_fold_printing.take();
1956        self.finish_removal("bookFoldPrinting")?;
1957        Ok(removed)
1958    }
1959
1960    /// Return the signed sheet count per book-fold booklet.
1961    pub fn book_fold_printing_sheets(&self) -> Option<i32> {
1962        self.book_fold_printing_sheets
1963    }
1964
1965    pub fn set_book_fold_printing_sheets(&mut self, value: i32) -> Result<()> {
1966        let replacement = write_valued_setting("w:bookFoldPrintingSheets", &value.to_string())?;
1967        self.book_fold_printing_sheets = Some(value);
1968        self.finish_set("bookFoldPrintingSheets", replacement)
1969    }
1970
1971    pub fn remove_book_fold_printing_sheets(&mut self) -> Result<Option<i32>> {
1972        if !self.begin_removal("bookFoldPrintingSheets")? {
1973            return Ok(None);
1974        }
1975        let removed = self.book_fold_printing_sheets.take();
1976        self.finish_removal("bookFoldPrintingSheets")?;
1977        Ok(removed)
1978    }
1979
1980    pub fn character_spacing_control(&self) -> Option<CharacterSpacingControl> {
1981        self.character_spacing_control
1982    }
1983
1984    pub fn set_character_spacing_control(&mut self, value: CharacterSpacingControl) -> Result<()> {
1985        let replacement = write_valued_setting("w:characterSpacingControl", value.as_str())?;
1986        self.character_spacing_control = Some(value);
1987        self.finish_set("characterSpacingControl", replacement)
1988    }
1989
1990    pub fn remove_character_spacing_control(&mut self) -> Result<Option<CharacterSpacingControl>> {
1991        if !self.begin_removal("characterSpacingControl")? {
1992            return Ok(None);
1993        }
1994        let removed = self.character_spacing_control.take();
1995        self.finish_removal("characterSpacingControl")?;
1996        Ok(removed)
1997    }
1998
1999    /// Return the `w:updateFields` toggle, which asks Word to update fields
2000    /// when it opens the document.
2001    ///
2002    /// `None` means the part omits the toggle or carries an unmodelled form.
2003    pub fn update_fields(&self) -> Option<bool> {
2004        self.update_fields
2005    }
2006
2007    /// Set the `w:updateFields` toggle, or remove it with `None`.
2008    pub fn set_update_fields(&mut self, value: Option<bool>) -> Result<()> {
2009        let total = self.tally.total(&["updateFields"]);
2010        if total > 1 || (total == 1 && self.update_fields.is_none()) {
2011            return Err(OxmlError::InvalidValue(
2012                "cannot rewrite ambiguous or malformed w:updateFields".to_owned(),
2013            ));
2014        }
2015        match value {
2016            Some(enabled) => {
2017                self.update_fields = Some(enabled);
2018                self.finish_set(
2019                    "updateFields",
2020                    write_toggle_setting("w:updateFields", enabled)?,
2021                )
2022            }
2023            None => {
2024                if total == 0 {
2025                    return Ok(());
2026                }
2027                self.update_fields = None;
2028                self.finish_removal("updateFields")
2029            }
2030        }
2031    }
2032
2033    /// Return document-wide OfficeMath defaults, when present and valid.
2034    pub fn math_properties(&self) -> Option<&MathProperties> {
2035        self.math_properties.as_ref()
2036    }
2037
2038    /// Replace the single schema-positioned OfficeMath defaults subtree.
2039    pub fn set_math_properties(&mut self, properties: MathProperties) -> Result<()> {
2040        if let Some(source) = &self.source_xml {
2041            self.source_xml = Some(rewrite_math_properties(source, Some(&properties))?);
2042        }
2043        self.math_properties = Some(properties);
2044        self.tally.set(&["mathPr"], true);
2045        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
2046        Ok(())
2047    }
2048
2049    pub fn remove_math_properties(&mut self) -> Result<Option<MathProperties>> {
2050        if !self.begin_removal("mathPr")? {
2051            return Ok(None);
2052        }
2053        let removed = self.math_properties.take();
2054        if let Some(source) = &self.source_xml {
2055            self.source_xml = Some(rewrite_math_properties(source, None)?);
2056        }
2057        self.tally.set(&["mathPr"], false);
2058        self.diagnostics = self.tally.diagnostics(SETTINGS_REPEATABLE);
2059        Ok(removed)
2060    }
2061
2062    pub fn theme_font_language(&self) -> Option<&ThemeFontLanguage> {
2063        self.theme_font_language.as_ref()
2064    }
2065
2066    pub fn set_theme_font_language(&mut self, value: ThemeFontLanguage) -> Result<()> {
2067        let replacement = write_theme_font_language(&value)?;
2068        self.theme_font_language = Some(value);
2069        self.finish_set("themeFontLang", replacement)
2070    }
2071
2072    pub fn remove_theme_font_language(&mut self) -> Result<Option<ThemeFontLanguage>> {
2073        if !self.begin_removal("themeFontLang")? {
2074            return Ok(None);
2075        }
2076        let removed = self.theme_font_language.take();
2077        self.finish_removal("themeFontLang")?;
2078        Ok(removed)
2079    }
2080
2081    /// Return the decimal separator Word uses in fields.
2082    pub fn decimal_symbol(&self) -> Option<&str> {
2083        self.decimal_symbol.as_deref()
2084    }
2085
2086    pub fn set_decimal_symbol(&mut self, value: String) -> Result<()> {
2087        let replacement = write_valued_setting("w:decimalSymbol", &value)?;
2088        self.decimal_symbol = Some(value);
2089        self.finish_set("decimalSymbol", replacement)
2090    }
2091
2092    pub fn remove_decimal_symbol(&mut self) -> Result<Option<String>> {
2093        if !self.begin_removal("decimalSymbol")? {
2094            return Ok(None);
2095        }
2096        let removed = self.decimal_symbol.take();
2097        self.finish_removal("decimalSymbol")?;
2098        Ok(removed)
2099    }
2100
2101    /// Return the list separator Word uses in fields.
2102    pub fn list_separator(&self) -> Option<&str> {
2103        self.list_separator.as_deref()
2104    }
2105
2106    pub fn set_list_separator(&mut self, value: String) -> Result<()> {
2107        let replacement = write_valued_setting("w:listSeparator", &value)?;
2108        self.list_separator = Some(value);
2109        self.finish_set("listSeparator", replacement)
2110    }
2111
2112    pub fn remove_list_separator(&mut self) -> Result<Option<String>> {
2113        if !self.begin_removal("listSeparator")? {
2114            return Ok(None);
2115        }
2116        let removed = self.list_separator.take();
2117        self.finish_removal("listSeparator")?;
2118        Ok(removed)
2119    }
2120
2121    /// Return whether the model has no typed or retained settings content.
2122    pub fn is_empty(&self) -> bool {
2123        self.view.is_none()
2124            && self.zoom.is_none()
2125            && self.remove_personal_information.is_none()
2126            && self.remove_date_and_time.is_none()
2127            && self.mirror_margins.is_none()
2128            && self.gutter_at_top.is_none()
2129            && self.proof_state.is_none()
2130            && self.link_styles.is_none()
2131            && self.mail_merge.is_none()
2132            && self.track_revisions.is_none()
2133            && self.do_not_track_moves.is_none()
2134            && self.do_not_track_formatting.is_none()
2135            && self.document_protection.is_none()
2136            && self.footnote_properties.is_none()
2137            && self.endnote_properties.is_none()
2138            && self.default_tab_stop.is_none()
2139            && self.automatic_hyphenation.is_none()
2140            && self.consecutive_hyphen_limit.is_none()
2141            && self.hyphenation_zone.is_none()
2142            && self.do_not_hyphenate_caps.is_none()
2143            && self.default_table_style.is_none()
2144            && self.even_and_odd_headers.is_none()
2145            && self.book_fold_rev_printing.is_none()
2146            && self.book_fold_printing.is_none()
2147            && self.book_fold_printing_sheets.is_none()
2148            && self.character_spacing_control.is_none()
2149            && self.update_fields.is_none()
2150            && self.compatibility_options.is_empty()
2151            && self.compatibility_settings.is_empty()
2152            && self.document_variables.is_empty()
2153            && self.math_properties.is_none()
2154            && self.theme_font_language.is_none()
2155            && self.decimal_symbol.is_none()
2156            && self.list_separator.is_none()
2157            && self.source_xml.is_none()
2158    }
2159
2160    /// Serialize settings with fixed Word prefixes and schema child order.
2161    pub fn to_xml(&self) -> Result<Vec<u8>> {
2162        if let Some(source) = &self.source_xml {
2163            return Ok(source.clone());
2164        }
2165
2166        let mut writer = Writer::new(Vec::new());
2167        writer.write_event(Event::Decl(BytesDecl::new(
2168            "1.0",
2169            Some("UTF-8"),
2170            Some("yes"),
2171        )))?;
2172        let mut root = BytesStart::new("w:settings");
2173        root.push_attribute(("xmlns:w", W_NS));
2174        writer.write_event(Event::Start(root))?;
2175        let mut emit = |bytes: Vec<u8>| writer.get_mut().extend_from_slice(&bytes);
2176        if let Some(value) = self.view {
2177            emit(write_valued_setting("w:view", value.as_str())?);
2178        }
2179        if let Some(value) = &self.zoom {
2180            emit(write_zoom(value)?);
2181        }
2182        for (name, value) in [
2183            (
2184                "w:removePersonalInformation",
2185                self.remove_personal_information,
2186            ),
2187            ("w:removeDateAndTime", self.remove_date_and_time),
2188            ("w:mirrorMargins", self.mirror_margins),
2189            ("w:gutterAtTop", self.gutter_at_top),
2190        ] {
2191            if let Some(enabled) = value {
2192                emit(write_toggle_setting(name, enabled)?);
2193            }
2194        }
2195        if let Some(value) = &self.proof_state {
2196            emit(write_proof_state(value)?);
2197        }
2198        if let Some(enabled) = self.link_styles {
2199            emit(write_toggle_setting("w:linkStyles", enabled)?);
2200        }
2201        if let Some(value) = &self.mail_merge {
2202            let children = mail_merge_children(value)?;
2203            if children.iter().any(|(_, bytes)| !bytes.is_empty()) {
2204                emit(wrap_group("w:mailMerge", &concatenate(&children))?);
2205            }
2206        }
2207        for (name, value) in [
2208            ("w:trackRevisions", self.track_revisions),
2209            ("w:doNotTrackMoves", self.do_not_track_moves),
2210            ("w:doNotTrackFormatting", self.do_not_track_formatting),
2211        ] {
2212            if let Some(enabled) = value {
2213                emit(write_toggle_setting(name, enabled)?);
2214            }
2215        }
2216        if let Some(protection) = &self.document_protection {
2217            emit(write_document_protection(protection)?);
2218        }
2219        if let Some(properties) = &self.footnote_properties {
2220            let mut child = Writer::new(Vec::new());
2221            properties.to_xml(&mut child, "w:footnotePr")?;
2222            emit(child.into_inner());
2223        }
2224        if let Some(properties) = &self.endnote_properties {
2225            let mut child = Writer::new(Vec::new());
2226            properties.to_xml(&mut child, "w:endnotePr")?;
2227            emit(child.into_inner());
2228        }
2229        if let Some(value) = self.default_tab_stop {
2230            emit(write_valued_setting(
2231                "w:defaultTabStop",
2232                &value.0.to_string(),
2233            )?);
2234        }
2235        if let Some(enabled) = self.automatic_hyphenation {
2236            emit(write_toggle_setting("w:autoHyphenation", enabled)?);
2237        }
2238        if let Some(value) = self.consecutive_hyphen_limit {
2239            emit(write_valued_setting(
2240                "w:consecutiveHyphenLimit",
2241                &value.to_string(),
2242            )?);
2243        }
2244        if let Some(value) = self.hyphenation_zone {
2245            emit(write_valued_setting(
2246                "w:hyphenationZone",
2247                &value.0.to_string(),
2248            )?);
2249        }
2250        if let Some(enabled) = self.do_not_hyphenate_caps {
2251            emit(write_toggle_setting("w:doNotHyphenateCaps", enabled)?);
2252        }
2253        if let Some(value) = &self.default_table_style {
2254            emit(write_valued_setting("w:defaultTableStyle", value)?);
2255        }
2256        for (name, value) in [
2257            ("w:evenAndOddHeaders", self.even_and_odd_headers),
2258            ("w:bookFoldRevPrinting", self.book_fold_rev_printing),
2259            ("w:bookFoldPrinting", self.book_fold_printing),
2260        ] {
2261            if let Some(enabled) = value {
2262                emit(write_toggle_setting(name, enabled)?);
2263            }
2264        }
2265        if let Some(value) = self.book_fold_printing_sheets {
2266            emit(write_valued_setting(
2267                "w:bookFoldPrintingSheets",
2268                &value.to_string(),
2269            )?);
2270        }
2271        if let Some(value) = self.character_spacing_control {
2272            emit(write_valued_setting(
2273                "w:characterSpacingControl",
2274                value.as_str(),
2275            )?);
2276        }
2277        if let Some(enabled) = self.update_fields {
2278            emit(write_toggle_setting("w:updateFields", enabled)?);
2279        }
2280        if !self.compatibility_options.is_empty() || !self.compatibility_settings.is_empty() {
2281            let mut children = Vec::new();
2282            for (option, enabled) in &self.compatibility_options {
2283                children.extend_from_slice(&write_toggle_setting(
2284                    &format!("w:{}", option.local_name()),
2285                    *enabled,
2286                )?);
2287            }
2288            children
2289                .extend_from_slice(&write_compatibility_settings(&self.compatibility_settings)?);
2290            emit(wrap_group("w:compat", &children)?);
2291        }
2292        if !self.document_variables.is_empty() {
2293            emit(wrap_group(
2294                "w:docVars",
2295                &write_document_variables(&self.document_variables)?,
2296            )?);
2297        }
2298        if let Some(properties) = &self.math_properties {
2299            properties.write_xml(&mut writer)?;
2300        }
2301        let mut emit = |bytes: Vec<u8>| writer.get_mut().extend_from_slice(&bytes);
2302        if let Some(language) = &self.theme_font_language {
2303            emit(write_theme_font_language(language)?);
2304        }
2305        if let Some(value) = &self.decimal_symbol {
2306            emit(write_valued_setting("w:decimalSymbol", value)?);
2307        }
2308        if let Some(value) = &self.list_separator {
2309            emit(write_valued_setting("w:listSeparator", value)?);
2310        }
2311        writer.write_event(Event::End(BytesEnd::new("w:settings")))?;
2312        Ok(writer.into_inner())
2313    }
2314}
2315
2316/// The complete closed `CT_Compat` on-off child set.
2317///
2318/// Every `w:compat` child except `w:compatSetting` is a `CT_OnOff` of
2319/// identical shape, so one ordered enum carries the family in one place
2320/// instead of roughly eighty near-identical struct fields. The variants are
2321/// declared in `xsd:sequence` order, which is what `Ord` sorts by.
2322#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2323pub enum CompatibilityOption {
2324    UseSingleBorderforContiguousCells,
2325    WpJustification,
2326    NoTabHangInd,
2327    NoLeading,
2328    SpaceForUL,
2329    NoColumnBalance,
2330    BalanceSingleByteDoubleByteWidth,
2331    NoExtraLineSpacing,
2332    DoNotLeaveBackslashAlone,
2333    UlTrailSpace,
2334    DoNotExpandShiftReturn,
2335    SpacingInWholePoints,
2336    LineWrapLikeWord6,
2337    PrintBodyTextBeforeHeader,
2338    PrintColBlack,
2339    WpSpaceWidth,
2340    ShowBreaksInFrames,
2341    SubFontBySize,
2342    SuppressBottomSpacing,
2343    SuppressTopSpacing,
2344    SuppressSpacingAtTopOfPage,
2345    SuppressTopSpacingWP,
2346    SuppressSpBfAfterPgBrk,
2347    SwapBordersFacingPages,
2348    ConvMailMergeEsc,
2349    TruncateFontHeightsLikeWP6,
2350    MwSmallCaps,
2351    UsePrinterMetrics,
2352    DoNotSuppressParagraphBorders,
2353    WrapTrailSpaces,
2354    FootnoteLayoutLikeWW8,
2355    ShapeLayoutLikeWW8,
2356    AlignTablesRowByRow,
2357    ForgetLastTabAlignment,
2358    AdjustLineHeightInTable,
2359    AutoSpaceLikeWord95,
2360    NoSpaceRaiseLower,
2361    DoNotUseHTMLParagraphAutoSpacing,
2362    LayoutRawTableWidth,
2363    LayoutTableRowsApart,
2364    UseWord97LineBreakRules,
2365    DoNotBreakWrappedTables,
2366    DoNotSnapToGridInCell,
2367    SelectFldWithFirstOrLastChar,
2368    ApplyBreakingRules,
2369    DoNotWrapTextWithPunct,
2370    DoNotUseEastAsianBreakRules,
2371    UseWord2002TableStyleRules,
2372    GrowAutofit,
2373    UseFELayout,
2374    UseNormalStyleForList,
2375    DoNotUseIndentAsNumberingTabStop,
2376    UseAltKinsokuLineBreakRules,
2377    AllowSpaceOfSameStyleInTable,
2378    DoNotSuppressIndentation,
2379    DoNotAutofitConstrainedTables,
2380    AutofitToFirstFixedWidthCell,
2381    UnderlineTabInNumList,
2382    DisplayHangulFixedWidth,
2383    SplitPgBreakAndParaMark,
2384    DoNotVertAlignCellWithSp,
2385    DoNotBreakConstrainedForcedTable,
2386    DoNotVertAlignInTxbx,
2387    UseAnsiKerningPairs,
2388    CachedColBalance,
2389}
2390
2391impl CompatibilityOption {
2392    /// Every option in `xsd:sequence` order.
2393    pub const ALL: &'static [Self] = &[
2394        Self::UseSingleBorderforContiguousCells,
2395        Self::WpJustification,
2396        Self::NoTabHangInd,
2397        Self::NoLeading,
2398        Self::SpaceForUL,
2399        Self::NoColumnBalance,
2400        Self::BalanceSingleByteDoubleByteWidth,
2401        Self::NoExtraLineSpacing,
2402        Self::DoNotLeaveBackslashAlone,
2403        Self::UlTrailSpace,
2404        Self::DoNotExpandShiftReturn,
2405        Self::SpacingInWholePoints,
2406        Self::LineWrapLikeWord6,
2407        Self::PrintBodyTextBeforeHeader,
2408        Self::PrintColBlack,
2409        Self::WpSpaceWidth,
2410        Self::ShowBreaksInFrames,
2411        Self::SubFontBySize,
2412        Self::SuppressBottomSpacing,
2413        Self::SuppressTopSpacing,
2414        Self::SuppressSpacingAtTopOfPage,
2415        Self::SuppressTopSpacingWP,
2416        Self::SuppressSpBfAfterPgBrk,
2417        Self::SwapBordersFacingPages,
2418        Self::ConvMailMergeEsc,
2419        Self::TruncateFontHeightsLikeWP6,
2420        Self::MwSmallCaps,
2421        Self::UsePrinterMetrics,
2422        Self::DoNotSuppressParagraphBorders,
2423        Self::WrapTrailSpaces,
2424        Self::FootnoteLayoutLikeWW8,
2425        Self::ShapeLayoutLikeWW8,
2426        Self::AlignTablesRowByRow,
2427        Self::ForgetLastTabAlignment,
2428        Self::AdjustLineHeightInTable,
2429        Self::AutoSpaceLikeWord95,
2430        Self::NoSpaceRaiseLower,
2431        Self::DoNotUseHTMLParagraphAutoSpacing,
2432        Self::LayoutRawTableWidth,
2433        Self::LayoutTableRowsApart,
2434        Self::UseWord97LineBreakRules,
2435        Self::DoNotBreakWrappedTables,
2436        Self::DoNotSnapToGridInCell,
2437        Self::SelectFldWithFirstOrLastChar,
2438        Self::ApplyBreakingRules,
2439        Self::DoNotWrapTextWithPunct,
2440        Self::DoNotUseEastAsianBreakRules,
2441        Self::UseWord2002TableStyleRules,
2442        Self::GrowAutofit,
2443        Self::UseFELayout,
2444        Self::UseNormalStyleForList,
2445        Self::DoNotUseIndentAsNumberingTabStop,
2446        Self::UseAltKinsokuLineBreakRules,
2447        Self::AllowSpaceOfSameStyleInTable,
2448        Self::DoNotSuppressIndentation,
2449        Self::DoNotAutofitConstrainedTables,
2450        Self::AutofitToFirstFixedWidthCell,
2451        Self::UnderlineTabInNumList,
2452        Self::DisplayHangulFixedWidth,
2453        Self::SplitPgBreakAndParaMark,
2454        Self::DoNotVertAlignCellWithSp,
2455        Self::DoNotBreakConstrainedForcedTable,
2456        Self::DoNotVertAlignInTxbx,
2457        Self::UseAnsiKerningPairs,
2458        Self::CachedColBalance,
2459    ];
2460
2461    pub fn local_name(self) -> &'static str {
2462        match self {
2463            Self::UseSingleBorderforContiguousCells => "useSingleBorderforContiguousCells",
2464            Self::WpJustification => "wpJustification",
2465            Self::NoTabHangInd => "noTabHangInd",
2466            Self::NoLeading => "noLeading",
2467            Self::SpaceForUL => "spaceForUL",
2468            Self::NoColumnBalance => "noColumnBalance",
2469            Self::BalanceSingleByteDoubleByteWidth => "balanceSingleByteDoubleByteWidth",
2470            Self::NoExtraLineSpacing => "noExtraLineSpacing",
2471            Self::DoNotLeaveBackslashAlone => "doNotLeaveBackslashAlone",
2472            Self::UlTrailSpace => "ulTrailSpace",
2473            Self::DoNotExpandShiftReturn => "doNotExpandShiftReturn",
2474            Self::SpacingInWholePoints => "spacingInWholePoints",
2475            Self::LineWrapLikeWord6 => "lineWrapLikeWord6",
2476            Self::PrintBodyTextBeforeHeader => "printBodyTextBeforeHeader",
2477            Self::PrintColBlack => "printColBlack",
2478            Self::WpSpaceWidth => "wpSpaceWidth",
2479            Self::ShowBreaksInFrames => "showBreaksInFrames",
2480            Self::SubFontBySize => "subFontBySize",
2481            Self::SuppressBottomSpacing => "suppressBottomSpacing",
2482            Self::SuppressTopSpacing => "suppressTopSpacing",
2483            Self::SuppressSpacingAtTopOfPage => "suppressSpacingAtTopOfPage",
2484            Self::SuppressTopSpacingWP => "suppressTopSpacingWP",
2485            Self::SuppressSpBfAfterPgBrk => "suppressSpBfAfterPgBrk",
2486            Self::SwapBordersFacingPages => "swapBordersFacingPages",
2487            Self::ConvMailMergeEsc => "convMailMergeEsc",
2488            Self::TruncateFontHeightsLikeWP6 => "truncateFontHeightsLikeWP6",
2489            Self::MwSmallCaps => "mwSmallCaps",
2490            Self::UsePrinterMetrics => "usePrinterMetrics",
2491            Self::DoNotSuppressParagraphBorders => "doNotSuppressParagraphBorders",
2492            Self::WrapTrailSpaces => "wrapTrailSpaces",
2493            Self::FootnoteLayoutLikeWW8 => "footnoteLayoutLikeWW8",
2494            Self::ShapeLayoutLikeWW8 => "shapeLayoutLikeWW8",
2495            Self::AlignTablesRowByRow => "alignTablesRowByRow",
2496            Self::ForgetLastTabAlignment => "forgetLastTabAlignment",
2497            Self::AdjustLineHeightInTable => "adjustLineHeightInTable",
2498            Self::AutoSpaceLikeWord95 => "autoSpaceLikeWord95",
2499            Self::NoSpaceRaiseLower => "noSpaceRaiseLower",
2500            Self::DoNotUseHTMLParagraphAutoSpacing => "doNotUseHTMLParagraphAutoSpacing",
2501            Self::LayoutRawTableWidth => "layoutRawTableWidth",
2502            Self::LayoutTableRowsApart => "layoutTableRowsApart",
2503            Self::UseWord97LineBreakRules => "useWord97LineBreakRules",
2504            Self::DoNotBreakWrappedTables => "doNotBreakWrappedTables",
2505            Self::DoNotSnapToGridInCell => "doNotSnapToGridInCell",
2506            Self::SelectFldWithFirstOrLastChar => "selectFldWithFirstOrLastChar",
2507            Self::ApplyBreakingRules => "applyBreakingRules",
2508            Self::DoNotWrapTextWithPunct => "doNotWrapTextWithPunct",
2509            Self::DoNotUseEastAsianBreakRules => "doNotUseEastAsianBreakRules",
2510            Self::UseWord2002TableStyleRules => "useWord2002TableStyleRules",
2511            Self::GrowAutofit => "growAutofit",
2512            Self::UseFELayout => "useFELayout",
2513            Self::UseNormalStyleForList => "useNormalStyleForList",
2514            Self::DoNotUseIndentAsNumberingTabStop => "doNotUseIndentAsNumberingTabStop",
2515            Self::UseAltKinsokuLineBreakRules => "useAltKinsokuLineBreakRules",
2516            Self::AllowSpaceOfSameStyleInTable => "allowSpaceOfSameStyleInTable",
2517            Self::DoNotSuppressIndentation => "doNotSuppressIndentation",
2518            Self::DoNotAutofitConstrainedTables => "doNotAutofitConstrainedTables",
2519            Self::AutofitToFirstFixedWidthCell => "autofitToFirstFixedWidthCell",
2520            Self::UnderlineTabInNumList => "underlineTabInNumList",
2521            Self::DisplayHangulFixedWidth => "displayHangulFixedWidth",
2522            Self::SplitPgBreakAndParaMark => "splitPgBreakAndParaMark",
2523            Self::DoNotVertAlignCellWithSp => "doNotVertAlignCellWithSp",
2524            Self::DoNotBreakConstrainedForcedTable => "doNotBreakConstrainedForcedTable",
2525            Self::DoNotVertAlignInTxbx => "doNotVertAlignInTxbx",
2526            Self::UseAnsiKerningPairs => "useAnsiKerningPairs",
2527            Self::CachedColBalance => "cachedColBalance",
2528        }
2529    }
2530
2531    /// Return the option one `w:compat` child names, when it names one.
2532    fn find(element: &BytesStart<'_>, prefixes: &[String]) -> Option<Self> {
2533        Self::ALL.iter().copied().find(|option| {
2534            is_word_element(
2535                element.name().as_ref(),
2536                option.local_name().as_bytes(),
2537                prefixes,
2538            )
2539        })
2540    }
2541}
2542
2543/// `CT_Compat` child order, the on-off family followed by `w:compatSetting`.
2544const COMPAT_ORDER: &[&[u8]] = &[
2545    b"useSingleBorderforContiguousCells",
2546    b"wpJustification",
2547    b"noTabHangInd",
2548    b"noLeading",
2549    b"spaceForUL",
2550    b"noColumnBalance",
2551    b"balanceSingleByteDoubleByteWidth",
2552    b"noExtraLineSpacing",
2553    b"doNotLeaveBackslashAlone",
2554    b"ulTrailSpace",
2555    b"doNotExpandShiftReturn",
2556    b"spacingInWholePoints",
2557    b"lineWrapLikeWord6",
2558    b"printBodyTextBeforeHeader",
2559    b"printColBlack",
2560    b"wpSpaceWidth",
2561    b"showBreaksInFrames",
2562    b"subFontBySize",
2563    b"suppressBottomSpacing",
2564    b"suppressTopSpacing",
2565    b"suppressSpacingAtTopOfPage",
2566    b"suppressTopSpacingWP",
2567    b"suppressSpBfAfterPgBrk",
2568    b"swapBordersFacingPages",
2569    b"convMailMergeEsc",
2570    b"truncateFontHeightsLikeWP6",
2571    b"mwSmallCaps",
2572    b"usePrinterMetrics",
2573    b"doNotSuppressParagraphBorders",
2574    b"wrapTrailSpaces",
2575    b"footnoteLayoutLikeWW8",
2576    b"shapeLayoutLikeWW8",
2577    b"alignTablesRowByRow",
2578    b"forgetLastTabAlignment",
2579    b"adjustLineHeightInTable",
2580    b"autoSpaceLikeWord95",
2581    b"noSpaceRaiseLower",
2582    b"doNotUseHTMLParagraphAutoSpacing",
2583    b"layoutRawTableWidth",
2584    b"layoutTableRowsApart",
2585    b"useWord97LineBreakRules",
2586    b"doNotBreakWrappedTables",
2587    b"doNotSnapToGridInCell",
2588    b"selectFldWithFirstOrLastChar",
2589    b"applyBreakingRules",
2590    b"doNotWrapTextWithPunct",
2591    b"doNotUseEastAsianBreakRules",
2592    b"useWord2002TableStyleRules",
2593    b"growAutofit",
2594    b"useFELayout",
2595    b"useNormalStyleForList",
2596    b"doNotUseIndentAsNumberingTabStop",
2597    b"useAltKinsokuLineBreakRules",
2598    b"allowSpaceOfSameStyleInTable",
2599    b"doNotSuppressIndentation",
2600    b"doNotAutofitConstrainedTables",
2601    b"autofitToFirstFixedWidthCell",
2602    b"underlineTabInNumList",
2603    b"displayHangulFixedWidth",
2604    b"splitPgBreakAndParaMark",
2605    b"doNotVertAlignCellWithSp",
2606    b"doNotBreakConstrainedForcedTable",
2607    b"doNotVertAlignInTxbx",
2608    b"useAnsiKerningPairs",
2609    b"cachedColBalance",
2610    b"compatSetting",
2611];
2612
2613/// Every top-level `w:settings` child in `xsd:sequence` order.
2614///
2615/// One table drives both schema-position insertion and the closed supported
2616/// set, so a new member is added in one place rather than three.
2617pub(crate) const SETTINGS_ORDER: &[&[u8]] = &[
2618    b"writeProtection",
2619    b"view",
2620    b"zoom",
2621    b"removePersonalInformation",
2622    b"removeDateAndTime",
2623    b"doNotDisplayPageBoundaries",
2624    b"displayBackgroundShape",
2625    b"printPostScriptOverText",
2626    b"printFractionalCharacterWidth",
2627    b"printFormsData",
2628    b"embedTrueTypeFonts",
2629    b"embedSystemFonts",
2630    b"saveSubsetFonts",
2631    b"saveFormsData",
2632    b"mirrorMargins",
2633    b"alignBordersAndEdges",
2634    b"bordersDoNotSurroundHeader",
2635    b"bordersDoNotSurroundFooter",
2636    b"gutterAtTop",
2637    b"hideSpellingErrors",
2638    b"hideGrammaticalErrors",
2639    b"activeWritingStyle",
2640    b"proofState",
2641    b"formsDesign",
2642    b"attachedTemplate",
2643    b"linkStyles",
2644    b"stylePaneFormatFilter",
2645    b"stylePaneSortMethod",
2646    b"documentType",
2647    b"mailMerge",
2648    b"revisionView",
2649    b"trackRevisions",
2650    b"doNotTrackMoves",
2651    b"doNotTrackFormatting",
2652    b"documentProtection",
2653    b"autoFormatOverride",
2654    b"styleLockTheme",
2655    b"styleLockQFSet",
2656    b"defaultTabStop",
2657    b"autoHyphenation",
2658    b"consecutiveHyphenLimit",
2659    b"hyphenationZone",
2660    b"doNotHyphenateCaps",
2661    b"showEnvelope",
2662    b"summaryLength",
2663    b"clickAndTypeStyle",
2664    b"defaultTableStyle",
2665    b"evenAndOddHeaders",
2666    b"bookFoldRevPrinting",
2667    b"bookFoldPrinting",
2668    b"bookFoldPrintingSheets",
2669    b"drawingGridHorizontalSpacing",
2670    b"drawingGridVerticalSpacing",
2671    b"displayHorizontalDrawingGridEvery",
2672    b"displayVerticalDrawingGridEvery",
2673    b"doNotUseMarginsForDrawingGridOrigin",
2674    b"drawingGridHorizontalOrigin",
2675    b"drawingGridVerticalOrigin",
2676    b"doNotShadeFormData",
2677    b"noPunctuationKerning",
2678    b"characterSpacingControl",
2679    b"printTwoOnOne",
2680    b"strictFirstAndLastChars",
2681    b"noLineBreaksAfter",
2682    b"noLineBreaksBefore",
2683    b"savePreviewPicture",
2684    b"doNotValidateAgainstSchema",
2685    b"saveInvalidXml",
2686    b"ignoreMixedContent",
2687    b"alwaysShowPlaceholderText",
2688    b"doNotDemarcateInvalidXml",
2689    b"saveXmlDataOnly",
2690    b"useXSLTWhenSaving",
2691    b"saveThroughXslt",
2692    b"showXMLTags",
2693    b"alwaysMergeEmptyNamespace",
2694    b"updateFields",
2695    b"hdrShapeDefaults",
2696    b"footnotePr",
2697    b"endnotePr",
2698    b"compat",
2699    b"docVars",
2700    b"rsids",
2701    b"mathPr",
2702    b"attachedSchema",
2703    b"themeFontLang",
2704    b"clrSchemeMapping",
2705    b"doNotIncludeSubdocsInStats",
2706    b"doNotAutoCompressPictures",
2707    b"forceUpgrade",
2708    b"captions",
2709    b"readModeInkLockDown",
2710    b"smartTagType",
2711    b"schemaLibrary",
2712    b"shapeDefaults",
2713    b"doNotEmbedSmartTags",
2714    b"decimalSymbol",
2715    b"listSeparator",
2716];
2717
2718/// The closed set of top-level `w:settings` children the typed model owns.
2719///
2720/// "Supported" is a checkable constant rather than a judgement. A name outside
2721/// this list is preservation-only and is never reported as a diagnostic, which
2722/// is what makes "no unmodeled supported children" decidable. The nested
2723/// supported names are `compat/compatSetting`, every `CompatibilityOption`
2724/// local name under `compat`, `docVars/docVar` and `MAIL_MERGE_MEMBERS`.
2725pub const SUPPORTED_SETTINGS: &[&str] = &[
2726    "view",
2727    "zoom",
2728    "removePersonalInformation",
2729    "removeDateAndTime",
2730    "mirrorMargins",
2731    "gutterAtTop",
2732    "proofState",
2733    "linkStyles",
2734    "mailMerge",
2735    "trackRevisions",
2736    "doNotTrackMoves",
2737    "doNotTrackFormatting",
2738    "documentProtection",
2739    "defaultTabStop",
2740    "autoHyphenation",
2741    "consecutiveHyphenLimit",
2742    "hyphenationZone",
2743    "doNotHyphenateCaps",
2744    "defaultTableStyle",
2745    "evenAndOddHeaders",
2746    "bookFoldRevPrinting",
2747    "bookFoldPrinting",
2748    "bookFoldPrintingSheets",
2749    "characterSpacingControl",
2750    "updateFields",
2751    "compat",
2752    "docVars",
2753    "mathPr",
2754    "themeFontLang",
2755    "decimalSymbol",
2756    "listSeparator",
2757];
2758
2759/// Supported paths whose schema allows more than one occurrence.
2760const SETTINGS_REPEATABLE: &[&[&str]] = &[&["docVars", "docVar"], &["compat", "compatSetting"]];
2761
2762/// `CT_MailMerge` child order, including the three preservation-only members.
2763const MAIL_MERGE_ORDER: &[&[u8]] = &[
2764    b"mainDocumentType",
2765    b"linkToQuery",
2766    b"dataType",
2767    b"connectString",
2768    b"query",
2769    b"dataSource",
2770    b"headerSource",
2771    b"doNotSuppressBlankLines",
2772    b"destination",
2773    b"addressFieldName",
2774    b"mailSubject",
2775    b"mailAsAttachment",
2776    b"viewMergedData",
2777    b"activeRecord",
2778    b"checkErrors",
2779    b"odso",
2780];
2781
2782/// The bounded `w:mailMerge` children this model authors, in schema order.
2783pub const MAIL_MERGE_MEMBERS: &[&str] = &[
2784    "mainDocumentType",
2785    "linkToQuery",
2786    "dataType",
2787    "connectString",
2788    "query",
2789    "doNotSuppressBlankLines",
2790    "destination",
2791    "addressFieldName",
2792    "mailSubject",
2793    "mailAsAttachment",
2794    "viewMergedData",
2795    "activeRecord",
2796    "checkErrors",
2797];
2798
2799/// Return the supported name one element carries, when it carries one.
2800pub(crate) fn supported_name(
2801    names: &[&'static str],
2802    element: &BytesStart<'_>,
2803    prefixes: &[String],
2804) -> Option<&'static str> {
2805    names
2806        .iter()
2807        .copied()
2808        .find(|name| is_word_element(element.name().as_ref(), name.as_bytes(), prefixes))
2809}
2810
2811pub(crate) fn capture_empty_element(element: &BytesStart<'_>) -> Result<Vec<u8>> {
2812    let mut raw = Vec::new();
2813    Writer::new(&mut raw).write_event(Event::Empty(element.to_owned().into_owned()))?;
2814    Ok(raw)
2815}
2816
2817/// Replace one qualified scalar attribute without serializing producer bytes.
2818pub(crate) fn rewrite_word_root_attribute(
2819    raw: &[u8],
2820    inherited: &[String],
2821    local: &[u8],
2822    value: &str,
2823) -> Result<Vec<u8>> {
2824    let mut reader = Reader::from_reader(raw);
2825    let element = loop {
2826        match reader.read_event()? {
2827            Event::Start(element) | Event::Empty(element) => break element,
2828            Event::Decl(_) | Event::Comment(_) | Event::PI(_) => {}
2829            Event::Text(text) if text.as_ref().iter().all(u8::is_ascii_whitespace) => {}
2830            _ => return Err(OxmlError::InvalidValue("missing property root".to_owned())),
2831        }
2832    };
2833    let prefixes = word_prefixes_at(&element, inherited)?;
2834    let mut owned = None;
2835    for attribute in element.attributes() {
2836        let attribute = attribute.map_err(|error| OxmlError::InvalidValue(error.to_string()))?;
2837        if is_word_attribute(attribute.key.as_ref(), local, &prefixes) {
2838            if owned.is_some() {
2839                return Err(OxmlError::InvalidValue(
2840                    "duplicate expanded property attribute".to_owned(),
2841                ));
2842            }
2843            let start = (attribute.value.as_ref().as_ptr() as usize)
2844                .checked_sub(raw.as_ptr() as usize)
2845                .ok_or_else(|| {
2846                    OxmlError::InvalidValue("attribute source was not retained".to_owned())
2847                })?;
2848            let end = start + attribute.value.len();
2849            if end > raw.len() {
2850                return Err(OxmlError::InvalidValue(
2851                    "attribute source was not retained".to_owned(),
2852                ));
2853            }
2854            owned = Some((start, end));
2855        }
2856    }
2857    let mut output = raw.to_vec();
2858    if let Some((start, end)) = owned {
2859        output.splice(start..end, value.bytes());
2860    } else {
2861        let prefix = prefixes
2862            .iter()
2863            .find(|prefix| !prefix.is_empty() && !prefix.starts_with('\0'));
2864        let bindings = namespace_bindings(&prefixes);
2865        let (prefix, declaration) = match prefix {
2866            Some(prefix) => (prefix.clone(), String::new()),
2867            None => {
2868                let mut prefix = "pagePropertyWord".to_owned();
2869                while bindings.iter().any(|(name, _)| name == &prefix)
2870                    || element.attributes().flatten().any(|attribute| {
2871                        attribute.key.as_ref() == format!("xmlns:{prefix}").as_bytes()
2872                    })
2873                {
2874                    prefix.push('x');
2875                }
2876                let declaration = format!(" xmlns:{prefix}=\"{W_NS}\"");
2877                (prefix, declaration)
2878            }
2879        };
2880        let end = reader.buffer_position() as usize;
2881        let insert = if raw.get(end.saturating_sub(2)) == Some(&b'/') {
2882            end - 2
2883        } else {
2884            end - 1
2885        };
2886        let attribute = format!(
2887            "{declaration} {prefix}:{}=\"{value}\"",
2888            std::str::from_utf8(local)?
2889        );
2890        output.splice(insert..insert, attribute.bytes());
2891    }
2892    Ok(output)
2893}
2894
2895pub(crate) fn word_value(element: &BytesStart<'_>, prefixes: &[String]) -> Option<String> {
2896    word_attribute(element, b"val", prefixes).ok().flatten()
2897}
2898
2899fn parse_twips(element: &BytesStart<'_>, prefixes: &[String]) -> Option<Twips> {
2900    let value = word_value(element, prefixes)?;
2901    parse_integer_measurement::<i32>(element.name().as_ref(), b"w:val", &value)
2902        .ok()
2903        .filter(|value| *value >= 0)
2904        .map(Twips)
2905}
2906
2907pub(crate) fn parse_decimal(element: &BytesStart<'_>, prefixes: &[String]) -> Option<i32> {
2908    word_value(element, prefixes)?.parse::<i32>().ok()
2909}
2910
2911fn parse_zoom(element: &BytesStart<'_>, prefixes: &[String]) -> Option<DocumentZoom> {
2912    let kind = match word_value(element, prefixes) {
2913        Some(value) => Some(ZoomKind::parse(&value)?),
2914        None => None,
2915    };
2916    let percent = match word_attribute(element, b"percent", prefixes).ok().flatten() {
2917        Some(value) => Some(value.trim_end_matches('%').parse::<u32>().ok()?),
2918        None => None,
2919    };
2920    Some(DocumentZoom { kind, percent })
2921}
2922
2923fn parse_proof_state(element: &BytesStart<'_>, prefixes: &[String]) -> Option<DocumentProofState> {
2924    let attribute = |local| word_attribute(element, local, prefixes).ok().flatten();
2925    let spelling = match attribute(b"spelling") {
2926        Some(value) => Some(ProofState::parse(&value)?),
2927        None => None,
2928    };
2929    let grammar = match attribute(b"grammar") {
2930        Some(value) => Some(ProofState::parse(&value)?),
2931        None => None,
2932    };
2933    Some(DocumentProofState { spelling, grammar })
2934}
2935
2936fn parse_theme_font_language(
2937    element: &BytesStart<'_>,
2938    prefixes: &[String],
2939) -> Result<ThemeFontLanguage> {
2940    Ok(ThemeFontLanguage {
2941        latin: word_attribute(element, b"val", prefixes)?,
2942        east_asia: word_attribute(element, b"eastAsia", prefixes)?,
2943        bidi: word_attribute(element, b"bidi", prefixes)?,
2944    })
2945}
2946
2947fn parse_compatibility_setting(
2948    element: &BytesStart<'_>,
2949    prefixes: &[String],
2950) -> Option<CompatibilitySetting> {
2951    Some(CompatibilitySetting {
2952        name: word_attribute(element, b"name", prefixes).ok().flatten()?,
2953        uri: word_attribute(element, b"uri", prefixes).ok().flatten()?,
2954        value: word_value(element, prefixes)?,
2955    })
2956}
2957
2958pub(crate) fn write_valued_setting(name: &str, value: &str) -> Result<Vec<u8>> {
2959    let mut writer = Writer::new(Vec::new());
2960    let mut element = BytesStart::new(name);
2961    element.push_attribute(("w:val", value));
2962    writer.write_event(Event::Empty(element))?;
2963    Ok(writer.into_inner())
2964}
2965
2966fn write_zoom(value: &DocumentZoom) -> Result<Vec<u8>> {
2967    let mut writer = Writer::new(Vec::new());
2968    let mut element = BytesStart::new("w:zoom");
2969    if let Some(kind) = value.kind {
2970        element.push_attribute(("w:val", kind.as_str()));
2971    }
2972    let percent = value.percent.map(|percent| percent.to_string());
2973    if let Some(percent) = &percent {
2974        element.push_attribute(("w:percent", percent.as_str()));
2975    }
2976    writer.write_event(Event::Empty(element))?;
2977    Ok(writer.into_inner())
2978}
2979
2980fn write_proof_state(value: &DocumentProofState) -> Result<Vec<u8>> {
2981    let mut writer = Writer::new(Vec::new());
2982    let mut element = BytesStart::new("w:proofState");
2983    if let Some(spelling) = value.spelling {
2984        element.push_attribute(("w:spelling", spelling.as_str()));
2985    }
2986    if let Some(grammar) = value.grammar {
2987        element.push_attribute(("w:grammar", grammar.as_str()));
2988    }
2989    writer.write_event(Event::Empty(element))?;
2990    Ok(writer.into_inner())
2991}
2992
2993fn write_theme_font_language(language: &ThemeFontLanguage) -> Result<Vec<u8>> {
2994    let mut writer = Writer::new(Vec::new());
2995    let mut element = BytesStart::new("w:themeFontLang");
2996    if let Some(value) = &language.latin {
2997        element.push_attribute(("w:val", value.as_str()));
2998    }
2999    if let Some(value) = &language.east_asia {
3000        element.push_attribute(("w:eastAsia", value.as_str()));
3001    }
3002    if let Some(value) = &language.bidi {
3003        element.push_attribute(("w:bidi", value.as_str()));
3004    }
3005    writer.write_event(Event::Empty(element))?;
3006    Ok(writer.into_inner())
3007}
3008
3009fn write_document_variables(variables: &[DocumentVariable]) -> Result<Vec<u8>> {
3010    let mut writer = Writer::new(Vec::new());
3011    for variable in variables {
3012        let mut element = BytesStart::new("w:docVar");
3013        element.push_attribute(("w:name", variable.name.as_str()));
3014        element.push_attribute(("w:val", variable.value.as_str()));
3015        writer.write_event(Event::Empty(element))?;
3016    }
3017    Ok(writer.into_inner())
3018}
3019
3020fn write_compatibility_settings(settings: &[CompatibilitySetting]) -> Result<Vec<u8>> {
3021    let mut writer = Writer::new(Vec::new());
3022    for setting in settings {
3023        let mut element = BytesStart::new("w:compatSetting");
3024        element.push_attribute(("w:name", setting.name.as_str()));
3025        element.push_attribute(("w:uri", setting.uri.as_str()));
3026        element.push_attribute(("w:val", setting.value.as_str()));
3027        writer.write_event(Event::Empty(element))?;
3028    }
3029    Ok(writer.into_inner())
3030}
3031
3032/// Wrap modeled children in one fixed-prefix group element.
3033fn wrap_group(name: &str, children: &[u8]) -> Result<Vec<u8>> {
3034    let mut writer = Writer::new(Vec::with_capacity(children.len() + 2 * name.len() + 5));
3035    writer.write_event(Event::Start(BytesStart::new(name)))?;
3036    writer.get_mut().extend_from_slice(children);
3037    writer.write_event(Event::End(BytesEnd::new(name)))?;
3038    Ok(writer.into_inner())
3039}
3040
3041/// Join every serialized group child in order.
3042fn concatenate(children: &[(&'static str, Vec<u8>)]) -> Vec<u8> {
3043    let mut joined = Vec::new();
3044    for (_, bytes) in children {
3045        joined.extend_from_slice(bytes);
3046    }
3047    joined
3048}
3049
3050/// Serialize every modeled `w:mailMerge` member, empty where absent.
3051fn mail_merge_children(value: &MailMerge) -> Result<Vec<(&'static str, Vec<u8>)>> {
3052    let valued = |local: &str, text: Option<&str>| -> Result<Vec<u8>> {
3053        match text {
3054            Some(text) => write_valued_setting(&format!("w:{local}"), text),
3055            None => Ok(Vec::new()),
3056        }
3057    };
3058    let toggled = |local: &str, enabled: Option<bool>| -> Result<Vec<u8>> {
3059        match enabled {
3060            Some(enabled) => write_toggle_setting(&format!("w:{local}"), enabled),
3061            None => Ok(Vec::new()),
3062        }
3063    };
3064    let active_record = value.active_record.map(|value| value.to_string());
3065    let check_errors = value.check_errors.map(|value| value.to_string());
3066    Ok(vec![
3067        (
3068            "mainDocumentType",
3069            valued(
3070                "mainDocumentType",
3071                value.main_document_type.map(MailMergeDocumentType::as_str),
3072            )?,
3073        ),
3074        ("linkToQuery", toggled("linkToQuery", value.link_to_query)?),
3075        ("dataType", valued("dataType", value.data_type.as_deref())?),
3076        (
3077            "connectString",
3078            valued("connectString", value.connect_string.as_deref())?,
3079        ),
3080        ("query", valued("query", value.query.as_deref())?),
3081        (
3082            "doNotSuppressBlankLines",
3083            toggled("doNotSuppressBlankLines", value.do_not_suppress_blank_lines)?,
3084        ),
3085        (
3086            "destination",
3087            valued(
3088                "destination",
3089                value.destination.map(MailMergeDestination::as_str),
3090            )?,
3091        ),
3092        (
3093            "addressFieldName",
3094            valued("addressFieldName", value.address_field_name.as_deref())?,
3095        ),
3096        (
3097            "mailSubject",
3098            valued("mailSubject", value.mail_subject.as_deref())?,
3099        ),
3100        (
3101            "mailAsAttachment",
3102            toggled("mailAsAttachment", value.mail_as_attachment)?,
3103        ),
3104        (
3105            "viewMergedData",
3106            toggled("viewMergedData", value.view_merged_data)?,
3107        ),
3108        (
3109            "activeRecord",
3110            valued("activeRecord", active_record.as_deref())?,
3111        ),
3112        (
3113            "checkErrors",
3114            valued("checkErrors", check_errors.as_deref())?,
3115        ),
3116    ])
3117}
3118
3119/// Replace every modeled occurrence of one repeated child inside one group.
3120fn rewrite_group_setting(
3121    source: &[u8],
3122    group_local: &[u8],
3123    child_local: &[u8],
3124    modeled_children: Vec<u8>,
3125) -> Result<Vec<u8>> {
3126    let existing = find_top_level_children(source, group_local)?;
3127    let replacement = if existing.is_empty() {
3128        if modeled_children.is_empty() {
3129            Vec::new()
3130        } else {
3131            let group_name = format!("w:{}", String::from_utf8_lossy(group_local));
3132            let mut writer = Writer::new(Vec::new());
3133            writer.write_event(Event::Start(BytesStart::new(&group_name)))?;
3134            writer.get_mut().extend_from_slice(&modeled_children);
3135            writer.write_event(Event::End(BytesEnd::new(&group_name)))?;
3136            writer.into_inner()
3137        }
3138    } else {
3139        let mut replacement = Vec::new();
3140        for (index, (raw, inherited_prefixes)) in existing.into_iter().enumerate() {
3141            replacement.extend_from_slice(&rewrite_group_children(
3142                &raw,
3143                child_local,
3144                if index == 0 { &modeled_children } else { &[] },
3145                &inherited_prefixes,
3146            )?);
3147        }
3148        replacement
3149    };
3150    rewrite_ordered_child(
3151        source,
3152        SETTINGS_ORDER,
3153        group_local,
3154        &replacement,
3155        is_word_element,
3156    )
3157}
3158
3159/// Capture every top-level child with one local name, with its inherited scope.
3160pub(crate) fn find_top_level_children(
3161    source: &[u8],
3162    local: &[u8],
3163) -> Result<Vec<(Vec<u8>, Vec<String>)>> {
3164    let mut reader = Reader::from_reader(source);
3165    reader.config_mut().trim_text(false);
3166    let mut root_prefixes = Vec::new();
3167    let mut matches = Vec::new();
3168    let mut depth = 0usize;
3169    let mut buffer = Vec::new();
3170    loop {
3171        match reader.read_event_into(&mut buffer)? {
3172            Event::Start(element) if depth == 0 => {
3173                root_prefixes = word_prefixes_at(&element, &[])?;
3174                depth = 1;
3175            }
3176            Event::Start(element) if depth == 1 => {
3177                let prefixes = word_prefixes_at(&element, &root_prefixes)?;
3178                if is_word_element(element.name().as_ref(), local, &prefixes) {
3179                    matches.push((
3180                        capture_element(&mut reader, &element)?,
3181                        root_prefixes.clone(),
3182                    ));
3183                } else {
3184                    depth += 1;
3185                }
3186            }
3187            Event::Empty(element) if depth == 1 => {
3188                let prefixes = word_prefixes_at(&element, &root_prefixes)?;
3189                if is_word_element(element.name().as_ref(), local, &prefixes) {
3190                    matches.push((capture_empty_element(&element)?, root_prefixes.clone()));
3191                }
3192            }
3193            Event::Start(_) => depth += 1,
3194            Event::End(_) => depth = depth.saturating_sub(1),
3195            Event::Eof => return Ok(matches),
3196            _ => {}
3197        }
3198        buffer.clear();
3199    }
3200}
3201
3202fn rewrite_group_children(
3203    raw: &[u8],
3204    child_local: &[u8],
3205    modeled: &[u8],
3206    inherited_prefixes: &[String],
3207) -> Result<Vec<u8>> {
3208    let mut reader = Reader::from_reader(raw);
3209    reader.config_mut().trim_text(false);
3210    let mut writer = Writer::new(Vec::with_capacity(raw.len() + modeled.len()));
3211    let mut prefixes = Vec::new();
3212    let mut depth = 0usize;
3213    let mut inserted = false;
3214    let mut buffer = Vec::new();
3215    loop {
3216        match reader.read_event_into(&mut buffer)? {
3217            Event::Start(element) if depth == 0 => {
3218                prefixes = word_prefixes_at(&element, inherited_prefixes)?;
3219                writer.write_event(Event::Start(element.into_owned()))?;
3220                depth = 1;
3221            }
3222            Event::Empty(element) if depth == 0 => {
3223                let name = std::str::from_utf8(element.name().as_ref())?.to_owned();
3224                writer.write_event(Event::Start(element.into_owned()))?;
3225                writer.get_mut().extend_from_slice(modeled);
3226                writer.write_event(Event::End(BytesEnd::new(name)))?;
3227                inserted = true;
3228            }
3229            Event::Start(element) if depth == 1 => {
3230                let child_prefixes = word_prefixes_at(&element, &prefixes)?;
3231                if is_word_element(element.name().as_ref(), child_local, &child_prefixes) {
3232                    if !inserted {
3233                        writer.get_mut().extend_from_slice(modeled);
3234                        inserted = true;
3235                    }
3236                    capture_element(&mut reader, &element)?;
3237                } else {
3238                    writer.write_event(Event::Start(element.into_owned()))?;
3239                    depth += 1;
3240                }
3241            }
3242            Event::Empty(element) if depth == 1 => {
3243                let child_prefixes = word_prefixes_at(&element, &prefixes)?;
3244                if is_word_element(element.name().as_ref(), child_local, &child_prefixes) {
3245                    if !inserted {
3246                        writer.get_mut().extend_from_slice(modeled);
3247                        inserted = true;
3248                    }
3249                } else {
3250                    writer.write_event(Event::Empty(element.into_owned()))?;
3251                }
3252            }
3253            Event::End(element) if depth == 1 => {
3254                if !inserted {
3255                    writer.get_mut().extend_from_slice(modeled);
3256                }
3257                writer.write_event(Event::End(element.into_owned()))?;
3258                depth = 0;
3259            }
3260            Event::Start(element) => {
3261                writer.write_event(Event::Start(element.into_owned()))?;
3262                depth += 1;
3263            }
3264            Event::End(element) => {
3265                writer.write_event(Event::End(element.into_owned()))?;
3266                depth = depth.saturating_sub(1);
3267            }
3268            Event::Eof => break,
3269            event => writer.write_event(event.into_owned())?,
3270        }
3271        buffer.clear();
3272    }
3273    Ok(writer.into_inner())
3274}
3275
3276/// Replace one modeled child at its `xsd:sequence` position inside a group.
3277fn rewrite_group_child(
3278    source: &[u8],
3279    group_local: &[u8],
3280    child_local: &[u8],
3281    replacement: &[u8],
3282    order: &[&[u8]],
3283) -> Result<Vec<u8>> {
3284    let existing = find_top_level_children(source, group_local)?;
3285    if existing.is_empty() {
3286        return Ok(source.to_vec());
3287    }
3288    let mut rebuilt = Vec::new();
3289    for (index, (raw, inherited_prefixes)) in existing.into_iter().enumerate() {
3290        rebuilt.extend_from_slice(&rewrite_child_in_raw(
3291            &raw,
3292            child_local,
3293            if index == 0 { replacement } else { &[] },
3294            &inherited_prefixes,
3295            order,
3296        )?);
3297    }
3298    rewrite_ordered_child(
3299        source,
3300        SETTINGS_ORDER,
3301        group_local,
3302        &rebuilt,
3303        is_word_element,
3304    )
3305}
3306
3307fn rewrite_child_in_raw(
3308    raw: &[u8],
3309    child_local: &[u8],
3310    replacement: &[u8],
3311    inherited_prefixes: &[String],
3312    order: &[&[u8]],
3313) -> Result<Vec<u8>> {
3314    let mut reader = Reader::from_reader(raw);
3315    reader.config_mut().trim_text(false);
3316    let mut writer = Writer::new(Vec::with_capacity(raw.len() + replacement.len()));
3317    let mut prefixes = Vec::new();
3318    let mut depth = 0usize;
3319    let mut inserted = replacement.is_empty();
3320    let mut buffer = Vec::new();
3321    loop {
3322        match reader.read_event_into(&mut buffer)? {
3323            Event::Start(element) if depth == 0 => {
3324                prefixes = word_prefixes_at(&element, inherited_prefixes)?;
3325                writer.write_event(Event::Start(element.into_owned()))?;
3326                depth = 1;
3327            }
3328            Event::Empty(element) if depth == 0 => {
3329                if inserted {
3330                    writer.write_event(Event::Empty(element.into_owned()))?;
3331                } else {
3332                    let name = std::str::from_utf8(element.name().as_ref())?.to_owned();
3333                    writer.write_event(Event::Start(element.into_owned()))?;
3334                    writer.get_mut().extend_from_slice(replacement);
3335                    writer.write_event(Event::End(BytesEnd::new(name)))?;
3336                    inserted = true;
3337                }
3338            }
3339            Event::Start(element) if depth == 1 => {
3340                let child_prefixes = word_prefixes_at(&element, &prefixes)?;
3341                if is_word_element(element.name().as_ref(), child_local, &child_prefixes) {
3342                    if !inserted {
3343                        writer.get_mut().extend_from_slice(replacement);
3344                        inserted = true;
3345                    }
3346                    capture_element(&mut reader, &element)?;
3347                } else {
3348                    if !inserted && element_follows(order, child_local, &element, &child_prefixes) {
3349                        writer.get_mut().extend_from_slice(replacement);
3350                        inserted = true;
3351                    }
3352                    writer.write_event(Event::Start(element.into_owned()))?;
3353                    depth += 1;
3354                }
3355            }
3356            Event::Empty(element) if depth == 1 => {
3357                let child_prefixes = word_prefixes_at(&element, &prefixes)?;
3358                if is_word_element(element.name().as_ref(), child_local, &child_prefixes) {
3359                    if !inserted {
3360                        writer.get_mut().extend_from_slice(replacement);
3361                        inserted = true;
3362                    }
3363                } else {
3364                    if !inserted && element_follows(order, child_local, &element, &child_prefixes) {
3365                        writer.get_mut().extend_from_slice(replacement);
3366                        inserted = true;
3367                    }
3368                    writer.write_event(Event::Empty(element.into_owned()))?;
3369                }
3370            }
3371            Event::End(element) if depth == 1 => {
3372                if !inserted {
3373                    writer.get_mut().extend_from_slice(replacement);
3374                    inserted = true;
3375                }
3376                writer.write_event(Event::End(element.into_owned()))?;
3377                depth = 0;
3378            }
3379            Event::Start(element) => {
3380                writer.write_event(Event::Start(element.into_owned()))?;
3381                depth += 1;
3382            }
3383            Event::End(element) => {
3384                writer.write_event(Event::End(element.into_owned()))?;
3385                depth = depth.saturating_sub(1);
3386            }
3387            Event::Eof => break,
3388            event => writer.write_event(event.into_owned())?,
3389        }
3390        buffer.clear();
3391    }
3392    Ok(writer.into_inner())
3393}
3394
3395/// Create one empty top-level group at its schema position when it is absent.
3396fn ensure_top_level_group(source: &[u8], local: &[u8]) -> Result<Vec<u8>> {
3397    if !find_top_level_children(source, local)?.is_empty() {
3398        return Ok(source.to_vec());
3399    }
3400    let name = format!("w:{}", std::str::from_utf8(local)?);
3401    let mut writer = Writer::new(Vec::new());
3402    writer.write_event(Event::Start(BytesStart::new(name.as_str())))?;
3403    writer.write_event(Event::End(BytesEnd::new(name.as_str())))?;
3404    let group = writer.into_inner();
3405    rewrite_ordered_child(source, SETTINGS_ORDER, local, &group, is_word_element)
3406}
3407
3408/// Report whether every top-level group with one name holds no element child.
3409fn top_level_group_is_childless(source: &[u8], local: &[u8]) -> Result<bool> {
3410    for (raw, _) in find_top_level_children(source, local)? {
3411        let mut reader = Reader::from_reader(raw.as_slice());
3412        reader.config_mut().trim_text(false);
3413        let mut seen_root = false;
3414        let mut buffer = Vec::new();
3415        loop {
3416            match reader.read_event_into(&mut buffer)? {
3417                Event::Start(_) | Event::Empty(_) if !seen_root => seen_root = true,
3418                Event::Start(_) | Event::Empty(_) => return Ok(false),
3419                Event::Eof => break,
3420                _ => {}
3421            }
3422            buffer.clear();
3423        }
3424    }
3425    Ok(true)
3426}
3427
3428/// Replace one child at its `xsd:sequence` position under a part root.
3429///
3430/// `matches` selects the modeled element, which lets the OfficeMath defaults
3431/// reuse the same insertion walk from a different namespace.
3432pub(crate) fn rewrite_ordered_child(
3433    source: &[u8],
3434    order: &[&[u8]],
3435    local: &[u8],
3436    replacement: &[u8],
3437    matches: fn(&[u8], &[u8], &[String]) -> bool,
3438) -> Result<Vec<u8>> {
3439    let mut reader = Reader::from_reader(source);
3440    reader.config_mut().trim_text(false);
3441    let mut writer = Writer::new(Vec::with_capacity(source.len() + replacement.len()));
3442    let mut root_prefixes = Vec::new();
3443    let mut depth = 0usize;
3444    let mut inserted = false;
3445    let mut buffer = Vec::new();
3446    loop {
3447        match reader.read_event_into(&mut buffer)? {
3448            Event::Start(element) if depth == 0 => {
3449                root_prefixes = word_prefixes_at(&element, &[])?;
3450                let mut root = element.into_owned();
3451                ensure_fixed_word_prefix(&mut root)?;
3452                writer.write_event(Event::Start(root))?;
3453                depth = 1;
3454            }
3455            Event::Empty(element) if depth == 0 => {
3456                let root_name = std::str::from_utf8(element.name().as_ref())?.to_owned();
3457                let mut root = element.into_owned();
3458                ensure_fixed_word_prefix(&mut root)?;
3459                writer.write_event(Event::Start(root))?;
3460                writer.get_mut().extend_from_slice(replacement);
3461                writer.write_event(Event::End(BytesEnd::new(root_name)))?;
3462                inserted = true;
3463            }
3464            Event::Start(element) if depth == 1 => {
3465                let prefixes = word_prefixes_at(&element, &root_prefixes)?;
3466                if matches(element.name().as_ref(), local, &prefixes) {
3467                    if !inserted {
3468                        writer.get_mut().extend_from_slice(replacement);
3469                        inserted = true;
3470                    }
3471                    capture_element(&mut reader, &element)?;
3472                } else {
3473                    if !inserted && element_follows(order, local, &element, &prefixes) {
3474                        writer.get_mut().extend_from_slice(replacement);
3475                        inserted = true;
3476                    }
3477                    writer.write_event(Event::Start(element.into_owned()))?;
3478                    depth += 1;
3479                }
3480            }
3481            Event::Empty(element) if depth == 1 => {
3482                let prefixes = word_prefixes_at(&element, &root_prefixes)?;
3483                if matches(element.name().as_ref(), local, &prefixes) {
3484                    if !inserted {
3485                        writer.get_mut().extend_from_slice(replacement);
3486                        inserted = true;
3487                    }
3488                } else {
3489                    if !inserted && element_follows(order, local, &element, &prefixes) {
3490                        writer.get_mut().extend_from_slice(replacement);
3491                        inserted = true;
3492                    }
3493                    writer.write_event(Event::Empty(element.into_owned()))?;
3494                }
3495            }
3496            Event::End(element) if depth == 1 => {
3497                if !inserted {
3498                    writer.get_mut().extend_from_slice(replacement);
3499                }
3500                writer.write_event(Event::End(element.into_owned()))?;
3501                depth = 0;
3502            }
3503            Event::Start(element) => {
3504                writer.write_event(Event::Start(element.into_owned()))?;
3505                depth += 1;
3506            }
3507            Event::End(element) => {
3508                writer.write_event(Event::End(element.into_owned()))?;
3509                depth = depth.saturating_sub(1);
3510            }
3511            Event::Eof => break,
3512            event => writer.write_event(event.into_owned())?,
3513        }
3514        buffer.clear();
3515    }
3516    Ok(writer.into_inner())
3517}
3518
3519/// Report whether one element sits after `local` in an `xsd:sequence` table.
3520pub(crate) fn element_follows(
3521    order: &[&[u8]],
3522    local: &[u8],
3523    element: &BytesStart<'_>,
3524    prefixes: &[String],
3525) -> bool {
3526    let Some(target_index) = order.iter().position(|candidate| *candidate == local) else {
3527        return false;
3528    };
3529    order
3530        .iter()
3531        .skip(target_index + 1)
3532        .any(|candidate| is_word_element(element.name().as_ref(), candidate, prefixes))
3533}
3534
3535fn rewrite_math_properties(source: &[u8], properties: Option<&MathProperties>) -> Result<Vec<u8>> {
3536    let replacement = match properties {
3537        Some(properties) => {
3538            let mut writer = Writer::new(Vec::new());
3539            properties.write_xml(&mut writer)?;
3540            writer.into_inner()
3541        }
3542        None => Vec::new(),
3543    };
3544    rewrite_ordered_child(
3545        source,
3546        SETTINGS_ORDER,
3547        b"mathPr",
3548        &replacement,
3549        is_math_element,
3550    )
3551}
3552
3553pub(crate) fn parse_toggle(element: &BytesStart<'_>, prefixes: &[String]) -> Result<Option<bool>> {
3554    Ok(match word_attribute(element, b"val", prefixes)? {
3555        Some(value) => parse_on_off(&value),
3556        None => Some(true),
3557    })
3558}
3559
3560pub(crate) fn write_toggle_setting(name: &str, value: bool) -> Result<Vec<u8>> {
3561    let mut writer = Writer::new(Vec::new());
3562    let mut element = BytesStart::new(name);
3563    if !value {
3564        element.push_attribute(("w:val", "false"));
3565    }
3566    writer.write_event(Event::Empty(element))?;
3567    Ok(writer.into_inner())
3568}
3569
3570pub(crate) fn ensure_fixed_word_prefix(root: &mut BytesStart<'_>) -> Result<()> {
3571    let mut fixed_binding = false;
3572    let mut conflicting_binding = false;
3573    for attribute in root.attributes() {
3574        let attribute = attribute?;
3575        if attribute.key.as_ref() == b"xmlns:w" {
3576            let value =
3577                attribute.decoded_and_normalized_value(XmlVersion::Implicit1_0, root.decoder())?;
3578            fixed_binding = value.as_bytes() == W_NS.as_bytes();
3579            conflicting_binding = !fixed_binding;
3580        }
3581    }
3582    if conflicting_binding {
3583        return Err(OxmlError::InvalidValue(
3584            "settings binds the reserved w prefix to a foreign namespace".to_owned(),
3585        ));
3586    }
3587    if !fixed_binding {
3588        root.push_attribute(("xmlns:w", W_NS));
3589    }
3590    Ok(())
3591}
3592
3593fn parse_document_variable(
3594    element: &BytesStart<'_>,
3595    prefixes: &[String],
3596) -> Option<DocumentVariable> {
3597    Some(DocumentVariable {
3598        name: word_attribute(element, b"name", prefixes).ok().flatten()?,
3599        value: word_value(element, prefixes)?,
3600    })
3601}
3602
3603fn parse_document_protection(
3604    element: &BytesStart<'_>,
3605    prefixes: &[String],
3606) -> Option<DocumentProtection> {
3607    let value = |name| word_attribute(element, name, prefixes).ok().flatten();
3608    let mode = ProtectionMode::parse(&value(b"edit")?)?;
3609    let enforcement = match value(b"enforcement") {
3610        Some(value) => Some(parse_on_off(&value)?),
3611        None => None,
3612    };
3613    let formatting = match value(b"formatting") {
3614        Some(value) => Some(parse_on_off(&value)?),
3615        None => None,
3616    };
3617    let provider_type = match value(b"cryptProviderType") {
3618        Some(value) => Some(CryptProviderType::parse(&value)?),
3619        None => None,
3620    };
3621    let algorithm_class = match value(b"cryptAlgorithmClass") {
3622        Some(value) => Some(CryptAlgorithmClass::parse(&value)?),
3623        None => None,
3624    };
3625    let algorithm_type = match value(b"cryptAlgorithmType") {
3626        Some(value) => Some(CryptAlgorithmType::parse(&value)?),
3627        None => None,
3628    };
3629    let algorithm_sid = match value(b"cryptAlgorithmSid") {
3630        Some(value) => Some(value.parse::<u32>().ok()?),
3631        None => None,
3632    };
3633    let spin_count = match value(b"cryptSpinCount") {
3634        Some(value) => Some(value.parse::<u32>().ok()?),
3635        None => None,
3636    };
3637    Some(DocumentProtection {
3638        mode,
3639        enforcement,
3640        formatting,
3641        provider_type,
3642        algorithm_class,
3643        algorithm_type,
3644        algorithm_sid,
3645        spin_count,
3646        hash: value(b"hash"),
3647        salt: value(b"salt"),
3648    })
3649}
3650
3651pub(crate) fn word_attribute(
3652    element: &BytesStart<'_>,
3653    local: &[u8],
3654    prefixes: &[String],
3655) -> Result<Option<String>> {
3656    for attribute in element.attributes() {
3657        let attribute = attribute?;
3658        if is_word_attribute(attribute.key.as_ref(), local, prefixes) {
3659            return Ok(Some(
3660                attribute
3661                    .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?
3662                    .into_owned(),
3663            ));
3664        }
3665    }
3666    Ok(None)
3667}
3668
3669pub(crate) fn parse_on_off(value: &str) -> Option<bool> {
3670    match value {
3671        "1" | "true" | "on" => Some(true),
3672        "0" | "false" | "off" => Some(false),
3673        _ => None,
3674    }
3675}
3676
3677fn write_document_protection(protection: &DocumentProtection) -> Result<Vec<u8>> {
3678    let mut writer = Writer::new(Vec::new());
3679    let mut element = BytesStart::new("w:documentProtection");
3680    element.push_attribute(("w:edit", protection.mode.as_str()));
3681    if let Some(formatting) = protection.formatting {
3682        element.push_attribute(("w:formatting", if formatting { "1" } else { "0" }));
3683    }
3684    if let Some(enforcement) = protection.enforcement {
3685        element.push_attribute(("w:enforcement", if enforcement { "1" } else { "0" }));
3686    }
3687    if let Some(provider_type) = protection.provider_type {
3688        element.push_attribute(("w:cryptProviderType", provider_type.as_str()));
3689    }
3690    if let Some(algorithm_class) = protection.algorithm_class {
3691        element.push_attribute(("w:cryptAlgorithmClass", algorithm_class.as_str()));
3692    }
3693    if let Some(algorithm_type) = protection.algorithm_type {
3694        element.push_attribute(("w:cryptAlgorithmType", algorithm_type.as_str()));
3695    }
3696    let algorithm_sid = protection.algorithm_sid.map(|value| value.to_string());
3697    if let Some(value) = &algorithm_sid {
3698        element.push_attribute(("w:cryptAlgorithmSid", value.as_str()));
3699    }
3700    let spin_count = protection.spin_count.map(|value| value.to_string());
3701    if let Some(value) = &spin_count {
3702        element.push_attribute(("w:cryptSpinCount", value.as_str()));
3703    }
3704    if let Some(value) = &protection.hash {
3705        element.push_attribute(("w:hash", value.as_str()));
3706    }
3707    if let Some(value) = &protection.salt {
3708        element.push_attribute(("w:salt", value.as_str()));
3709    }
3710    writer.write_event(Event::Empty(element))?;
3711    Ok(writer.into_inner())
3712}
3713
3714#[cfg(test)]
3715mod tests {
3716    #[test]
3717    fn fx220_font_flags_preserve_presence_aliases_payload_and_order() {
3718        for (local, getter, setter) in [
3719            (
3720                "embedTrueTypeFonts",
3721                CT_Settings::embed_true_type_fonts as fn(&CT_Settings) -> Result<Option<bool>>,
3722                CT_Settings::set_embed_true_type_fonts
3723                    as fn(&mut CT_Settings, Option<bool>) -> Result<()>,
3724            ),
3725            (
3726                "embedSystemFonts",
3727                CT_Settings::embed_system_fonts,
3728                CT_Settings::set_embed_system_fonts,
3729            ),
3730            (
3731                "saveSubsetFonts",
3732                CT_Settings::save_subset_fonts,
3733                CT_Settings::set_save_subset_fonts,
3734            ),
3735        ] {
3736            for prefix in ["a:", ""] {
3737                let binding = if prefix.is_empty() {
3738                    format!("xmlns=\"{W_NS}\"")
3739                } else {
3740                    format!("xmlns:a=\"{W_NS}\"")
3741                };
3742                let source = format!(
3743                    "<{prefix}settings {binding} xmlns:z=\"{W_NS}\" xmlns:x=\"urn:foreign\"><{prefix}view z:val=\"print\"/><x:{local} x:val=\"foreign\"/><{prefix}{local} z:val='off' x:keep='&amp;'><x:payload exact='yes'/></{prefix}{local}><{prefix}defaultTabStop z:val=\"720\"/></{prefix}settings>"
3744                );
3745                let mut model = CT_Settings::from_xml(source.as_bytes()).unwrap();
3746                assert_eq!(getter(&model).unwrap(), Some(false));
3747                setter(&mut model, Some(false)).unwrap();
3748                assert_eq!(model.to_xml().unwrap(), source.as_bytes());
3749                setter(&mut model, Some(true)).unwrap();
3750                assert_eq!(getter(&model).unwrap(), Some(true));
3751                let written = String::from_utf8(model.to_xml().unwrap()).unwrap();
3752                assert!(
3753                    written.contains("x:keep='&amp;'")
3754                        && written.contains("<x:payload exact='yes'/>")
3755                        && written.contains(&format!("<x:{local} x:val=\"foreign\"/>"))
3756                );
3757                let before = model.to_xml().unwrap();
3758                assert!(setter(&mut model, None).is_err());
3759                assert_eq!(model.to_xml().unwrap(), before);
3760            }
3761            let mut model = CT_Settings::new();
3762            assert_eq!(getter(&model).unwrap(), None);
3763            setter(&mut model, Some(false)).unwrap();
3764            assert_eq!(getter(&model).unwrap(), Some(false));
3765            setter(&mut model, None).unwrap();
3766            assert_eq!(getter(&model).unwrap(), None);
3767            for payload in [
3768                format!("<w:{local} w:val=\"invalid\"/>"),
3769                format!("<w:{local}/><w:{local}/>"),
3770                format!("<w:{local} w:val=\"1\" a:val=\"0\"/>"),
3771            ] {
3772                let source = format!(
3773                    "<w:settings xmlns:w=\"{W_NS}\" xmlns:a=\"{W_NS}\">{payload}</w:settings>"
3774                );
3775                let mut model = CT_Settings::from_xml(source.as_bytes()).unwrap();
3776                assert!(getter(&model).is_err());
3777                assert!(setter(&mut model, Some(true)).is_err());
3778                assert_eq!(model.to_xml().unwrap(), source.as_bytes());
3779            }
3780        }
3781        let source = format!(
3782            "<w:settings xmlns:w=\"{W_NS}\"><w:view w:val=\"print\"/><w:defaultTabStop w:val=\"720\"/></w:settings>"
3783        );
3784        let mut model = CT_Settings::from_xml(source.as_bytes()).unwrap();
3785        model.set_save_subset_fonts(Some(false)).unwrap();
3786        model.set_embed_system_fonts(Some(true)).unwrap();
3787        model.set_embed_true_type_fonts(Some(true)).unwrap();
3788        let xml = String::from_utf8(model.to_xml().unwrap()).unwrap();
3789        assert!(xml.find("embedTrueTypeFonts").unwrap() < xml.find("embedSystemFonts").unwrap());
3790        assert!(xml.find("embedSystemFonts").unwrap() < xml.find("saveSubsetFonts").unwrap());
3791        assert!(xml.find("saveSubsetFonts").unwrap() < xml.find("defaultTabStop").unwrap());
3792    }
3793
3794    #[test]
3795    fn fx220_multiple_font_flags_serialize_after_true_to_false_change() {
3796        let mut model = CT_Settings::new();
3797        model.set_embed_true_type_fonts(Some(true)).unwrap();
3798        model.set_save_subset_fonts(Some(false)).unwrap();
3799        model.set_embed_true_type_fonts(Some(false)).unwrap();
3800        let bytes = model.to_xml().unwrap();
3801        assert!(
3802            oxml_core::xml::validate_strict_xml_1_0(&bytes).is_ok(),
3803            "settings: {bytes:?}"
3804        );
3805        let reopened = CT_Settings::from_xml(&bytes).unwrap();
3806        assert_eq!(reopened.embed_true_type_fonts().unwrap(), Some(false));
3807        assert_eq!(reopened.save_subset_fonts().unwrap(), Some(false));
3808    }
3809
3810    #[test]
3811    fn fx220_root_attribute_insertions_preserve_namespace_context_for_all_consumers() {
3812        let opaque = "<pagePropertyWord:payload fact='kept'/>";
3813        for prefix in ["a:", ""] {
3814            let binding = if prefix.is_empty() {
3815                format!("xmlns=\"{W_NS}\"")
3816            } else {
3817                format!("xmlns:a=\"{W_NS}\"")
3818            };
3819            for local in ["embedTrueTypeFonts", "displayBackgroundShape"] {
3820                let source = format!(
3821                    "<{prefix}settings {binding} xmlns:pagePropertyWord='urn:producer'><{prefix}{local} xmlns:w='urn:foreign'>{opaque}</{prefix}{local}></{prefix}settings>"
3822                );
3823                let mut model = CT_Settings::from_xml(source.as_bytes()).unwrap();
3824                if local == "embedTrueTypeFonts" {
3825                    model.set_embed_true_type_fonts(Some(false)).unwrap();
3826                    assert_eq!(model.embed_true_type_fonts().unwrap(), Some(false));
3827                } else {
3828                    model.set_display_background_shape(false).unwrap();
3829                }
3830                let bytes = model.to_xml().unwrap();
3831                oxml_core::xml::validate_strict_xml_1_0(&bytes).unwrap();
3832                let xml = std::str::from_utf8(&bytes).unwrap();
3833                assert!(xml.contains(opaque));
3834                assert!(xml.contains("xmlns:pagePropertyWord='urn:producer'"));
3835                assert!(xml.contains("xmlns:w='urn:foreign'"));
3836                if prefix.is_empty() {
3837                    assert!(xml.contains("xmlns:pagePropertyWordx="));
3838                    assert!(xml.contains("pagePropertyWordx:val=\"0\""));
3839                } else {
3840                    assert!(xml.contains("a:val=\"0\""));
3841                }
3842            }
3843        }
3844        let raw = format!(
3845            "<background xmlns='{W_NS}' xmlns:w='urn:foreign' xmlns:pagePropertyWord='urn:producer'>{opaque}</background>"
3846        );
3847        let mut document = crate::document::CT_Document::new();
3848        document.background_xml = Some(raw.into_bytes());
3849        document.set_background_color("FF1122").unwrap();
3850        let bytes = document.to_xml().unwrap();
3851        oxml_core::xml::validate_strict_xml_1_0(&bytes).unwrap();
3852        let xml = std::str::from_utf8(&bytes).unwrap();
3853        assert!(xml.contains(opaque));
3854        assert!(xml.contains("xmlns:pagePropertyWord='urn:producer'"));
3855        assert!(xml.contains("xmlns:w='urn:foreign'"));
3856        assert!(xml.contains("pagePropertyWordx:color=\"FF1122\""));
3857    }
3858
3859    use super::*;
3860
3861    fn settings(mode: &str, enforcement: &str, formatting: &str) -> Vec<u8> {
3862        format!(
3863            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><x:settings xmlns:x="{W_NS}" xmlns:p="urn:producer" p:root="kept"><p:before p:v="1"/><x:documentProtection x:edit="{mode}" x:enforcement="{enforcement}" x:formatting="{formatting}" x:cryptProviderType="rsaAES" x:cryptAlgorithmClass="hash" x:cryptAlgorithmType="typeAny" x:cryptAlgorithmSid="14" x:cryptSpinCount="100000" x:hash="HASH-{mode}" x:salt="SALT-{mode}"/><p:after>keep me</p:after></x:settings>"#
3864        )
3865        .into_bytes()
3866    }
3867
3868    /// One well-formed instance of every supported name, in schema order.
3869    fn every_supported_child() -> String {
3870        format!(
3871            concat!(
3872                r#"<w:settings xmlns:w="{word}" xmlns:m="{math}">"#,
3873                r#"<w:view w:val="print"/>"#,
3874                r#"<w:zoom w:val="fullPage" w:percent="120"/>"#,
3875                r#"<w:removePersonalInformation/>"#,
3876                r#"<w:removeDateAndTime w:val="false"/>"#,
3877                r#"<w:mirrorMargins/>"#,
3878                r#"<w:gutterAtTop/>"#,
3879                r#"<w:proofState w:spelling="clean" w:grammar="dirty"/>"#,
3880                r#"<w:linkStyles/>"#,
3881                r#"<w:mailMerge>"#,
3882                r#"<w:mainDocumentType w:val="formLetters"/>"#,
3883                r#"<w:linkToQuery/>"#,
3884                r#"<w:dataType w:val="native"/>"#,
3885                r#"<w:connectString w:val="DSN=Contacts"/>"#,
3886                r#"<w:query w:val="SELECT * FROM People"/>"#,
3887                r#"<w:doNotSuppressBlankLines/>"#,
3888                r#"<w:destination w:val="printer"/>"#,
3889                r#"<w:addressFieldName w:val="Address"/>"#,
3890                r#"<w:mailSubject w:val="Invitation"/>"#,
3891                r#"<w:mailAsAttachment/>"#,
3892                r#"<w:viewMergedData/>"#,
3893                r#"<w:activeRecord w:val="3"/>"#,
3894                r#"<w:checkErrors w:val="2"/>"#,
3895                r#"</w:mailMerge>"#,
3896                r#"<w:trackRevisions/>"#,
3897                r#"<w:doNotTrackMoves/>"#,
3898                r#"<w:doNotTrackFormatting/>"#,
3899                r#"<w:documentProtection w:edit="readOnly"/>"#,
3900                r#"<w:defaultTabStop w:val="720"/>"#,
3901                r#"<w:autoHyphenation/>"#,
3902                r#"<w:consecutiveHyphenLimit w:val="2"/>"#,
3903                r#"<w:hyphenationZone w:val="360"/>"#,
3904                r#"<w:doNotHyphenateCaps/>"#,
3905                r#"<w:defaultTableStyle w:val="TableNormal"/>"#,
3906                r#"<w:evenAndOddHeaders/>"#,
3907                r#"<w:bookFoldRevPrinting/>"#,
3908                r#"<w:bookFoldPrinting/>"#,
3909                r#"<w:bookFoldPrintingSheets w:val="4"/>"#,
3910                r#"<w:characterSpacingControl w:val="doNotCompress"/>"#,
3911                r#"<w:updateFields/>"#,
3912                r#"<w:compat><w:noTabHangInd/><w:cachedColBalance w:val="false"/>"#,
3913                r#"<w:compatSetting w:name="compatibilityMode" w:uri="urn:office" w:val="15"/>"#,
3914                r#"</w:compat>"#,
3915                r#"<w:docVars><w:docVar w:name="Customer" w:val="Ada"/></w:docVars>"#,
3916                r#"<m:mathPr><m:mathFont m:val="Cambria Math"/></m:mathPr>"#,
3917                r#"<w:themeFontLang w:val="en-US"/>"#,
3918                r#"<w:decimalSymbol w:val="."/>"#,
3919                r#"<w:listSeparator w:val=","/>"#,
3920                r#"</w:settings>"#,
3921            ),
3922            word = W_NS,
3923            math = crate::namespace::M_NS,
3924        )
3925    }
3926
3927    #[test]
3928    fn document_protection_modes_and_metadata_parse_through_aliases() {
3929        for (name, expected) in [
3930            ("readOnly", ProtectionMode::ReadOnly),
3931            ("comments", ProtectionMode::Comments),
3932            ("trackedChanges", ProtectionMode::TrackedChanges),
3933            ("forms", ProtectionMode::Forms),
3934        ] {
3935            let parsed = CT_Settings::from_xml(&settings(name, "true", "0")).unwrap();
3936            let protection = parsed.document_protection().unwrap();
3937            assert_eq!(protection.mode, expected);
3938            assert_eq!(protection.enforcement, Some(true));
3939            assert_eq!(protection.formatting, Some(false));
3940            assert_eq!(protection.provider_type, Some(CryptProviderType::RsaAes));
3941            assert_eq!(protection.algorithm_class, Some(CryptAlgorithmClass::Hash));
3942            assert_eq!(protection.algorithm_type, Some(CryptAlgorithmType::Any));
3943            assert_eq!(protection.algorithm_sid, Some(14));
3944            assert_eq!(protection.spin_count, Some(100_000));
3945            assert_eq!(
3946                protection.hash.as_deref(),
3947                Some(format!("HASH-{name}").as_str())
3948            );
3949            assert_eq!(
3950                protection.salt.as_deref(),
3951                Some(format!("SALT-{name}").as_str())
3952            );
3953        }
3954
3955        let false_and_on = CT_Settings::from_xml(&settings("forms", "false", "on")).unwrap();
3956        let protection = false_and_on.document_protection().unwrap();
3957        assert_eq!(protection.enforcement, Some(false));
3958        assert_eq!(protection.formatting, Some(true));
3959    }
3960
3961    #[test]
3962    fn settings_keep_document_protection_and_unmodelled_children_byte_identical() {
3963        for mode in ["readOnly", "comments", "trackedChanges", "forms"] {
3964            let xml = settings(mode, "1", "off");
3965            let parsed = CT_Settings::from_xml(&xml).unwrap();
3966            assert_eq!(parsed.to_xml().unwrap(), xml);
3967        }
3968    }
3969
3970    #[test]
3971    fn document_variables_are_alias_safe_and_leave_settings_bytes_unchanged() {
3972        let xml = format!(
3973            r#"<?xml version="1.0"?><q:settings xmlns:q="{W_NS}" xmlns:p="urn:producer"><q:docVars xmlns:v="{W_NS}"><v:docVar v:name="Customer" v:val="Ada"/><v:docVar v:name="Region" v:val="West"></v:docVar><p:docVar p:name="Foreign" p:val="ignored"/><v:docVar v:name="Malformed"/></q:docVars><p:after/></q:settings>"#
3974        )
3975        .into_bytes();
3976        let settings = CT_Settings::from_xml(&xml).unwrap();
3977        assert_eq!(
3978            settings.document_variables(),
3979            [
3980                DocumentVariable {
3981                    name: "Customer".to_owned(),
3982                    value: "Ada".to_owned(),
3983                },
3984                DocumentVariable {
3985                    name: "Region".to_owned(),
3986                    value: "West".to_owned(),
3987                },
3988            ]
3989        );
3990        assert_eq!(settings.to_xml().unwrap(), xml);
3991    }
3992
3993    #[test]
3994    fn constructed_settings_use_fixed_prefix_and_schema_order() {
3995        let mut settings = CT_Settings::new();
3996        settings
3997            .set_document_protection(DocumentProtection {
3998                mode: ProtectionMode::ReadOnly,
3999                enforcement: Some(true),
4000                formatting: Some(false),
4001                provider_type: Some(CryptProviderType::RsaAes),
4002                algorithm_class: Some(CryptAlgorithmClass::Hash),
4003                algorithm_type: Some(CryptAlgorithmType::Any),
4004                algorithm_sid: Some(14),
4005                spin_count: Some(100_000),
4006                hash: Some("HASH".to_owned()),
4007                salt: Some("SALT".to_owned()),
4008            })
4009            .unwrap();
4010        let xml = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4011        assert!(xml.contains("<w:settings xmlns:w="));
4012        assert!(xml.contains("<w:documentProtection w:edit=\"readOnly\""));
4013        assert!(xml.find("w:formatting").unwrap() < xml.find("w:enforcement").unwrap());
4014        assert!(xml.find("w:cryptAlgorithmSid").unwrap() < xml.find("w:cryptSpinCount").unwrap());
4015        assert!(xml.find("w:cryptSpinCount").unwrap() < xml.find("w:hash").unwrap());
4016    }
4017
4018    #[test]
4019    fn automatic_hyphenation_defaults_off_and_parses_only_word_settings() {
4020        let omitted =
4021            CT_Settings::from_xml(format!(r#"<w:settings xmlns:w="{W_NS}"/>"#).as_bytes()).unwrap();
4022        assert!(!omitted.automatic_hyphenation());
4023
4024        let xml = format!(
4025            r#"<q:settings xmlns:q="{W_NS}" xmlns:x="urn:foreign"><x:autoHyphenation/><q:autoHyphenation q:val="on"/></q:settings>"#,
4026        );
4027        let parsed = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4028        assert!(parsed.automatic_hyphenation());
4029        assert_eq!(parsed.to_xml().unwrap(), xml.as_bytes());
4030    }
4031
4032    #[test]
4033    fn authored_automatic_hyphenation_uses_schema_order_and_preserves_raw_children() {
4034        let xml = format!(
4035            r#"<q:settings xmlns:q="{W_NS}" xmlns:x="urn:foreign"><q:defaultTabStop q:val="720"/><x:kept x:value="raw"/><q:consecutiveHyphenLimit q:val="2"/></q:settings>"#,
4036        );
4037        let mut parsed = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4038        parsed.set_automatic_hyphenation(true).unwrap();
4039        let output = String::from_utf8(parsed.to_xml().unwrap()).unwrap();
4040        assert!(output.contains(r#"<x:kept x:value="raw"/>"#));
4041        assert!(output.contains("<w:autoHyphenation/>"));
4042        assert!(output.find("defaultTabStop").unwrap() < output.find("autoHyphenation").unwrap());
4043        assert!(
4044            output.find("autoHyphenation").unwrap()
4045                < output.find("consecutiveHyphenLimit").unwrap()
4046        );
4047    }
4048
4049    #[test]
4050    fn authored_automatic_hyphenation_expands_a_self_closing_settings_root() {
4051        let xml = format!(r#"<q:settings xmlns:q="{W_NS}"/>"#);
4052        let mut parsed = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4053
4054        parsed.set_automatic_hyphenation(true).unwrap();
4055
4056        let output = parsed.to_xml().unwrap();
4057        assert!(
4058            std::str::from_utf8(&output)
4059                .unwrap()
4060                .contains("<w:autoHyphenation/>")
4061        );
4062        assert!(
4063            CT_Settings::from_xml(&output)
4064                .unwrap()
4065                .automatic_hyphenation()
4066        );
4067    }
4068
4069    #[test]
4070    fn even_and_odd_headers_are_alias_safe_and_rewrite_in_schema_order() {
4071        let xml = format!(
4072            r#"<q:settings xmlns:q="{W_NS}" xmlns:x="urn:foreign"><q:defaultTableStyle q:val="TableNormal"/><x:evenAndOddHeaders/><q:evenAndOddHeaders q:val="off"/><q:bookFoldPrinting/><x:kept/></q:settings>"#,
4073        );
4074        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4075        assert!(!settings.even_and_odd_headers());
4076        settings.set_even_and_odd_headers(true).unwrap();
4077        let output = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4078        assert!(output.contains("<w:evenAndOddHeaders/>"));
4079        assert!(output.contains("<x:evenAndOddHeaders/>"));
4080        assert!(output.contains("<x:kept/>"));
4081        assert!(
4082            output.find("defaultTableStyle").unwrap()
4083                < output.find("<w:evenAndOddHeaders").unwrap()
4084        );
4085        assert!(
4086            output.find("<w:evenAndOddHeaders").unwrap() < output.find("bookFoldPrinting").unwrap()
4087        );
4088        let reopened = CT_Settings::from_xml(output.as_bytes()).unwrap();
4089        assert!(reopened.even_and_odd_headers());
4090    }
4091
4092    #[test]
4093    fn update_fields_toggle_reads_sets_and_removes_in_schema_order() {
4094        let xml = format!(
4095            r#"<q:settings xmlns:q="{W_NS}" xmlns:x="urn:foreign"><q:characterSpacingControl q:val="doNotCompress"/><q:updateFields q:val="true"/><x:updateFields/><x:kept/><q:compat/></q:settings>"#,
4096        );
4097        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4098        assert_eq!(settings.update_fields(), Some(true));
4099
4100        settings.set_update_fields(Some(false)).unwrap();
4101        let output = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4102        assert!(
4103            output.contains(r#"<w:updateFields w:val="false"/>"#),
4104            "{output}"
4105        );
4106        assert!(!output.contains("<q:updateFields"), "{output}");
4107        assert!(output.contains("<x:updateFields/>"), "{output}");
4108        assert!(output.contains("<x:kept/>"), "{output}");
4109        let reopened = CT_Settings::from_xml(output.as_bytes()).unwrap();
4110        assert_eq!(reopened.update_fields(), Some(false));
4111
4112        settings.set_update_fields(None).unwrap();
4113        let output = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4114        assert!(!output.contains("<w:updateFields"), "{output}");
4115        assert!(output.contains("<x:updateFields/>"), "{output}");
4116        let reopened = CT_Settings::from_xml(output.as_bytes()).unwrap();
4117        assert_eq!(reopened.update_fields(), None);
4118
4119        // A part without the toggle receives it at its schema position.
4120        let xml = format!(
4121            r#"<w:settings xmlns:w="{W_NS}"><w:characterSpacingControl w:val="doNotCompress"/><w:compat/></w:settings>"#,
4122        );
4123        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4124        settings.set_update_fields(Some(true)).unwrap();
4125        let output = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4126        let position = output.find("<w:updateFields/>").unwrap();
4127        assert!(
4128            output.find("characterSpacingControl").unwrap() < position,
4129            "{output}"
4130        );
4131        assert!(position < output.find("<w:compat").unwrap(), "{output}");
4132
4133        let mut authored = CT_Settings::new();
4134        authored.set_update_fields(Some(true)).unwrap();
4135        let output = String::from_utf8(authored.to_xml().unwrap()).unwrap();
4136        assert!(output.contains("<w:updateFields/>"), "{output}");
4137
4138        let xml = format!(
4139            r#"<q:settings xmlns:q="{W_NS}" xmlns:x="urn:foreign"><x:kept/></q:settings>"#,
4140        );
4141        let mut absent = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4142        absent.set_update_fields(None).unwrap();
4143        assert_eq!(absent.to_xml().unwrap(), xml.as_bytes());
4144    }
4145
4146    #[test]
4147    fn update_fields_ambiguous_forms_are_not_rewritten() {
4148        for xml in [
4149            format!(
4150                r#"<w:settings xmlns:w="{W_NS}"><w:updateFields/><w:updateFields w:val="false"/></w:settings>"#,
4151            ),
4152            format!(
4153                r#"<w:settings xmlns:w="{W_NS}"><w:updateFields w:val="not-a-toggle"/></w:settings>"#,
4154            ),
4155        ] {
4156            let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4157            assert_eq!(settings.update_fields(), None);
4158            let before = settings.to_xml().unwrap();
4159            assert!(settings.set_update_fields(Some(true)).is_err());
4160            assert_eq!(settings.to_xml().unwrap(), before);
4161        }
4162    }
4163
4164    #[test]
4165    fn math_properties_accept_aliases_and_replace_in_schema_order() {
4166        let xml = format!(
4167            r#"<q:settings xmlns:q="{W_NS}" xmlns:z="{}" xmlns:x="urn:producer"><q:rsids/><z:mathPr x:keep="yes"><z:mathFont z:val="Cambria Math"/><x:inside/></z:mathPr><x:outside/><q:attachedSchema q:val="urn:test"/></q:settings>"#,
4168            crate::namespace::M_NS,
4169        );
4170        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4171        assert_eq!(
4172            settings.math_properties().unwrap().math_font.as_deref(),
4173            Some("Cambria Math")
4174        );
4175        let mut properties = settings.math_properties().unwrap().clone();
4176        properties.math_font = Some("STIX Two Math".to_owned());
4177        properties.justification = Some(crate::math::MathJustification::CenterGroup);
4178        settings.set_math_properties(properties).unwrap();
4179
4180        let output = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4181        assert!(output.contains(r#"x:keep="yes""#));
4182        assert!(output.contains("<x:inside/>"));
4183        assert!(output.contains("<x:outside/>"));
4184        assert!(output.contains(r#"<m:mathFont m:val="STIX Two Math"/>"#));
4185        assert!(output.find("<q:rsids").unwrap() < output.find("<m:mathPr").unwrap());
4186        assert!(output.find("<m:mathPr").unwrap() < output.find("<q:attachedSchema").unwrap());
4187        assert_eq!(
4188            CT_Settings::from_xml(output.as_bytes())
4189                .unwrap()
4190                .math_properties()
4191                .unwrap()
4192                .justification,
4193            Some(crate::math::MathJustification::CenterGroup)
4194        );
4195    }
4196
4197    #[test]
4198    fn math_properties_with_a_conflicting_m_binding_remain_untyped() {
4199        let xml = format!(
4200            r#"<w:settings xmlns:w="{W_NS}" xmlns:q="{}" xmlns:m="urn:producer"><q:mathPr><m:opaque/></q:mathPr></w:settings>"#,
4201            crate::namespace::M_NS,
4202        );
4203        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4204        assert!(settings.math_properties().is_none());
4205        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4206    }
4207
4208    #[test]
4209    fn supported_settings_is_a_strict_subsequence_of_the_order_table() {
4210        let mut seen = Vec::new();
4211        for name in SUPPORTED_SETTINGS {
4212            assert!(!seen.contains(name), "duplicate supported name {name}");
4213            seen.push(name);
4214        }
4215        assert_eq!(SUPPORTED_SETTINGS.len(), 31);
4216
4217        let mut cursor = 0usize;
4218        for name in SUPPORTED_SETTINGS {
4219            let position = SETTINGS_ORDER
4220                .iter()
4221                .position(|candidate| *candidate == name.as_bytes())
4222                .unwrap_or_else(|| panic!("{name} is missing from SETTINGS_ORDER"));
4223            assert!(
4224                position >= cursor,
4225                "{name} breaks the schema order of SETTINGS_ORDER"
4226            );
4227            cursor = position + 1;
4228        }
4229    }
4230
4231    #[test]
4232    fn every_supported_name_is_projected_by_from_xml() {
4233        let xml = every_supported_child();
4234        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4235
4236        assert_eq!(settings.view(), Some(DocumentView::Print));
4237        assert_eq!(
4238            settings.zoom(),
4239            Some(DocumentZoom {
4240                kind: Some(ZoomKind::FullPage),
4241                percent: Some(120),
4242            })
4243        );
4244        assert_eq!(settings.remove_personal_information(), Some(true));
4245        assert_eq!(settings.remove_date_and_time(), Some(false));
4246        assert_eq!(settings.mirror_margins(), Some(true));
4247        assert_eq!(settings.gutter_at_top(), Some(true));
4248        assert_eq!(
4249            settings.proof_state(),
4250            Some(DocumentProofState {
4251                spelling: Some(ProofState::Clean),
4252                grammar: Some(ProofState::Dirty),
4253            })
4254        );
4255        assert_eq!(settings.link_styles(), Some(true));
4256        let mail_merge = settings.mail_merge().unwrap();
4257        assert_eq!(
4258            mail_merge.main_document_type,
4259            Some(MailMergeDocumentType::FormLetters)
4260        );
4261        assert_eq!(mail_merge.link_to_query, Some(true));
4262        assert_eq!(mail_merge.data_type.as_deref(), Some("native"));
4263        assert_eq!(mail_merge.connect_string.as_deref(), Some("DSN=Contacts"));
4264        assert_eq!(mail_merge.query.as_deref(), Some("SELECT * FROM People"));
4265        assert_eq!(mail_merge.do_not_suppress_blank_lines, Some(true));
4266        assert_eq!(mail_merge.destination, Some(MailMergeDestination::Printer));
4267        assert_eq!(mail_merge.address_field_name.as_deref(), Some("Address"));
4268        assert_eq!(mail_merge.mail_subject.as_deref(), Some("Invitation"));
4269        assert_eq!(mail_merge.mail_as_attachment, Some(true));
4270        assert_eq!(mail_merge.view_merged_data, Some(true));
4271        assert_eq!(mail_merge.active_record, Some(3));
4272        assert_eq!(mail_merge.check_errors, Some(2));
4273        assert_eq!(settings.track_revisions(), Some(true));
4274        assert_eq!(settings.do_not_track_moves(), Some(true));
4275        assert_eq!(settings.do_not_track_formatting(), Some(true));
4276        assert_eq!(
4277            settings.document_protection().map(|value| value.mode),
4278            Some(ProtectionMode::ReadOnly)
4279        );
4280        assert_eq!(settings.default_tab_stop(), Some(Twips(720)));
4281        assert!(settings.automatic_hyphenation());
4282        assert_eq!(settings.consecutive_hyphen_limit(), Some(2));
4283        assert_eq!(settings.hyphenation_zone(), Some(Twips(360)));
4284        assert_eq!(settings.do_not_hyphenate_caps(), Some(true));
4285        assert_eq!(settings.default_table_style(), Some("TableNormal"));
4286        assert!(settings.even_and_odd_headers());
4287        assert_eq!(settings.book_fold_rev_printing(), Some(true));
4288        assert_eq!(settings.book_fold_printing(), Some(true));
4289        assert_eq!(settings.book_fold_printing_sheets(), Some(4));
4290        assert_eq!(
4291            settings.character_spacing_control(),
4292            Some(CharacterSpacingControl::DoNotCompress)
4293        );
4294        assert_eq!(settings.update_fields(), Some(true));
4295        assert_eq!(
4296            settings.compatibility_options(),
4297            [
4298                (CompatibilityOption::NoTabHangInd, true),
4299                (CompatibilityOption::CachedColBalance, false),
4300            ]
4301        );
4302        assert_eq!(settings.compatibility_settings().len(), 1);
4303        assert_eq!(settings.document_variable("Customer"), Some("Ada"));
4304        assert_eq!(
4305            settings.math_properties().unwrap().math_font.as_deref(),
4306            Some("Cambria Math")
4307        );
4308        assert_eq!(
4309            settings.theme_font_language().unwrap().latin.as_deref(),
4310            Some("en-US")
4311        );
4312        assert_eq!(settings.decimal_symbol(), Some("."));
4313        assert_eq!(settings.list_separator(), Some(","));
4314
4315        assert_eq!(settings.diagnostics(), &[]);
4316        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4317    }
4318
4319    #[test]
4320    fn decimal_twips_settings_round_and_keep_their_bytes() {
4321        let xml = format!(
4322            r#"<w:settings xmlns:w="{W_NS}"><w:defaultTabStop w:val="720.0"/><w:hyphenationZone w:val="356.5"/></w:settings>"#,
4323        );
4324        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4325
4326        assert_eq!(settings.default_tab_stop(), Some(Twips(720)));
4327        assert_eq!(settings.hyphenation_zone(), Some(Twips(357)));
4328        assert!(settings.diagnostics().is_empty());
4329        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4330    }
4331
4332    #[test]
4333    fn duplicate_and_malformed_supported_children_report_diagnostics() {
4334        let xml = format!(
4335            r#"<w:settings xmlns:w="{W_NS}"><w:view w:val="print"/><w:view w:val="normal"/><w:zoom w:val="nonsense"/><w:defaultTabStop w:val="-12"/><w:compat><w:noTabHangInd w:val="maybe"/></w:compat><w:docVars><w:docVar w:name="A"/></w:docVars></w:settings>"#,
4336        );
4337        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4338
4339        assert_eq!(
4340            settings.diagnostics(),
4341            &[
4342                SettingsDiagnostic {
4343                    path: vec!["view"],
4344                    occurrences: 2,
4345                    reason: SettingsDiagnosticReason::Duplicated,
4346                },
4347                SettingsDiagnostic {
4348                    path: vec!["zoom"],
4349                    occurrences: 1,
4350                    reason: SettingsDiagnosticReason::Malformed,
4351                },
4352                SettingsDiagnostic {
4353                    path: vec!["defaultTabStop"],
4354                    occurrences: 1,
4355                    reason: SettingsDiagnosticReason::Malformed,
4356                },
4357                SettingsDiagnostic {
4358                    path: vec!["compat", "noTabHangInd"],
4359                    occurrences: 1,
4360                    reason: SettingsDiagnosticReason::Malformed,
4361                },
4362                SettingsDiagnostic {
4363                    path: vec!["docVars", "docVar"],
4364                    occurrences: 1,
4365                    reason: SettingsDiagnosticReason::Malformed,
4366                },
4367            ]
4368        );
4369        assert!(settings.view().is_none());
4370        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4371    }
4372
4373    #[test]
4374    fn unsupported_settings_children_are_never_diagnostics() {
4375        let xml = format!(
4376            r#"<w:settings xmlns:w="{W_NS}" xmlns:w15="urn:w15" xmlns:x="urn:foreign"><w:rsids><w:rsid w:val="00AA00AA"/></w:rsids><w:clrSchemeMapping w:bg1="light1"/><w:shapeDefaults><x:anything/></w:shapeDefaults><w15:docId w15:val="{{GUID}}"/><x:view x:val="print"/><x:view x:val="normal"/></w:settings>"#,
4377        );
4378        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4379        assert_eq!(settings.diagnostics(), &[]);
4380        assert!(settings.view().is_none());
4381        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4382    }
4383
4384    #[test]
4385    fn compatibility_option_covers_the_complete_compat_on_off_set() {
4386        assert_eq!(CompatibilityOption::ALL.len(), 65);
4387        let mut names = Vec::new();
4388        for option in CompatibilityOption::ALL {
4389            let name = option.local_name();
4390            assert!(!names.contains(&name), "duplicate compat option {name}");
4391            names.push(name);
4392        }
4393        assert_eq!(COMPAT_ORDER.len(), names.len() + 1);
4394        for (index, name) in names.iter().enumerate() {
4395            assert_eq!(COMPAT_ORDER[index], name.as_bytes());
4396        }
4397        assert_eq!(COMPAT_ORDER[names.len()], b"compatSetting");
4398
4399        let children = names
4400            .iter()
4401            .map(|name| format!("<w:{name}/>"))
4402            .collect::<String>();
4403        let xml =
4404            format!(r#"<w:settings xmlns:w="{W_NS}"><w:compat>{children}</w:compat></w:settings>"#);
4405        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4406        assert_eq!(settings.diagnostics(), &[]);
4407        assert_eq!(settings.compatibility_options().len(), names.len());
4408        for option in CompatibilityOption::ALL {
4409            assert_eq!(settings.compatibility_option(*option), Some(true));
4410        }
4411    }
4412
4413    #[test]
4414    fn document_protection_authoring_records_caller_metadata_verbatim() {
4415        let protection = DocumentProtection {
4416            mode: ProtectionMode::Forms,
4417            enforcement: Some(true),
4418            formatting: None,
4419            provider_type: Some(CryptProviderType::RsaAes),
4420            algorithm_class: Some(CryptAlgorithmClass::Hash),
4421            algorithm_type: Some(CryptAlgorithmType::Any),
4422            algorithm_sid: Some(4),
4423            spin_count: Some(100_000),
4424            hash: Some("CALLER-HASH".to_owned()),
4425            salt: Some("CALLER-SALT".to_owned()),
4426        };
4427        let xml =
4428            format!(r#"<w:settings xmlns:w="{W_NS}"><w:defaultTabStop w:val="720"/></w:settings>"#);
4429
4430        let mut first = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4431        first.set_document_protection(protection.clone()).unwrap();
4432        let mut second = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4433        second.set_document_protection(protection.clone()).unwrap();
4434        assert_eq!(first.to_xml().unwrap(), second.to_xml().unwrap());
4435
4436        let output = String::from_utf8(first.to_xml().unwrap()).unwrap();
4437        assert!(output.contains(r#"w:hash="CALLER-HASH""#), "{output}");
4438        assert!(output.contains(r#"w:salt="CALLER-SALT""#), "{output}");
4439        assert!(output.contains(r#"w:cryptSpinCount="100000""#), "{output}");
4440        assert!(
4441            output.find("<w:documentProtection").unwrap() < output.find("defaultTabStop").unwrap(),
4442            "{output}"
4443        );
4444
4445        let reopened = CT_Settings::from_xml(output.as_bytes()).unwrap();
4446        assert_eq!(reopened.document_protection(), Some(&protection));
4447        assert_eq!(reopened.diagnostics(), &[]);
4448    }
4449
4450    #[test]
4451    fn removing_a_duplicated_member_fails_and_changes_nothing() {
4452        let xml = format!(
4453            r#"<w:settings xmlns:w="{W_NS}"><w:mirrorMargins/><w:mirrorMargins w:val="false"/></w:settings>"#,
4454        );
4455        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4456        assert!(settings.remove_mirror_margins().is_err());
4457        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4458
4459        let absent = format!(r#"<w:settings xmlns:w="{W_NS}"><w:kept/></w:settings>"#);
4460        let mut settings = CT_Settings::from_xml(absent.as_bytes()).unwrap();
4461        assert_eq!(settings.remove_mirror_margins().unwrap(), None);
4462        assert_eq!(settings.to_xml().unwrap(), absent.as_bytes());
4463    }
4464
4465    #[test]
4466    fn a_self_closing_group_does_not_leak_its_namespace_scope() {
4467        // `xmlns:z` is declared on the self-closing group, so it is out of
4468        // scope for every following sibling.
4469        let xml = format!(
4470            r#"<w:settings xmlns:w="{W_NS}"><w:compat xmlns:z="{W_NS}"/><z:view z:val="print"/><w:mirrorMargins/></w:settings>"#,
4471        );
4472        let settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4473        assert_eq!(settings.view(), None);
4474        assert_eq!(settings.mirror_margins(), Some(true));
4475        assert_eq!(settings.diagnostics(), &[]);
4476        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4477    }
4478
4479    #[test]
4480    fn mail_merge_members_land_between_their_preserved_neighbours() {
4481        let xml = format!(
4482            concat!(
4483                r#"<w:settings xmlns:w="{word}" xmlns:r="urn:rel">"#,
4484                r#"<w:mailMerge><w:dataSource r:id="rId7"/><w:odso><w:udl w:val="keep"/></w:odso></w:mailMerge>"#,
4485                r#"</w:settings>"#
4486            ),
4487            word = W_NS,
4488        );
4489        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4490        settings
4491            .set_mail_merge(MailMerge {
4492                main_document_type: Some(MailMergeDocumentType::FormLetters),
4493                check_errors: Some(2),
4494                ..MailMerge::default()
4495            })
4496            .unwrap();
4497        let output = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4498        assert!(
4499            output.contains(r#"<w:dataSource r:id="rId7"/>"#),
4500            "{output}"
4501        );
4502        assert!(output.contains(r#"<w:udl w:val="keep"/>"#), "{output}");
4503        assert!(
4504            output.find("mainDocumentType").unwrap() < output.find("dataSource").unwrap(),
4505            "{output}"
4506        );
4507        assert!(
4508            output.find("dataSource").unwrap() < output.find("checkErrors").unwrap(),
4509            "{output}"
4510        );
4511        assert!(
4512            output.find("checkErrors").unwrap() < output.find("<w:odso").unwrap(),
4513            "{output}"
4514        );
4515
4516        let reopened = CT_Settings::from_xml(output.as_bytes()).unwrap();
4517        assert_eq!(reopened.mail_merge().unwrap().check_errors, Some(2));
4518        assert_eq!(reopened.diagnostics(), &[]);
4519    }
4520
4521    #[test]
4522    fn note_policy_preserves_unknown_note_and_section_xml() {
4523        let xml = format!(
4524            r#"<q:settings xmlns:q="{W_NS}" xmlns:x="urn:producer"><q:footnotePr><q:pos q:val="beneathText"/><x:keep x:flag="exact"/><q:numFmt q:val="decimal"/><q:endnote q:id="9"/><q:footnote q:id="-1"/></q:footnotePr><q:endnotePr><q:numStart q:val="4"/></q:endnotePr></q:settings>"#
4525        );
4526        let mut settings = CT_Settings::from_xml(xml.as_bytes()).unwrap();
4527        assert_eq!(settings.to_xml().unwrap(), xml.as_bytes());
4528        let mut footnote = settings.footnote_properties().unwrap().clone();
4529        assert_eq!(footnote.pos.as_deref(), Some("beneathText"));
4530        assert_eq!(footnote.special_references, vec![-1]);
4531        footnote.num_start = Some(3);
4532        settings.set_footnote_properties(footnote).unwrap();
4533        let written = String::from_utf8(settings.to_xml().unwrap()).unwrap();
4534        assert!(written.contains(r#"<x:keep x:flag="exact"/>"#), "{written}");
4535        assert!(written.contains(r#"<q:endnote q:id="9"/>"#), "{written}");
4536        assert!(written.contains(r#"<w:numStart w:val="3"/>"#), "{written}");
4537        let reopened = CT_Settings::from_xml(written.as_bytes()).unwrap();
4538        assert_eq!(reopened.footnote_properties().unwrap().num_start, Some(3));
4539        assert_eq!(
4540            reopened.footnote_properties().unwrap().special_references,
4541            vec![-1]
4542        );
4543        assert_eq!(reopened.endnote_properties().unwrap().num_start, Some(4));
4544
4545        let section_xml = format!(
4546            r#"<q:document xmlns:q="{W_NS}" xmlns:x="urn:producer"><q:body><q:p/><q:sectPr><q:footnotePr><q:pos q:val="pageBottom"/><x:keep x:flag="section-exact"/><q:numFmt q:val="decimal"/></q:footnotePr></q:sectPr></q:body></q:document>"#
4547        );
4548        let mut document = crate::document::CT_Document::from_xml(section_xml.as_bytes()).unwrap();
4549        document
4550            .body
4551            .sect_pr
4552            .as_mut()
4553            .unwrap()
4554            .footnote_pr
4555            .as_mut()
4556            .unwrap()
4557            .num_start = Some(5);
4558        let rewritten = String::from_utf8(document.to_xml().unwrap()).unwrap();
4559        assert!(
4560            rewritten.contains(r#"<x:keep x:flag="section-exact"/>"#),
4561            "{rewritten}"
4562        );
4563        assert!(
4564            rewritten.contains(r#"<w:numStart w:val="5"/>"#),
4565            "{rewritten}"
4566        );
4567        let reopened_section =
4568            crate::document::CT_Document::from_xml(rewritten.as_bytes()).unwrap();
4569        assert_eq!(
4570            reopened_section
4571                .body
4572                .sect_pr
4573                .unwrap()
4574                .footnote_pr
4575                .unwrap()
4576                .num_start,
4577            Some(5)
4578        );
4579    }
4580}