1use super::header_or_footer_rels::{read_header_or_footer_rels, ReadHeaderOrFooterRels};
2use super::namespace::*;
3use super::*;
4
5use zip::ZipArchive;
6
7fn read_headers(
8 rels: &ReadDocumentRels,
9 archive: &mut ZipArchive<Cursor<&[u8]>>,
10) -> HashMap<RId, (Header, ReadHeaderOrFooterRels)> {
11 let header_paths = rels.target_paths(HEADER_TYPE);
12 let headers: HashMap<RId, (Header, ReadHeaderOrFooterRels)> = header_paths
13 .into_iter()
14 .flatten()
15 .filter_map(|(rid, path, ..)| {
16 let data = read_zip(archive, path.to_str().expect("should have header path."));
17 if let Ok(d) = data {
18 if let Ok(h) = Header::from_xml(&d[..]) {
19 let rels = read_header_or_footer_rels(archive, path).unwrap_or_default();
20 return Some((rid.clone(), (h, rels)));
21 }
22 }
23 None
24 })
25 .collect();
26 headers
27}
28
29fn read_footers(
30 rels: &ReadDocumentRels,
31 archive: &mut ZipArchive<Cursor<&[u8]>>,
32) -> HashMap<RId, (Footer, ReadHeaderOrFooterRels)> {
33 let footer_paths = rels.target_paths(FOOTER_TYPE);
34 let footers: HashMap<RId, (Footer, ReadHeaderOrFooterRels)> = footer_paths
35 .into_iter()
36 .flatten()
37 .filter_map(|(rid, path, ..)| {
38 let data = read_zip(archive, path.to_str().expect("should have footer path."));
39 if let Ok(d) = data {
40 if let Ok(h) = Footer::from_xml(&d[..]) {
41 let rels = read_header_or_footer_rels(archive, path).unwrap_or_default();
42 return Some((rid.clone(), (h, rels)));
43 }
44 }
45 None
46 })
47 .collect();
48 footers
49}
50
51fn read_themes(rels: &ReadDocumentRels, archive: &mut ZipArchive<Cursor<&[u8]>>) -> Vec<Theme> {
52 let theme_paths = rels.target_paths(THEME_TYPE);
53 theme_paths
54 .into_iter()
55 .flatten()
56 .filter_map(|(_rid, path, ..)| {
57 let data = read_zip(archive, path.to_str().expect("should have footer path."));
58 if let Ok(d) = data {
59 if let Ok(h) = Theme::from_xml(&d[..]) {
60 return Some(h);
61 }
62 }
63 None
64 })
65 .collect()
66}
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70#[non_exhaustive]
71pub struct ReadDocxOptions {
72 pub generate_image_previews: bool,
77}
78
79impl ReadDocxOptions {
80 pub const fn with_image_previews(mut self, generate: bool) -> Self {
82 self.generate_image_previews = generate;
83 self
84 }
85}
86
87impl Default for ReadDocxOptions {
88 fn default() -> Self {
89 Self {
90 generate_image_previews: true,
91 }
92 }
93}
94
95pub fn read_docx(buf: &[u8]) -> Result<Docx, ReaderError> {
96 read_docx_with_options(buf, ReadDocxOptions::default())
97}
98
99pub fn read_docx_with_options(buf: &[u8], options: ReadDocxOptions) -> Result<Docx, ReaderError> {
100 let mut docx = Docx::new();
101 let cur = Cursor::new(buf);
102 let mut archive = zip::ZipArchive::new(cur)?;
103 let _content_types = {
106 let data = read_zip(&mut archive, "[Content_Types].xml")?;
107 ContentTypes::from_xml(&data[..])?
108 };
109
110 let rels = {
113 let data = read_zip(&mut archive, "_rels/.rels")?;
114 Rels::from_xml(&data[..])?
115 };
116
117 let main_rel = rels
120 .find_target(DOC_RELATIONSHIP_TYPE)
121 .ok_or(ReaderError::DocumentNotFoundError);
122
123 let document_path = if let Ok(rel) = main_rel {
124 rel.2.clone()
125 } else {
126 "word/document.xml".to_owned()
127 };
128
129 if let Some(custom_props) = rels.find_target(CUSTOM_PROPERTIES_TYPE) {
130 let data = read_zip(&mut archive, &custom_props.2);
131 if let Ok(data) = data {
132 if let Ok(custom) = CustomProps::from_xml(&data[..]) {
133 docx.doc_props.custom = custom;
134 }
135 }
136 }
137
138 let rels = read_document_rels(&mut archive, &document_path)?;
139
140 let headers = read_headers(&rels, &mut archive);
141 let footers = read_footers(&rels, &mut archive);
142
143 docx.themes = read_themes(&rels, &mut archive);
144
145 let comments_extended_path = rels.target_paths(COMMENTS_EXTENDED_TYPE);
147 let comments_extended = if let Some(comments_extended_path) = comments_extended_path {
148 if let Some((_, comments_extended_path, ..)) = comments_extended_path.first() {
149 let data = read_zip(
150 &mut archive,
151 comments_extended_path
152 .to_str()
153 .expect("should have comments extended."),
154 );
155 if let Ok(data) = data {
156 CommentsExtended::from_xml(&data[..])?
157 } else {
158 CommentsExtended::default()
159 }
160 } else {
161 CommentsExtended::default()
162 }
163 } else {
164 CommentsExtended::default()
165 };
166
167 let comments_path = rels.target_paths(COMMENTS_TYPE);
169 let comments = if let Some(paths) = comments_path {
170 if let Some((_, comments_path, ..)) = paths.first() {
171 let data = read_zip(
172 &mut archive,
173 comments_path.to_str().expect("should have comments."),
174 );
175 if let Ok(data) = data {
176 let mut comments = Comments::from_xml(&data[..])?.into_inner();
177 if !comments.is_empty() && !comments_extended.children.is_empty() {
178 let mut comment_id_by_paragraph: HashMap<String, usize> = HashMap::new();
179 for comment in &comments {
180 for child in &comment.children {
181 if let CommentChild::Paragraph(p) = child {
182 comment_id_by_paragraph.insert(p.id.clone(), comment.id);
183 }
184 }
185 }
186
187 let mut parent_paragraph_by_paragraph: HashMap<String, String> = HashMap::new();
188 for extended in &comments_extended.children {
189 if let Some(parent_paragraph_id) = extended.parent_paragraph_id.as_deref() {
190 parent_paragraph_by_paragraph.insert(
191 extended.paragraph_id.clone(),
192 parent_paragraph_id.to_string(),
193 );
194 }
195 }
196
197 for comment in &mut comments {
198 let mut parent_comment_id = None;
199 for child in &comment.children {
200 if let CommentChild::Paragraph(p) = child {
201 if let Some(parent_paragraph_id) =
202 parent_paragraph_by_paragraph.get(&p.id)
203 {
204 parent_comment_id =
205 comment_id_by_paragraph.get(parent_paragraph_id).copied();
206 if parent_comment_id.is_some() {
207 break;
208 }
209 }
210 }
211 }
212 comment.parent_comment_id = parent_comment_id;
213 }
214 }
215 Comments { comments }
216 } else {
217 Comments::default()
218 }
219 } else {
220 Comments::default()
221 }
222 } else {
223 Comments::default()
224 };
225
226 let document = {
227 let data = read_zip(&mut archive, &document_path)?;
228 Document::from_xml(&data[..])?
229 };
230 docx = docx.document(document);
231
232 if let Some(h) = docx.document.section_property.header_reference.clone() {
234 if let Some((header, rels)) = headers.get(&h.id) {
235 docx.document = docx.document.header(header.clone(), &h.id);
236 let count = docx.document_rels.header_count + 1;
237 docx.document_rels.header_count = count;
238 docx.content_type = docx.content_type.add_header();
239 let media = rels.target_paths(IMAGE_TYPE);
241 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
242 }
243 }
244 if let Some(ref h) = docx
245 .document
246 .section_property
247 .first_header_reference
248 .clone()
249 {
250 if let Some((header, rels)) = headers.get(&h.id) {
251 docx.document = docx
252 .document
253 .first_header_without_title_pg(header.clone(), &h.id);
254 let count = docx.document_rels.header_count + 1;
255 docx.document_rels.header_count = count;
256 docx.content_type = docx.content_type.add_header();
257 let media = rels.target_paths(IMAGE_TYPE);
259 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
260 }
261 }
262 if let Some(ref h) = docx.document.section_property.even_header_reference.clone() {
263 if let Some((header, rels)) = headers.get(&h.id) {
264 docx.document = docx.document.even_header(header.clone(), &h.id);
265 let count = docx.document_rels.header_count + 1;
266 docx.document_rels.header_count = count;
267 docx.content_type = docx.content_type.add_header();
268
269 let media = rels.target_paths(IMAGE_TYPE);
271 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
272 }
273 }
274
275 if let Some(f) = docx.document.section_property.footer_reference.clone() {
277 if let Some((footer, rels)) = footers.get(&f.id) {
278 docx.document = docx.document.footer(footer.clone(), &f.id);
279 let count = docx.document_rels.footer_count + 1;
280 docx.document_rels.footer_count = count;
281 docx.content_type = docx.content_type.add_footer();
282
283 let media = rels.target_paths(IMAGE_TYPE);
285 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
286 }
287 }
288
289 if let Some(ref f) = docx
290 .document
291 .section_property
292 .first_footer_reference
293 .clone()
294 {
295 if let Some((footer, rels)) = footers.get(&f.id) {
296 docx.document = docx
297 .document
298 .first_footer_without_title_pg(footer.clone(), &f.id);
299 let count = docx.document_rels.footer_count + 1;
300 docx.document_rels.footer_count = count;
301 docx.content_type = docx.content_type.add_footer();
302
303 let media = rels.target_paths(IMAGE_TYPE);
305 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
306 }
307 }
308 if let Some(ref f) = docx.document.section_property.even_footer_reference.clone() {
309 if let Some((footer, rels)) = footers.get(&f.id) {
310 docx.document = docx.document.even_footer(footer.clone(), &f.id);
311 let count = docx.document_rels.footer_count + 1;
312 docx.document_rels.footer_count = count;
313 docx.content_type = docx.content_type.add_footer();
314
315 let media = rels.target_paths(IMAGE_TYPE);
317 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
318 }
319 }
320
321 if !comments.inner().is_empty() {
323 docx.store_comments(comments.inner());
324 docx = docx.comments(comments);
325 docx = docx.comments_extended(comments_extended);
326 }
327
328 let style_path = rels.target_paths(STYLE_RELATIONSHIP_TYPE);
331 if let Some(paths) = style_path {
332 if let Some((_, style_path, ..)) = paths.first() {
333 let data = read_zip(
334 &mut archive,
335 style_path.to_str().expect("should have styles"),
336 )?;
337 let styles = Styles::from_xml(&data[..])?;
338 docx = docx.styles(styles);
339 }
340 }
341
342 let num_path = rels.target_paths(NUMBERING_RELATIONSHIP_TYPE);
344 if let Some(paths) = num_path {
345 if let Some((_, num_path, ..)) = paths.first() {
346 let data = read_zip(
347 &mut archive,
348 num_path.to_str().expect("should have numberings"),
349 )?;
350 let nums = Numberings::from_xml(&data[..])?;
351 docx = docx.numberings(nums);
352 }
353 }
354
355 let settings_path = rels.target_paths(SETTINGS_TYPE);
357 if let Some(paths) = settings_path {
358 if let Some((_, settings_path, ..)) = paths.first() {
359 let data = read_zip(
360 &mut archive,
361 settings_path.to_str().expect("should have settings"),
362 )?;
363 let settings = Settings::from_xml(&data[..])?;
364 docx = docx.settings(settings);
365 }
366 }
367
368 let web_settings_path = rels.target_paths(WEB_SETTINGS_TYPE);
370 if let Some(paths) = web_settings_path {
371 if let Some((_, web_settings_path, ..)) = paths.first() {
372 let data = read_zip(
373 &mut archive,
374 web_settings_path
375 .to_str()
376 .expect("should have web settings"),
377 )?;
378 let web_settings = WebSettings::from_xml(&data[..])?;
379 docx = docx.web_settings(web_settings);
380 }
381 }
382 let media = rels.target_paths(IMAGE_TYPE);
384 docx = add_images(docx, media, &mut archive, options.generate_image_previews);
385
386 let links = rels.target_paths(HYPERLINK_TYPE);
388 if let Some(paths) = links {
389 for (id, target, mode) in paths {
390 if let Some(mode) = mode {
391 docx = docx.add_hyperlink(
392 id.clone(),
393 target.to_str().expect("should convert to str"),
394 mode.clone(),
395 );
396 }
397 }
398 }
399
400 Ok(docx)
401}
402
403fn add_images(
404 mut docx: Docx,
405 media: Option<&std::collections::BTreeSet<(RId, PathBuf, Option<String>)>>,
406 archive: &mut ZipArchive<Cursor<&[u8]>>,
407 generate_previews: bool,
408) -> Docx {
409 if let Some(paths) = media {
411 for (id, media, ..) in paths {
412 if let Ok(data) = read_zip(archive, media.to_str().expect("should have media")) {
413 docx = docx.add_image_with_options(
414 id.clone(),
415 media.to_str().unwrap().to_string(),
416 data,
417 generate_previews,
418 );
419 }
420 }
421 }
422 docx
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 static JPEG_DOCX: &[u8] = include_bytes!("../../../fixtures/image_output/image.docx");
430
431 #[test]
432 fn read_without_image_previews_preserves_original_image() {
433 let docx = read_docx_with_options(
434 JPEG_DOCX,
435 ReadDocxOptions::default().with_image_previews(false),
436 )
437 .unwrap();
438
439 assert_eq!(docx.images.len(), 1);
440 let (_, _, image, preview) = &docx.images[0];
441 assert!(!image.0.is_empty());
442 assert!(preview.0.is_empty());
443 }
444
445 #[cfg(feature = "image")]
446 #[test]
447 fn read_with_default_options_generates_image_preview() {
448 let docx = read_docx(JPEG_DOCX).unwrap();
449
450 assert_eq!(docx.images.len(), 1);
451 let (_, _, _, preview) = &docx.images[0];
452 assert!(!preview.0.is_empty());
453 }
454}