easydoc_reader/extractor/
mod.rs1pub mod image;
4pub mod numbering;
5pub mod sax;
6pub(crate) mod semantic;
7pub mod table;
8pub mod text;
9
10use std::path::Path;
11
12#[must_use]
14pub fn detect_format(path: &Path) -> Option<DocumentFormat> {
15 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
16 match ext.to_lowercase().as_str() {
17 "docx" => return Some(DocumentFormat::Docx),
18 "doc" => return Some(DocumentFormat::Doc),
19 _ => {}
20 }
21 }
22
23 std::fs::read(path)
24 .ok()
25 .and_then(|bytes| detect_format_from_bytes(&bytes))
26}
27
28#[must_use]
34pub fn detect_format_from_bytes(bytes: &[u8]) -> Option<DocumentFormat> {
35 if bytes.len() >= 4 && &bytes[0..4] == b"PK\x03\x04" {
36 return Some(DocumentFormat::Docx);
37 }
38 if bytes.len() >= 8 {
39 let ole2_magic: [u8; 8] = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
40 if bytes[0..8] == ole2_magic {
41 return Some(DocumentFormat::Doc);
42 }
43 }
44 None
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum DocumentFormat {
51 Docx,
53 Doc,
55}
56
57impl std::fmt::Display for DocumentFormat {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::Docx => write!(f, "DOCX"),
61 Self::Doc => write!(f, "DOC"),
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use std::io::Write;
70
71 #[test]
72 fn detect_format_docx_extension() {
73 let path = Path::new("test.docx");
74 assert_eq!(detect_format(path), Some(DocumentFormat::Docx));
75 }
76
77 #[test]
78 fn detect_format_doc_extension() {
79 let path = Path::new("test.doc");
80 assert_eq!(detect_format(path), Some(DocumentFormat::Doc));
81 }
82
83 #[test]
84 fn detect_format_unknown_extension() {
85 let path = Path::new("test.pdf");
86 assert_eq!(detect_format(path), None);
87 }
88
89 #[test]
90 fn detect_format_docx_magic_bytes() {
91 let dir = std::env::temp_dir().join("easydoc_test_fmt1");
92 std::fs::create_dir_all(&dir).unwrap();
93 let path = dir.join("test.unknown");
94 let mut f = std::fs::File::create(&path).unwrap();
95 f.write_all(b"PK ").unwrap();
96 drop(f);
97 assert_eq!(detect_format(&path), Some(DocumentFormat::Docx));
98 std::fs::remove_file(&path).ok();
99 }
100
101 #[test]
102 fn detect_format_doc_magic_bytes() {
103 let dir = std::env::temp_dir().join("easydoc_test_fmt2");
104 std::fs::create_dir_all(&dir).unwrap();
105 let path = dir.join("test.unknown");
106 let mut f = std::fs::File::create(&path).unwrap();
107 f.write_all(&[0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])
108 .unwrap();
109 drop(f);
110 assert_eq!(detect_format(&path), Some(DocumentFormat::Doc));
111 std::fs::remove_file(&path).ok();
112 }
113
114 #[test]
115 fn detect_format_short_file() {
116 let dir = std::env::temp_dir().join("easydoc_test_fmt3");
117 std::fs::create_dir_all(&dir).unwrap();
118 let path = dir.join("test.short");
119 std::fs::write(&path, b"AB").unwrap();
120 assert_eq!(detect_format(&path), None);
121 std::fs::remove_file(&path).ok();
122 }
123
124 #[test]
125 fn document_format_display() {
126 assert_eq!(format!("{}", DocumentFormat::Docx), "DOCX");
127 assert_eq!(format!("{}", DocumentFormat::Doc), "DOC");
128 }
129
130 #[test]
131 fn document_format_debug() {
132 assert_eq!(format!("{:?}", DocumentFormat::Docx), "Docx");
133 assert_eq!(format!("{:?}", DocumentFormat::Doc), "Doc");
134 }
135
136 #[test]
137 fn document_format_clone_eq() {
138 let f = DocumentFormat::Docx;
139 let f2 = f;
140 assert_eq!(f, f2);
141 }
142}