ravenrs_sheet/xlsx/
mod.rs1#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
12#![forbid(unsafe_code)]
13
14mod array_formula;
15mod cfb;
16mod chart;
17mod chartex;
18mod controls;
19mod crypto;
20mod csv;
21mod drawing;
22mod feature_bags;
23mod fmla;
24mod ods;
25mod omml;
26mod package;
27mod passthrough;
28mod pivot;
29mod python_part;
30mod read;
31mod richdata;
32mod sheet_read;
33mod sheet_write;
34mod styles;
35mod tables;
36#[cfg(any(test, feature = "test-support"))]
37pub mod testing;
38mod theme;
39mod write;
40mod xls;
41mod xlsb;
42mod xml;
43
44#[cfg(test)]
45mod tests;
46
47use crate::base::DateSystem;
48use crate::model::Workbook;
49
50pub use crypto::{DecryptError, check_encryption, decrypt};
51pub use csv::{MAX_CSV_BYTES, decode_text, read_csv, read_csv_with_report, write_csv, write_csv_limited};
52pub use ods::read_ods;
53pub use package::MAX_PART;
54pub use passthrough::rebase_links;
55pub use xls::read_xls;
56pub use xlsb::read_xlsb;
57
58#[derive(Debug, thiserror::Error)]
60pub enum IoError {
61 #[error("the file is not a valid zip package: {0}")]
62 Zip(String),
63 #[error("the file contains invalid XML: {0}")]
64 Xml(String),
65 #[error("the file is not a valid spreadsheet: {0}")]
66 Format(String),
67 #[error("the file is too large: {0}")]
68 TooLarge(String),
69}
70
71#[derive(Clone, Debug, Default, PartialEq)]
73pub struct ReadReport {
74 pub warnings: Vec<String>,
75}
76
77impl ReadReport {
78 pub(crate) fn warn(&mut self, msg: impl Into<String>) {
79 let m = msg.into();
80 if self.warnings.len() < 1000 && !self.warnings.contains(&m) {
81 self.warnings.push(m);
82 }
83 }
84}
85
86pub fn read_xlsx(bytes: &[u8]) -> Result<(Workbook, ReadReport), IoError> {
89 read::read_xlsx(bytes, None)
90}
91
92pub fn read_xlsx_with(bytes: &[u8], known: &dyn Fn(&str) -> bool) -> Result<(Workbook, ReadReport), IoError> {
104 read::read_xlsx(bytes, Some(known))
105}
106
107pub fn oversized_part(bytes: &[u8]) -> Option<(String, u64)> {
110 package::oversized_part(bytes)
111}
112
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
116pub enum XlsxKind {
117 #[default]
119 Workbook,
120 MacroWorkbook,
122 Template,
124 MacroTemplate,
126}
127
128impl XlsxKind {
129 pub fn from_path(path: &str) -> XlsxKind {
131 let ext = std::path::Path::new(path).extension().and_then(std::ffi::OsStr::to_str).unwrap_or_default().to_ascii_lowercase();
132 match ext.as_str() {
133 "xlsm" => XlsxKind::MacroWorkbook,
134 "xltx" => XlsxKind::Template,
135 "xltm" => XlsxKind::MacroTemplate,
136 _ => XlsxKind::Workbook,
137 }
138 }
139
140 pub fn main_content_type(self) -> &'static str {
142 match self {
143 XlsxKind::Workbook => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
144 XlsxKind::MacroWorkbook => "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
145 XlsxKind::Template => "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml",
146 XlsxKind::MacroTemplate => "application/vnd.ms-excel.template.macroEnabled.main+xml",
147 }
148 }
149
150 pub fn is_macro_enabled(self) -> bool {
152 matches!(self, XlsxKind::MacroWorkbook | XlsxKind::MacroTemplate)
153 }
154}
155
156pub fn write_xlsx(wb: &Workbook) -> Result<Vec<u8>, IoError> {
158 write::write_xlsx(wb, if wb.vba_project.is_some() { XlsxKind::MacroWorkbook } else { XlsxKind::Workbook })
159}
160
161pub fn write_xlsx_as(wb: &Workbook, kind: XlsxKind) -> Result<Vec<u8>, IoError> {
163 write::write_xlsx(wb, kind)
164}
165
166pub fn write_xlsx_named(wb: &Workbook, kind: XlsxKind, book_name: &str) -> Result<Vec<u8>, IoError> {
169 write::write_xlsx_named(wb, kind, Some(book_name))
170}
171
172#[derive(Clone, Debug, PartialEq)]
174pub struct CsvOptions {
175 pub delimiter: u8,
177 pub quote: u8,
178 pub date_system: DateSystem,
179 pub parse_values: bool,
181}
182
183impl Default for CsvOptions {
184 fn default() -> Self {
185 CsvOptions { delimiter: b',', quote: b'"', date_system: DateSystem::D1900, parse_values: true }
186 }
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191pub enum Format {
192 Xlsx,
193 Xlsb,
194 Ods,
195 Csv,
196 Encrypted,
198 Xls,
200 LegacyBinary,
202 Unknown,
203}
204
205pub fn sniff(bytes: &[u8]) -> Format {
207 if bytes.starts_with(b"PK\x03\x04") || bytes.starts_with(b"PK\x05\x06") || bytes.starts_with(b"PK\x07\x08") {
208 return if ods::has_mimetype(bytes) {
209 Format::Ods
210 } else if xlsb::is_xlsb(bytes) {
211 Format::Xlsb
212 } else {
213 Format::Xlsx
214 };
215 }
216 if bytes.starts_with(&cfb::SIGNATURE) {
220 return if cfb::is_encrypted(bytes) {
221 Format::Encrypted
222 } else if xls::is_xls(bytes) {
223 Format::Xls
224 } else {
225 Format::LegacyBinary
226 };
227 }
228 if bytes.starts_with(&[0xFF, 0xFE]) || bytes.starts_with(&[0xFE, 0xFF]) || bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
229 return Format::Csv;
230 }
231 if bytes.is_empty() {
232 return Format::Unknown;
233 }
234 let head = bytes.get(..bytes.len().min(4096)).unwrap_or(bytes);
235 let control = head.iter().filter(|&&b| b < 0x09 || (0x0E..0x20).contains(&b) || b == 0x7F).count();
237 if head.contains(&0) || control * 20 > head.len() {
238 return Format::Unknown;
239 }
240 Format::Csv
241}