use std::sync::Arc;
#[cfg(any(feature = "docx", feature = "xlsx"))]
use docparser_core::error::{DocError, Result};
use docparser_core::plugin::{FormatPlugin, RawDoc};
use docparser_core::registry::Registry;
#[cfg(feature = "docx")]
pub mod docx;
#[cfg(feature = "pptx")]
pub mod pptx;
#[cfg(feature = "xlsx")]
pub mod xlsx;
pub const FORMATS: &[&str] = &[
#[cfg(feature = "docx")]
"docx",
#[cfg(feature = "xlsx")]
"xlsx",
#[cfg(feature = "pptx")]
"pptx",
];
pub mod ns {
pub const W: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
pub const A: &str = "http://schemas.openxmlformats.org/drawingml/2006/main";
pub const P: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
pub const C: &str = "http://schemas.openxmlformats.org/drawingml/2006/chart";
pub const R: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
pub const SHEET: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
pub const REL: &str = "http://schemas.openxmlformats.org/package/2006/relationships";
pub const VML: &str = "urn:schemas-microsoft-com:vml";
pub const WP: &str = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
}
pub mod tags {
pub const W_P: &str = "p";
pub const W_TBL: &str = "tbl";
pub const W_TR: &str = "tr";
pub const W_TC: &str = "tc";
pub const W_T: &str = "t";
pub const W_R: &str = "r";
pub const W_TAB: &str = "tab";
pub const W_BR: &str = "br";
pub const W_CR: &str = "cr";
pub const W_SDT: &str = "sdt";
pub const W_SDTC: &str = "sdtContent";
pub const W_PPR: &str = "pPr";
pub const W_BODY: &str = "body";
pub const W_TXBX: &str = "txbxContent";
pub const SHEET_DATA: &str = "sheetData";
pub const ROW: &str = "row";
pub const C: &str = "c";
pub const V: &str = "v";
pub const IS: &str = "is";
pub const T: &str = "t";
pub const SI: &str = "si";
pub const SST: &str = "sst";
pub const NUM_FMT: &str = "numFmt";
pub const CELL_XFS: &str = "cellXfs";
pub const XF: &str = "xf";
pub const P_SP: &str = "sp";
pub const P_GRPSP: &str = "grpSp";
pub const P_GFRAME: &str = "graphicFrame";
pub const P_CXNSP: &str = "cxnSp";
pub const P_SPTREE: &str = "spTree";
pub const P_TXBODY: &str = "txBody";
pub const P_PH: &str = "ph";
pub const P_SLD: &str = "sld";
pub const P_SLDID: &str = "sldId";
pub const A_T: &str = "t";
pub const A_BR: &str = "br";
pub const A_TBL: &str = "tbl";
pub const A_TR: &str = "tr";
pub const A_TC: &str = "tc";
pub const A_BLIP: &str = "blip";
pub const A_OFF: &str = "off";
pub const A_POS: &str = "pos";
pub const C_TX: &str = "tx";
pub const C_V: &str = "v";
}
pub struct OfficePlugin;
impl FormatPlugin for OfficePlugin {
fn id(&self) -> &str {
"office"
}
fn formats(&self) -> &[&str] {
FORMATS
}
fn default_parse_chain(&self) -> Vec<&'static str> {
vec!["office-raw"] }
fn default_process_chain(&self) -> Vec<&'static str> {
vec![]
}
fn assemble(&self, raw: &RawDoc) -> docparser_core::model::Document {
let pages: Vec<docparser_core::model::Page> = raw
.pages
.iter()
.map(|rp| {
let blocks: Vec<docparser_core::model::Block> = rp
.items
.iter()
.filter(|it| !it.text.trim().is_empty())
.map(|it| docparser_core::model::Block {
kind: docparser_core::model::BlockKind::Paragraph,
content: it.text.clone(),
..Default::default()
})
.collect();
let text = rp
.items
.iter()
.map(|it| it.text.as_str())
.collect::<Vec<_>>()
.join("\n");
docparser_core::model::Page {
name: rp.name.clone(),
blocks,
text,
..Default::default()
}
})
.collect();
docparser_core::model::Document {
pages,
..Default::default()
}
}
}
pub fn register_defaults(reg: &mut Registry) {
#[cfg(feature = "docx")]
reg.register_parse(Arc::new(docx::DocxRawBackend), None)
.ok();
#[cfg(feature = "xlsx")]
reg.register_parse(Arc::new(xlsx::XlsxRawBackend), None)
.ok();
#[cfg(feature = "pptx")]
reg.register_parse(Arc::new(pptx::PptxRawBackend), None)
.ok();
reg.register_plugin(Arc::new(OfficePlugin));
}
#[cfg(any(feature = "docx", feature = "xlsx", feature = "pptx"))]
pub(crate) fn push_general_ref(buf: &mut String, e: &quick_xml::events::BytesRef<'_>) {
match e.resolve_char_ref() {
Ok(Some(ch)) => buf.push(ch),
_ => {
let entity = format!("&{};", e.xml10_content());
if let Ok(t) = quick_xml::escape::unescape(&entity) {
buf.push_str(&t);
}
}
}
}
#[cfg(any(feature = "docx", feature = "xlsx"))]
pub(crate) fn zip_read_str(
zip: &mut zip::ZipArchive<std::fs::File>,
name: &str,
) -> Result<Option<String>> {
match zip.by_name(name) {
Ok(mut f) => {
let mut buf = Vec::new();
std::io::Read::read_to_end(&mut f, &mut buf)
.map_err(|e| DocError::corrupt(format!("读取 {name} 失败: {e}")))?;
Ok(Some(String::from_utf8(buf).map_err(|e| {
DocError::corrupt(format!("{name} 不是 UTF-8: {e}"))
})?))
}
Err(zip::result::ZipError::FileNotFound) => Ok(None),
Err(e) => Err(DocError::corrupt(format!("ZIP 读取 {name} 失败: {e}"))),
}
}
#[cfg(feature = "pptx")]
pub(crate) fn parse_rels(xml: &str) -> std::collections::HashMap<String, String> {
use quick_xml::events::Event;
use quick_xml::Reader;
let mut map = std::collections::HashMap::new();
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(ref e)) | Ok(Event::Start(ref e)) => {
if e.name().local_name().as_ref() == "Relationship" {
let id = e
.attributes()
.filter_map(|a| a.ok())
.find(|a| a.key.as_ref() == "Id")
.map(|a| a.value.to_string());
let target = e
.attributes()
.filter_map(|a| a.ok())
.find(|a| a.key.as_ref() == "Target")
.map(|a| a.value.to_string());
if let (Some(id), Some(target)) = (id, target) {
map.insert(id, target);
}
}
}
Ok(Event::Eof) => break,
Err(e) => {
log::warn!("parse_rels 失败: {e}");
break;
}
_ => {}
}
buf.clear();
}
map
}
#[cfg(test)]
mod tests {
use super::FORMATS;
#[test]
fn formats_follow_features() {
let mut want: Vec<&str> = Vec::new();
if cfg!(feature = "docx") {
want.push("docx");
}
if cfg!(feature = "xlsx") {
want.push("xlsx");
}
if cfg!(feature = "pptx") {
want.push("pptx");
}
assert_eq!(FORMATS, want.as_slice());
}
}