1use 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#[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#[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#[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#[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct DocumentVariable {
142 pub name: String,
143 pub value: String,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct CompatibilitySetting {
149 pub name: String,
150 pub uri: String,
151 pub value: String,
152}
153
154#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
257pub struct DocumentZoom {
258 pub kind: Option<ZoomKind>,
259 pub percent: Option<u32>,
260}
261
262#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
288pub struct DocumentProofState {
289 pub spelling: Option<ProofState>,
290 pub grammar: Option<ProofState>,
291}
292
293#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum SettingsDiagnosticReason {
384 Duplicated,
386 Malformed,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
395pub struct SettingsDiagnostic {
396 pub path: Vec<&'static str>,
398 pub occurrences: usize,
399 pub reason: SettingsDiagnosticReason,
400}
401
402#[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 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 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 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
500pub(crate) fn assign<T>(slot: &mut Option<T>, value: Option<T>, total: usize) {
505 *slot = if total == 1 { value } else { None };
506}
507
508#[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 source_xml: Option<Vec<u8>>,
551}
552
553impl CT_Settings {
554 pub fn embed_true_type_fonts(&self) -> Result<Option<bool>> {
556 self.font_flag(b"embedTrueTypeFonts")
557 }
558
559 pub fn set_embed_true_type_fonts(&mut self, value: Option<bool>) -> Result<()> {
561 self.set_font_flag(b"embedTrueTypeFonts", value)
562 }
563
564 pub fn embed_system_fonts(&self) -> Result<Option<bool>> {
566 self.font_flag(b"embedSystemFonts")
567 }
568
569 pub fn set_embed_system_fonts(&mut self, value: Option<bool>) -> Result<()> {
571 self.set_font_flag(b"embedSystemFonts", value)
572 }
573
574 pub fn save_subset_fonts(&self) -> Result<Option<bool>> {
576 self.font_flag(b"saveSubsetFonts")
577 }
578
579 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 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 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 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 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 _ => {}
993 }
994 Ok(())
995 }
996
997 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 pub fn footnote_properties(&self) -> Option<&CT_NoteProperties> {
1122 self.footnote_properties.as_ref()
1123 }
1124
1125 pub fn endnote_properties(&self) -> Option<&CT_NoteProperties> {
1127 self.endnote_properties.as_ref()
1128 }
1129
1130 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 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 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 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 pub fn diagnostics(&self) -> &[SettingsDiagnostic] {
1171 &self.diagnostics
1172 }
1173
1174 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn mail_merge(&self) -> Option<&MailMerge> {
1391 self.mail_merge.as_ref()
1392 }
1393
1394 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 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 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 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 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 pub fn document_protection(&self) -> Option<&DocumentProtection> {
1543 self.document_protection.as_ref()
1544 }
1545
1546 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 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 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 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 pub fn automatic_hyphenation(&self) -> bool {
1780 self.automatic_hyphenation.unwrap_or(false)
1781 }
1782
1783 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 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 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 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 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 pub fn even_and_odd_headers(&self) -> bool {
1895 self.even_and_odd_headers.unwrap_or(false)
1896 }
1897
1898 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 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 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 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 pub fn update_fields(&self) -> Option<bool> {
2004 self.update_fields
2005 }
2006
2007 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 pub fn math_properties(&self) -> Option<&MathProperties> {
2035 self.math_properties.as_ref()
2036 }
2037
2038 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 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 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 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 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#[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 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 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
2543const 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
2613pub(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
2718pub 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
2759const SETTINGS_REPEATABLE: &[&[&str]] = &[&["docVars", "docVar"], &["compat", "compatSetting"]];
2761
2762const 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
2782pub 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
2799pub(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
2817pub(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
3032fn 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
3041fn 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
3050fn 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
3119fn 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
3159pub(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
3276fn 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
3395fn 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
3408fn 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
3428pub(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
3519pub(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='&'><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='&'")
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 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 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 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}