1use std::sync::Arc;
14
15#[cfg(any(feature = "docx", feature = "xlsx"))]
17use docparser_core::error::{DocError, Result};
18use docparser_core::plugin::{FormatPlugin, RawDoc};
19use docparser_core::registry::Registry;
20
21#[cfg(feature = "docx")]
22pub mod docx;
23#[cfg(feature = "pptx")]
24pub mod pptx;
25#[cfg(feature = "xlsx")]
26pub mod xlsx;
27
28pub const FORMATS: &[&str] = &[
30 #[cfg(feature = "docx")]
31 "docx",
32 #[cfg(feature = "xlsx")]
33 "xlsx",
34 #[cfg(feature = "pptx")]
35 "pptx",
36];
37
38pub mod ns {
40 pub const W: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
41 pub const A: &str = "http://schemas.openxmlformats.org/drawingml/2006/main";
42 pub const P: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
43 pub const C: &str = "http://schemas.openxmlformats.org/drawingml/2006/chart";
44 pub const R: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
45 pub const SHEET: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
46 pub const REL: &str = "http://schemas.openxmlformats.org/package/2006/relationships";
47 pub const VML: &str = "urn:schemas-microsoft-com:vml";
48 pub const WP: &str = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
49}
50
51pub mod tags {
57 pub const W_P: &str = "p";
59 pub const W_TBL: &str = "tbl";
60 pub const W_TR: &str = "tr";
61 pub const W_TC: &str = "tc";
62 pub const W_T: &str = "t";
63 pub const W_R: &str = "r";
64 pub const W_TAB: &str = "tab";
65 pub const W_BR: &str = "br";
66 pub const W_CR: &str = "cr";
67 pub const W_SDT: &str = "sdt";
68 pub const W_SDTC: &str = "sdtContent";
69 pub const W_PPR: &str = "pPr";
70 pub const W_BODY: &str = "body";
71 pub const W_TXBX: &str = "txbxContent";
72 pub const SHEET_DATA: &str = "sheetData";
74 pub const ROW: &str = "row";
75 pub const C: &str = "c";
76 pub const V: &str = "v";
77 pub const IS: &str = "is";
78 pub const T: &str = "t";
79 pub const SI: &str = "si";
80 pub const SST: &str = "sst";
81 pub const NUM_FMT: &str = "numFmt";
82 pub const CELL_XFS: &str = "cellXfs";
83 pub const XF: &str = "xf";
84 pub const P_SP: &str = "sp";
86 pub const P_GRPSP: &str = "grpSp";
87 pub const P_GFRAME: &str = "graphicFrame";
88 pub const P_CXNSP: &str = "cxnSp";
89 pub const P_SPTREE: &str = "spTree";
90 pub const P_TXBODY: &str = "txBody";
91 pub const P_PH: &str = "ph";
92 pub const P_SLD: &str = "sld";
93 pub const P_SLDID: &str = "sldId";
94 pub const A_T: &str = "t";
95 pub const A_BR: &str = "br";
96 pub const A_TBL: &str = "tbl";
97 pub const A_TR: &str = "tr";
98 pub const A_TC: &str = "tc";
99 pub const A_BLIP: &str = "blip";
100 pub const A_OFF: &str = "off";
101 pub const A_POS: &str = "pos";
102 pub const C_TX: &str = "tx";
104 pub const C_V: &str = "v";
105}
106
107pub struct OfficePlugin;
109
110impl FormatPlugin for OfficePlugin {
111 fn id(&self) -> &str {
112 "office"
113 }
114 fn formats(&self) -> &[&str] {
115 FORMATS
116 }
117 fn default_parse_chain(&self) -> Vec<&'static str> {
118 vec!["office-raw"] }
120 fn default_process_chain(&self) -> Vec<&'static str> {
121 vec![]
122 }
123 fn assemble(&self, raw: &RawDoc) -> docparser_core::model::Document {
124 let pages: Vec<docparser_core::model::Page> = raw
126 .pages
127 .iter()
128 .map(|rp| {
129 let blocks: Vec<docparser_core::model::Block> = rp
130 .items
131 .iter()
132 .filter(|it| !it.text.trim().is_empty())
133 .map(|it| docparser_core::model::Block {
134 kind: docparser_core::model::BlockKind::Paragraph,
135 content: it.text.clone(),
136 ..Default::default()
137 })
138 .collect();
139 let text = rp
140 .items
141 .iter()
142 .map(|it| it.text.as_str())
143 .collect::<Vec<_>>()
144 .join("\n");
145 docparser_core::model::Page {
146 name: rp.name.clone(),
147 blocks,
148 text,
149 ..Default::default()
150 }
151 })
152 .collect();
153 docparser_core::model::Document {
154 pages,
155 ..Default::default()
156 }
157 }
158}
159
160pub fn register_defaults(reg: &mut Registry) {
165 #[cfg(feature = "docx")]
167 reg.register_parse(Arc::new(docx::DocxRawBackend), None)
168 .ok();
169 #[cfg(feature = "xlsx")]
170 reg.register_parse(Arc::new(xlsx::XlsxRawBackend), None)
171 .ok();
172 #[cfg(feature = "pptx")]
173 reg.register_parse(Arc::new(pptx::PptxRawBackend), None)
174 .ok();
175 reg.register_plugin(Arc::new(OfficePlugin));
176}
177
178#[cfg(any(feature = "docx", feature = "xlsx", feature = "pptx"))]
181pub(crate) fn push_general_ref(buf: &mut String, e: &quick_xml::events::BytesRef<'_>) {
182 match e.resolve_char_ref() {
183 Ok(Some(ch)) => buf.push(ch),
184 _ => {
185 let entity = format!("&{};", e.xml10_content());
187 if let Ok(t) = quick_xml::escape::unescape(&entity) {
188 buf.push_str(&t);
189 }
190 }
191 }
192}
193
194#[cfg(any(feature = "docx", feature = "xlsx"))]
196pub(crate) fn zip_read_str(
197 zip: &mut zip::ZipArchive<std::fs::File>,
198 name: &str,
199) -> Result<Option<String>> {
200 match zip.by_name(name) {
201 Ok(mut f) => {
202 let mut buf = Vec::new();
203 std::io::Read::read_to_end(&mut f, &mut buf)
204 .map_err(|e| DocError::corrupt(format!("读取 {name} 失败: {e}")))?;
205 Ok(Some(String::from_utf8(buf).map_err(|e| {
206 DocError::corrupt(format!("{name} 不是 UTF-8: {e}"))
207 })?))
208 }
209 Err(zip::result::ZipError::FileNotFound) => Ok(None),
210 Err(e) => Err(DocError::corrupt(format!("ZIP 读取 {name} 失败: {e}"))),
211 }
212}
213
214#[cfg(feature = "pptx")]
216pub(crate) fn parse_rels(xml: &str) -> std::collections::HashMap<String, String> {
217 use quick_xml::events::Event;
218 use quick_xml::Reader;
219
220 let mut map = std::collections::HashMap::new();
221 let mut reader = Reader::from_str(xml);
222 let mut buf = Vec::new();
223 loop {
224 match reader.read_event_into(&mut buf) {
225 Ok(Event::Empty(ref e)) | Ok(Event::Start(ref e)) => {
226 if e.name().local_name().as_ref() == "Relationship" {
228 let id = e
229 .attributes()
230 .filter_map(|a| a.ok())
231 .find(|a| a.key.as_ref() == "Id")
232 .map(|a| a.value.to_string());
233 let target = e
234 .attributes()
235 .filter_map(|a| a.ok())
236 .find(|a| a.key.as_ref() == "Target")
237 .map(|a| a.value.to_string());
238 if let (Some(id), Some(target)) = (id, target) {
239 map.insert(id, target);
240 }
241 }
242 }
243 Ok(Event::Eof) => break,
244 Err(e) => {
245 log::warn!("parse_rels 失败: {e}");
247 break;
248 }
249 _ => {}
250 }
251 buf.clear();
252 }
253 map
254}
255
256#[cfg(test)]
257mod tests {
258 use super::FORMATS;
259
260 #[test]
262 fn formats_follow_features() {
263 let mut want: Vec<&str> = Vec::new();
264 if cfg!(feature = "docx") {
265 want.push("docx");
266 }
267 if cfg!(feature = "xlsx") {
268 want.push("xlsx");
269 }
270 if cfg!(feature = "pptx") {
271 want.push("pptx");
272 }
273 assert_eq!(FORMATS, want.as_slice());
274 }
275}