setzer_import/docx/
mod.rs1mod inline;
10mod media;
11mod package;
12mod pictures;
13mod story;
14mod structure;
15mod styles;
16mod table;
17mod xml;
18
19use crate::{ImportError, ImportWarning, Imported};
20use media::Media;
21use package::{Package, RelKind};
22use quick_xml::Reader;
23use quick_xml::events::Event;
24use setzer_model::{Block, Book};
25use std::collections::HashMap;
26use story::{Env, NoteKind, Para};
27use structure::Core;
28use styles::{Numbering, Styles};
29
30fn internal(rels: &HashMap<String, package::Rel>, kind: RelKind) -> Option<String> {
32 rels.values()
33 .find(|r| r.kind == kind && !r.external)
34 .map(|r| r.target.clone())
35}
36
37fn main_part(pkg: &Package, root: &HashMap<String, package::Rel>) -> Result<String, ImportError> {
40 internal(root, RelKind::OfficeDocument)
41 .filter(|p| pkg.has(p))
42 .or_else(|| {
43 pkg.has("word/document.xml")
44 .then(|| "word/document.xml".into())
45 })
46 .ok_or_else(|| ImportError::NotDocx("it has no main document part".into()))
47}
48
49pub(crate) struct Located {
54 pub main: String,
56 pub core: String,
58 pub app: String,
60}
61
62pub(crate) fn locate(bytes: &[u8]) -> Result<Located, ImportError> {
64 let mut pkg = Package::open(bytes)?;
65 let root = pkg.rels("");
66 Ok(Located {
67 main: main_part(&pkg, &root)?,
68 core: internal(&root, RelKind::CoreProperties)
69 .unwrap_or_else(|| "docProps/core.xml".into()),
70 app: internal(&root, RelKind::ExtendedProperties)
71 .unwrap_or_else(|| "docProps/app.xml".into()),
72 })
73}
74
75pub fn import(bytes: &[u8]) -> Result<Imported, ImportError> {
78 let mut pkg = Package::open(bytes)?;
79 let root = pkg.rels("");
80 let main = main_part(&pkg, &root)?;
81 let xml = match pkg.read_xml(&main) {
82 Some(Ok(x)) => x,
83 Some(Err(e)) => return Err(ImportError::NotDocx(e)),
84 None => return Err(ImportError::NotDocx(format!("{main} is missing"))),
85 };
86
87 let core_path =
88 internal(&root, RelKind::CoreProperties).unwrap_or_else(|| "docProps/core.xml".into());
89 let core = match pkg.read_xml(&core_path) {
90 Some(Ok(x)) => parse_core(&x),
91 _ => Core::default(),
92 };
93 let rels = pkg.rels(&main);
94 let mut read_part = |kind| {
95 internal(&rels, kind)
96 .and_then(|p| pkg.read_xml(&p))
97 .and_then(Result::ok)
98 };
99 let styles = read_part(RelKind::Styles)
100 .map(|x| Styles::parse(&x))
101 .unwrap_or_default();
102 let numbering = read_part(RelKind::Numbering)
103 .map(|x| Numbering::parse(&x))
104 .unwrap_or_default();
105
106 let mut media = Media::default();
107 let mut warnings = Vec::new();
108 let no_notes = HashMap::new();
109 let mut notes: HashMap<(NoteKind, i64), Vec<Block>> = HashMap::new();
110 for kind in [RelKind::Footnotes, RelKind::Endnotes] {
111 let Some(path) = internal(&rels, kind) else {
112 continue;
113 };
114 let Some(Ok(x)) = pkg.read_xml(&path) else {
115 continue;
116 };
117 let note_rels = pkg.rels(&path);
118 let before = warnings.len();
119 let paras = story::parse(
120 &x,
121 &mut Env {
122 pkg: &mut pkg,
123 styles: &styles,
124 numbering: &numbering,
125 rels: ¬e_rels,
126 notes: &no_notes,
127 media: &mut media,
128 warnings: &mut warnings,
129 },
130 );
131 let label = if kind == RelKind::Footnotes {
133 "In a footnote"
134 } else {
135 "In an endnote"
136 };
137 for w in &mut warnings[before..] {
138 w.location = 0;
139 if let Some((_, rest)) = w.message.split_once(": ")
140 && w.message.starts_with("Paragraph ")
141 {
142 w.message = format!("{label}: {rest}");
143 }
144 }
145 for mut p in paras {
146 let Some(key) = p.note else { continue };
147 inline::normalize(&mut p.segs);
148 notes
149 .entry(key)
150 .or_default()
151 .extend(structure::note_blocks(&p));
152 }
153 }
154
155 let body_start = warnings.len();
156 let paras: Vec<Para> = story::parse(
157 &xml,
158 &mut Env {
159 pkg: &mut pkg,
160 styles: &styles,
161 numbering: &numbering,
162 rels: &rels,
163 notes: ¬es,
164 media: &mut media,
165 warnings: &mut warnings,
166 },
167 );
168 if !paras.iter().any(Para::is_visible) {
169 return Err(ImportError::Empty);
170 }
171 let s = structure::build(paras, styles.lang.as_deref(), &core, &mut warnings);
172 attach_sections(&mut warnings[body_start..], &s.starts);
173
174 let mut book = Book::new(s.metadata);
175 book.sections = s.sections;
176 Ok(Imported {
177 book,
178 assets: media.assets,
179 warnings,
180 })
181}
182
183fn attach_sections(warnings: &mut [ImportWarning], starts: &[usize]) {
186 for w in warnings {
187 if w.section.is_none() && w.location > 0 {
188 w.section = starts.partition_point(|&s| s <= w.location).checked_sub(1);
189 }
190 }
191}
192
193fn parse_core(xml: &str) -> Core {
195 let mut core = Core::default();
196 let mut reader = Reader::from_str(xml);
197 let mut field: Option<&mut Option<String>> = None;
198 let mut buf = String::new();
199 loop {
200 match reader.read_event() {
201 Ok(Event::Start(e)) => {
202 buf.clear();
203 field = match e.local_name().into_inner() {
204 "title" => Some(&mut core.title),
205 "creator" => Some(&mut core.creator),
206 "language" => Some(&mut core.language),
207 _ => None,
208 };
209 }
210 Ok(Event::Text(t)) if field.is_some() => buf.push_str(&t.xml10_content()),
211 Ok(Event::GeneralRef(r)) if field.is_some() => {
212 if let Some(c) = xml::resolve_ref(&r) {
213 buf.push(c);
214 }
215 }
216 Ok(Event::End(_)) => {
217 if let Some(f) = field.take() {
218 let v = buf.trim();
219 if !v.is_empty() && f.is_none() {
220 *f = Some(v.to_string());
221 }
222 }
223 }
224 Ok(Event::Eof) | Err(_) => break,
225 _ => {}
226 }
227 }
228 core
229}
230
231#[cfg(test)]
232pub(crate) mod tests;