easydoc-reader 0.1.0-alpha.2

DOCX/DOC document reader for easydoc-rust
Documentation
//! Content extractors for DOCX/DOC files.

pub mod image;
pub mod numbering;
pub mod sax;
pub(crate) mod semantic;
pub mod table;
pub mod text;

use std::path::Path;

/// Detects the document format from file extension and magic bytes.
#[must_use]
pub fn detect_format(path: &Path) -> Option<DocumentFormat> {
    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
        match ext.to_lowercase().as_str() {
            "docx" => return Some(DocumentFormat::Docx),
            "doc" => return Some(DocumentFormat::Doc),
            _ => {}
        }
    }

    if let Ok(bytes) = std::fs::read(path)
        && bytes.len() >= 8
    {
        if &bytes[0..4] == b"PK" {
            return Some(DocumentFormat::Docx);
        }
        let ole2_magic: [u8; 8] = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
        if bytes[0..8] == ole2_magic {
            return Some(DocumentFormat::Doc);
        }
    }

    None
}

/// Supported document formats.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocumentFormat {
    /// Office Open XML (.docx).
    Docx,
    /// Legacy Word Binary (.doc).
    Doc,
}

impl std::fmt::Display for DocumentFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Docx => write!(f, "DOCX"),
            Self::Doc => write!(f, "DOC"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    #[test]
    fn detect_format_docx_extension() {
        let path = Path::new("test.docx");
        assert_eq!(detect_format(path), Some(DocumentFormat::Docx));
    }

    #[test]
    fn detect_format_doc_extension() {
        let path = Path::new("test.doc");
        assert_eq!(detect_format(path), Some(DocumentFormat::Doc));
    }

    #[test]
    fn detect_format_unknown_extension() {
        let path = Path::new("test.pdf");
        assert_eq!(detect_format(path), None);
    }

    #[test]
    fn detect_format_docx_magic_bytes() {
        let dir = std::env::temp_dir().join("easydoc_test_fmt1");
        std::fs::create_dir_all(&dir).unwrap();
        let path = dir.join("test.unknown");
        let mut f = std::fs::File::create(&path).unwrap();
        f.write_all(b"PK").unwrap();
        drop(f);
        assert_eq!(detect_format(&path), Some(DocumentFormat::Docx));
        std::fs::remove_file(&path).ok();
    }

    #[test]
    fn detect_format_doc_magic_bytes() {
        let dir = std::env::temp_dir().join("easydoc_test_fmt2");
        std::fs::create_dir_all(&dir).unwrap();
        let path = dir.join("test.unknown");
        let mut f = std::fs::File::create(&path).unwrap();
        f.write_all(&[0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])
            .unwrap();
        drop(f);
        assert_eq!(detect_format(&path), Some(DocumentFormat::Doc));
        std::fs::remove_file(&path).ok();
    }

    #[test]
    fn detect_format_short_file() {
        let dir = std::env::temp_dir().join("easydoc_test_fmt3");
        std::fs::create_dir_all(&dir).unwrap();
        let path = dir.join("test.short");
        std::fs::write(&path, b"AB").unwrap();
        assert_eq!(detect_format(&path), None);
        std::fs::remove_file(&path).ok();
    }

    #[test]
    fn document_format_display() {
        assert_eq!(format!("{}", DocumentFormat::Docx), "DOCX");
        assert_eq!(format!("{}", DocumentFormat::Doc), "DOC");
    }

    #[test]
    fn document_format_debug() {
        assert_eq!(format!("{:?}", DocumentFormat::Docx), "Docx");
        assert_eq!(format!("{:?}", DocumentFormat::Doc), "Doc");
    }

    #[test]
    fn document_format_clone_eq() {
        let f = DocumentFormat::Docx;
        let f2 = f;
        assert_eq!(f, f2);
    }
}