docx_rust/
docx.rs

1#[cfg(feature = "async")]
2use async_zip::{Compression, ZipEntryBuilder};
3#[cfg(feature = "async")]
4use futures_io::{AsyncBufRead, AsyncWrite};
5use hard_xml::{XmlRead, XmlWrite, XmlWriter};
6use std::borrow::Cow;
7use std::collections::HashMap;
8use std::fs::File;
9use std::io::{Read, Seek, Write};
10use std::path::Path;
11use zip::write::SimpleFileOptions;
12use zip::{result::ZipError, CompressionMethod, ZipArchive, ZipWriter};
13
14use crate::document::{Comments, EndNotes, FootNotes, Footer, Header, Numbering, Theme};
15use crate::media::MediaType;
16use crate::schema::{
17    SCHEMA_COMMENTS, SCHEMA_ENDNOTES, SCHEMA_FOOTNOTES, SCHEMA_HEADER, SCHEMA_NUMBERING,
18    SCHEMA_SETTINGS, SCHEMA_THEME, SCHEMA_WEB_SETTINGS,
19};
20use crate::settings::Settings;
21use crate::web_settings::WebSettings;
22use crate::{
23    app::App,
24    content_type::ContentTypes,
25    core::Core,
26    document::Document,
27    error::DocxResult,
28    font_table::FontTable,
29    rels::Relationships,
30    schema::{
31        SCHEMA_CORE, SCHEMA_FONT_TABLE, SCHEMA_OFFICE_DOCUMENT, SCHEMA_REL_EXTENDED, SCHEMA_STYLES,
32    },
33    styles::Styles,
34};
35
36/// A WordprocessingML package
37#[derive(Debug, Default, Clone)]
38pub struct Docx<'a> {
39    /// Specifies package-level properties part
40    pub app: Option<App<'a>>,
41    /// Specifies core properties part
42    pub core: Option<Core<'a>>,
43    /// Specifies the content type of relationship parts and the main document part.
44    pub content_types: ContentTypes<'a>,
45    /// Specifies the main document part.
46    pub document: Document<'a>,
47    /// Specifies the font table part
48    pub font_table: Option<FontTable<'a>>,
49    /// Specifies the style definitions part
50    pub styles: Styles<'a>,
51    /// Specifies the package-level relationship to the main document part
52    pub rels: Relationships<'a>,
53    /// Specifies the part-level relationship to the main document part
54    pub document_rels: Option<Relationships<'a>>,
55    pub settings_rels: Option<Relationships<'a>>,
56    pub headers: HashMap<String, Header<'a>>,
57    pub footers: HashMap<String, Footer<'a>>,
58    pub themes: HashMap<String, Theme<'a>>,
59    pub media: HashMap<String, (MediaType, &'a Vec<u8>)>,
60    pub footnotes: Option<FootNotes<'a>>,
61    pub endnotes: Option<EndNotes<'a>>,
62    pub settings: Option<Settings<'a>>,
63    pub web_settings: Option<WebSettings>,
64    pub comments: Option<Comments<'a>>,
65    pub numbering: Option<Numbering<'a>>,
66    pub custom_xml: HashMap<String, Cow<'a, [u8]>>,
67}
68
69impl<'a> Docx<'a> {
70    pub fn write<W: Write + Seek>(&'a mut self, writer: W) -> DocxResult<W> {
71        let mut writer = XmlWriter::new(ZipWriter::new(writer));
72
73        let opt = SimpleFileOptions::default()
74            .compression_method(CompressionMethod::Deflated)
75            .unix_permissions(0o755);
76
77        // ==== Add Relationships ====
78
79        if self.app.is_some() {
80            self.rels.add_rel(SCHEMA_REL_EXTENDED, "docProps/app.xml");
81        }
82
83        if self.core.is_some() {
84            self.rels.add_rel(SCHEMA_CORE, "docProps/core.xml");
85        }
86
87        self.rels
88            .add_rel(SCHEMA_OFFICE_DOCUMENT, "word/document.xml");
89
90        self.document_rels
91            .get_or_insert(Relationships::default())
92            .add_rel(SCHEMA_STYLES, "styles.xml");
93
94        if self.font_table.is_some() {
95            self.document_rels
96                .get_or_insert(Relationships::default())
97                .add_rel(SCHEMA_FONT_TABLE, "fontTable.xml");
98        }
99
100        if self.footnotes.is_some() {
101            self.document_rels
102                .get_or_insert(Relationships::default())
103                .add_rel(SCHEMA_FOOTNOTES, "footnotes.xml");
104        }
105
106        if self.endnotes.is_some() {
107            self.document_rels
108                .get_or_insert(Relationships::default())
109                .add_rel(SCHEMA_ENDNOTES, "endnotes.xml");
110        }
111
112        if self.settings.is_some() {
113            self.document_rels
114                .get_or_insert(Relationships::default())
115                .add_rel(SCHEMA_SETTINGS, "settings.xml");
116        }
117
118        if self.web_settings.is_some() {
119            self.document_rels
120                .get_or_insert(Relationships::default())
121                .add_rel(SCHEMA_WEB_SETTINGS, "webSettings.xml");
122        }
123
124        if self.comments.is_some() {
125            self.document_rels
126                .get_or_insert(Relationships::default())
127                .add_rel(SCHEMA_COMMENTS, "comments.xml");
128        }
129
130        if self.numbering.is_some() {
131            self.document_rels
132                .get_or_insert(Relationships::default())
133                .add_rel(SCHEMA_NUMBERING, "numbering.xml");
134        }
135
136        for hd in &self.headers {
137            self.document_rels
138                .get_or_insert(Relationships::default())
139                .add_rel(SCHEMA_HEADER, hd.0);
140        }
141
142        for ft in &self.footers {
143            self.document_rels
144                .get_or_insert(Relationships::default())
145                .add_rel(SCHEMA_HEADER, ft.0);
146        }
147
148        for theme in &self.themes {
149            self.document_rels
150                .get_or_insert(Relationships::default())
151                .add_rel(SCHEMA_THEME, theme.0);
152        }
153
154        for media in &self.media {
155            let rel = crate::media::get_media_type_relation_type(&media.1 .0);
156            self.document_rels
157                .get_or_insert(Relationships::default())
158                .add_rel(rel, media.0);
159        }
160
161        // ==== Write Zip Item ====
162
163        macro_rules! write_xml {
164            (Some($xml:expr) => $name:tt) => {
165                if let Some(ref xml) = $xml {
166                    write_xml!(xml => $name);
167                }
168            };
169            (Some($xml:expr) => $name:tt $($rest:tt)*) => {
170                write_xml!(Some($xml) => $name);
171                write_xml!($($rest)*);
172            };
173            ($xml:expr => $name:tt) => {
174                writer.inner.start_file($name, opt)?;
175                $xml.to_writer(&mut writer)?;
176            };
177            ($xml:expr => $name:tt $($rest:tt)*) => {
178                write_xml!($xml => $name);
179                write_xml!($($rest)*);
180            };
181        }
182
183        write_xml!(
184            self.content_types        => "[Content_Types].xml"
185            Some(self.app)            => "docProps/app.xml"
186            Some(self.core)           => "docProps/core.xml"
187            self.rels                 => "_rels/.rels"
188            self.document             => "word/document.xml"
189            self.styles               => "word/styles.xml"
190            Some(self.font_table)     => "word/fontTable.xml"
191            Some(self.footnotes)      => "word/footnotes.xml"
192            Some(self.endnotes)       => "word/endnotes.xml"
193            Some(self.settings)       => "word/settings.xml"
194            Some(self.web_settings)   => "word/webSettings.xml"
195            Some(self.comments)       => "word/comments.xml"
196            Some(self.numbering)      => "word/numbering.xml"
197            Some(self.document_rels)  => "word/_rels/document.xml.rels"
198            Some(self.settings_rels)  => "word/_rels/settings.xml.rels"
199        );
200
201        for hd in self.headers.iter() {
202            let file_path = format!("word/{}", hd.0);
203            let content = hd.1;
204            write_xml!(
205                content => file_path
206            );
207        }
208
209        for hd in self.footers.iter() {
210            let file_path = format!("word/{}", hd.0);
211            let content = hd.1;
212            write_xml!(
213                content => file_path
214            );
215        }
216
217        for theme in self.themes.iter() {
218            let file_path = format!("word/{}", theme.0);
219            let content = theme.1;
220            write_xml!(
221                content => file_path
222            );
223        }
224
225        for media in self.media.iter() {
226            let file_path = format!("word/{}", media.0);
227            writer.inner.start_file(file_path, opt)?;
228            writer.inner.write_all(media.1 .1)?;
229        }
230
231        for (file_path, content) in &self.custom_xml {
232            writer.inner.start_file(file_path.clone(), opt)?;
233            writer.inner.write_all(content)?;
234        }
235
236        Ok(writer.inner.finish()?)
237    }
238
239    pub fn write_file<P: AsRef<Path>>(&'a mut self, path: P) -> DocxResult<File> {
240        if let Some(p) = path.as_ref().parent() {
241            std::fs::create_dir_all(p)?;
242        }
243        let file = File::create(path)?;
244        self.write(file)
245    }
246}
247
248#[cfg(feature = "async")]
249impl<'a> Docx<'a> {
250    pub async fn write_async<W: AsyncWrite + Unpin>(&'a mut self, writer: W) -> DocxResult<W> {
251        use async_zip::base::write::ZipFileWriter;
252
253        let mut writer = ZipFileWriter::new(writer);
254
255        // ==== Add Relationships ====
256
257        if self.app.is_some() {
258            self.rels.add_rel(SCHEMA_REL_EXTENDED, "docProps/app.xml");
259        }
260
261        if self.core.is_some() {
262            self.rels.add_rel(SCHEMA_CORE, "docProps/core.xml");
263        }
264
265        self.rels
266            .add_rel(SCHEMA_OFFICE_DOCUMENT, "word/document.xml");
267
268        self.document_rels
269            .get_or_insert(Relationships::default())
270            .add_rel(SCHEMA_STYLES, "styles.xml");
271
272        if self.font_table.is_some() {
273            self.document_rels
274                .get_or_insert(Relationships::default())
275                .add_rel(SCHEMA_FONT_TABLE, "fontTable.xml");
276        }
277
278        if self.footnotes.is_some() {
279            self.document_rels
280                .get_or_insert(Relationships::default())
281                .add_rel(SCHEMA_FOOTNOTES, "footnotes.xml");
282        }
283
284        if self.endnotes.is_some() {
285            self.document_rels
286                .get_or_insert(Relationships::default())
287                .add_rel(SCHEMA_ENDNOTES, "endnotes.xml");
288        }
289
290        if self.settings.is_some() {
291            self.document_rels
292                .get_or_insert(Relationships::default())
293                .add_rel(SCHEMA_SETTINGS, "settings.xml");
294        }
295
296        if self.web_settings.is_some() {
297            self.document_rels
298                .get_or_insert(Relationships::default())
299                .add_rel(SCHEMA_WEB_SETTINGS, "webSettings.xml");
300        }
301
302        if self.comments.is_some() {
303            self.document_rels
304                .get_or_insert(Relationships::default())
305                .add_rel(SCHEMA_COMMENTS, "comments.xml");
306        }
307
308        if self.numbering.is_some() {
309            self.document_rels
310                .get_or_insert(Relationships::default())
311                .add_rel(SCHEMA_NUMBERING, "numbering.xml");
312        }
313
314        for hd in &self.headers {
315            self.document_rels
316                .get_or_insert(Relationships::default())
317                .add_rel(SCHEMA_HEADER, hd.0);
318        }
319
320        for ft in &self.footers {
321            self.document_rels
322                .get_or_insert(Relationships::default())
323                .add_rel(SCHEMA_HEADER, ft.0);
324        }
325
326        for theme in &self.themes {
327            self.document_rels
328                .get_or_insert(Relationships::default())
329                .add_rel(SCHEMA_THEME, theme.0);
330        }
331
332        for media in &self.media {
333            let rel = crate::media::get_media_type_relation_type(&media.1 .0);
334            self.document_rels
335                .get_or_insert(Relationships::default())
336                .add_rel(rel, media.0);
337        }
338
339        // ==== Write Zip Item ====
340
341        macro_rules! write_xml {
342            (Some($xml:expr) => $name:tt) => {
343                if let Some(ref xml) = $xml {
344                    write_xml!(xml => $name);
345                }
346            };
347            (Some($xml:expr) => $name:tt $($rest:tt)*) => {
348                write_xml!(Some($xml) => $name);
349                write_xml!($($rest)*);
350            };
351            ($xml:expr => $name:tt) => {
352                let mut buf = XmlWriter::new(Vec::new());
353                $xml.to_writer(&mut buf)?;
354                let opt = ZipEntryBuilder::new(($name.as_ref() as &str).into(), Compression::Deflate);
355                writer.write_entry_whole(opt, &buf.into_inner()).await?;
356            };
357            ($xml:expr => $name:tt $($rest:tt)*) => {
358                write_xml!($xml => $name);
359                write_xml!($($rest)*);
360            };
361        }
362
363        write_xml!(
364            self.content_types        => "[Content_Types].xml"
365            Some(self.app)            => "docProps/app.xml"
366            Some(self.core)           => "docProps/core.xml"
367            self.rels                 => "_rels/.rels"
368            self.document             => "word/document.xml"
369            self.styles               => "word/styles.xml"
370            Some(self.font_table)     => "word/fontTable.xml"
371            Some(self.footnotes)      => "word/footnotes.xml"
372            Some(self.endnotes)       => "word/endnotes.xml"
373            Some(self.settings)       => "word/settings.xml"
374            Some(self.web_settings)   => "word/webSettings.xml"
375            Some(self.comments)       => "word/comments.xml"
376            Some(self.numbering)      => "word/numbering.xml"
377            Some(self.document_rels)  => "word/_rels/document.xml.rels"
378            Some(self.settings_rels)  => "word/_rels/settings.xml.rels"
379        );
380
381        for (filename, content) in self.headers.iter() {
382            let file_path = format!("word/{}", filename);
383            write_xml!(
384                content => file_path
385            );
386        }
387
388        for (filename, content) in self.footers.iter() {
389            let file_path = format!("word/{}", filename);
390            write_xml!(
391                content => file_path
392            );
393        }
394
395        for (filename, content) in self.themes.iter() {
396            let file_path = format!("word/{}", filename);
397            write_xml!(
398                content => file_path
399            );
400        }
401
402        for (filename, (_, content)) in self.media.iter() {
403            let file_path = format!("word/{}", filename);
404            let opt = ZipEntryBuilder::new(file_path.as_str().into(), Compression::Deflate);
405            writer.write_entry_whole(opt, content).await?;
406        }
407
408        for (file_path, content) in &self.custom_xml {
409            let opt = ZipEntryBuilder::new(file_path.as_str().into(), Compression::Deflate);
410            writer.write_entry_whole(opt, &content).await?;
411        }
412
413        Ok(writer.close().await?)
414    }
415}
416
417/// An extracted docx file
418pub struct DocxFile {
419    app: Option<String>,
420    content_types: String,
421    core: Option<String>,
422    document: String,
423    document_rels: Option<String>,
424    settings_rels: Option<String>,
425    font_table: Option<String>,
426    rels: String,
427    styles: Option<String>,
428    settings: Option<String>,
429    web_settings: Option<String>,
430    headers: Vec<(String, String)>,
431    footers: Vec<(String, String)>,
432    themes: Vec<(String, String)>,
433    medias: Vec<(String, Vec<u8>)>,
434    footnotes: Option<String>,
435    endnotes: Option<String>,
436    comments: Option<String>,
437    numbering: Option<String>,
438    custom_xml: Vec<(String, Vec<u8>)>,
439}
440
441impl DocxFile {
442    /// Extracts from reader
443    pub fn from_reader<T: Read + Seek>(reader: T) -> DocxResult<Self> {
444        let mut zip = ZipArchive::new(reader)?;
445
446        macro_rules! read {
447            ($xml:tt, $name:expr) => {{
448                let mut file = zip.by_name($name)?;
449                let mut buffer = String::new();
450                file.read_to_string(&mut buffer)?;
451                buffer
452            }};
453        }
454
455        macro_rules! option_read {
456            ($xml:tt, $name:expr) => {
457                match zip.by_name($name) {
458                    Err(ZipError::FileNotFound) => None,
459                    Err(e) => return Err(e.into()),
460                    Ok(mut file) => {
461                        let mut buffer = String::new();
462                        file.read_to_string(&mut buffer)?;
463                        Some(buffer)
464                    }
465                }
466            };
467        }
468
469        macro_rules! option_read_multiple {
470            ($xml:tt, $name:expr) => {{
471                let names: Vec<_> = zip.file_names().map(|x| x.to_string()).collect();
472                let name_and_value: Vec<_> = names
473                    .iter()
474                    .filter(|n| n.contains($name))
475                    .filter_map(|f| {
476                        zip.by_name(f).ok().and_then(|mut file| {
477                            let mut buffer = String::new();
478                            file.read_to_string(&mut buffer).ok()?;
479                            Some((f.to_string(), buffer))
480                        })
481                    })
482                    .collect();
483                name_and_value
484            }};
485        }
486
487        macro_rules! option_read_multiple_files {
488            ($xml:tt, $name:expr) => {{
489                let names: Vec<_> = zip.file_names().map(|x| x.to_string()).collect();
490                let name_and_value: Vec<_> = names
491                    .iter()
492                    .filter(|n| n.contains($name))
493                    .filter_map(|f| {
494                        zip.by_name(f).ok().and_then(|mut file| {
495                            let mut buffer = Vec::new();
496                            file.read_to_end(&mut buffer).ok()?;
497                            Some((f.to_string(), buffer))
498                        })
499                    })
500                    .collect();
501                name_and_value
502            }};
503        }
504
505        let app = option_read!(App, "docProps/app.xml");
506        let content_types = read!(ContentTypes, "[Content_Types].xml");
507        let core = option_read!(Core, "docProps/core.xml");
508        let document_rels = option_read!(Relationships, "word/_rels/document.xml.rels");
509        let settings_rels = option_read!(Relationships, "word/_rels/settings.xml.rels");
510        let document = read!(Document, "word/document.xml");
511        let font_table = option_read!(FontTable, "word/fontTable.xml");
512        let rels = read!(Relationships, "_rels/.rels");
513        let styles = option_read!(Styles, "word/styles.xml");
514        let settings = option_read!(Settings, "word/settings.xml");
515        let web_settings = option_read!(WebSettings, "word/webSettings.xml");
516        let footnotes = option_read!(Footnotes, "word/footnotes.xml");
517        let endnotes = option_read!(Endnotes, "word/endnotes.xml");
518        let comments = option_read!(Comments, "word/comments.xml");
519        let numbering = option_read!(Numbering, "word/numbering.xml");
520
521        let headers = option_read_multiple!(Headers, "word/header");
522        let footers = option_read_multiple!(Footers, "word/footer");
523        let themes = option_read_multiple!(Themes, "word/theme/theme");
524        let medias = option_read_multiple_files!(Medias, "word/media");
525        let custom_xml = option_read_multiple_files!(_, "custom");
526
527        Ok(DocxFile {
528            app,
529            content_types,
530            core,
531            document_rels,
532            settings_rels,
533            document,
534            font_table,
535            rels,
536            styles,
537            settings,
538            web_settings,
539            headers,
540            footers,
541            themes,
542            medias,
543            footnotes,
544            endnotes,
545            comments,
546            numbering,
547            custom_xml,
548        })
549    }
550
551    /// Extracts from file
552    #[inline]
553    pub fn from_file<P: AsRef<Path>>(path: P) -> DocxResult<Self> {
554        Self::from_reader(File::open(path)?)
555    }
556
557    /// Parses content into `Docx` struct
558    pub fn parse(&self) -> DocxResult<Docx<'_>> {
559        let app = if let Some(content) = &self.app {
560            Some(App::from_str(content)?)
561        } else {
562            None
563        };
564
565        let document = Document::from_str(&self.document)?;
566
567        let mut headers = HashMap::new();
568        for f in self.headers.iter() {
569            let hd = Header::from_str(&f.1)?;
570            let name = f.0.replace("word/", "");
571            headers.insert(name, hd);
572        }
573
574        let mut footers = HashMap::new();
575        for f in self.footers.iter() {
576            let ft = Footer::from_str(&f.1)?;
577            let name = f.0.replace("word/", "");
578            footers.insert(name, ft);
579        }
580
581        let mut media = HashMap::new();
582        for m in self.medias.iter() {
583            let mt = crate::media::get_media_type(&m.0);
584            if let Some(mt) = mt {
585                let name = m.0.replace("word/", "");
586                let m = (mt, &m.1);
587                media.insert(name, m);
588            }
589        }
590
591        let mut themes = HashMap::new();
592        // turn off for now
593        for t in self.themes.iter() {
594            let th = Theme::from_str(&t.1)?;
595            let name = t.0.replace("word/", "");
596            themes.insert(name, th);
597        }
598
599        let content_types = ContentTypes::from_str(&self.content_types)?;
600
601        let core = if let Some(content) = &self.core {
602            Some(Core::from_str(content)?)
603        } else {
604            None
605        };
606
607        let document_rels: Option<Relationships> = if let Some(content) = &self.document_rels {
608            Some(Relationships::from_str(content)?)
609        } else {
610            None
611        };
612        let document_rels = document_rels.map(|rel: Relationships| {
613            let rrr: Vec<_> = rel
614                .relationships
615                .iter()
616                .filter(|r2| {
617                    matches!(
618                        r2.ty.to_string().as_str(),
619                        crate::schema::SCHEMA_HEADER
620                            | crate::schema::SCHEMA_FOOTER
621                            | crate::schema::SCHEMA_THEME
622                            | crate::schema::SCHEMA_FONT_TABLE
623                            | crate::schema::SCHEMA_STYLES
624                            | crate::schema::SCHEMA_FOOTNOTES
625                            | crate::schema::SCHEMA_ENDNOTES
626                            | crate::schema::SCHEMA_SETTINGS
627                            | crate::schema::SCHEMA_WEB_SETTINGS
628                            | crate::schema::SCHEMA_COMMENTS
629                            | crate::schema::SCHEMA_IMAGE
630                            | crate::schema::SCHEMA_HYPERLINK
631                            | crate::schema::SCHEMA_NUMBERING
632                    )
633                })
634                .map(|d| d.to_owned())
635                .collect();
636            Relationships { relationships: rrr }
637        });
638
639        let settings_rels = self
640            .settings_rels
641            .as_deref()
642            .map(Relationships::from_str)
643            .transpose()?;
644
645        let font_table = if let Some(content) = &self.font_table {
646            Some(FontTable::from_str(content)?)
647        } else {
648            None
649        };
650
651        let footnotes = if let Some(content) = &self.footnotes {
652            Some(FootNotes::from_str(content)?)
653        } else {
654            None
655        };
656
657        let endnotes = if let Some(content) = &self.endnotes {
658            Some(EndNotes::from_str(content)?)
659        } else {
660            None
661        };
662
663        let settings = if let Some(content) = &self.settings {
664            Some(Settings::from_str(content)?)
665        } else {
666            None
667        };
668
669        let web_settings = if let Some(content) = &self.web_settings {
670            Some(WebSettings::from_str(
671                &content.replace("ns0:", "w:").to_string(),
672            )?)
673        } else {
674            None
675        };
676
677        let comments = if let Some(content) = &self.comments {
678            Some(Comments::from_str(content)?)
679        } else {
680            None
681        };
682
683        let numbering = if let Some(content) = &self.numbering {
684            Some(Numbering::from_str(content)?)
685        } else {
686            None
687        };
688
689        let rels = Relationships::from_str(&self.rels)?;
690        let rels = {
691            let rrr: Vec<_> = rels
692                .relationships
693                .iter()
694                .filter(|r2| {
695                    matches!(
696                        r2.ty.to_string().as_str(),
697                        crate::schema::SCHEMA_CORE
698                            | crate::schema::SCHEMA_REL_EXTENDED
699                            | crate::schema::SCHEMA_OFFICE_DOCUMENT
700                    )
701                })
702                .map(|d| d.to_owned())
703                .collect();
704            Relationships { relationships: rrr }
705        };
706
707        let styles = self
708            .styles
709            .as_ref()
710            .map(|content| Styles::from_str(content))
711            .transpose()?
712            .unwrap_or_default();
713
714        let custom_xml = self
715            .custom_xml
716            .iter()
717            .map(|(name, content)| (name.to_string(), Cow::Borrowed(content.as_slice())))
718            .collect();
719
720        Ok(Docx {
721            app,
722            content_types,
723            core,
724            document,
725            document_rels,
726            settings_rels,
727            font_table,
728            rels,
729            styles,
730            headers,
731            footers,
732            themes,
733            media,
734            footnotes,
735            endnotes,
736            settings,
737            web_settings,
738            comments,
739            numbering,
740            custom_xml,
741        })
742    }
743}
744
745#[cfg(feature = "async")]
746impl DocxFile {
747    #[inline]
748    pub async fn from_async_reader<T: AsyncBufRead + Unpin>(reader: T) -> DocxResult<Self> {
749        use async_zip::base::read::stream::ZipFileReader;
750        let mut reader = ZipFileReader::new(reader);
751
752        let mut docx = DocxFile {
753            app: None,
754            content_types: String::new(),
755            core: None,
756            document: String::new(),
757            document_rels: None,
758            settings_rels: None,
759            font_table: None,
760            rels: String::new(),
761            styles: None,
762            settings: None,
763            web_settings: None,
764            headers: vec![],
765            footers: vec![],
766            themes: vec![],
767            medias: vec![],
768            footnotes: None,
769            endnotes: None,
770            comments: None,
771            numbering: None,
772            custom_xml: vec![],
773        };
774
775        while let Some(mut next) = reader.next_with_entry().await? {
776            let entry_reader = next.reader_mut();
777            let filename = entry_reader.entry().filename().as_str()?.to_string();
778
779            macro_rules! read_to_string {
780                ($field:expr) => {{
781                    let mut buffer = String::new();
782                    entry_reader.read_to_string_checked(&mut buffer).await?;
783                    $field = buffer.into();
784                }};
785            }
786
787            macro_rules! read_multiple_to_string {
788                ($field:expr) => {{
789                    let mut buffer = String::new();
790                    entry_reader.read_to_string_checked(&mut buffer).await?;
791                    $field.push((filename, buffer));
792                }};
793            }
794
795            macro_rules! read_multiple_to_bytes {
796                ($field:expr) => {{
797                    let mut buffer = Vec::new();
798                    entry_reader.read_to_end_checked(&mut buffer).await?;
799                    $field.push((filename, buffer));
800                }};
801            }
802
803            match filename.as_str() {
804                "docProps/app.xml" => read_to_string!(docx.app),
805                "[Content_Types].xml" => read_to_string!(docx.content_types),
806                "docProps/core.xml" => read_to_string!(docx.core),
807                "word/_rels/document.xml.rels" => read_to_string!(docx.document_rels),
808                "word/_rels/settings.xml.rels" => read_to_string!(docx.settings_rels),
809                "word/document.xml" => read_to_string!(docx.document),
810                "word/fontTable.xml" => read_to_string!(docx.font_table),
811                "_rels/.rels" => read_to_string!(docx.rels),
812                "word/styles.xml" => read_to_string!(docx.styles),
813                "word/settings.xml" => read_to_string!(docx.settings),
814                "word/webSettings.xml" => read_to_string!(docx.web_settings),
815                "word/footnotes.xml" => read_to_string!(docx.footnotes),
816                "word/endnotes.xml" => read_to_string!(docx.endnotes),
817                "word/comments.xml" => read_to_string!(docx.comments),
818                "word/numbering.xml" => read_to_string!(docx.numbering),
819                _ if filename.contains("word/header") => read_multiple_to_string!(docx.headers),
820                _ if filename.contains("word/footer") => read_multiple_to_string!(docx.footers),
821                _ if filename.contains("word/theme/theme") => {
822                    read_multiple_to_string!(docx.themes)
823                }
824                _ if filename.contains("word/media") => read_multiple_to_bytes!(docx.medias),
825                _ if filename.contains("custom") => read_multiple_to_bytes!(docx.custom_xml),
826                _ => {}
827            }
828            reader = next.skip().await?;
829        }
830
831        Ok(docx)
832    }
833}