use std::io::Read;
use std::path::{Path, PathBuf};
const TIMEOUT_SECS: u64 = 60;
fn collect(path: &Path, out: &mut Vec<PathBuf>) {
if path.is_dir() {
let mut entries: Vec<_> = std::fs::read_dir(path)
.into_iter()
.flatten()
.flatten()
.map(|e| e.path())
.collect();
entries.sort();
for e in entries {
collect(&e, out);
}
} else if path.extension().is_some_and(|e| e == "pdf") {
out.push(path.to_path_buf());
}
}
#[derive(Default)]
struct Stats {
docs: usize,
failed: usize,
timed_out: usize,
tables: usize,
cells: usize,
glyph_cells: usize,
max_cols: usize,
}
fn main() {
let mut files = Vec::new();
for arg in std::env::args().skip(1) {
collect(Path::new(&arg), &mut files);
}
let mut total = Stats::default();
for f in &files {
let (tx, rx) = std::sync::mpsc::channel();
let path = f.clone();
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(move || {
let r = std::panic::catch_unwind(|| {
let data = std::fs::read(&path).ok()?;
pdf_docx::convert_pdf_bytes_to_docx(&data).ok()
});
let _ = tx.send(r);
})
.expect("spawn");
let result = match rx.recv_timeout(std::time::Duration::from_secs(TIMEOUT_SECS)) {
Ok(r) => r,
Err(_) => {
eprintln!("TIMEOUT after {TIMEOUT_SECS}s: {}", f.display());
total.timed_out += 1;
continue;
}
};
let Ok(Some(docx)) = result else {
eprintln!("FAILED: {}", f.display());
total.failed += 1;
continue;
};
let mut zip = zip::ZipArchive::new(std::io::Cursor::new(docx)).expect("zip");
let mut xml = String::new();
zip.by_name("word/document.xml")
.expect("document.xml")
.read_to_string(&mut xml)
.expect("utf-8");
let mut tables = 0;
let mut cells = 0;
let mut glyph_cells = 0;
let mut max_cols = 0;
for t in xml.split("<w:tbl>").skip(1) {
let t = t.split("</w:tbl>").next().unwrap_or("");
tables += 1;
max_cols = max_cols.max(t.matches("<w:gridCol").count());
for c in t.split("<w:tc>").skip(1) {
cells += 1;
let text: String = c
.split("<w:t ")
.skip(1)
.filter_map(|s| {
s.split_once('>')
.and_then(|(_, r)| r.split("</w:t>").next())
})
.collect();
if text.trim().chars().count() <= 1 {
glyph_cells += 1;
}
}
}
println!(
"{}\ttables={tables}\tcells={cells}\tglyph_cells={glyph_cells}\tmax_cols={max_cols}",
f.display()
);
total.docs += 1;
total.tables += tables;
total.cells += cells;
total.glyph_cells += glyph_cells;
total.max_cols = total.max_cols.max(max_cols);
}
println!(
"TOTAL docs={} timed_out={} failed={} tables={} cells={} glyph_cells={} max_cols={}",
total.docs,
total.timed_out,
total.failed,
total.tables,
total.cells,
total.glyph_cells,
total.max_cols
);
std::process::exit(0);
}