1pub mod types;
10pub mod xml_utils;
11pub mod zip_utils;
12
13pub use types::*;
15
16pub use xml_utils::{
17 NamespaceManager,
18 XmlElement,
19 XmlGenerator,
20 XmlParser,
21 utils as xml_utils_functions,
22};
23
24pub use zip_utils::{ ZipEntry, ZipReader, ZipWriter, utils as zip_utils_functions };
25
26pub mod mime_types {
28 pub const EXCEL_XLSX: &str =
30 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
31 pub const EXCEL_XLS: &str = "application/vnd.ms-excel";
32
33 pub const WORD_DOCX: &str =
35 "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
36 pub const WORD_DOC: &str = "application/msword";
37
38 pub const POWERPOINT_PPTX: &str =
40 "application/vnd.openxmlformats-officedocument.presentationml.presentation";
41 pub const POWERPOINT_PPT: &str = "application/vnd.ms-powerpoint";
42}
43
44pub mod file_extensions {
46 pub const EXCEL_EXTENSIONS: &[&str] = &["xlsx", "xls", "xlsm", "xlsb"];
48
49 pub const WORD_EXTENSIONS: &[&str] = &["docx", "doc", "docm", "dotx", "dotm"];
51
52 pub const POWERPOINT_EXTENSIONS: &[&str] = &["pptx", "ppt", "pptm", "potx", "potm"];
54
55 pub const ALL_OFFICE_EXTENSIONS: &[&str] = &[
57 "xlsx",
58 "xls",
59 "xlsm",
60 "xlsb",
61 "docx",
62 "doc",
63 "docm",
64 "dotx",
65 "dotm",
66 "pptx",
67 "ppt",
68 "pptm",
69 "potx",
70 "potm",
71 ];
72}
73
74pub mod namespaces {
76 pub mod excel {
78 pub const MAIN: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
79 pub const RELATIONSHIPS: &str =
80 "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
81 pub const SHARED_STRINGS: &str =
82 "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
83 pub const STYLES: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
84 }
85
86 pub mod word {
88 pub const MAIN: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
89 pub const RELATIONSHIPS: &str =
90 "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
91 pub const DOCUMENT: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
92 }
93
94 pub mod powerpoint {
96 pub const MAIN: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
97 pub const RELATIONSHIPS: &str =
98 "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
99 pub const SLIDE: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
100 }
101
102 pub mod common {
104 pub const PACKAGE_RELATIONSHIPS: &str =
105 "http://schemas.openxmlformats.org/package/2006/relationships";
106 pub const CONTENT_TYPES: &str =
107 "http://schemas.openxmlformats.org/package/2006/content-types";
108 pub const CORE_PROPERTIES: &str =
109 "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
110 pub const DUBLIN_CORE: &str = "http://purl.org/dc/elements/1.1/";
111 pub const DUBLIN_CORE_TERMS: &str = "http://purl.org/dc/terms/";
112 }
113}
114
115pub mod utils {
117 use crate::error::{ OfficeError, Result };
118 use std::path::Path;
119
120 #[derive(Debug, Clone, PartialEq)]
122 pub enum OfficeDocumentType {
123 Excel,
124 Word,
125 PowerPoint,
126 Unknown,
127 }
128
129 pub fn detect_document_type<P: AsRef<Path>>(path: P) -> OfficeDocumentType {
131 if
132 let Some(extension) = path
133 .as_ref()
134 .extension()
135 .and_then(|ext| ext.to_str())
136 {
137 let ext_lower = extension.to_lowercase();
138
139 if super::file_extensions::EXCEL_EXTENSIONS.contains(&ext_lower.as_str()) {
140 OfficeDocumentType::Excel
141 } else if super::file_extensions::WORD_EXTENSIONS.contains(&ext_lower.as_str()) {
142 OfficeDocumentType::Word
143 } else if super::file_extensions::POWERPOINT_EXTENSIONS.contains(&ext_lower.as_str()) {
144 OfficeDocumentType::PowerPoint
145 } else {
146 OfficeDocumentType::Unknown
147 }
148 } else {
149 OfficeDocumentType::Unknown
150 }
151 }
152
153 pub fn is_supported_office_file<P: AsRef<Path>>(path: P) -> bool {
155 detect_document_type(path) != OfficeDocumentType::Unknown
156 }
157
158 pub fn get_mime_type(doc_type: &OfficeDocumentType, is_legacy: bool) -> Option<&'static str> {
160 match (doc_type, is_legacy) {
161 (OfficeDocumentType::Excel, false) => Some(super::mime_types::EXCEL_XLSX),
162 (OfficeDocumentType::Excel, true) => Some(super::mime_types::EXCEL_XLS),
163 (OfficeDocumentType::Word, false) => Some(super::mime_types::WORD_DOCX),
164 (OfficeDocumentType::Word, true) => Some(super::mime_types::WORD_DOC),
165 (OfficeDocumentType::PowerPoint, false) => Some(super::mime_types::POWERPOINT_PPTX),
166 (OfficeDocumentType::PowerPoint, true) => Some(super::mime_types::POWERPOINT_PPT),
167 (OfficeDocumentType::Unknown, _) => None,
168 }
169 }
170
171 pub fn normalize_path(path: &str) -> String {
173 path.replace('\\', "/")
174 }
175
176 pub fn validate_xml_name(name: &str) -> Result<()> {
178 if super::xml_utils_functions::is_valid_xml_name(name) {
179 Ok(())
180 } else {
181 Err(OfficeError::Format(format!("无效的XML元素名称: {}", name)))
182 }
183 }
184
185 pub fn ensure_directory_exists<P: AsRef<Path>>(path: P) -> Result<()> {
187 std::fs::create_dir_all(&path).map_err(|e| OfficeError::Io(e))
188 }
189
190 pub fn generate_temp_filename(prefix: &str, extension: &str) -> String {
192 use std::time::{ SystemTime, UNIX_EPOCH };
193
194 let timestamp = SystemTime::now()
195 .duration_since(UNIX_EPOCH)
196 .unwrap_or_default()
197 .as_millis();
198
199 format!("{}_{}_{}.{}", prefix, timestamp, fastrand::u32(..), extension)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_document_type_detection() {
209 assert_eq!(utils::detect_document_type("test.xlsx"), utils::OfficeDocumentType::Excel);
210 assert_eq!(utils::detect_document_type("test.docx"), utils::OfficeDocumentType::Word);
211 assert_eq!(utils::detect_document_type("test.pptx"), utils::OfficeDocumentType::PowerPoint);
212 assert_eq!(utils::detect_document_type("test.txt"), utils::OfficeDocumentType::Unknown);
213 }
214
215 #[test]
216 fn test_mime_type_detection() {
217 let excel_type = utils::OfficeDocumentType::Excel;
218 assert_eq!(utils::get_mime_type(&excel_type, false), Some(mime_types::EXCEL_XLSX));
219 assert_eq!(utils::get_mime_type(&excel_type, true), Some(mime_types::EXCEL_XLS));
220 }
221
222 #[test]
223 fn test_path_normalization() {
224 assert_eq!(utils::normalize_path("folder\\file.xml"), "folder/file.xml");
225 assert_eq!(utils::normalize_path("folder/file.xml"), "folder/file.xml");
226 }
227}