1use std::{
2 collections::{HashMap, HashSet},
3 str::FromStr,
4};
5
6mod bookmark_id;
7mod build_xml;
8mod comments;
9mod comments_extended;
10mod content_types;
11mod custom_item;
12mod custom_item_property;
13mod custom_item_rels;
14mod doc_props;
15mod document;
16mod document_rels;
17mod document_tree;
18mod elements;
19mod font_table;
20mod footer;
21mod footer_id;
22mod footer_rels;
23mod footnote_id;
24mod footnotes;
25mod header;
26mod header_id;
27mod header_rels;
28mod history_id;
29mod hyperlink_id;
30mod image_collector;
31mod numberings;
32mod paragraph_id;
33mod paragraph_property_change_id;
34mod pic_id;
35mod preset_styles;
36mod rels;
37mod settings;
38mod styles;
39mod taskpanes;
40mod taskpanes_rels;
41mod theme;
42mod toc_key;
43mod web_settings;
44mod webextension;
45mod xml_docx;
46
47pub use build_xml::BuildXML;
48pub(crate) use history_id::HistoryId;
49pub(crate) use hyperlink_id::*;
50pub(crate) use paragraph_id::*;
51pub(crate) use paragraph_property_change_id::ParagraphPropertyChangeId;
52pub(crate) use pic_id::*;
53
54pub use bookmark_id::*;
55pub use comments::*;
56pub use comments_extended::*;
57pub use content_types::*;
58pub use custom_item::*;
59pub use custom_item_property::*;
60pub use custom_item_rels::*;
61pub use doc_props::*;
62pub use document::*;
63pub use document_rels::*;
64pub use elements::*;
65pub use font_table::*;
66pub use footer::*;
67pub use footer_id::*;
68pub use footer_rels::*;
69pub use footnotes::*;
70pub use header::*;
71pub use header_id::*;
72pub use header_rels::*;
73pub use numberings::*;
74pub use rels::*;
75pub use settings::*;
76pub use styles::*;
77pub use taskpanes::*;
78pub use taskpanes_rels::*;
79pub use theme::*;
80pub use toc_key::*;
81pub use web_settings::*;
82pub use webextension::*;
83pub use xml_docx::*;
84
85use base64::Engine;
86use serde::{ser, Serialize};
87
88use self::image_collector::{
89 collect_document_footnotes, collect_document_part, collect_footer_part, collect_header_part,
90 MediaRegistry,
91};
92
93#[derive(Debug, Clone)]
94pub struct Image(pub Vec<u8>);
95
96#[derive(Debug, Clone)]
104pub struct Png(pub Vec<u8>);
105
106pub type ImageIdAndPath = (String, String);
107pub type ImageIdAndBuf = (String, Vec<u8>);
108
109const PNG_SIGNATURE: &[u8; 8] = &[137, 80, 78, 71, 13, 10, 26, 10];
110
111fn is_emf(path: &str, buf: &[u8]) -> bool {
112 if path.to_ascii_lowercase().ends_with(".emf") {
113 return true;
114 }
115 buf.len() >= 44
119 && buf[0..4] == [0x01, 0x00, 0x00, 0x00]
120 && buf[40..44] == [0x20, 0x45, 0x4D, 0x46]
121}
122
123impl ser::Serialize for Image {
124 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
125 where
126 S: ser::Serializer,
127 {
128 let base64 = base64::engine::general_purpose::STANDARD.encode(&self.0);
129 serializer.collect_str(&base64)
130 }
131}
132
133impl ser::Serialize for Png {
134 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135 where
136 S: ser::Serializer,
137 {
138 let base64 = base64::engine::general_purpose::STANDARD.encode(&self.0);
139 serializer.collect_str(&base64)
140 }
141}
142
143#[derive(Debug, Clone, Serialize)]
144#[serde(rename_all = "camelCase")]
145pub struct Docx {
146 pub content_type: ContentTypes,
147 pub rels: Rels,
148 pub document_rels: DocumentRels,
149 pub doc_props: DocProps,
150 pub styles: Styles,
151 pub document: Document,
152 pub comments: Comments,
153 pub numberings: Numberings,
154 pub settings: Settings,
155 pub font_table: FontTable,
156 pub media: Vec<(String, Vec<u8>)>,
157 pub comments_extended: CommentsExtended,
158 pub web_settings: WebSettings,
159 pub taskpanes: Option<Taskpanes>,
160 pub taskpanes_rels: TaskpanesRels,
161 pub web_extensions: Vec<WebExtension>,
162 pub custom_items: Vec<CustomItem>,
163 pub custom_item_props: Vec<CustomItemProperty>,
164 pub custom_item_rels: Vec<CustomItemRels>,
165 pub themes: Vec<Theme>,
167 pub images: Vec<(String, String, Image, Png)>,
174 pub hyperlinks: Vec<(String, String, String)>,
176 pub footnotes: Footnotes,
177}
178
179pub(crate) struct PackageMetadata {
181 pub(crate) media: Vec<ImageIdAndBuf>,
182 pub(crate) header_rels: Vec<HeaderRels>,
183 pub(crate) footer_rels: Vec<FooterRels>,
184}
185
186impl Default for Docx {
187 fn default() -> Self {
188 let content_type = ContentTypes::new().set_default();
189 let rels = Rels::new().set_default();
190 let doc_props = DocProps::new(CorePropsConfig::new());
191 let styles = Styles::new();
192 let document = Document::new();
193 let document_rels = DocumentRels::new();
194 let settings = Settings::new();
195 let font_table = FontTable::new();
196 let comments = Comments::new();
197 let numberings = Numberings::new();
198 let media = vec![];
199 let comments_extended = CommentsExtended::new();
200 let web_settings = WebSettings::new();
201 let footnotes = Footnotes::default();
202
203 Docx {
204 content_type,
205 rels,
206 document_rels,
207 doc_props,
208 styles,
209 document,
210 comments,
211 numberings,
212 settings,
213 font_table,
214 media,
215 comments_extended,
216 web_settings,
217 taskpanes: None,
218 taskpanes_rels: TaskpanesRels::new(),
219 web_extensions: vec![],
220 custom_items: vec![],
221 custom_item_props: vec![],
222 custom_item_rels: vec![],
223 themes: vec![],
224 images: vec![],
225 hyperlinks: vec![],
226 footnotes,
227 }
228 }
229}
230
231impl Docx {
232 pub fn new() -> Docx {
233 Default::default()
234 }
235
236 pub fn document(mut self, d: Document) -> Docx {
237 for child in &self.document.children {
238 match child {
239 DocumentChild::Paragraph(paragraph) => {
240 if paragraph.has_numbering {
241 self.document_rels.has_numberings = true;
242 }
243 }
244 DocumentChild::Table(table) if table.has_numbering => {
245 self.document_rels.has_numberings = true;
246 }
247 _ => {}
248 }
249 }
250 self.document = d;
251 self
252 }
253
254 pub fn styles(mut self, s: Styles) -> Self {
255 self.styles = s;
256 self
257 }
258
259 pub fn add_style(mut self, s: Style) -> Self {
260 self.styles = self.styles.add_style(s);
261 self
262 }
263
264 pub fn numberings(mut self, n: Numberings) -> Self {
265 self.numberings = n;
266 self
267 }
268
269 pub fn settings(mut self, s: Settings) -> Self {
270 self.settings = s;
271 self
272 }
273
274 pub(crate) fn web_settings(mut self, s: WebSettings) -> Self {
276 self.web_settings = s;
277 self
278 }
279
280 pub(crate) fn add_image(
282 self,
283 id: impl Into<String>,
284 path: impl Into<String>,
285 buf: Vec<u8>,
286 ) -> Self {
287 self.add_image_with_options(id, path, buf, true)
288 }
289
290 pub(crate) fn add_image_with_options(
292 mut self,
293 id: impl Into<String>,
294 path: impl Into<String>,
295 buf: Vec<u8>,
296 generate_preview: bool,
297 ) -> Self {
298 let path: String = path.into();
299
300 if is_emf(&path, &buf) {
301 self.images
302 .push((id.into(), path, Image(buf), Png(Vec::new())));
303 return self;
304 }
305
306 if !generate_preview {
307 self.images
308 .push((id.into(), path, Image(buf), Png(Vec::new())));
309 return self;
310 }
311
312 if buf.starts_with(PNG_SIGNATURE) {
313 self.images
314 .push((id.into(), path, Image(buf.clone()), Png(buf)));
315 return self;
316 }
317
318 #[cfg(feature = "image")]
319 if let Ok(dimg) = image::load_from_memory(&buf) {
320 let mut png = std::io::Cursor::new(vec![]);
321 dimg.write_to(&mut png, image::ImageFormat::Png)
323 .expect("Unable to write dynamic image");
324
325 self.images
326 .push((id.into(), path, Image(buf), Png(png.into_inner())));
327 }
328 self
329 }
330
331 pub(crate) fn add_hyperlink(
333 mut self,
334 id: impl Into<String>,
335 path: impl Into<String>,
336 r#type: impl Into<String>,
337 ) -> Self {
338 self.hyperlinks
339 .push((id.into(), path.into(), r#type.into()));
340 self
341 }
342
343 pub fn comments(mut self, c: Comments) -> Self {
344 self.comments = c;
345 self
346 }
347
348 pub fn comments_extended(mut self, c: CommentsExtended) -> Self {
349 self.comments_extended = c;
350 self
351 }
352
353 pub fn add_paragraph(mut self, p: Paragraph) -> Docx {
354 if p.has_numbering {
355 self.document_rels.has_numberings = true;
358 }
359 self.document = self.document.add_paragraph(p);
360 self
361 }
362
363 pub fn add_section(mut self, s: Section) -> Docx {
364 if s.has_numbering {
365 self.document_rels.has_numberings = true;
368 }
369
370 let mut new_section = s;
371
372 if let Some(header) = new_section.temp_header {
374 if header.has_numbering {
375 self.document_rels.has_numberings = true;
376 }
377 let count = self.document_rels.header_count + 1;
378 new_section = Section {
379 property: new_section
380 .property
381 .header(header, &create_header_rid(count)),
382 children: new_section.children,
383 has_numbering: new_section.has_numbering,
384 temp_header: None,
385 ..Default::default()
386 };
387 self.document_rels.header_count = count;
388 self.content_type = self.content_type.add_header();
389 }
390
391 if let Some(header) = new_section.temp_first_header {
392 if header.has_numbering {
393 self.document_rels.has_numberings = true;
394 }
395 let count = self.document_rels.header_count + 1;
396 new_section = Section {
397 property: new_section
398 .property
399 .first_header(header, &create_header_rid(count)),
400 children: new_section.children,
401 has_numbering: new_section.has_numbering,
402 temp_first_header: None,
403 ..Default::default()
404 };
405 self.document_rels.header_count = count;
406 self.content_type = self.content_type.add_header();
407 }
408
409 if let Some(header) = new_section.temp_even_header {
410 if header.has_numbering {
411 self.document_rels.has_numberings = true;
412 }
413 let count = self.document_rels.header_count + 1;
414 new_section = Section {
415 property: new_section
416 .property
417 .even_header(header, &create_header_rid(count)),
418 children: new_section.children,
419 has_numbering: new_section.has_numbering,
420 temp_even_header: None,
421 ..Default::default()
422 };
423 self.document_rels.header_count = count;
424 self.content_type = self.content_type.add_header();
425 }
426
427 if let Some(header) = new_section.temp_header {
429 if header.has_numbering {
430 self.document_rels.has_numberings = true;
431 }
432 let count = self.document_rels.header_count + 1;
433 new_section = Section {
434 property: new_section
435 .property
436 .header(header, &create_header_rid(count)),
437 children: new_section.children,
438 has_numbering: new_section.has_numbering,
439 temp_header: None,
440 ..Default::default()
441 };
442 self.document_rels.header_count = count;
443 self.content_type = self.content_type.add_header();
444 }
445
446 if let Some(header) = new_section.temp_first_header {
447 if header.has_numbering {
448 self.document_rels.has_numberings = true;
449 }
450 let count = self.document_rels.header_count + 1;
451 new_section = Section {
452 property: new_section
453 .property
454 .first_header(header, &create_header_rid(count)),
455 children: new_section.children,
456 has_numbering: new_section.has_numbering,
457 temp_first_header: None,
458 ..Default::default()
459 };
460 self.document_rels.header_count = count;
461 self.content_type = self.content_type.add_header();
462 }
463
464 if let Some(header) = new_section.temp_even_header {
465 if header.has_numbering {
466 self.document_rels.has_numberings = true;
467 }
468 let count = self.document_rels.header_count + 1;
469 new_section = Section {
470 property: new_section
471 .property
472 .even_header(header, &create_header_rid(count)),
473 children: new_section.children,
474 has_numbering: new_section.has_numbering,
475 temp_even_header: None,
476 ..Default::default()
477 };
478 self.document_rels.header_count = count;
479 self.content_type = self.content_type.add_header();
480 }
481
482 if let Some(footer) = new_section.temp_footer {
484 if footer.has_numbering {
485 self.document_rels.has_numberings = true;
486 }
487 let count = self.document_rels.footer_count + 1;
488 new_section = Section {
489 property: new_section
490 .property
491 .footer(footer, &create_footer_rid(count)),
492 children: new_section.children,
493 has_numbering: new_section.has_numbering,
494 temp_footer: None,
495 ..Default::default()
496 };
497 self.document_rels.footer_count = count;
498 self.content_type = self.content_type.add_footer();
499 }
500
501 if let Some(footer) = new_section.temp_first_footer {
502 if footer.has_numbering {
503 self.document_rels.has_numberings = true;
504 }
505 let count = self.document_rels.footer_count + 1;
506 new_section = Section {
507 property: new_section
508 .property
509 .first_footer(footer, &create_footer_rid(count)),
510 children: new_section.children,
511 has_numbering: new_section.has_numbering,
512 temp_first_footer: None,
513 ..Default::default()
514 };
515 self.document_rels.footer_count = count;
516 self.content_type = self.content_type.add_footer();
517 }
518
519 if let Some(footer) = new_section.temp_even_footer {
520 if footer.has_numbering {
521 self.document_rels.has_numberings = true;
522 }
523 let count = self.document_rels.footer_count + 1;
524 new_section = Section {
525 property: new_section
526 .property
527 .even_footer(footer, &create_footer_rid(count)),
528 children: new_section.children,
529 has_numbering: new_section.has_numbering,
530 temp_even_footer: None,
531 ..Default::default()
532 };
533 self.document_rels.footer_count = count;
534 self.content_type = self.content_type.add_footer();
535 }
536
537 self.document = self.document.add_section(new_section);
538 self
539 }
540
541 pub fn add_structured_data_tag(mut self, t: StructuredDataTag) -> Docx {
542 if t.has_numbering {
543 self.document_rels.has_numberings = true;
546 }
547 self.document = self.document.add_structured_data_tag(t);
548 self
549 }
550
551 pub fn add_table_of_contents(mut self, t: TableOfContents) -> Docx {
552 self.document = self.document.add_table_of_contents(t);
553 self
554 }
555
556 pub fn add_bookmark_start(mut self, id: usize, name: impl Into<String>) -> Docx {
557 self.document = self.document.add_bookmark_start(id, name);
558 self
559 }
560
561 pub fn add_bookmark_end(mut self, id: usize) -> Docx {
562 self.document = self.document.add_bookmark_end(id);
563 self
564 }
565
566 pub fn add_table(mut self, t: Table) -> Docx {
567 if t.has_numbering {
568 self.document_rels.has_numberings = true;
571 }
572 self.document = self.document.add_table(t);
573 self
574 }
575
576 pub fn header(mut self, header: Header) -> Self {
577 if header.has_numbering {
578 self.document_rels.has_numberings = true;
579 }
580 let count = self.document_rels.header_count + 1;
581 self.document.section_property = self
582 .document
583 .section_property
584 .header(header, &create_header_rid(count));
585 self.document_rels.header_count = count;
586 self.content_type = self.content_type.add_header();
587 self
588 }
589
590 pub fn first_header(mut self, header: Header) -> Self {
591 if header.has_numbering {
592 self.document_rels.has_numberings = true;
593 }
594 let count = self.document_rels.header_count + 1;
595 self.document.section_property = self
596 .document
597 .section_property
598 .first_header(header, &create_header_rid(count));
599 self.document_rels.header_count = count;
600 self.content_type = self.content_type.add_header();
601 self
602 }
603
604 pub fn even_header(mut self, header: Header) -> Self {
605 if header.has_numbering {
606 self.document_rels.has_numberings = true;
607 }
608 let count = self.document_rels.header_count + 1;
609 self.document.section_property = self
610 .document
611 .section_property
612 .even_header(header, &create_header_rid(count));
613 self.document_rels.header_count = count;
614 self.content_type = self.content_type.add_header();
615 self.settings = self.settings.even_and_odd_headers();
616 self
617 }
618
619 pub fn footer(mut self, footer: Footer) -> Self {
620 if footer.has_numbering {
621 self.document_rels.has_numberings = true;
622 }
623 let count = self.document_rels.footer_count + 1;
624 self.document.section_property = self
625 .document
626 .section_property
627 .footer(footer, &create_footer_rid(count));
628 self.document_rels.footer_count = count;
629 self.content_type = self.content_type.add_footer();
630 self
631 }
632
633 pub fn first_footer(mut self, footer: Footer) -> Self {
634 if footer.has_numbering {
635 self.document_rels.has_numberings = true;
636 }
637 let count = self.document_rels.footer_count + 1;
638 self.document.section_property = self
639 .document
640 .section_property
641 .first_footer(footer, &create_footer_rid(count));
642 self.document_rels.footer_count = count;
643 self.content_type = self.content_type.add_footer();
644 self
645 }
646
647 pub fn even_footer(mut self, footer: Footer) -> Self {
648 if footer.has_numbering {
649 self.document_rels.has_numberings = true;
650 }
651 let count = self.document_rels.footer_count + 1;
652 self.document.section_property = self
653 .document
654 .section_property
655 .even_footer(footer, &create_footer_rid(count));
656 self.document_rels.footer_count = count;
657 self.content_type = self.content_type.add_footer();
658 self.settings = self.settings.even_and_odd_headers();
659 self
660 }
661
662 pub fn add_abstract_numbering(mut self, num: AbstractNumbering) -> Docx {
663 self.numberings = self.numberings.add_abstract_numbering(num);
664 self
665 }
666
667 pub fn add_numbering(mut self, num: Numbering) -> Docx {
668 self.numberings = self.numberings.add_numbering(num);
669 self
670 }
671
672 pub fn created_at(mut self, date: &str) -> Self {
673 self.doc_props = self.doc_props.created_at(date);
674 self
675 }
676
677 pub fn updated_at(mut self, date: &str) -> Self {
678 self.doc_props = self.doc_props.updated_at(date);
679 self
680 }
681
682 pub fn custom_property(mut self, name: impl Into<String>, item: impl Into<String>) -> Self {
683 self.doc_props = self.doc_props.custom_property(name, item);
684 self
685 }
686
687 pub fn doc_id(mut self, id: &str) -> Self {
688 self.settings = self.settings.doc_id(id);
689 self
690 }
691
692 pub fn default_tab_stop(mut self, stop: usize) -> Self {
693 self.settings = self.settings.default_tab_stop(stop);
694 self
695 }
696
697 pub fn add_doc_var(mut self, name: &str, val: &str) -> Self {
698 self.settings = self.settings.add_doc_var(name, val);
699 self
700 }
701
702 pub fn title_pg(mut self) -> Self {
703 self.document = self.document.title_pg();
704 self
705 }
706
707 pub fn page_size(mut self, w: u32, h: u32) -> Self {
708 self.document = self.document.page_size(PageSize::new().size(w, h));
709 self
710 }
711
712 pub fn page_margin(mut self, margin: crate::types::PageMargin) -> Self {
713 self.document = self.document.page_margin(margin);
714 self
715 }
716
717 pub fn page_orient(mut self, o: crate::types::PageOrientationType) -> Self {
718 self.document = self.document.page_orient(o);
719 self
720 }
721
722 pub fn default_size(mut self, size: usize) -> Self {
723 self.styles = self.styles.default_size(size);
724 self
725 }
726
727 pub fn default_spacing(mut self, spacing: i32) -> Self {
728 self.styles = self.styles.default_spacing(spacing);
729 self
730 }
731
732 pub fn default_fonts(mut self, font: RunFonts) -> Self {
733 self.styles = self.styles.default_fonts(font);
734 self
735 }
736
737 pub fn default_line_spacing(mut self, spacing: LineSpacing) -> Self {
738 self.styles = self.styles.default_line_spacing(spacing);
739 self
740 }
741
742 pub fn taskpanes(mut self) -> Self {
743 self.taskpanes = Some(Taskpanes::new());
744 self.rels = self.rels.add_taskpanes_rel();
745 self.content_type = self.content_type.add_taskpanes();
746 self
747 }
748
749 pub fn web_extension(mut self, ext: WebExtension) -> Self {
750 self.web_extensions.push(ext);
751 self.taskpanes_rels = self.taskpanes_rels.add_rel();
752 self.content_type = self.content_type.add_web_extensions();
753 self
754 }
755
756 pub fn add_custom_item(mut self, id: &str, xml: &str) -> Self {
757 let x = CustomItem::from_str(xml).expect("should parse xml string");
758 self.content_type = self.content_type.add_custom_xml();
759 let rel = CustomItemRels::new().add_item();
760 self.custom_item_props.push(CustomItemProperty::new(id));
761 self.document_rels = self.document_rels.add_custom_item();
762 self.custom_item_rels.push(rel);
763 self.custom_items.push(x);
764 self
765 }
766
767 pub fn page_num_type(mut self, p: PageNumType) -> Self {
768 self.document = self.document.page_num_type(p);
769 self
770 }
771
772 pub fn build(mut self) -> XMLDocx {
778 let package = self.prepare_package();
779
780 let web_extensions = self.web_extensions.iter().map(|ext| ext.build()).collect();
781 let custom_items = self.custom_items.iter().map(|xml| xml.build()).collect();
782 let custom_item_props = self.custom_item_props.iter().map(|p| p.build()).collect();
783 let custom_item_rels = self
784 .custom_item_rels
785 .iter()
786 .map(|rel| rel.build())
787 .collect();
788
789 let mut headers: Vec<&(String, Header)> = self.document.section_property.get_headers();
790
791 self.document.children.iter().for_each(|child| {
792 if let DocumentChild::Section(section) = child {
793 for h in section.property.get_headers() {
794 headers.push(h);
795 }
796 }
797 });
798
799 headers.sort_by(|a, b| a.0.cmp(&b.0));
800 let headers = headers.iter().map(|h| h.1.build()).collect();
801
802 let mut footers: Vec<&(String, Footer)> = self.document.section_property.get_footers();
803
804 self.document.children.iter().for_each(|child| {
805 if let DocumentChild::Section(section) = child {
806 for h in section.property.get_footers() {
807 footers.push(h);
808 }
809 }
810 });
811
812 footers.sort_by(|a, b| a.0.cmp(&b.0));
813 let footers = footers.iter().map(|h| h.1.build()).collect();
814
815 XMLDocx {
816 content_type: self.content_type.build(),
817 rels: self.rels.build(),
818 doc_props: self.doc_props.build(),
819 styles: self.styles.build(),
820 document: self.document.build(),
821 comments: self.comments.build(),
822 document_rels: self.document_rels.build(),
823 header_rels: package.header_rels.into_iter().map(|r| r.build()).collect(),
824 footer_rels: package.footer_rels.into_iter().map(|r| r.build()).collect(),
825 settings: self.settings.build(),
826 font_table: self.font_table.build(),
827 numberings: self.numberings.build(),
828 media: package.media,
829 headers,
830 footers,
831 comments_extended: self.comments_extended.build(),
832 taskpanes: self.taskpanes.map(|taskpanes| taskpanes.build()),
833 taskpanes_rels: self.taskpanes_rels.build(),
834 web_extensions,
835 custom_items,
836 custom_item_rels,
837 custom_item_props,
838 footnotes: self.footnotes.build(),
839 }
840 }
841
842 pub fn pack<W>(mut self, writer: W) -> zip::result::ZipResult<()>
848 where
849 W: std::io::Write + std::io::Seek,
850 {
851 let package = self.prepare_package();
852 crate::zipper::zip_docx(writer, self, package)
853 }
854
855 pub fn json(&self) -> String {
856 self.reset();
857
858 serde_json::to_string_pretty(&self).unwrap()
859 }
860
861 pub fn json_with_update_comments(&mut self) -> String {
863 self.reset();
864
865 self.update_dependencies();
866 serde_json::to_string_pretty(&self).unwrap()
867 }
868
869 pub fn comments_json(&mut self) -> String {
871 self.reset();
872 self.update_dependencies();
873 serde_json::to_string_pretty(&self.comments).unwrap()
874 }
875
876 fn reset(&self) {
877 crate::reset_para_id();
878 }
879
880 fn expand_auto_tocs(&mut self) -> bool {
883 let tocs: Vec<(usize, Box<TableOfContents>)> = self
884 .document
885 .children
886 .iter()
887 .enumerate()
888 .filter_map(|(index, child)| match child {
889 DocumentChild::TableOfContents(toc) => Some((index, toc.clone())),
890 _ => None,
891 })
892 .collect();
893
894 for (index, toc) in &tocs {
895 if toc.items.is_empty() && toc.auto {
896 let children = std::mem::take(&mut self.document.children);
897 self.document.children =
898 update_document_by_toc(children, &self.styles, *toc.clone(), *index);
899 }
900 }
901
902 !tocs.is_empty()
903 }
904
905 fn prepare_for_build(&mut self) -> bool {
907 self.reset();
908 let has_toc = self.expand_auto_tocs();
909 self.refresh_duplicate_para_ids();
910 self.update_dependencies();
911 has_toc
912 }
913
914 fn prepare_package(&mut self) -> PackageMetadata {
917 let has_toc = self.prepare_for_build();
918
919 let mut media = MediaRegistry::default();
920 let document_part = collect_document_part(&mut self.document, &mut media);
921 let header_images = self.collect_header_images(&mut media);
922 let footer_images = self.collect_footer_images(&mut media);
923 self.document_rels.images = document_part.relationships;
924
925 let mut header_rels = vec![HeaderRels::new(); 3];
926 for (rels, images) in header_rels.iter_mut().zip(header_images) {
927 rels.set_images(images);
928 }
929
930 let mut footer_rels = vec![FooterRels::new(); 3];
931 for (rels, images) in footer_rels.iter_mut().zip(footer_images) {
932 rels.set_images(images);
933 }
934
935 if !document_part.footnotes.is_empty() {
936 self.footnotes.add(document_part.footnotes);
937 self.content_type = std::mem::take(&mut self.content_type).add_footnotes();
938 self.document_rels.has_footnotes = true;
939 }
940
941 if has_toc {
942 for level in 1..=9 {
943 if !self
944 .styles
945 .styles
946 .iter()
947 .any(|style| style.name == Name::new(format!("toc {level}")))
948 {
949 self.styles = std::mem::take(&mut self.styles)
950 .add_style(crate::documents::preset_styles::toc(level));
951 }
952 }
953 }
954
955 PackageMetadata {
956 media: media.into_media(),
957 header_rels,
958 footer_rels,
959 }
960 }
961
962 fn refresh_duplicate_para_ids(&mut self) {
964 let mut counts: HashMap<&str, usize> = HashMap::new();
965 collect_para_ids_in_docx(self, &mut counts);
966
967 let duplicates: HashSet<String> = counts
968 .iter()
969 .filter(|(_, count)| **count > 1)
970 .map(|(id, _)| (*id).to_owned())
971 .collect();
972 let has_empty_id = counts.contains_key("");
973 if duplicates.is_empty() && !has_empty_id {
974 return;
975 }
976
977 let mut used: HashSet<String> = counts.into_keys().map(str::to_owned).collect();
978 let mut seen: HashSet<String> = HashSet::new();
979
980 refresh_para_ids_in_docx(self, &duplicates, &mut used, &mut seen);
981 }
982
983 fn insert_comment_to_map(
984 &self,
985 comment_map: &mut HashMap<usize, String>,
986 c: &CommentRangeStart,
987 ) {
988 let comment = c.get_comment_ref();
989 let comment_id = comment.id();
990 for child in &comment.children {
991 if let CommentChild::Paragraph(child) = child {
992 let para_id = child.id.clone();
993 comment_map.insert(comment_id, para_id);
994 }
995 }
997 }
998
999 fn update_dependencies(&mut self) {
1001 let mut comments: Vec<Comment> = vec![];
1002 let mut comments_extended: Vec<CommentExtended> = vec![];
1003 let mut comment_map: HashMap<usize, String> = HashMap::new();
1004
1005 let mut hyperlink_map: HashMap<String, String> = HashMap::new();
1006
1007 for child in &self.document.children {
1008 match child {
1009 DocumentChild::Paragraph(paragraph) => {
1010 for child in ¶graph.children {
1011 if let ParagraphChild::CommentStart(c) = child {
1012 self.insert_comment_to_map(&mut comment_map, c);
1013 }
1014 if let ParagraphChild::Hyperlink(h) = child {
1015 if let HyperlinkData::External { rid, path } = &h.link {
1016 hyperlink_map.insert(rid.clone(), path.clone());
1017 }
1018 for child in &h.children {
1019 if let ParagraphChild::CommentStart(c) = child {
1020 self.insert_comment_to_map(&mut comment_map, c);
1021 }
1022 }
1023 }
1024 }
1025 }
1026 DocumentChild::Table(table) => {
1027 collect_comment_map_in_table(table, &mut comment_map, &mut hyperlink_map);
1028 }
1029 DocumentChild::TableOfContents(toc) => {
1030 for child in &toc.before_contents {
1031 if let TocContent::Paragraph(paragraph) = child {
1032 collect_comment_map_in_paragraph(
1033 paragraph,
1034 &mut comment_map,
1035 &mut hyperlink_map,
1036 );
1037 }
1038 if let TocContent::Table(table) = child {
1039 collect_comment_map_in_table(
1040 table,
1041 &mut comment_map,
1042 &mut hyperlink_map,
1043 );
1044 }
1045 }
1046 for child in &toc.after_contents {
1047 if let TocContent::Paragraph(paragraph) = child {
1048 collect_comment_map_in_paragraph(
1049 paragraph,
1050 &mut comment_map,
1051 &mut hyperlink_map,
1052 );
1053 }
1054 if let TocContent::Table(table) = child {
1055 collect_comment_map_in_table(
1056 table,
1057 &mut comment_map,
1058 &mut hyperlink_map,
1059 );
1060 }
1061 }
1062 }
1063 _ => {}
1064 }
1065 }
1066
1067 if comment_map.is_empty() {
1068 for (id, path) in hyperlink_map {
1069 self.document_rels
1070 .hyperlinks
1071 .push((id, path, "External".to_owned()));
1072 }
1073 return;
1074 }
1075
1076 for child in &self.document.children {
1077 match child {
1078 DocumentChild::Paragraph(paragraph) => {
1079 for child in ¶graph.children {
1080 if let ParagraphChild::CommentStart(c) = child {
1081 push_comment_and_comment_extended(
1082 &mut comments,
1083 &mut comments_extended,
1084 &comment_map,
1085 c,
1086 );
1087 }
1088 if let ParagraphChild::Hyperlink(h) = child {
1089 if let HyperlinkData::External { rid, path } = &h.link {
1090 hyperlink_map.insert(rid.clone(), path.clone());
1091 };
1092 for child in &h.children {
1093 if let ParagraphChild::CommentStart(c) = child {
1094 push_comment_and_comment_extended(
1095 &mut comments,
1096 &mut comments_extended,
1097 &comment_map,
1098 c,
1099 );
1100 }
1101 }
1102 }
1103 }
1104 }
1105 DocumentChild::Table(table) => {
1106 collect_dependencies_in_table(
1107 table,
1108 &mut comments,
1109 &mut comments_extended,
1110 &mut comment_map,
1111 &mut hyperlink_map,
1112 );
1113 }
1114 DocumentChild::TableOfContents(toc) => {
1115 for child in &toc.before_contents {
1117 if let TocContent::Paragraph(paragraph) = child {
1118 for child in ¶graph.children {
1119 if let ParagraphChild::CommentStart(c) = child {
1120 push_comment_and_comment_extended(
1121 &mut comments,
1122 &mut comments_extended,
1123 &comment_map,
1124 c,
1125 );
1126 }
1127 if let ParagraphChild::Hyperlink(h) = child {
1128 if let HyperlinkData::External { rid, path } = &h.link {
1129 hyperlink_map.insert(rid.clone(), path.clone());
1130 };
1131 for child in &h.children {
1132 if let ParagraphChild::CommentStart(c) = child {
1133 push_comment_and_comment_extended(
1134 &mut comments,
1135 &mut comments_extended,
1136 &comment_map,
1137 c,
1138 );
1139 }
1140 }
1141 }
1142 }
1143 }
1144 if let TocContent::Table(table) = child {
1145 collect_dependencies_in_table(
1146 table,
1147 &mut comments,
1148 &mut comments_extended,
1149 &mut comment_map,
1150 &mut hyperlink_map,
1151 );
1152 }
1153 }
1154 for child in &toc.after_contents {
1155 if let TocContent::Paragraph(paragraph) = child {
1156 for child in ¶graph.children {
1157 if let ParagraphChild::CommentStart(c) = child {
1158 push_comment_and_comment_extended(
1159 &mut comments,
1160 &mut comments_extended,
1161 &comment_map,
1162 c,
1163 );
1164 }
1165 if let ParagraphChild::Hyperlink(h) = child {
1166 if let HyperlinkData::External { rid, path } = &h.link {
1167 hyperlink_map.insert(rid.clone(), path.clone());
1168 };
1169 for child in &h.children {
1170 if let ParagraphChild::CommentStart(c) = child {
1171 push_comment_and_comment_extended(
1172 &mut comments,
1173 &mut comments_extended,
1174 &comment_map,
1175 c,
1176 );
1177 }
1178 }
1179 }
1180 }
1181 }
1182 if let TocContent::Table(table) = child {
1183 collect_dependencies_in_table(
1184 table,
1185 &mut comments,
1186 &mut comments_extended,
1187 &mut comment_map,
1188 &mut hyperlink_map,
1189 );
1190 }
1191 }
1192 }
1193 _ => {}
1194 }
1195 }
1196 if !comments.is_empty() {
1199 self.document_rels.has_comments = true;
1200 }
1201
1202 self.comments_extended
1203 .add_comments_extended(comments_extended);
1204
1205 self.comments.add_comments(comments);
1206
1207 for (id, d) in hyperlink_map {
1208 self.document_rels
1209 .hyperlinks
1210 .push((id, d, "External".to_string())); }
1212 }
1213
1214 pub(crate) fn store_comments(&mut self, comments: &[Comment]) {
1217 let comments_by_id: HashMap<usize, &Comment> = comments
1218 .iter()
1219 .map(|comment| (comment.id(), comment))
1220 .collect();
1221 for child in &mut self.document.children {
1222 match child {
1223 DocumentChild::Paragraph(paragraph) => {
1224 for child in &mut paragraph.children {
1225 if let ParagraphChild::CommentStart(ref mut c) = child {
1226 let comment_id = c.get_id();
1227 if let Some(comment) = comments_by_id.get(&comment_id).copied() {
1228 let comment = Comment::clone(comment);
1229 c.as_mut().comment(comment);
1230 }
1231 }
1232 if let ParagraphChild::Insert(ref mut insert) = child {
1233 for child in &mut insert.children {
1234 if let InsertChild::CommentStart(ref mut c) = child {
1235 let comment_id = c.get_id();
1236 if let Some(comment) = comments_by_id.get(&comment_id).copied()
1237 {
1238 let comment = Comment::clone(comment);
1239 c.as_mut().comment(comment);
1240 }
1241 } else if let InsertChild::Delete(ref mut d) = child {
1242 for child in &mut d.children {
1243 if let DeleteChild::CommentStart(ref mut c) = child {
1244 let comment_id = c.get_id();
1245 if let Some(comment) =
1246 comments_by_id.get(&comment_id).copied()
1247 {
1248 let comment = Comment::clone(comment);
1249 c.as_mut().comment(comment);
1250 }
1251 }
1252 }
1253 }
1254 }
1255 }
1256 if let ParagraphChild::Delete(ref mut delete) = child {
1257 for child in &mut delete.children {
1258 if let DeleteChild::CommentStart(ref mut c) = child {
1259 let comment_id = c.get_id();
1260 if let Some(comment) = comments_by_id.get(&comment_id).copied()
1261 {
1262 let comment = Comment::clone(comment);
1263 c.as_mut().comment(comment);
1264 }
1265 }
1266 }
1267 }
1268 }
1269 }
1270 DocumentChild::Table(table) => store_comments_in_table(table, &comments_by_id),
1271 _ => {}
1272 }
1273 }
1274
1275 if !comments.is_empty() {
1276 self.document_rels.has_comments = true;
1277 }
1278 }
1279
1280 fn collect_header_images(&mut self, media: &mut MediaRegistry) -> Vec<Vec<ImageIdAndPath>> {
1286 let mut images = vec![Vec::new(); 3];
1287 let headers = [
1288 &mut self.document.section_property.header,
1289 &mut self.document.section_property.first_header,
1290 &mut self.document.section_property.even_header,
1291 ];
1292
1293 for (images, header) in images.iter_mut().zip(headers) {
1294 if let Some((_, header)) = header.as_mut() {
1295 *images = collect_header_part(header, media).relationships;
1296 }
1297 }
1298 images
1299 }
1300
1301 fn collect_footer_images(&mut self, media: &mut MediaRegistry) -> Vec<Vec<ImageIdAndPath>> {
1307 let mut images = vec![Vec::new(); 3];
1308 let footers = [
1309 &mut self.document.section_property.footer,
1310 &mut self.document.section_property.first_footer,
1311 &mut self.document.section_property.even_footer,
1312 ];
1313
1314 for (images, footer) in images.iter_mut().zip(footers) {
1315 if let Some((_, footer)) = footer.as_mut() {
1316 *images = collect_footer_part(footer, media).relationships;
1317 }
1318 }
1319 images
1320 }
1321
1322 pub fn collect_footnotes(&mut self) -> bool {
1328 let footnotes = collect_document_footnotes(&mut self.document);
1329 let is_footnotes = !footnotes.is_empty();
1330 self.footnotes.add(footnotes);
1331 is_footnotes
1332 }
1333}
1334
1335fn collect_dependencies_in_paragraph(
1336 paragraph: &Paragraph,
1337 comments: &mut Vec<Comment>,
1338 comments_extended: &mut Vec<CommentExtended>,
1339 comment_map: &mut HashMap<usize, String>,
1340 hyperlink_map: &mut HashMap<String, String>,
1341) {
1342 for child in ¶graph.children {
1343 if let ParagraphChild::CommentStart(c) = child {
1344 push_comment_and_comment_extended(comments, comments_extended, comment_map, c);
1345 }
1346 if let ParagraphChild::Hyperlink(h) = child {
1347 if let HyperlinkData::External { rid, path } = &h.link {
1348 hyperlink_map.insert(rid.clone(), path.clone());
1349 };
1350 for child in &h.children {
1351 if let ParagraphChild::CommentStart(c) = child {
1352 push_comment_and_comment_extended(comments, comments_extended, comment_map, c);
1353 }
1354 }
1355 }
1356 }
1357}
1358
1359fn collect_comment_map_in_paragraph(
1360 paragraph: &Paragraph,
1361 comment_map: &mut HashMap<usize, String>,
1362 hyperlink_map: &mut HashMap<String, String>,
1363) {
1364 for child in ¶graph.children {
1365 if let ParagraphChild::CommentStart(c) = child {
1366 let comment = c.get_comment_ref();
1367 let comment_id = comment.id();
1368 for child in &comment.children {
1369 if let CommentChild::Paragraph(child) = child {
1370 comment_map.insert(comment_id, child.id.clone());
1371 }
1372 }
1373 }
1374 if let ParagraphChild::Hyperlink(h) = child {
1375 if let HyperlinkData::External { rid, path } = &h.link {
1376 hyperlink_map.insert(rid.clone(), path.clone());
1377 }
1378 for child in &h.children {
1379 if let ParagraphChild::CommentStart(c) = child {
1380 let comment = c.get_comment_ref();
1381 let comment_id = comment.id();
1382 for child in &comment.children {
1383 if let CommentChild::Paragraph(child) = child {
1384 comment_map.insert(comment_id, child.id.clone());
1385 }
1386 }
1387 }
1388 }
1389 }
1390 }
1391}
1392
1393fn collect_comment_map_in_table(
1394 table: &Table,
1395 comment_map: &mut HashMap<usize, String>,
1396 hyperlink_map: &mut HashMap<String, String>,
1397) {
1398 for TableChild::TableRow(row) in &table.rows {
1399 for TableRowChild::TableCell(cell) in &row.cells {
1400 for content in &cell.children {
1401 match content {
1402 TableCellContent::Paragraph(paragraph) => {
1403 collect_comment_map_in_paragraph(paragraph, comment_map, hyperlink_map);
1404 }
1405 TableCellContent::Table(table) => {
1406 collect_comment_map_in_table(table, comment_map, hyperlink_map)
1407 }
1408 TableCellContent::StructuredDataTag(tag) => {
1409 for child in &tag.children {
1410 if let StructuredDataTagChild::Paragraph(paragraph) = child {
1411 collect_comment_map_in_paragraph(
1412 paragraph,
1413 comment_map,
1414 hyperlink_map,
1415 );
1416 }
1417 if let StructuredDataTagChild::Table(table) = child {
1418 collect_comment_map_in_table(table, comment_map, hyperlink_map);
1419 }
1420 }
1421 }
1422 TableCellContent::TableOfContents(t) => {
1423 for child in &t.before_contents {
1424 if let TocContent::Paragraph(paragraph) = child {
1425 collect_comment_map_in_paragraph(
1426 paragraph,
1427 comment_map,
1428 hyperlink_map,
1429 );
1430 }
1431 if let TocContent::Table(table) = child {
1432 collect_comment_map_in_table(table, comment_map, hyperlink_map);
1433 }
1434 }
1435 for child in &t.after_contents {
1436 if let TocContent::Paragraph(paragraph) = child {
1437 collect_comment_map_in_paragraph(
1438 paragraph,
1439 comment_map,
1440 hyperlink_map,
1441 );
1442 }
1443 if let TocContent::Table(table) = child {
1444 collect_comment_map_in_table(table, comment_map, hyperlink_map);
1445 }
1446 }
1447 }
1448 }
1449 }
1450 }
1451 }
1452}
1453
1454fn collect_dependencies_in_table(
1455 table: &Table,
1456 comments: &mut Vec<Comment>,
1457 comments_extended: &mut Vec<CommentExtended>,
1458 comment_map: &mut HashMap<usize, String>,
1459 hyperlink_map: &mut HashMap<String, String>,
1460) {
1461 for TableChild::TableRow(row) in &table.rows {
1462 for TableRowChild::TableCell(cell) in &row.cells {
1463 for content in &cell.children {
1464 match content {
1465 TableCellContent::Paragraph(paragraph) => {
1466 collect_dependencies_in_paragraph(
1467 paragraph,
1468 comments,
1469 comments_extended,
1470 comment_map,
1471 hyperlink_map,
1472 );
1473 }
1474 TableCellContent::Table(table) => collect_dependencies_in_table(
1475 table,
1476 comments,
1477 comments_extended,
1478 comment_map,
1479 hyperlink_map,
1480 ),
1481 TableCellContent::StructuredDataTag(tag) => {
1482 for child in &tag.children {
1483 if let StructuredDataTagChild::Paragraph(paragraph) = child {
1484 collect_dependencies_in_paragraph(
1485 paragraph,
1486 comments,
1487 comments_extended,
1488 comment_map,
1489 hyperlink_map,
1490 );
1491 }
1492 if let StructuredDataTagChild::Table(table) = child {
1493 collect_dependencies_in_table(
1494 table,
1495 comments,
1496 comments_extended,
1497 comment_map,
1498 hyperlink_map,
1499 );
1500 }
1501 }
1502 }
1503 TableCellContent::TableOfContents(t) => {
1504 for child in &t.before_contents {
1505 if let TocContent::Paragraph(paragraph) = child {
1506 collect_dependencies_in_paragraph(
1507 paragraph,
1508 comments,
1509 comments_extended,
1510 comment_map,
1511 hyperlink_map,
1512 );
1513 }
1514 if let TocContent::Table(table) = child {
1515 collect_dependencies_in_table(
1516 table,
1517 comments,
1518 comments_extended,
1519 comment_map,
1520 hyperlink_map,
1521 );
1522 }
1523 }
1524
1525 for child in &t.after_contents {
1526 if let TocContent::Paragraph(paragraph) = child {
1527 collect_dependencies_in_paragraph(
1528 paragraph,
1529 comments,
1530 comments_extended,
1531 comment_map,
1532 hyperlink_map,
1533 );
1534 }
1535 if let TocContent::Table(table) = child {
1536 collect_dependencies_in_table(
1537 table,
1538 comments,
1539 comments_extended,
1540 comment_map,
1541 hyperlink_map,
1542 );
1543 }
1544 }
1545 }
1546 }
1547 }
1548 }
1549 }
1550}
1551
1552fn store_comments_in_paragraph(
1553 paragraph: &mut Paragraph,
1554 comments_by_id: &HashMap<usize, &Comment>,
1555) {
1556 for child in &mut paragraph.children {
1557 if let ParagraphChild::CommentStart(ref mut c) = child {
1558 let comment_id = c.get_id();
1559 if let Some(comment) = comments_by_id.get(&comment_id).copied() {
1560 let comment = Comment::clone(comment);
1561 c.as_mut().comment(comment);
1562 }
1563 }
1564 if let ParagraphChild::Insert(ref mut insert) = child {
1565 for child in &mut insert.children {
1566 if let InsertChild::CommentStart(ref mut c) = child {
1567 let comment_id = c.get_id();
1568 if let Some(comment) = comments_by_id.get(&comment_id).copied() {
1569 let comment = Comment::clone(comment);
1570 c.as_mut().comment(comment);
1571 }
1572 }
1573 }
1574 }
1575 if let ParagraphChild::Delete(ref mut delete) = child {
1576 for child in &mut delete.children {
1577 if let DeleteChild::CommentStart(ref mut c) = child {
1578 let comment_id = c.get_id();
1579 if let Some(comment) = comments_by_id.get(&comment_id).copied() {
1580 let comment = Comment::clone(comment);
1581 c.as_mut().comment(comment);
1582 }
1583 }
1584 }
1585 }
1586 }
1587}
1588
1589fn store_comments_in_table(table: &mut Table, comments_by_id: &HashMap<usize, &Comment>) {
1590 for TableChild::TableRow(row) in &mut table.rows {
1591 for TableRowChild::TableCell(cell) in &mut row.cells {
1592 for content in &mut cell.children {
1593 match content {
1594 TableCellContent::Paragraph(paragraph) => {
1595 store_comments_in_paragraph(paragraph, comments_by_id)
1596 }
1597 TableCellContent::Table(ref mut table) => {
1598 store_comments_in_table(table, comments_by_id);
1599 }
1600 TableCellContent::StructuredDataTag(ref mut tag) => {
1601 for child in &mut tag.children {
1602 if let StructuredDataTagChild::Paragraph(paragraph) = child {
1603 store_comments_in_paragraph(paragraph, comments_by_id);
1604 }
1605 if let StructuredDataTagChild::Table(table) = child {
1606 store_comments_in_table(table, comments_by_id);
1607 }
1608 }
1609 }
1610 TableCellContent::TableOfContents(ref mut t) => {
1611 for child in &mut t.before_contents {
1612 if let TocContent::Paragraph(paragraph) = child {
1613 store_comments_in_paragraph(paragraph, comments_by_id);
1614 }
1615 if let TocContent::Table(table) = child {
1616 store_comments_in_table(table, comments_by_id);
1617 }
1618 }
1619
1620 for child in &mut t.after_contents {
1621 if let TocContent::Paragraph(paragraph) = child {
1622 store_comments_in_paragraph(paragraph, comments_by_id);
1623 }
1624 if let TocContent::Table(table) = child {
1625 store_comments_in_table(table, comments_by_id);
1626 }
1627 }
1628 }
1629 }
1630 }
1631 }
1632 }
1633}
1634
1635fn collect_para_ids_in_docx<'a>(docx: &'a Docx, counts: &mut HashMap<&'a str, usize>) {
1636 for child in &docx.document.children {
1637 collect_para_ids_in_document_child(child, counts);
1638 }
1639 collect_para_ids_in_section_property(&docx.document.section_property, counts);
1640
1641 for comment in &docx.comments.comments {
1642 collect_para_ids_in_comment(comment, counts);
1643 }
1644
1645 for footnote in &docx.footnotes.footnotes {
1646 for paragraph in &footnote.content {
1647 collect_para_ids_in_paragraph(paragraph, counts);
1648 }
1649 }
1650}
1651
1652fn collect_para_ids_in_document_child<'a>(
1653 child: &'a DocumentChild,
1654 counts: &mut HashMap<&'a str, usize>,
1655) {
1656 match child {
1657 DocumentChild::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1658 DocumentChild::Table(table) => collect_para_ids_in_table(table, counts),
1659 DocumentChild::StructuredDataTag(tag) => {
1660 collect_para_ids_in_structured_data_tag(tag, counts)
1661 }
1662 DocumentChild::TableOfContents(toc) => collect_para_ids_in_toc(toc, counts),
1663 DocumentChild::Section(section) => collect_para_ids_in_section(section, counts),
1664 DocumentChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1665 _ => {}
1666 }
1667}
1668
1669fn collect_para_ids_in_section<'a>(section: &'a Section, counts: &mut HashMap<&'a str, usize>) {
1670 for child in §ion.children {
1671 match child {
1672 SectionChild::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1673 SectionChild::Table(table) => collect_para_ids_in_table(table, counts),
1674 SectionChild::StructuredDataTag(tag) => {
1675 collect_para_ids_in_structured_data_tag(tag, counts)
1676 }
1677 SectionChild::TableOfContents(toc) => collect_para_ids_in_toc(toc, counts),
1678 SectionChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1679 _ => {}
1680 }
1681 }
1682 collect_para_ids_in_section_property(§ion.property, counts);
1683}
1684
1685fn collect_para_ids_in_section_property<'a>(
1686 property: &'a SectionProperty,
1687 counts: &mut HashMap<&'a str, usize>,
1688) {
1689 if let Some((_, header)) = property.header.as_ref() {
1690 collect_para_ids_in_header(header, counts);
1691 }
1692 if let Some((_, header)) = property.first_header.as_ref() {
1693 collect_para_ids_in_header(header, counts);
1694 }
1695 if let Some((_, header)) = property.even_header.as_ref() {
1696 collect_para_ids_in_header(header, counts);
1697 }
1698 if let Some((_, footer)) = property.footer.as_ref() {
1699 collect_para_ids_in_footer(footer, counts);
1700 }
1701 if let Some((_, footer)) = property.first_footer.as_ref() {
1702 collect_para_ids_in_footer(footer, counts);
1703 }
1704 if let Some((_, footer)) = property.even_footer.as_ref() {
1705 collect_para_ids_in_footer(footer, counts);
1706 }
1707}
1708
1709fn collect_para_ids_in_header<'a>(header: &'a Header, counts: &mut HashMap<&'a str, usize>) {
1710 for child in &header.children {
1711 match child {
1712 HeaderChild::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1713 HeaderChild::Table(table) => collect_para_ids_in_table(table, counts),
1714 HeaderChild::StructuredDataTag(tag) => {
1715 collect_para_ids_in_structured_data_tag(tag, counts)
1716 }
1717 }
1718 }
1719}
1720
1721fn collect_para_ids_in_footer<'a>(footer: &'a Footer, counts: &mut HashMap<&'a str, usize>) {
1722 for child in &footer.children {
1723 match child {
1724 FooterChild::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1725 FooterChild::Table(table) => collect_para_ids_in_table(table, counts),
1726 FooterChild::StructuredDataTag(tag) => {
1727 collect_para_ids_in_structured_data_tag(tag, counts)
1728 }
1729 }
1730 }
1731}
1732
1733fn collect_para_ids_in_toc<'a>(toc: &'a TableOfContents, counts: &mut HashMap<&'a str, usize>) {
1734 for child in &toc.before_contents {
1735 match child {
1736 TocContent::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1737 TocContent::Table(table) => collect_para_ids_in_table(table, counts),
1738 }
1739 }
1740 for child in &toc.after_contents {
1741 match child {
1742 TocContent::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1743 TocContent::Table(table) => collect_para_ids_in_table(table, counts),
1744 }
1745 }
1746}
1747
1748fn collect_para_ids_in_table<'a>(table: &'a Table, counts: &mut HashMap<&'a str, usize>) {
1749 for TableChild::TableRow(row) in &table.rows {
1750 for TableRowChild::TableCell(cell) in &row.cells {
1751 for content in &cell.children {
1752 match content {
1753 TableCellContent::Paragraph(paragraph) => {
1754 collect_para_ids_in_paragraph(paragraph, counts)
1755 }
1756 TableCellContent::Table(table) => collect_para_ids_in_table(table, counts),
1757 TableCellContent::StructuredDataTag(tag) => {
1758 collect_para_ids_in_structured_data_tag(tag, counts)
1759 }
1760 TableCellContent::TableOfContents(toc) => collect_para_ids_in_toc(toc, counts),
1761 }
1762 }
1763 }
1764 }
1765}
1766
1767fn collect_para_ids_in_paragraph<'a>(
1768 paragraph: &'a Paragraph,
1769 counts: &mut HashMap<&'a str, usize>,
1770) {
1771 *counts.entry(paragraph.id.as_str()).or_insert(0) += 1;
1772
1773 for child in ¶graph.children {
1774 match child {
1775 ParagraphChild::Run(run) => collect_para_ids_in_run(run, counts),
1776 ParagraphChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1777 ParagraphChild::Insert(insert) => collect_para_ids_in_insert(insert, counts),
1778 ParagraphChild::Delete(delete) => collect_para_ids_in_delete(delete, counts),
1779 ParagraphChild::MoveFrom(moved) => collect_para_ids_in_move_from(moved, counts),
1780 ParagraphChild::MoveTo(moved) => collect_para_ids_in_move_to(moved, counts),
1781 ParagraphChild::Hyperlink(hyperlink) => {
1782 collect_para_ids_in_hyperlink(hyperlink, counts)
1783 }
1784 ParagraphChild::StructuredDataTag(tag) => {
1785 collect_para_ids_in_structured_data_tag(tag, counts)
1786 }
1787 _ => {}
1788 }
1789 }
1790}
1791
1792fn collect_para_ids_in_hyperlink<'a>(
1793 hyperlink: &'a Hyperlink,
1794 counts: &mut HashMap<&'a str, usize>,
1795) {
1796 for child in &hyperlink.children {
1797 match child {
1798 ParagraphChild::Run(run) => collect_para_ids_in_run(run, counts),
1799 ParagraphChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1800 ParagraphChild::Insert(insert) => collect_para_ids_in_insert(insert, counts),
1801 ParagraphChild::Delete(delete) => collect_para_ids_in_delete(delete, counts),
1802 ParagraphChild::MoveFrom(moved) => collect_para_ids_in_move_from(moved, counts),
1803 ParagraphChild::MoveTo(moved) => collect_para_ids_in_move_to(moved, counts),
1804 ParagraphChild::Hyperlink(hyperlink) => {
1805 collect_para_ids_in_hyperlink(hyperlink, counts)
1806 }
1807 ParagraphChild::StructuredDataTag(tag) => {
1808 collect_para_ids_in_structured_data_tag(tag, counts)
1809 }
1810 _ => {}
1811 }
1812 }
1813}
1814
1815fn collect_para_ids_in_insert<'a>(insert: &'a Insert, counts: &mut HashMap<&'a str, usize>) {
1816 for child in &insert.children {
1817 match child {
1818 InsertChild::Run(run) => collect_para_ids_in_run(run, counts),
1819 InsertChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1820 InsertChild::Delete(delete) => collect_para_ids_in_delete(delete, counts),
1821 _ => {}
1822 }
1823 }
1824}
1825
1826fn collect_para_ids_in_delete<'a>(delete: &'a Delete, counts: &mut HashMap<&'a str, usize>) {
1827 for child in &delete.children {
1828 match child {
1829 DeleteChild::Run(run) => collect_para_ids_in_run(run, counts),
1830 DeleteChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1831 DeleteChild::CommentEnd(_) => {}
1832 }
1833 }
1834}
1835
1836fn collect_para_ids_in_move_from<'a>(moved: &'a MoveFrom, counts: &mut HashMap<&'a str, usize>) {
1837 for child in &moved.children {
1838 match child {
1839 MoveFromChild::Run(run) => collect_para_ids_in_run(run, counts),
1840 MoveFromChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1841 MoveFromChild::CommentEnd(_) => {}
1842 }
1843 }
1844}
1845
1846fn collect_para_ids_in_move_to<'a>(moved: &'a MoveTo, counts: &mut HashMap<&'a str, usize>) {
1847 for child in &moved.children {
1848 match child {
1849 MoveToChild::Run(run) => collect_para_ids_in_run(run, counts),
1850 MoveToChild::Delete(delete) => collect_para_ids_in_delete(delete, counts),
1851 MoveToChild::CommentStart(c) => collect_para_ids_in_comment(&c.comment, counts),
1852 _ => {}
1853 }
1854 }
1855}
1856
1857fn collect_para_ids_in_run<'a>(run: &'a Run, counts: &mut HashMap<&'a str, usize>) {
1858 for child in &run.children {
1859 if let RunChild::CommentStart(c) = child {
1860 collect_para_ids_in_comment(&c.comment, counts);
1861 }
1862 }
1863}
1864
1865fn collect_para_ids_in_structured_data_tag<'a>(
1866 tag: &'a StructuredDataTag,
1867 counts: &mut HashMap<&'a str, usize>,
1868) {
1869 for child in &tag.children {
1870 match child {
1871 StructuredDataTagChild::Run(run) => collect_para_ids_in_run(run, counts),
1872 StructuredDataTagChild::Paragraph(paragraph) => {
1873 collect_para_ids_in_paragraph(paragraph, counts)
1874 }
1875 StructuredDataTagChild::Table(table) => collect_para_ids_in_table(table, counts),
1876 StructuredDataTagChild::CommentStart(c) => {
1877 collect_para_ids_in_comment(&c.comment, counts)
1878 }
1879 StructuredDataTagChild::StructuredDataTag(inner) => {
1880 collect_para_ids_in_structured_data_tag(inner, counts)
1881 }
1882 _ => {}
1883 }
1884 }
1885}
1886
1887fn collect_para_ids_in_comment<'a>(comment: &'a Comment, counts: &mut HashMap<&'a str, usize>) {
1888 for child in &comment.children {
1889 match child {
1890 CommentChild::Paragraph(paragraph) => collect_para_ids_in_paragraph(paragraph, counts),
1891 CommentChild::Table(table) => collect_para_ids_in_table(table, counts),
1892 }
1893 }
1894}
1895
1896fn refresh_para_ids_in_docx(
1897 docx: &mut Docx,
1898 duplicates: &HashSet<String>,
1899 used: &mut HashSet<String>,
1900 seen: &mut HashSet<String>,
1901) {
1902 for child in &mut docx.document.children {
1903 refresh_para_ids_in_document_child(child, duplicates, used, seen);
1904 }
1905 refresh_para_ids_in_section_property(
1906 &mut docx.document.section_property,
1907 duplicates,
1908 used,
1909 seen,
1910 );
1911
1912 for comment in &mut docx.comments.comments {
1913 refresh_para_ids_in_comment(comment, duplicates, used, seen);
1914 }
1915
1916 for footnote in &mut docx.footnotes.footnotes {
1917 for paragraph in &mut footnote.content {
1918 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen);
1919 }
1920 }
1921}
1922
1923fn refresh_para_ids_in_document_child(
1924 child: &mut DocumentChild,
1925 duplicates: &HashSet<String>,
1926 used: &mut HashSet<String>,
1927 seen: &mut HashSet<String>,
1928) {
1929 match child {
1930 DocumentChild::Paragraph(paragraph) => {
1931 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
1932 }
1933 DocumentChild::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
1934 DocumentChild::StructuredDataTag(tag) => {
1935 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
1936 }
1937 DocumentChild::TableOfContents(toc) => refresh_para_ids_in_toc(toc, duplicates, used, seen),
1938 DocumentChild::Section(section) => {
1939 refresh_para_ids_in_section(section, duplicates, used, seen)
1940 }
1941 DocumentChild::CommentStart(c) => {
1942 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
1943 }
1944 _ => {}
1945 }
1946}
1947
1948fn refresh_para_ids_in_section(
1949 section: &mut Section,
1950 duplicates: &HashSet<String>,
1951 used: &mut HashSet<String>,
1952 seen: &mut HashSet<String>,
1953) {
1954 for child in &mut section.children {
1955 match child {
1956 SectionChild::Paragraph(paragraph) => {
1957 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
1958 }
1959 SectionChild::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
1960 SectionChild::StructuredDataTag(tag) => {
1961 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
1962 }
1963 SectionChild::TableOfContents(toc) => {
1964 refresh_para_ids_in_toc(toc, duplicates, used, seen)
1965 }
1966 SectionChild::CommentStart(c) => {
1967 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
1968 }
1969 _ => {}
1970 }
1971 }
1972 refresh_para_ids_in_section_property(&mut section.property, duplicates, used, seen);
1973}
1974
1975fn refresh_para_ids_in_section_property(
1976 property: &mut SectionProperty,
1977 duplicates: &HashSet<String>,
1978 used: &mut HashSet<String>,
1979 seen: &mut HashSet<String>,
1980) {
1981 if let Some((_, header)) = property.header.as_mut() {
1982 refresh_para_ids_in_header(header, duplicates, used, seen);
1983 }
1984 if let Some((_, header)) = property.first_header.as_mut() {
1985 refresh_para_ids_in_header(header, duplicates, used, seen);
1986 }
1987 if let Some((_, header)) = property.even_header.as_mut() {
1988 refresh_para_ids_in_header(header, duplicates, used, seen);
1989 }
1990 if let Some((_, footer)) = property.footer.as_mut() {
1991 refresh_para_ids_in_footer(footer, duplicates, used, seen);
1992 }
1993 if let Some((_, footer)) = property.first_footer.as_mut() {
1994 refresh_para_ids_in_footer(footer, duplicates, used, seen);
1995 }
1996 if let Some((_, footer)) = property.even_footer.as_mut() {
1997 refresh_para_ids_in_footer(footer, duplicates, used, seen);
1998 }
1999}
2000
2001fn refresh_para_ids_in_header(
2002 header: &mut Header,
2003 duplicates: &HashSet<String>,
2004 used: &mut HashSet<String>,
2005 seen: &mut HashSet<String>,
2006) {
2007 for child in &mut header.children {
2008 match child {
2009 HeaderChild::Paragraph(paragraph) => {
2010 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2011 }
2012 HeaderChild::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
2013 HeaderChild::StructuredDataTag(tag) => {
2014 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
2015 }
2016 }
2017 }
2018}
2019
2020fn refresh_para_ids_in_footer(
2021 footer: &mut Footer,
2022 duplicates: &HashSet<String>,
2023 used: &mut HashSet<String>,
2024 seen: &mut HashSet<String>,
2025) {
2026 for child in &mut footer.children {
2027 match child {
2028 FooterChild::Paragraph(paragraph) => {
2029 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2030 }
2031 FooterChild::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
2032 FooterChild::StructuredDataTag(tag) => {
2033 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
2034 }
2035 }
2036 }
2037}
2038
2039fn refresh_para_ids_in_toc(
2040 toc: &mut TableOfContents,
2041 duplicates: &HashSet<String>,
2042 used: &mut HashSet<String>,
2043 seen: &mut HashSet<String>,
2044) {
2045 for child in &mut toc.before_contents {
2046 match child {
2047 TocContent::Paragraph(paragraph) => {
2048 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2049 }
2050 TocContent::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
2051 }
2052 }
2053 for child in &mut toc.after_contents {
2054 match child {
2055 TocContent::Paragraph(paragraph) => {
2056 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2057 }
2058 TocContent::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
2059 }
2060 }
2061}
2062
2063fn refresh_para_ids_in_table(
2064 table: &mut Table,
2065 duplicates: &HashSet<String>,
2066 used: &mut HashSet<String>,
2067 seen: &mut HashSet<String>,
2068) {
2069 for TableChild::TableRow(row) in &mut table.rows {
2070 for TableRowChild::TableCell(cell) in &mut row.cells {
2071 for content in &mut cell.children {
2072 match content {
2073 TableCellContent::Paragraph(paragraph) => {
2074 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2075 }
2076 TableCellContent::Table(table) => {
2077 refresh_para_ids_in_table(table, duplicates, used, seen)
2078 }
2079 TableCellContent::StructuredDataTag(tag) => {
2080 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
2081 }
2082 TableCellContent::TableOfContents(toc) => {
2083 refresh_para_ids_in_toc(toc, duplicates, used, seen)
2084 }
2085 }
2086 }
2087 }
2088 }
2089}
2090
2091fn refresh_para_ids_in_paragraph(
2092 paragraph: &mut Paragraph,
2093 duplicates: &HashSet<String>,
2094 used: &mut HashSet<String>,
2095 seen: &mut HashSet<String>,
2096) {
2097 ensure_unique_paragraph_id(paragraph, duplicates, used, seen);
2098
2099 for child in &mut paragraph.children {
2100 match child {
2101 ParagraphChild::Run(run) => refresh_para_ids_in_run(run, duplicates, used, seen),
2102 ParagraphChild::CommentStart(c) => {
2103 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2104 }
2105 ParagraphChild::Insert(insert) => {
2106 refresh_para_ids_in_insert(insert, duplicates, used, seen)
2107 }
2108 ParagraphChild::Delete(delete) => {
2109 refresh_para_ids_in_delete(delete, duplicates, used, seen)
2110 }
2111 ParagraphChild::MoveFrom(moved) => {
2112 refresh_para_ids_in_move_from(moved, duplicates, used, seen)
2113 }
2114 ParagraphChild::MoveTo(moved) => {
2115 refresh_para_ids_in_move_to(moved, duplicates, used, seen)
2116 }
2117 ParagraphChild::Hyperlink(hyperlink) => {
2118 refresh_para_ids_in_hyperlink(hyperlink, duplicates, used, seen)
2119 }
2120 ParagraphChild::StructuredDataTag(tag) => {
2121 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
2122 }
2123 _ => {}
2124 }
2125 }
2126}
2127
2128fn refresh_para_ids_in_hyperlink(
2129 hyperlink: &mut Hyperlink,
2130 duplicates: &HashSet<String>,
2131 used: &mut HashSet<String>,
2132 seen: &mut HashSet<String>,
2133) {
2134 for child in &mut hyperlink.children {
2135 match child {
2136 ParagraphChild::Run(run) => refresh_para_ids_in_run(run, duplicates, used, seen),
2137 ParagraphChild::CommentStart(c) => {
2138 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2139 }
2140 ParagraphChild::Insert(insert) => {
2141 refresh_para_ids_in_insert(insert, duplicates, used, seen)
2142 }
2143 ParagraphChild::Delete(delete) => {
2144 refresh_para_ids_in_delete(delete, duplicates, used, seen)
2145 }
2146 ParagraphChild::MoveFrom(moved) => {
2147 refresh_para_ids_in_move_from(moved, duplicates, used, seen)
2148 }
2149 ParagraphChild::MoveTo(moved) => {
2150 refresh_para_ids_in_move_to(moved, duplicates, used, seen)
2151 }
2152 ParagraphChild::Hyperlink(hyperlink) => {
2153 refresh_para_ids_in_hyperlink(hyperlink, duplicates, used, seen)
2154 }
2155 ParagraphChild::StructuredDataTag(tag) => {
2156 refresh_para_ids_in_structured_data_tag(tag, duplicates, used, seen)
2157 }
2158 _ => {}
2159 }
2160 }
2161}
2162
2163fn refresh_para_ids_in_insert(
2164 insert: &mut Insert,
2165 duplicates: &HashSet<String>,
2166 used: &mut HashSet<String>,
2167 seen: &mut HashSet<String>,
2168) {
2169 for child in &mut insert.children {
2170 match child {
2171 InsertChild::Run(run) => refresh_para_ids_in_run(run, duplicates, used, seen),
2172 InsertChild::CommentStart(c) => {
2173 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2174 }
2175 InsertChild::Delete(delete) => {
2176 refresh_para_ids_in_delete(delete, duplicates, used, seen)
2177 }
2178 _ => {}
2179 }
2180 }
2181}
2182
2183fn refresh_para_ids_in_delete(
2184 delete: &mut Delete,
2185 duplicates: &HashSet<String>,
2186 used: &mut HashSet<String>,
2187 seen: &mut HashSet<String>,
2188) {
2189 for child in &mut delete.children {
2190 match child {
2191 DeleteChild::Run(run) => refresh_para_ids_in_run(run, duplicates, used, seen),
2192 DeleteChild::CommentStart(c) => {
2193 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2194 }
2195 DeleteChild::CommentEnd(_) => {}
2196 }
2197 }
2198}
2199
2200fn refresh_para_ids_in_move_from(
2201 moved: &mut MoveFrom,
2202 duplicates: &HashSet<String>,
2203 used: &mut HashSet<String>,
2204 seen: &mut HashSet<String>,
2205) {
2206 for child in &mut moved.children {
2207 match child {
2208 MoveFromChild::Run(run) => refresh_para_ids_in_run(run, duplicates, used, seen),
2209 MoveFromChild::CommentStart(c) => {
2210 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2211 }
2212 MoveFromChild::CommentEnd(_) => {}
2213 }
2214 }
2215}
2216
2217fn refresh_para_ids_in_move_to(
2218 moved: &mut MoveTo,
2219 duplicates: &HashSet<String>,
2220 used: &mut HashSet<String>,
2221 seen: &mut HashSet<String>,
2222) {
2223 for child in &mut moved.children {
2224 match child {
2225 MoveToChild::Run(run) => refresh_para_ids_in_run(run, duplicates, used, seen),
2226 MoveToChild::Delete(delete) => {
2227 refresh_para_ids_in_delete(delete, duplicates, used, seen)
2228 }
2229 MoveToChild::CommentStart(c) => {
2230 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2231 }
2232 _ => {}
2233 }
2234 }
2235}
2236
2237fn refresh_para_ids_in_run(
2238 run: &mut Run,
2239 duplicates: &HashSet<String>,
2240 used: &mut HashSet<String>,
2241 seen: &mut HashSet<String>,
2242) {
2243 for child in &mut run.children {
2244 if let RunChild::CommentStart(c) = child {
2245 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen);
2246 }
2247 }
2248}
2249
2250fn refresh_para_ids_in_structured_data_tag(
2251 tag: &mut StructuredDataTag,
2252 duplicates: &HashSet<String>,
2253 used: &mut HashSet<String>,
2254 seen: &mut HashSet<String>,
2255) {
2256 for child in &mut tag.children {
2257 match child {
2258 StructuredDataTagChild::Run(run) => {
2259 refresh_para_ids_in_run(run, duplicates, used, seen)
2260 }
2261 StructuredDataTagChild::Paragraph(paragraph) => {
2262 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2263 }
2264 StructuredDataTagChild::Table(table) => {
2265 refresh_para_ids_in_table(table, duplicates, used, seen)
2266 }
2267 StructuredDataTagChild::CommentStart(c) => {
2268 refresh_para_ids_in_comment(&mut c.comment, duplicates, used, seen)
2269 }
2270 StructuredDataTagChild::StructuredDataTag(inner) => {
2271 refresh_para_ids_in_structured_data_tag(inner, duplicates, used, seen)
2272 }
2273 _ => {}
2274 }
2275 }
2276}
2277
2278fn refresh_para_ids_in_comment(
2279 comment: &mut Comment,
2280 duplicates: &HashSet<String>,
2281 used: &mut HashSet<String>,
2282 seen: &mut HashSet<String>,
2283) {
2284 for child in &mut comment.children {
2285 match child {
2286 CommentChild::Paragraph(paragraph) => {
2287 refresh_para_ids_in_paragraph(paragraph, duplicates, used, seen)
2288 }
2289 CommentChild::Table(table) => refresh_para_ids_in_table(table, duplicates, used, seen),
2290 }
2291 }
2292}
2293
2294fn ensure_unique_paragraph_id(
2295 paragraph: &mut Paragraph,
2296 duplicates: &HashSet<String>,
2297 used: &mut HashSet<String>,
2298 seen: &mut HashSet<String>,
2299) {
2300 if !paragraph.id.is_empty() && !duplicates.contains(¶graph.id) {
2301 return;
2302 }
2303 if !paragraph.id.is_empty() && seen.insert(paragraph.id.clone()) {
2304 return;
2305 }
2306
2307 let new_id = next_unique_paragraph_id(used);
2308 paragraph.id = new_id.clone();
2309 used.insert(new_id);
2310}
2311
2312fn next_unique_paragraph_id(used: &HashSet<String>) -> String {
2315 let generated = crate::generate_para_id();
2316 if !used.contains(&generated) {
2317 return generated;
2318 }
2319
2320 (1usize..)
2321 .map(|value| format!("{value:08x}"))
2322 .find(|candidate| !used.contains(candidate))
2323 .expect("the paragraph ID space should not be exhausted")
2324}
2325
2326fn push_comment_and_comment_extended(
2327 comments: &mut Vec<Comment>,
2328 comments_extended: &mut Vec<CommentExtended>,
2329 comment_map: &HashMap<usize, String>,
2330 c: &CommentRangeStart,
2331) {
2332 let comment = c.get_comment_ref();
2333 for child in &comment.children {
2334 if let CommentChild::Paragraph(child) = child {
2335 let para_id = child.id.clone();
2336 comments.push(comment.clone());
2337 let comment_extended = CommentExtended::new(para_id);
2338 if let Some(parent_comment_id) = comment.parent_comment_id {
2339 if let Some(parent_para_id) = comment_map.get(&parent_comment_id) {
2340 comments_extended
2341 .push(comment_extended.parent_paragraph_id(parent_para_id.clone()));
2342 }
2343 } else {
2344 comments_extended.push(comment_extended);
2345 }
2346 }
2347 }
2349}
2350
2351fn update_document_by_toc(
2352 document_children: Vec<DocumentChild>,
2353 styles: &Styles,
2354 toc: TableOfContents,
2355 toc_index: usize,
2356) -> Vec<DocumentChild> {
2357 let heading_map = styles.create_heading_style_map();
2358 let mut items = vec![];
2359 let mut children = vec![];
2360 let style_map: std::collections::HashMap<String, usize> = toc
2361 .instr
2362 .styles_with_levels
2363 .iter()
2364 .map(|sl| sl.0.clone())
2365 .collect();
2366
2367 if toc.instr.heading_styles_range.is_none() && !toc.instr.styles_with_levels.is_empty() {
2368 return document_children;
2370 }
2371
2372 let (min, max) = toc.instr.heading_styles_range.unwrap_or((0, 9));
2373
2374 for child in document_children.into_iter() {
2375 match child {
2376 DocumentChild::Paragraph(mut paragraph) => {
2377 if let Some(heading_level) = paragraph
2378 .property
2379 .style
2380 .as_ref()
2381 .map(|p| p.val.to_string())
2382 .and_then(|sid| heading_map.get(&sid))
2383 {
2384 if min <= *heading_level && max >= *heading_level {
2385 let toc_key = TocKey::generate();
2386 items.push(
2387 TableOfContentsItem::new()
2388 .text(paragraph.raw_text())
2389 .toc_key(&toc_key)
2390 .level(*heading_level),
2391 );
2392 paragraph =
2393 Box::new(paragraph.wrap_by_bookmark(generate_bookmark_id(), &toc_key));
2394 }
2395
2396 if let Some((_min, _max)) = toc.instr.tc_field_level_range {
2397 }
2399 }
2400
2401 if let Some(level) = paragraph
2403 .property
2404 .style
2405 .as_ref()
2406 .and_then(|s| style_map.get(&s.val))
2407 {
2408 if min <= *level && max >= *level {
2409 let toc_key = TocKey::generate();
2410 items.push(
2411 TableOfContentsItem::new()
2412 .text(paragraph.raw_text())
2413 .toc_key(&toc_key)
2414 .level(*level),
2415 );
2416 paragraph =
2417 Box::new(paragraph.wrap_by_bookmark(generate_bookmark_id(), &toc_key));
2418 }
2419 }
2420
2421 children.push(DocumentChild::Paragraph(paragraph));
2422 }
2423 DocumentChild::Table(ref _table) => {
2424 children.push(child);
2438 }
2439 _ => {
2440 children.push(child);
2441 }
2442 }
2443 }
2444
2445 let mut toc = toc;
2446 toc.items = items;
2447 children[toc_index] = DocumentChild::TableOfContents(Box::new(toc));
2448 children
2449}
2450
2451#[cfg(test)]
2452mod image_collection_tests {
2453 use super::*;
2454
2455 fn picture(id: &str, bytes: Vec<u8>) -> Pic {
2456 let mut picture = Pic::new_with_dimensions(bytes, 1, 1);
2457 picture.id = id.to_owned();
2458 picture
2459 }
2460
2461 fn image_paragraph(id: &str, bytes: Vec<u8>) -> Paragraph {
2462 Paragraph::new().add_run(Run::new().add_image(picture(id, bytes)))
2463 }
2464
2465 #[test]
2466 fn structured_footer_images_use_footer_relationship_ids() {
2467 let tag = StructuredDataTag::new().add_paragraph(
2468 Paragraph::new().add_run(Run::new().add_image(Pic::new_with_dimensions(
2469 vec![1, 2, 3],
2470 1,
2471 1,
2472 ))),
2473 );
2474 let footer = Footer::new().add_structured_data_tag(tag);
2475 let xml = Docx::new().footer(footer).build();
2476
2477 assert!(String::from_utf8_lossy(&xml.footer_rels[0]).contains("Id=\"footer"));
2478 assert!(xml.media[0].0.starts_with("footer"));
2479 }
2480
2481 #[test]
2482 fn repeated_header_images_keep_relationships_in_each_part() {
2483 let bytes = vec![1, 2, 3, 4];
2484 let xml = Docx::new()
2485 .header(Header::new().add_paragraph(image_paragraph("default", bytes.clone())))
2486 .first_header(Header::new().add_paragraph(image_paragraph("first", bytes)))
2487 .build();
2488
2489 assert_eq!(xml.media.len(), 1);
2490 assert!(String::from_utf8_lossy(&xml.header_rels[0]).contains("relationships/image"));
2491 assert!(String::from_utf8_lossy(&xml.header_rels[1]).contains("relationships/image"));
2492 }
2493
2494 #[test]
2495 fn repeated_images_share_one_media_entry_across_package_parts() {
2496 let bytes = vec![5, 6, 7, 8];
2497 let xml = Docx::new()
2498 .add_paragraph(image_paragraph("body", bytes.clone()))
2499 .header(Header::new().add_paragraph(image_paragraph("header", bytes.clone())))
2500 .footer(Footer::new().add_paragraph(image_paragraph("footer", bytes)))
2501 .build();
2502
2503 assert_eq!(xml.media.len(), 1);
2504 assert!(String::from_utf8_lossy(&xml.document_rels).contains("relationships/image"));
2505 assert!(String::from_utf8_lossy(&xml.header_rels[0]).contains("relationships/image"));
2506 assert!(String::from_utf8_lossy(&xml.footer_rels[0]).contains("relationships/image"));
2507 }
2508
2509 #[test]
2510 fn images_are_collected_from_top_level_sdt_and_section_content() {
2511 let tag = StructuredDataTag::new().add_paragraph(image_paragraph("sdt", vec![9]));
2512 let section = Section::new().add_paragraph(image_paragraph("section", vec![10]));
2513 let xml = Docx::new()
2514 .add_structured_data_tag(tag)
2515 .add_section(section)
2516 .build();
2517
2518 assert_eq!(xml.media.len(), 2);
2519 assert_eq!(
2520 String::from_utf8_lossy(&xml.document_rels)
2521 .matches("relationships/image")
2522 .count(),
2523 2
2524 );
2525 }
2526}
2527
2528#[cfg(test)]
2529mod document_traversal_tests {
2530 use super::*;
2531
2532 #[test]
2533 fn footnotes_are_collected_from_table_paragraphs() {
2534 let footnote = Footnote::new()
2535 .add_content(Paragraph::new().add_run(Run::new().add_text("nested footnote")));
2536 let table = Table::new(vec![TableRow::new(vec![TableCell::new().add_paragraph(
2537 Paragraph::new().add_run(Run::new().add_footnote_reference(footnote)),
2538 )])]);
2539
2540 let xml = Docx::new().add_table(table).build();
2541
2542 assert!(String::from_utf8_lossy(&xml.footnotes).contains("nested footnote"));
2543 assert!(String::from_utf8_lossy(&xml.document_rels).contains("relationships/footnotes"));
2544 }
2545}
2546
2547#[cfg(test)]
2548mod pack_tests {
2549 use super::*;
2550 use std::io::Cursor;
2551
2552 #[test]
2553 fn direct_pack_matches_buffered_package_output() {
2554 let image = vec![1, 2, 3, 4];
2555 let header = Header::new().add_paragraph(
2556 Paragraph::new().add_run(Run::new().add_image(Pic::new_with_dimensions(
2557 image.clone(),
2558 1,
2559 1,
2560 ))),
2561 );
2562 let footer = Footer::new().add_paragraph(
2563 Paragraph::new().add_run(Run::new().add_image(Pic::new_with_dimensions(image, 1, 1))),
2564 );
2565 let docx = Docx::new()
2566 .header(header)
2567 .footer(footer)
2568 .add_paragraph(Paragraph::new().add_run(Run::new().add_text("streamed package")));
2569
2570 let mut buffered = Cursor::new(Vec::new());
2571 let xml = docx.clone().build();
2572 xml.pack(&mut buffered).unwrap();
2573
2574 let mut streamed = Cursor::new(Vec::new());
2575 docx.pack(&mut streamed).unwrap();
2576
2577 assert_eq!(streamed.into_inner(), buffered.into_inner());
2578 }
2579}
2580
2581#[cfg(test)]
2582mod paragraph_id_tests {
2583 use super::*;
2584
2585 fn paragraph_ids(docx: &Docx) -> Vec<&str> {
2586 docx.document
2587 .children
2588 .iter()
2589 .filter_map(|child| match child {
2590 DocumentChild::Paragraph(paragraph) => Some(paragraph.id.as_str()),
2591 _ => None,
2592 })
2593 .collect()
2594 }
2595
2596 #[test]
2597 fn unique_paragraph_ids_remain_unchanged() {
2598 let mut docx = Docx::new()
2599 .add_paragraph(Paragraph::new().id("first"))
2600 .add_paragraph(Paragraph::new().id("second"));
2601
2602 docx.refresh_duplicate_para_ids();
2603
2604 assert_eq!(paragraph_ids(&docx), ["first", "second"]);
2605 }
2606
2607 #[test]
2608 fn duplicate_paragraph_ids_are_refreshed() {
2609 let mut docx = Docx::new()
2610 .add_paragraph(Paragraph::new().id("duplicate"))
2611 .add_paragraph(Paragraph::new().id("duplicate"));
2612
2613 docx.refresh_duplicate_para_ids();
2614
2615 let ids = paragraph_ids(&docx);
2616 assert_eq!(ids[0], "duplicate");
2617 assert_ne!(ids[1], "duplicate");
2618 assert_eq!(ids.iter().copied().collect::<HashSet<_>>().len(), 2);
2619 }
2620
2621 #[test]
2622 fn empty_paragraph_id_is_refreshed() {
2623 let mut docx = Docx::new().add_paragraph(Paragraph::new().id(""));
2624
2625 docx.refresh_duplicate_para_ids();
2626
2627 assert!(!paragraph_ids(&docx)[0].is_empty());
2628 }
2629
2630 #[test]
2631 fn duplicate_paragraph_ids_in_tracked_change_comments_are_refreshed() {
2632 let moved_from_comment = Comment::new(1).add_paragraph(Paragraph::new().id("duplicate"));
2633 let moved_to_comment = Comment::new(2).add_paragraph(Paragraph::new().id("duplicate"));
2634 let paragraph = Paragraph::new()
2635 .id("outer")
2636 .add_move_from(MoveFrom::new().add_comment_start(moved_from_comment))
2637 .add_move_to(MoveTo::new_with_empty().add_comment_start(moved_to_comment));
2638 let mut docx = Docx::new().add_paragraph(paragraph);
2639
2640 docx.refresh_duplicate_para_ids();
2641
2642 let mut counts = HashMap::new();
2643 collect_para_ids_in_docx(&docx, &mut counts);
2644 assert_eq!(counts.len(), 3);
2645 assert!(counts.values().all(|count| *count == 1));
2646 }
2647
2648 #[test]
2649 fn auto_toc_paragraph_ids_are_normalized_after_expansion() {
2650 let heading = Paragraph::new()
2651 .id("00000001")
2652 .style("Heading1")
2653 .add_run(Run::new().add_text("Heading"));
2654 let mut docx = Docx::new()
2655 .add_style(Style::new("Heading1", crate::StyleType::Paragraph).name("Heading 1"))
2656 .add_table_of_contents(TableOfContents::new().heading_styles_range(1, 3).auto())
2657 .add_paragraph(heading);
2658
2659 docx.prepare_for_build();
2660
2661 let mut counts = HashMap::new();
2662 collect_para_ids_in_docx(&docx, &mut counts);
2663 assert!(!counts.contains_key(""));
2664 assert!(counts.values().all(|count| *count == 1));
2665 }
2666}
2667
2668#[cfg(test)]
2669mod emf_tests {
2670 use super::*;
2671
2672 pub(super) fn minimal_valid_emf() -> Vec<u8> {
2675 let mut buf = Vec::<u8>::with_capacity(108);
2676
2677 buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&88u32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes());
2682 buf.extend_from_slice(&0i32.to_le_bytes());
2683 buf.extend_from_slice(&100i32.to_le_bytes());
2684 buf.extend_from_slice(&100i32.to_le_bytes());
2685 buf.extend_from_slice(&0i32.to_le_bytes());
2687 buf.extend_from_slice(&0i32.to_le_bytes());
2688 buf.extend_from_slice(&2540i32.to_le_bytes());
2689 buf.extend_from_slice(&2540i32.to_le_bytes());
2690 buf.extend_from_slice(&0x464D_4520u32.to_le_bytes());
2692 buf.extend_from_slice(&0x0001_0000u32.to_le_bytes());
2693 buf.extend_from_slice(&108u32.to_le_bytes());
2694 buf.extend_from_slice(&2u32.to_le_bytes());
2695 buf.extend_from_slice(&0u16.to_le_bytes());
2697 buf.extend_from_slice(&0u16.to_le_bytes());
2698 buf.extend_from_slice(&0u32.to_le_bytes());
2699 buf.extend_from_slice(&0u32.to_le_bytes());
2700 buf.extend_from_slice(&0u32.to_le_bytes());
2701 buf.extend_from_slice(&1024i32.to_le_bytes());
2703 buf.extend_from_slice(&768i32.to_le_bytes());
2704 buf.extend_from_slice(&320i32.to_le_bytes());
2706 buf.extend_from_slice(&240i32.to_le_bytes());
2707 debug_assert_eq!(buf.len(), 88);
2708
2709 buf.extend_from_slice(&14u32.to_le_bytes()); buf.extend_from_slice(&20u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&20u32.to_le_bytes()); buf
2717 }
2718
2719 fn corrupt_emf_header() -> Vec<u8> {
2723 let mut buf = vec![0u8; 44];
2724 buf[0..4].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]);
2725 buf[4..8].copy_from_slice(&[0x2C, 0x00, 0x00, 0x00]);
2726 buf[40..44].copy_from_slice(&[0x20, 0x45, 0x4D, 0x46]);
2727 buf
2728 }
2729
2730 fn png_signature_bytes() -> Vec<u8> {
2731 let mut buf = vec![137, 80, 78, 71, 13, 10, 26, 10];
2734 buf.resize(64, 0);
2735 buf
2736 }
2737
2738 #[test]
2741 fn is_emf_detects_lowercase_extension() {
2742 assert!(is_emf("word/media/image1.emf", &[]));
2743 }
2744
2745 #[test]
2746 fn is_emf_detects_uppercase_extension() {
2747 assert!(is_emf("word/media/IMAGE1.EMF", &[]));
2748 }
2749
2750 #[test]
2751 fn is_emf_rejects_png_extension() {
2752 assert!(!is_emf("word/media/image1.png", &png_signature_bytes()));
2753 }
2754
2755 #[test]
2756 fn is_emf_rejects_empty_buffer_with_unrelated_extension() {
2757 assert!(!is_emf("word/media/image1.bin", &[]));
2758 }
2759
2760 #[test]
2761 fn is_emf_detects_magic_bytes_without_extension() {
2762 let buf = minimal_valid_emf();
2763 assert!(is_emf("word/media/image1.bin", &buf));
2764 }
2765
2766 #[test]
2767 fn is_emf_rejects_buffer_too_short_for_signature() {
2768 let buf = vec![0x01, 0x00, 0x00, 0x00];
2771 assert!(!is_emf("x.bin", &buf));
2772 }
2773
2774 #[test]
2775 fn is_emf_rejects_wrong_signature() {
2776 let mut buf = vec![0u8; 44];
2778 buf[0..4].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]);
2779 buf[40..44].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
2780 assert!(!is_emf("x.bin", &buf));
2781 }
2782
2783 #[test]
2786 fn add_image_routes_valid_emf_into_images_with_empty_preview() {
2787 let buf = minimal_valid_emf();
2788 let docx = Docx::new().add_image("rId1", "word/media/image1.emf", buf.clone());
2789
2790 assert_eq!(
2791 docx.images.len(),
2792 1,
2793 "EMF should land in the unified `images` vector"
2794 );
2795 let (id, path, original, preview) = &docx.images[0];
2796 assert_eq!(id, "rId1");
2797 assert_eq!(path, "word/media/image1.emf");
2798 assert_eq!(original.0, buf);
2799 assert!(preview.0.is_empty());
2800 }
2801
2802 #[test]
2803 fn add_image_preserves_emf_detected_by_magic_bytes() {
2804 let buf = corrupt_emf_header();
2805 let docx = Docx::new().add_image("rId1", "word/media/image1.bin", buf.clone());
2806 assert_eq!(docx.images.len(), 1);
2807 let (_, _, original, preview) = &docx.images[0];
2808 assert_eq!(original.0, buf);
2809 assert!(preview.0.is_empty());
2810 }
2811
2812 #[test]
2813 fn add_image_routes_png_as_png_preview() {
2814 let png_bytes = png_signature_bytes();
2815 let docx = Docx::new().add_image("rId1", "word/media/image1.png", png_bytes.clone());
2816 assert_eq!(docx.images.len(), 1);
2817 let (_, _, original, preview) = &docx.images[0];
2818 assert_eq!(&preview.0[..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
2820 assert_eq!(original.0, png_bytes);
2821 assert_eq!(preview.0, png_bytes);
2822 }
2823
2824 #[test]
2827 fn docx_serializes_emf_under_images_field() {
2828 let docx = Docx::new().add_image("rId1", "word/media/image1.emf", minimal_valid_emf());
2829 let json = serde_json::to_string(&docx).expect("should serialize");
2830 assert!(json.contains("\"images\""));
2833 assert!(
2834 !json.contains("imagesEmf"),
2835 "EMF is unified into `images`; no separate `imagesEmf` key"
2836 );
2837 use base64::Engine;
2839 let b64 = base64::engine::general_purpose::STANDARD.encode(minimal_valid_emf());
2840 assert!(json.contains(&b64));
2841 }
2842}
2843
2844#[cfg(test)]
2848mod emf_reader_tests {
2849 use super::emf_tests::minimal_valid_emf;
2850 use std::io::Write;
2851
2852 fn build_docx_with_emf() -> Vec<u8> {
2857 let buf = std::io::Cursor::new(Vec::<u8>::new());
2858 let mut zip = zip::ZipWriter::new(buf);
2859 let opts: zip::write::FileOptions<'_, ()> =
2860 zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
2861
2862 let content_types = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2863<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
2864 <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
2865 <Default Extension="xml" ContentType="application/xml"/>
2866 <Default Extension="emf" ContentType="image/x-emf"/>
2867 <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
2868</Types>"#;
2869 let root_rels = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2870<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
2871 <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
2872</Relationships>"#;
2873 let doc_rels = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2874<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
2875 <Relationship Id="rId10" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.emf"/>
2876</Relationships>"#;
2877 let document = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2878<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
2879 <w:body>
2880 <w:p><w:r><w:t>hello</w:t></w:r></w:p>
2881 </w:body>
2882</w:document>"#;
2883
2884 for (path, body) in [
2885 ("[Content_Types].xml", content_types),
2886 ("_rels/.rels", root_rels),
2887 ("word/_rels/document.xml.rels", doc_rels),
2888 ("word/document.xml", document),
2889 ] {
2890 zip.start_file(path, opts).unwrap();
2891 zip.write_all(body.as_bytes()).unwrap();
2892 }
2893 zip.start_file("word/media/image1.emf", opts).unwrap();
2894 zip.write_all(&minimal_valid_emf()).unwrap();
2895
2896 zip.finish().unwrap().into_inner()
2897 }
2898
2899 #[test]
2900 fn read_docx_routes_emf_media_to_images_with_empty_preview() {
2901 let docx_bytes = build_docx_with_emf();
2902 let docx = crate::reader::read_docx(&docx_bytes).expect("should read docx");
2903
2904 assert_eq!(
2905 docx.images.len(),
2906 1,
2907 "EMF media should be routed into the unified `images` vector"
2908 );
2909 let (id, path, original, preview) = &docx.images[0];
2910 assert_eq!(id, "rId10");
2911 assert!(path.ends_with("image1.emf"));
2912 assert_eq!(original.0, minimal_valid_emf());
2913 assert!(preview.0.is_empty());
2914 }
2915}