office-rs 0.1.1

A Rust library for reading and writing XML Office files
Documentation
//! 通用工具模块
//!
//! 提供Office文档处理的通用功能,包括:
//! - XML处理工具
//! - ZIP文件操作
//! - 通用数据类型
//! - 常用常量和工具函数

pub mod types;
pub mod xml_utils;
pub mod zip_utils;

// 重新导出常用类型和函数
pub use types::*;

pub use xml_utils::{
    NamespaceManager,
    XmlElement,
    XmlGenerator,
    XmlParser,
    utils as xml_utils_functions,
};

pub use zip_utils::{ ZipEntry, ZipReader, ZipWriter, utils as zip_utils_functions };

/// Office文档的常见MIME类型
pub mod mime_types {
    /// Excel文档MIME类型
    pub const EXCEL_XLSX: &str =
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    pub const EXCEL_XLS: &str = "application/vnd.ms-excel";

    /// Word文档MIME类型
    pub const WORD_DOCX: &str =
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    pub const WORD_DOC: &str = "application/msword";

    /// PowerPoint文档MIME类型
    pub const POWERPOINT_PPTX: &str =
        "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    pub const POWERPOINT_PPT: &str = "application/vnd.ms-powerpoint";
}

/// Office文档的常见文件扩展名
pub mod file_extensions {
    /// Excel文件扩展名
    pub const EXCEL_EXTENSIONS: &[&str] = &["xlsx", "xls", "xlsm", "xlsb"];

    /// Word文件扩展名
    pub const WORD_EXTENSIONS: &[&str] = &["docx", "doc", "docm", "dotx", "dotm"];

    /// PowerPoint文件扩展名
    pub const POWERPOINT_EXTENSIONS: &[&str] = &["pptx", "ppt", "pptm", "potx", "potm"];

    /// 所有支持的Office文件扩展名
    pub const ALL_OFFICE_EXTENSIONS: &[&str] = &[
        "xlsx",
        "xls",
        "xlsm",
        "xlsb",
        "docx",
        "doc",
        "docm",
        "dotx",
        "dotm",
        "pptx",
        "ppt",
        "pptm",
        "potx",
        "potm",
    ];
}

/// Office文档的常见命名空间
pub mod namespaces {
    /// Excel相关命名空间
    pub mod excel {
        pub const MAIN: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
        pub const RELATIONSHIPS: &str =
            "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
        pub const SHARED_STRINGS: &str =
            "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
        pub const STYLES: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
    }

    /// Word相关命名空间
    pub mod word {
        pub const MAIN: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
        pub const RELATIONSHIPS: &str =
            "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
        pub const DOCUMENT: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
    }

    /// PowerPoint相关命名空间
    pub mod powerpoint {
        pub const MAIN: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
        pub const RELATIONSHIPS: &str =
            "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
        pub const SLIDE: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
    }

    /// 通用命名空间
    pub mod common {
        pub const PACKAGE_RELATIONSHIPS: &str =
            "http://schemas.openxmlformats.org/package/2006/relationships";
        pub const CONTENT_TYPES: &str =
            "http://schemas.openxmlformats.org/package/2006/content-types";
        pub const CORE_PROPERTIES: &str =
            "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
        pub const DUBLIN_CORE: &str = "http://purl.org/dc/elements/1.1/";
        pub const DUBLIN_CORE_TERMS: &str = "http://purl.org/dc/terms/";
    }
}

/// 工具函数
pub mod utils {
    use crate::error::{ OfficeError, Result };
    use std::path::Path;

    /// 根据文件扩展名检测Office文档类型
    #[derive(Debug, Clone, PartialEq)]
    pub enum OfficeDocumentType {
        Excel,
        Word,
        PowerPoint,
        Unknown,
    }

    /// 根据文件路径检测文档类型
    pub fn detect_document_type<P: AsRef<Path>>(path: P) -> OfficeDocumentType {
        if
            let Some(extension) = path
                .as_ref()
                .extension()
                .and_then(|ext| ext.to_str())
        {
            let ext_lower = extension.to_lowercase();

            if super::file_extensions::EXCEL_EXTENSIONS.contains(&ext_lower.as_str()) {
                OfficeDocumentType::Excel
            } else if super::file_extensions::WORD_EXTENSIONS.contains(&ext_lower.as_str()) {
                OfficeDocumentType::Word
            } else if super::file_extensions::POWERPOINT_EXTENSIONS.contains(&ext_lower.as_str()) {
                OfficeDocumentType::PowerPoint
            } else {
                OfficeDocumentType::Unknown
            }
        } else {
            OfficeDocumentType::Unknown
        }
    }

    /// 验证文件是否为支持的Office文档
    pub fn is_supported_office_file<P: AsRef<Path>>(path: P) -> bool {
        detect_document_type(path) != OfficeDocumentType::Unknown
    }

    /// 获取文档类型对应的MIME类型
    pub fn get_mime_type(doc_type: &OfficeDocumentType, is_legacy: bool) -> Option<&'static str> {
        match (doc_type, is_legacy) {
            (OfficeDocumentType::Excel, false) => Some(super::mime_types::EXCEL_XLSX),
            (OfficeDocumentType::Excel, true) => Some(super::mime_types::EXCEL_XLS),
            (OfficeDocumentType::Word, false) => Some(super::mime_types::WORD_DOCX),
            (OfficeDocumentType::Word, true) => Some(super::mime_types::WORD_DOC),
            (OfficeDocumentType::PowerPoint, false) => Some(super::mime_types::POWERPOINT_PPTX),
            (OfficeDocumentType::PowerPoint, true) => Some(super::mime_types::POWERPOINT_PPT),
            (OfficeDocumentType::Unknown, _) => None,
        }
    }

    /// 标准化文件路径(将反斜杠转换为正斜杠)
    pub fn normalize_path(path: &str) -> String {
        path.replace('\\', "/")
    }

    /// 验证XML元素名称是否有效
    pub fn validate_xml_name(name: &str) -> Result<()> {
        if super::xml_utils_functions::is_valid_xml_name(name) {
            Ok(())
        } else {
            Err(OfficeError::Format(format!("无效的XML元素名称: {}", name)))
        }
    }

    /// 安全地创建目录路径
    pub fn ensure_directory_exists<P: AsRef<Path>>(path: P) -> Result<()> {
        std::fs::create_dir_all(&path).map_err(|e| OfficeError::Io(e))
    }

    /// 生成唯一的临时文件名
    pub fn generate_temp_filename(prefix: &str, extension: &str) -> String {
        use std::time::{ SystemTime, UNIX_EPOCH };

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis();

        format!("{}_{}_{}.{}", prefix, timestamp, fastrand::u32(..), extension)
    }
}

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

    #[test]
    fn test_document_type_detection() {
        assert_eq!(utils::detect_document_type("test.xlsx"), utils::OfficeDocumentType::Excel);
        assert_eq!(utils::detect_document_type("test.docx"), utils::OfficeDocumentType::Word);
        assert_eq!(utils::detect_document_type("test.pptx"), utils::OfficeDocumentType::PowerPoint);
        assert_eq!(utils::detect_document_type("test.txt"), utils::OfficeDocumentType::Unknown);
    }

    #[test]
    fn test_mime_type_detection() {
        let excel_type = utils::OfficeDocumentType::Excel;
        assert_eq!(utils::get_mime_type(&excel_type, false), Some(mime_types::EXCEL_XLSX));
        assert_eq!(utils::get_mime_type(&excel_type, true), Some(mime_types::EXCEL_XLS));
    }

    #[test]
    fn test_path_normalization() {
        assert_eq!(utils::normalize_path("folder\\file.xml"), "folder/file.xml");
        assert_eq!(utils::normalize_path("folder/file.xml"), "folder/file.xml");
    }
}