1use std::cell::RefCell;
7use std::collections::HashMap;
8use std::fmt;
9use std::fs;
10use std::io::{self, Cursor, Read, Seek, Write};
11use std::path::{Path, PathBuf};
12use std::sync::{Arc, Mutex};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use dashmap::DashMap;
16use quick_xml::de::from_reader as xml_from_reader;
17use quick_xml::se::to_string as xml_to_string;
18use zip::write::SimpleFileOptions;
19use zip::{CompressionMethod, ZipArchive};
20
21use crate::calc::arg::FormulaArg;
22use crate::constants::{
23 CONTENT_TYPE_RELATIONSHIPS, CONTENT_TYPE_VBA, DEFAULT_XML_PATH_CONTENT_TYPES,
24 DEFAULT_XML_PATH_DOC_PROPS_APP, DEFAULT_XML_PATH_DOC_PROPS_CORE, DEFAULT_XML_PATH_METADATA,
25 DEFAULT_XML_PATH_RD_RICH_VALUE, DEFAULT_XML_PATH_RD_RICH_VALUE_REL,
26 DEFAULT_XML_PATH_RD_RICH_VALUE_REL_RELS, DEFAULT_XML_PATH_RD_RICH_VALUE_STRUCTURE,
27 DEFAULT_XML_PATH_RD_RICH_VALUE_STRUCTURE_LEGACY,
28 DEFAULT_XML_PATH_RD_RICH_VALUE_WEB_IMAGE, DEFAULT_XML_PATH_RD_RICH_VALUE_WEB_IMAGE_RELS,
29 DEFAULT_XML_PATH_RELS, DEFAULT_XML_PATH_SHARED_STRINGS, DEFAULT_XML_PATH_SHEET,
30 DEFAULT_XML_PATH_STYLES, DEFAULT_XML_PATH_THEME, DEFAULT_XML_PATH_WORKBOOK,
31 DEFAULT_XML_PATH_WORKBOOK_RELS, MAX_FILE_PATH_LENGTH,
32 NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES, NAMESPACE_DRAWING_ML_MAIN,
33 NAMESPACE_EXTENDED_PROPERTIES, NAMESPACE_SPREADSHEET, NAMESPACE_SPREADSHEET_X14,
34 OLE_IDENTIFIER, SOURCE_RELATIONSHIP, SOURCE_RELATIONSHIP_CHART, SOURCE_RELATIONSHIP_COMMENTS,
35 SOURCE_RELATIONSHIP_CUSTOM_PROPERTIES, SOURCE_RELATIONSHIP_EXTEND_PROPERTIES,
36 SOURCE_RELATIONSHIP_IMAGE, SOURCE_RELATIONSHIP_OFFICE_DOCUMENT,
37 SOURCE_RELATIONSHIP_SHARED_STRINGS, SOURCE_RELATIONSHIP_VBA_PROJECT, STREAM_CHUNK_SIZE,
38 STRICT_NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES, STRICT_NAMESPACE_DRAWING_ML_MAIN,
39 STRICT_NAMESPACE_EXTENDED_PROPERTIES, STRICT_NAMESPACE_SPREADSHEET, STRICT_SOURCE_RELATIONSHIP,
40 STRICT_SOURCE_RELATIONSHIP_CHART, STRICT_SOURCE_RELATIONSHIP_COMMENTS,
41 STRICT_SOURCE_RELATIONSHIP_EXTEND_PROPERTIES, STRICT_SOURCE_RELATIONSHIP_IMAGE,
42 STRICT_SOURCE_RELATIONSHIP_OFFICE_DOCUMENT, UNZIP_SIZE_LIMIT, XML_HEADER,
43};
44use crate::crypt;
45use crate::errors::Result;
46use crate::errors::{
47 ErrDefinedNameDuplicate, ErrDefinedNameScope, ErrMaxFilePathLength, ErrOptionsUnzipSizeLimit,
48 ErrParameterInvalid, ErrSave, ErrUnprotectWorkbook, ErrUnprotectWorkbookPassword,
49 ErrWorkbookFileFormat, ErrWorkbookPassword,
50};
51use crate::lib_util::{count_utf16_string, in_str_slice};
52use crate::numfmt;
53use crate::options::{CULTURE_NAME_UNKNOWN, Options};
54use crate::templates::{
55 TEMPLATE_CONTENT_TYPES, TEMPLATE_DOC_PROPS_APP, TEMPLATE_DOC_PROPS_CORE, TEMPLATE_RELS,
56 TEMPLATE_SHEET, TEMPLATE_STYLES, TEMPLATE_THEME, TEMPLATE_WORKBOOK, TEMPLATE_WORKBOOK_RELS,
57};
58use crate::xml::calc_chain::{XlsxCalcChain, XlsxVolTypes};
59use crate::xml::content_types::{XlsxDefault, XlsxTypes};
60use crate::xml::drawing::XlsxWsDr;
61use crate::xml::styles::XlsxStyleSheet;
62use crate::xml::table::{XlsxSingleXmlCells, XlsxTable};
63use crate::xml::theme::{
64 DecodeTheme, XlsxBaseStyles, XlsxCtColor, XlsxFontCollection, XlsxSysClr, XlsxTheme,
65};
66use crate::xml::workbook::{
67 CalcPropsOptions, WorkbookPropsOptions, WorkbookProtectionOptions, XlsxCalcPr,
68 XlsxRelationship, XlsxRelationships, XlsxWorkbook, XlsxWorkbookPr, XlsxWorkbookProtection,
69};
70use crate::xml::worksheet::XlsxWorksheet;
71
72const SUPPORTED_CALC_MODE: &[&str] = &["manual", "auto", "autoNoTable"];
73const SUPPORTED_REF_MODE: &[&str] = &["A1", "R1C1"];
74const WORKBOOK_PROTECTION_SPIN_COUNT: i32 = 100_000;
75
76pub trait WriteSeek: Write + Seek {}
78impl<T: Write + Seek> WriteSeek for T {}
79
80pub trait ZipWriter {
85 fn start_file(&mut self, name: &str, options: SimpleFileOptions) -> Result<()>;
87 fn write_all(&mut self, buf: &[u8]) -> Result<()>;
89 fn finish(self: Box<Self>) -> Result<()>;
91}
92
93impl<W: Write + Seek> ZipWriter for zip::ZipWriter<W> {
94 fn start_file(&mut self, name: &str, options: SimpleFileOptions) -> Result<()> {
95 zip::ZipWriter::start_file(self, name, options)?;
96 Ok(())
97 }
98 fn write_all(&mut self, buf: &[u8]) -> Result<()> {
99 std::io::Write::write_all(self, buf)?;
100 Ok(())
101 }
102 fn finish(self: Box<Self>) -> Result<()> {
103 zip::ZipWriter::finish(*self)?;
104 Ok(())
105 }
106}
107
108pub type ZipWriterFactory =
110 Arc<dyn for<'a> Fn(&'a mut dyn WriteSeek) -> Box<dyn ZipWriter + 'a> + Send + Sync>;
111
112pub type CharsetTranscoderFn =
114 Arc<dyn Fn(&str, Box<dyn Read>) -> Result<Box<dyn Read>> + Send + Sync>;
115
116#[allow(dead_code)]
121pub(crate) struct StreamState {
122 pub tmp_path: PathBuf,
123 pub sheet_path: String,
124}
125
126pub struct File {
128 pub path: Mutex<String>,
130 pub options: Mutex<Options>,
132 pub sheet_count: Mutex<i32>,
134 pub pkg: DashMap<String, Vec<u8>>,
136 pub sheet: DashMap<String, XlsxWorksheet>,
138 pub relationships: DashMap<String, XlsxRelationships>,
140 pub temp_files: DashMap<String, String>,
142 pub sheet_map: Mutex<HashMap<String, String>>,
144 pub xml_attr: DashMap<String, String>,
146 pub checked: DashMap<String, bool>,
148 pub(crate) streams: RefCell<HashMap<String, StreamState>>,
150 pub zip64_entries: Mutex<Vec<String>>,
152 pub content_types: Mutex<Option<XlsxTypes>>,
154 pub styles: Mutex<Option<XlsxStyleSheet>>,
156 pub workbook: Mutex<Option<XlsxWorkbook>>,
158 pub calc_chain: Mutex<Option<XlsxCalcChain>>,
160 pub volatile_deps: Mutex<Option<XlsxVolTypes>>,
162 pub calc_cache: Mutex<HashMap<String, String>>,
164 pub calc_raw_cache: Mutex<HashMap<String, String>>,
166 pub formula_arg_cache: Mutex<HashMap<String, FormulaArg>>,
168 pub theme: Mutex<Option<DecodeTheme>>,
170 pub shared_strings: Mutex<Option<crate::xml::shared_strings::XlsxSst>>,
172 pub shared_strings_map: Mutex<HashMap<String, i32>>,
174 pub drawings: DashMap<String, XlsxWsDr>,
176 pub comments: DashMap<String, crate::xml::comments::XlsxComments>,
178 pub vml_drawing: DashMap<String, crate::xml::vml::VmlDrawing>,
180 pub charset_transcoder: Mutex<Option<CharsetTranscoderFn>>,
182 pub zip_writer_factory: Mutex<Option<ZipWriterFactory>>,
184}
185
186impl fmt::Debug for File {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 f.debug_struct("File")
189 .field("path", &self.path)
190 .field("options", &self.options)
191 .field("sheet_count", &self.sheet_count)
192 .field("pkg", &self.pkg)
193 .field("sheet", &self.sheet)
194 .field("relationships", &self.relationships)
195 .field("temp_files", &self.temp_files)
196 .field("sheet_map", &self.sheet_map)
197 .field("xml_attr", &self.xml_attr)
198 .field("checked", &self.checked)
199 .field("streams", &"...")
200 .field("zip64_entries", &self.zip64_entries)
201 .field("content_types", &self.content_types)
202 .field("styles", &self.styles)
203 .field("workbook", &self.workbook)
204 .field("calc_chain", &self.calc_chain)
205 .field("volatile_deps", &self.volatile_deps)
206 .field("calc_cache", &"...")
207 .field("calc_raw_cache", &"...")
208 .field("formula_arg_cache", &"...")
209 .field("theme", &self.theme)
210 .field("shared_strings", &self.shared_strings)
211 .field("shared_strings_map", &self.shared_strings_map)
212 .field("drawings", &self.drawings)
213 .field("comments", &self.comments)
214 .field("vml_drawing", &self.vml_drawing)
215 .field("charset_transcoder", &"...")
216 .field("zip_writer_factory", &"...")
217 .finish()
218 }
219}
220
221impl File {
222 fn new_file() -> Self {
224 Self {
225 path: Mutex::new(String::new()),
226 options: Mutex::new(Options::default()),
227 sheet_count: Mutex::new(0),
228 pkg: DashMap::new(),
229 sheet: DashMap::new(),
230 relationships: DashMap::new(),
231 temp_files: DashMap::new(),
232 sheet_map: Mutex::new(HashMap::new()),
233 xml_attr: DashMap::new(),
234 checked: DashMap::new(),
235 streams: RefCell::new(HashMap::new()),
236 zip64_entries: Mutex::new(Vec::new()),
237 content_types: Mutex::new(None),
238 styles: Mutex::new(None),
239 workbook: Mutex::new(None),
240 calc_chain: Mutex::new(None),
241 volatile_deps: Mutex::new(None),
242 calc_cache: Mutex::new(HashMap::new()),
243 calc_raw_cache: Mutex::new(HashMap::new()),
244 formula_arg_cache: Mutex::new(HashMap::new()),
245 theme: Mutex::new(None),
246 shared_strings: Mutex::new(None),
247 shared_strings_map: Mutex::new(HashMap::new()),
248 drawings: DashMap::new(),
249 comments: DashMap::new(),
250 vml_drawing: DashMap::new(),
251 charset_transcoder: Mutex::new(None),
252 zip_writer_factory: Mutex::new(None),
253 }
254 }
255
256 pub fn new() -> Self {
258 Self::new_with_options(Options::default())
259 }
260
261 pub fn new_with_options(opts: Options) -> Self {
263 Self::try_new_with_options(opts).expect("failed to create new workbook")
264 }
265
266 fn try_new_with_options(opts: Options) -> Result<Self> {
267 let f = Self::new_file();
268 *f.options.lock().unwrap() = normalize_options(opts);
269
270 f.store_template(DEFAULT_XML_PATH_RELS, TEMPLATE_RELS);
271 f.store_template(DEFAULT_XML_PATH_DOC_PROPS_APP, TEMPLATE_DOC_PROPS_APP);
272 f.store_template(DEFAULT_XML_PATH_DOC_PROPS_CORE, TEMPLATE_DOC_PROPS_CORE);
273 f.store_template(DEFAULT_XML_PATH_WORKBOOK_RELS, TEMPLATE_WORKBOOK_RELS);
274 f.store_template(DEFAULT_XML_PATH_THEME, TEMPLATE_THEME);
275 f.store_template(DEFAULT_XML_PATH_SHEET, TEMPLATE_SHEET);
276 f.store_template(DEFAULT_XML_PATH_STYLES, TEMPLATE_STYLES);
277 f.store_template(DEFAULT_XML_PATH_WORKBOOK, TEMPLATE_WORKBOOK);
278 f.store_template(DEFAULT_XML_PATH_CONTENT_TYPES, TEMPLATE_CONTENT_TYPES);
279
280 *f.sheet_count.lock().unwrap() = 1;
281
282 let _ = f.calc_chain_reader()?;
284 let _ = f.content_types_reader()?;
285 let _ = f.styles_reader()?;
286 let _ = f.workbook_reader()?;
287
288 f.relationships.insert(
289 DEFAULT_XML_PATH_WORKBOOK_RELS.to_string(),
290 f.rels_reader(DEFAULT_XML_PATH_WORKBOOK_RELS)?
291 .unwrap_or_default(),
292 );
293
294 f.sheet_map
295 .lock()
296 .unwrap()
297 .insert("Sheet1".to_string(), DEFAULT_XML_PATH_SHEET.to_string());
298
299 let ws = f.work_sheet_reader("Sheet1")?;
300 f.sheet.insert(DEFAULT_XML_PATH_SHEET.to_string(), ws);
301
302 let _ = f.theme_reader();
303 Ok(f)
304 }
305
306 pub fn open_file(path: &str, opts: Options) -> Result<Self> {
308 let file = fs::File::open(Path::new(path))?;
309 let size = file.metadata()?.len();
310 let f = Self::open_reader(file, size, opts)?;
311 *f.path.lock().unwrap() = path.to_string();
312 Ok(f)
313 }
314
315 pub fn open_reader<R: Read + Seek>(mut reader: R, _size: u64, opts: Options) -> Result<Self> {
317 let mut header = [0u8; 8];
318 reader.read_exact(&mut header)?;
319 reader.seek(io::SeekFrom::Start(0))?;
320
321 let has_password = !opts.password.is_empty();
322 if header == OLE_IDENTIFIER {
323 if !has_password {
324 return Err(Box::new(ErrWorkbookFileFormat));
325 }
326 let mut encrypted = Vec::new();
327 reader.read_to_end(&mut encrypted)?;
328 let decrypted = crypt::decrypt(&encrypted, &opts)?;
329 return Self::open_reader_internal(Cursor::new(decrypted), opts, true);
330 }
331 if has_password {
332 return Err(Box::new(ErrWorkbookPassword));
333 }
334 Self::open_reader_internal(reader, opts, false)
335 }
336
337 fn open_reader_internal<R: Read + Seek>(
338 reader: R,
339 opts: Options,
340 has_password: bool,
341 ) -> Result<Self> {
342 let f = Self::new_file();
343 *f.options.lock().unwrap() = normalize_options(opts);
344 f.check_open_reader_options()?;
345
346 let zip_result = (|| -> Result<()> {
347 let mut archive = ZipArchive::new(reader)?;
348 let (files, sheet_count) = f.read_zip_archive(&mut archive)?;
349 drop(archive);
350
351 for (k, v) in files {
352 f.pkg.insert(k, v);
353 }
354 *f.sheet_count.lock().unwrap() = sheet_count;
355 Ok(())
356 })();
357
358 match zip_result {
359 Ok(()) => {
360 let _ = f.calc_chain_reader()?;
361 {
362 let map = f.get_sheet_name_to_path_map()?;
363 *f.sheet_map.lock().unwrap() = map;
364 }
365 let _ = f.styles_reader()?;
366 let _ = f.theme_reader();
367 Ok(f)
368 }
369 Err(_) if has_password => Err(Box::new(ErrWorkbookPassword)),
370 Err(e) => Err(e),
371 }
372 }
373
374 pub fn save(&self) -> Result<()> {
376 let path = self.path.lock().unwrap().clone();
377 if path.is_empty() {
378 return Err(Box::new(ErrSave));
379 }
380 self.write_to_path(&path)
381 }
382
383 pub fn save_as(&mut self, path: &str) -> Result<()> {
385 {
386 let mut p = self.path.lock().unwrap();
387 *p = path.to_string();
388 }
389 self.write_to_path(path)
390 }
391
392 fn write_to_path(&self, path: &str) -> Result<()> {
393 if count_utf16_string(path) > MAX_FILE_PATH_LENGTH {
394 return Err(Box::new(ErrMaxFilePathLength));
395 }
396 let ext = Path::new(path)
397 .extension()
398 .and_then(|e| e.to_str())
399 .unwrap_or("")
400 .to_lowercase();
401 if !crate::templates::SUPPORTED_CONTENT_TYPES.contains_key(&format!(".{ext}")) {
402 return Err(Box::new(ErrWorkbookFileFormat));
403 }
404 let file = fs::OpenOptions::new()
405 .write(true)
406 .truncate(true)
407 .create(true)
408 .open(Path::new(path))?;
409 self.write_to(file)
410 }
411
412 pub fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
414 let path = self.path.lock().unwrap().clone();
415 if !path.is_empty() {
416 let ext = Path::new(&path)
417 .extension()
418 .and_then(|e| e.to_str())
419 .unwrap_or("")
420 .to_lowercase();
421 if let Some(ct) = crate::templates::SUPPORTED_CONTENT_TYPES.get(&format!(".{ext}")) {
422 self.set_content_type_part_project_extensions(ct)?;
423 } else {
424 return Err(Box::new(ErrWorkbookFileFormat));
425 }
426 }
427 let buf = self.write_to_buffer()?;
428 writer.write_all(&buf)?;
429 Ok(())
430 }
431
432 pub fn close(&mut self) -> Result<()> {
434 let mut first_err: Option<io::Error> = None;
435 for entry in self.temp_files.iter() {
436 if let Err(e) = fs::remove_file(entry.value()) {
437 if first_err.is_none() {
438 first_err = Some(e);
439 }
440 }
441 }
442 self.temp_files.clear();
443
444 for state in self.streams.borrow().values() {
445 if let Err(e) = fs::remove_file(&state.tmp_path) {
446 if first_err.is_none() {
447 first_err = Some(e);
448 }
449 }
450 }
451 self.streams.borrow_mut().clear();
452
453 match first_err {
454 Some(e) => Err(Box::new(e)),
455 None => Ok(()),
456 }
457 }
458
459 pub fn charset_transcoder<F>(&self, transcoder: F) -> &Self
463 where
464 F: Fn(&str, Box<dyn Read>) -> Result<Box<dyn Read>> + Send + Sync + 'static,
465 {
466 *self.charset_transcoder.lock().unwrap() = Some(Arc::new(transcoder));
467 self
468 }
469
470 pub fn set_zip_writer<F>(&self, factory: F) -> &Self
476 where
477 F: for<'a> Fn(&'a mut dyn WriteSeek) -> Box<dyn ZipWriter + 'a> + Send + Sync + 'static,
478 {
479 *self.zip_writer_factory.lock().unwrap() = Some(Arc::new(factory));
480 self
481 }
482
483 pub(crate) fn apply_charset_transcoder(&self, data: &[u8]) -> Result<Vec<u8>> {
486 let encoding = detect_xml_encoding(data).unwrap_or("UTF-8");
487 if encoding.eq_ignore_ascii_case("UTF-8") || encoding.eq_ignore_ascii_case("UTF8") {
488 return Ok(data.to_vec());
489 }
490 if let Some(transcoder) = self.charset_transcoder.lock().unwrap().clone() {
491 let input: Box<dyn Read> = Box::new(Cursor::new(data.to_vec()));
492 let mut converted = transcoder(encoding, input)?;
493 let mut out = Vec::new();
494 converted.read_to_end(&mut out)?;
495 return Ok(out);
496 }
497 Ok(data.to_vec())
498 }
499
500 fn store_template(&self, path: &str, template: &str) {
505 let bytes = format!("{XML_HEADER}{template}").into_bytes();
506 if let Some(attrs) = extract_root_namespace_attributes(&bytes) {
507 self.xml_attr.insert(path.to_string(), attrs);
508 }
509 self.pkg.insert(path.to_string(), bytes);
510 }
511
512 pub fn read_xml(&self, name: &str) -> Vec<u8> {
514 self.pkg
515 .get(name)
516 .map(|e| e.value().clone())
517 .unwrap_or_default()
518 }
519
520 pub fn read_bytes(&self, name: &str) -> Vec<u8> {
522 let content = self.read_xml(name);
523 if !content.is_empty() {
524 return content;
525 }
526 match self.read_temp(name) {
527 Ok(mut file) => {
528 let mut content = Vec::new();
529 if file.read_to_end(&mut content).is_ok() {
530 self.pkg.insert(name.to_string(), content.clone());
531 }
532 content
533 }
534 Err(_) => Vec::new(),
535 }
536 }
537
538 fn read_temp(&self, name: &str) -> io::Result<fs::File> {
539 let path = self
540 .temp_files
541 .get(name)
542 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no temp file"))?
543 .clone();
544 fs::File::open(path)
545 }
546
547 pub fn save_file_list(&self, name: &str, content: &[u8]) {
549 let mut out = Vec::with_capacity(XML_HEADER.len() + content.len());
550 out.extend_from_slice(XML_HEADER.as_bytes());
551 out.extend_from_slice(content);
552 self.pkg.insert(name.to_string(), out);
553 }
554
555 fn read_zip_archive<R: Read + Seek>(
557 &self,
558 archive: &mut ZipArchive<R>,
559 ) -> Result<(HashMap<String, Vec<u8>>, i32)> {
560 let mut files = HashMap::new();
561 let mut worksheets = 0;
562 let mut unzip_size: i64 = 0;
563 let opts = self.options.lock().unwrap();
564
565 for i in 0..archive.len() {
566 let mut zip_file = archive.by_index(i)?;
567 let file_size = zip_file.size() as i64;
568 unzip_size += file_size;
569 if unzip_size > opts.unzip_size_limit {
570 return Err(Box::new(io::Error::new(
571 io::ErrorKind::InvalidData,
572 crate::errors::new_unzip_size_limit_error(opts.unzip_size_limit),
573 )));
574 }
575 let mut file_name = zip_file.name().replace('\\', "/");
576 let lower = file_name.to_lowercase();
577 if lower == "[content_types].xml" {
578 file_name = DEFAULT_XML_PATH_CONTENT_TYPES.to_string();
579 } else if lower == "xl/sharedstrings.xml" {
580 file_name = DEFAULT_XML_PATH_SHARED_STRINGS.to_string();
581 }
582
583 if lower == DEFAULT_XML_PATH_SHARED_STRINGS.to_lowercase()
584 && file_size > opts.unzip_xml_size_limit
585 {
586 if let Ok(tmp) = self.unzip_to_temp(&mut zip_file) {
587 self.temp_files.insert(file_name, tmp);
588 }
589 continue;
590 }
591 if lower.starts_with("xl/worksheets/sheet") {
592 worksheets += 1;
593 if file_size > opts.unzip_xml_size_limit && !zip_file.is_dir() {
594 if let Ok(tmp) = self.unzip_to_temp(&mut zip_file) {
595 self.temp_files.insert(file_name, tmp);
596 }
597 continue;
598 }
599 }
600
601 let mut data = Vec::with_capacity(file_size.max(0) as usize);
602 zip_file.read_to_end(&mut data)?;
603 files.insert(file_name, data);
604 }
605 Ok((files, worksheets))
606 }
607
608 fn unzip_to_temp(&self, zip_file: &mut zip::read::ZipFile<'_>) -> io::Result<String> {
609 let tmp_dir = {
610 let opts = self.options.lock().unwrap();
611 if opts.tmp_dir.is_empty() {
612 std::env::temp_dir()
613 } else {
614 PathBuf::from(&opts.tmp_dir)
615 }
616 };
617 let now = SystemTime::now()
618 .duration_since(UNIX_EPOCH)
619 .unwrap_or_default();
620 let name = format!("excelize-{}-{}.xml", now.as_secs(), now.subsec_nanos());
621 let path = tmp_dir.join(name);
622 let mut file = fs::File::create(&path)?;
623 io::copy(zip_file, &mut file)?;
624 file.sync_all()?;
625 Ok(path.to_string_lossy().to_string())
626 }
627
628 pub fn content_types_reader(&self) -> Result<XlsxTypes> {
630 if self.content_types.lock().unwrap().is_none() {
631 let mut ct = XlsxTypes::default();
632 let data =
633 self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_CONTENT_TYPES))?;
634 let data = namespace_strict_to_transitional(&data);
635 if !data.is_empty() {
636 ct = xml_from_reader(data.as_slice())?;
637 }
638 *self.content_types.lock().unwrap() = Some(ct);
639 }
640 Ok(self.content_types.lock().unwrap().clone().unwrap())
641 }
642
643 pub fn styles_reader(&self) -> Result<XlsxStyleSheet> {
645 if self.styles.lock().unwrap().is_none() {
646 let data = self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_STYLES))?;
647 let data = namespace_strict_to_transitional(&data);
648 let st = if data.is_empty() {
649 XlsxStyleSheet::default()
650 } else {
651 if let Some(attrs) = extract_root_namespace_attributes(&data) {
652 self.xml_attr
653 .insert(DEFAULT_XML_PATH_STYLES.to_string(), attrs);
654 }
655 xml_from_reader(data.as_slice()).unwrap_or_default()
656 };
657 *self.styles.lock().unwrap() = Some(st);
658 }
659 Ok(self.styles.lock().unwrap().clone().unwrap())
660 }
661
662 pub fn workbook_reader(&self) -> Result<XlsxWorkbook> {
664 if self.workbook.lock().unwrap().is_none() {
665 let wb_path = self.get_workbook_path();
666 let data = self.apply_charset_transcoder(&self.read_xml(&wb_path))?;
667 let data = namespace_strict_to_transitional(&data);
668 let data = strip_xml_element(&data, "extLst");
671 let mut wb: XlsxWorkbook = if data.is_empty() {
672 XlsxWorkbook::default()
673 } else {
674 if let Some(attrs) = extract_root_namespace_attributes(&data) {
675 self.xml_attr.insert(wb_path.clone(), attrs);
676 }
677 xml_from_reader(data.as_slice()).unwrap_or_default()
678 };
679 for sheet in &mut wb.sheets.sheet {
683 if sheet.id.is_none() {
684 sheet.id = sheet.plain_id.take();
685 }
686 }
687 *self.workbook.lock().unwrap() = Some(wb);
688 }
689 Ok(self.workbook.lock().unwrap().clone().unwrap())
690 }
691
692 pub fn theme_reader(&self) -> Result<Option<DecodeTheme>> {
696 if self.theme.lock().unwrap().is_none() {
697 if self.pkg.contains_key(DEFAULT_XML_PATH_THEME)
698 || self.temp_files.contains_key(DEFAULT_XML_PATH_THEME)
699 {
700 let data = self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_THEME))?;
701 let data = namespace_strict_to_transitional(&data);
702 if !data.is_empty() {
703 if let Some(attrs) = extract_root_namespace_attributes(&data) {
704 self.xml_attr
705 .insert(DEFAULT_XML_PATH_THEME.to_string(), attrs);
706 }
707 if let Ok(theme) = xml_from_reader::<_, DecodeTheme>(data.as_slice()) {
708 *self.theme.lock().unwrap() = Some(theme);
709 }
710 }
711 }
712 }
713 Ok(self.theme.lock().unwrap().clone())
714 }
715
716 pub fn shared_strings_reader(&self) -> Result<crate::xml::shared_strings::XlsxSst> {
718 if self.shared_strings.lock().unwrap().is_none() {
719 let mut sst = crate::xml::shared_strings::XlsxSst::default();
720 if self.pkg.contains_key(DEFAULT_XML_PATH_SHARED_STRINGS)
721 || self
722 .temp_files
723 .contains_key(DEFAULT_XML_PATH_SHARED_STRINGS)
724 {
725 let data = self
726 .apply_charset_transcoder(&self.read_bytes(DEFAULT_XML_PATH_SHARED_STRINGS))?;
727 let data = namespace_strict_to_transitional(&data);
728 if !data.is_empty() {
729 sst = xml_from_reader(data.as_slice()).unwrap_or_default();
730 }
731 }
732 let mut map = self.shared_strings_map.lock().unwrap();
733 map.clear();
734 for (i, si) in sst.si.iter().enumerate() {
735 let text = if let Some(t) = &si.t {
736 t.val.clone()
737 } else {
738 si.r.iter()
739 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
740 .collect()
741 };
742 map.insert(text, i as i32);
743 }
744 *self.shared_strings.lock().unwrap() = Some(sst);
745 }
746 Ok(self.shared_strings.lock().unwrap().clone().unwrap())
747 }
748
749 pub fn rels_reader(&self, path: &str) -> Result<Option<XlsxRelationships>> {
751 if let Some(rels) = self.relationships.get(path) {
752 return Ok(Some(rels.clone()));
753 }
754 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
755 let data = self.apply_charset_transcoder(&self.read_xml(path))?;
756 let data = namespace_strict_to_transitional(&data);
757 let rels = if data.is_empty() {
758 XlsxRelationships::default()
759 } else {
760 if let Some(attrs) = extract_root_namespace_attributes(&data) {
761 self.xml_attr.insert(path.to_string(), attrs);
762 }
763 xml_from_reader(data.as_slice()).unwrap_or_default()
764 };
765 self.relationships.insert(path.to_string(), rels.clone());
766 Ok(Some(rels))
767 } else {
768 Ok(None)
769 }
770 }
771
772 pub fn metadata_reader(&self) -> Result<crate::xml::metadata::XlsxMetadata> {
774 let data = namespace_strict_to_transitional(
775 &self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_METADATA))?,
776 );
777 if data.is_empty() {
778 return Ok(crate::xml::metadata::XlsxMetadata::default());
779 }
780 let data = strip_xml_element(&data, "metadataTypes");
784 let data = strip_xml_element(&data, "extLst");
785 Ok(xml_from_reader(data.as_slice())?)
786 }
787
788 pub fn rich_value_reader(&self) -> Result<crate::xml::metadata::XlsxRichValueData> {
790 let data = namespace_strict_to_transitional(
791 &self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE))?,
792 );
793 if data.is_empty() {
794 return Ok(crate::xml::metadata::XlsxRichValueData::default());
795 }
796 Ok(xml_from_reader(data.as_slice())?)
797 }
798
799 pub fn rich_value_rel_reader(&self) -> Result<crate::xml::metadata::XlsxRichValueRels> {
801 let data = namespace_strict_to_transitional(
802 &self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_REL))?,
803 );
804 if data.is_empty() {
805 return Ok(crate::xml::metadata::XlsxRichValueRels::default());
806 }
807 Ok(xml_from_reader(data.as_slice())?)
808 }
809
810 pub fn rich_value_structures_reader(
812 &self,
813 ) -> Result<crate::xml::metadata::XlsxRichValueStructures> {
814 let mut raw = self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_STRUCTURE);
815 if raw.is_empty() {
816 raw = self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_STRUCTURE_LEGACY);
818 }
819 let data = namespace_strict_to_transitional(&self.apply_charset_transcoder(&raw)?);
820 if data.is_empty() {
821 return Ok(crate::xml::metadata::XlsxRichValueStructures::default());
822 }
823 Ok(xml_from_reader(data.as_slice())?)
824 }
825
826 pub fn rich_value_web_image_reader(
828 &self,
829 ) -> Result<crate::xml::metadata::XlsxWebImagesSupportingRichData> {
830 let data =
831 namespace_strict_to_transitional(&self.apply_charset_transcoder(
832 &self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_WEB_IMAGE),
833 )?);
834 if data.is_empty() {
835 return Ok(crate::xml::metadata::XlsxWebImagesSupportingRichData::default());
836 }
837 Ok(xml_from_reader(data.as_slice())?)
838 }
839
840 pub(crate) fn get_rich_data_rich_value_rel_relationship(
842 &self,
843 r_id: &str,
844 ) -> Option<XlsxRelationship> {
845 self.rels_reader(DEFAULT_XML_PATH_RD_RICH_VALUE_REL_RELS)
846 .ok()
847 .flatten()
848 .and_then(|rels| rels.relationships.into_iter().find(|rel| rel.id == r_id))
849 }
850
851 pub(crate) fn get_rich_value_web_image_relationship(
853 &self,
854 r_id: &str,
855 ) -> Option<XlsxRelationship> {
856 self.rels_reader(DEFAULT_XML_PATH_RD_RICH_VALUE_WEB_IMAGE_RELS)
857 .ok()
858 .flatten()
859 .and_then(|rels| rels.relationships.into_iter().find(|rel| rel.id == r_id))
860 }
861
862 pub fn set_rels(
864 &self,
865 r_id: &str,
866 rel_path: &str,
867 rel_type: &str,
868 target: &str,
869 target_mode: &str,
870 ) -> i32 {
871 if r_id.is_empty() {
872 return self.add_rels(rel_path, rel_type, target, target_mode);
873 }
874 let mut rels = self
875 .rels_reader(rel_path)
876 .unwrap_or_default()
877 .unwrap_or_default();
878 let mut out_id = 0;
879 for rel in &mut rels.relationships {
880 if rel.id == r_id {
881 rel.r#type = rel_type.to_string();
882 rel.target = target.to_string();
883 rel.target_mode = Some(target_mode.to_string());
884 out_id = r_id.trim_start_matches("rId").parse().unwrap_or(0);
885 break;
886 }
887 }
888 self.relationships.insert(rel_path.to_string(), rels);
889 out_id
890 }
891
892 pub fn add_rels(&self, rel_path: &str, rel_type: &str, target: &str, target_mode: &str) -> i32 {
894 let uniq_part: HashMap<String, String> = [
895 (
896 SOURCE_RELATIONSHIP_CUSTOM_PROPERTIES.to_string(),
897 "/docProps/custom.xml".to_string(),
898 ),
899 (
900 SOURCE_RELATIONSHIP_SHARED_STRINGS.to_string(),
901 "/xl/sharedStrings.xml".to_string(),
902 ),
903 ]
904 .into_iter()
905 .collect();
906
907 let mut rels = self
908 .rels_reader(rel_path)
909 .unwrap_or_default()
910 .unwrap_or_default();
911 let mut r_id = 0;
912 for rel in &rels.relationships {
913 let id: i32 = rel.id.trim_start_matches("rId").parse().unwrap_or(0);
914 if id > r_id {
915 r_id = id;
916 }
917 if rel.r#type == rel_type {
918 if let Some(part_name) = uniq_part.get(&rel.r#type) {
919 let _ = part_name;
921 }
922 }
923 }
924 r_id += 1;
925 let new_id = format!("rId{r_id}");
926 rels.relationships.push(XlsxRelationship {
927 id: new_id,
928 r#type: rel_type.to_string(),
929 target: target.to_string(),
930 target_mode: if target_mode.is_empty() {
931 None
932 } else {
933 Some(target_mode.to_string())
934 },
935 });
936 self.relationships.insert(rel_path.to_string(), rels);
937 r_id
938 }
939
940 pub fn get_workbook_path(&self) -> String {
942 if let Ok(Some(rels)) = self.rels_reader(DEFAULT_XML_PATH_RELS) {
943 for rel in &rels.relationships {
944 if rel.r#type == crate::constants::SOURCE_RELATIONSHIP_OFFICE_DOCUMENT {
945 return rel.target.trim_start_matches('/').to_string();
946 }
947 }
948 }
949 DEFAULT_XML_PATH_WORKBOOK.to_string()
950 }
951
952 pub fn get_workbook_rels_path(&self) -> String {
954 let wb = self.get_workbook_path();
955 let wb_dir = Path::new(&wb).parent().unwrap_or(Path::new("."));
956 let wb_base = Path::new(&wb).file_name().unwrap_or_default();
957 if wb_dir == Path::new(".") {
958 return format!("_rels/{}", wb_base.to_string_lossy()) + ".rels";
959 }
960 format!(
961 "{}/_rels/{}.rels",
962 wb_dir.to_string_lossy(),
963 wb_base.to_string_lossy()
964 )
965 .trim_start_matches('/')
966 .to_string()
967 }
968
969 pub fn get_worksheet_path(&self, rel_target: &str) -> String {
971 let wb_path = self.get_workbook_path();
972 let wb_dir = Path::new(&wb_path).parent().unwrap_or(Path::new("."));
973 let mut combined = wb_dir.to_path_buf();
974 for c in rel_target.split('/') {
975 if c == ".." {
976 combined.pop();
977 } else if !c.is_empty() && c != "." {
978 combined.push(c);
979 }
980 }
981 let mut s = combined.to_string_lossy().replace('\\', "/");
982 s = s.trim_start_matches('/').to_string();
983 if rel_target.starts_with('/') {
984 s = Path::new(rel_target)
985 .to_string_lossy()
986 .replace('\\', "/")
987 .trim_start_matches('/')
988 .to_string();
989 }
990 s
991 }
992
993 pub fn get_sheet_xml_path(&self, sheet: &str) -> Option<String> {
995 let map = self.sheet_map.lock().unwrap();
996 for (name, path) in map.iter() {
997 if name.eq_ignore_ascii_case(sheet) {
998 return Some(path.clone());
999 }
1000 }
1001 None
1002 }
1003
1004 pub fn work_sheet_reader(&self, sheet: &str) -> Result<XlsxWorksheet> {
1006 crate::excelize::check_sheet_name(sheet)?;
1007 let name = self.get_sheet_xml_path(sheet).ok_or_else(|| {
1008 Box::new(crate::errors::ErrSheetNotExist {
1009 sheet_name: sheet.to_string(),
1010 }) as Box<dyn std::error::Error + Send + Sync>
1011 })?;
1012 if let Some(ws) = self.sheet.get(&name) {
1013 return Ok(ws.clone());
1014 }
1015 let data = self.apply_charset_transcoder(&self.read_bytes(&name))?;
1016 let data = namespace_strict_to_transitional(&data);
1017 let mut data = data;
1018 let ext_lst = extract_ext_lst(&data);
1019 if ext_lst.is_some() {
1020 remove_ext_lst(&mut data);
1021 }
1022 let mut ws: XlsxWorksheet = xml_from_reader(data.as_slice())?;
1023 if let Some(xml) = ext_lst {
1024 ws.ext_lst = Some(crate::xml::common::parse_ext_lst_content(&xml)?);
1025 }
1026 if !self.checked.contains_key(&name) {
1027 self.checked.insert(name.clone(), true);
1029 }
1030 self.sheet.insert(name.clone(), ws.clone());
1031 Ok(ws)
1032 }
1033
1034 pub(crate) fn check_open_reader_options(&self) -> Result<()> {
1039 let mut opts = self.options.lock().unwrap();
1040 if opts.unzip_size_limit == 0 {
1041 opts.unzip_size_limit = opts.unzip_xml_size_limit.max(UNZIP_SIZE_LIMIT);
1042 }
1043 if opts.unzip_xml_size_limit == 0 {
1044 opts.unzip_xml_size_limit = opts.unzip_size_limit.min(STREAM_CHUNK_SIZE);
1045 }
1046 if opts.unzip_xml_size_limit > opts.unzip_size_limit {
1047 return Err(Box::new(ErrOptionsUnzipSizeLimit));
1048 }
1049 let patterns = [
1050 opts.short_date_pattern.clone(),
1051 opts.long_date_pattern.clone(),
1052 opts.long_time_pattern.clone(),
1053 ];
1054 drop(opts);
1055 self.check_date_time_pattern(&patterns)
1056 }
1057
1058 fn check_date_time_pattern(&self, patterns: &[String]) -> Result<()> {
1059 for pattern in patterns {
1060 if !pattern.is_empty() && !numfmt::is_date_time_pattern(pattern) {
1061 return Err(Box::new(crate::errors::ErrUnsupportedNumberFormat));
1062 }
1063 }
1064 Ok(())
1065 }
1066
1067 pub fn write_to_buffer(&self) -> Result<Vec<u8>> {
1073 let mut buf = Cursor::new(Vec::new());
1074 let factory = self.zip_writer_factory.lock().unwrap().clone();
1075 if let Some(factory) = factory {
1076 let mut zw = factory(&mut buf);
1077 self.write_to_zip(&mut *zw)?;
1078 zw.finish()?;
1079 } else {
1080 let mut zw = Box::new(zip::ZipWriter::new(&mut buf));
1081 self.write_to_zip(&mut *zw)?;
1082 zw.finish()?;
1083 }
1084 let mut inner = buf.into_inner();
1085 self.write_zip64_lfh(&mut inner)?;
1086 let opts = self.options.lock().unwrap().clone();
1087 if !opts.password.is_empty() {
1088 inner = crypt::encrypt(&inner, &opts)?;
1089 }
1090 Ok(inner)
1091 }
1092
1093 fn write_to_zip(&self, zw: &mut dyn ZipWriter) -> Result<()> {
1094 self.calc_chain_writer();
1095 self.comments_writer();
1096 self.shared_strings_registrar();
1097 self.content_types_writer();
1098 self.drawings_writer();
1099 self.volatile_deps_writer();
1100 self.vml_drawing_writer();
1101 self.workbook_writer();
1102 self.work_sheet_writer();
1103 self.rels_writer();
1104 let _ = self.shared_strings_loader();
1105 self.shared_strings_writer();
1106 self.style_sheet_writer();
1107 self.theme_writer();
1108
1109 let mut files: Vec<String> = self.pkg.iter().map(|e| e.key().clone()).collect();
1111 files.sort_unstable_by(|a, b| b.cmp(a));
1112 for path in files {
1113 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1114 zw.start_file(&path, opts)?;
1115 let content = self.read_xml(&path);
1116 zw.write_all(&content)?;
1117 if content.len() as u64 > u32::MAX as u64 {
1118 self.zip64_entries.lock().unwrap().push(path.clone());
1119 }
1120 }
1121
1122 let mut temp_files: Vec<String> = self
1124 .temp_files
1125 .iter()
1126 .filter(|e| !self.pkg.contains_key(e.key()))
1127 .map(|e| e.key().clone())
1128 .collect();
1129 temp_files.sort_unstable_by(|a, b| b.cmp(a));
1130 for path in temp_files {
1131 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1132 zw.start_file(&path, opts)?;
1133 let content = self.read_bytes(&path);
1134 zw.write_all(&content)?;
1135 if content.len() as u64 > u32::MAX as u64 {
1136 self.zip64_entries.lock().unwrap().push(path.clone());
1137 }
1138 }
1139
1140 let mut streams: Vec<(String, PathBuf)> = self
1143 .streams
1144 .borrow_mut()
1145 .drain()
1146 .map(|(path, state)| (path, state.tmp_path))
1147 .collect();
1148 streams.sort_unstable_by(|a, b| b.0.cmp(&a.0));
1149 for (path, tmp_path) in streams {
1150 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1151 zw.start_file(&path, opts)?;
1152 let mut file = fs::File::open(&tmp_path)?;
1153 let size = file.metadata()?.len();
1154 let mut buf = [0u8; 8192];
1155 loop {
1156 let n = file.read(&mut buf)?;
1157 if n == 0 {
1158 break;
1159 }
1160 zw.write_all(&buf[..n])?;
1161 }
1162 if size > u32::MAX as u64 {
1163 self.zip64_entries.lock().unwrap().push(path.clone());
1164 }
1165 let _ = fs::remove_file(&tmp_path);
1166 }
1167 Ok(())
1168 }
1169
1170 fn write_zip64_lfh(&self, buf: &mut [u8]) -> Result<()> {
1171 let entries = self.zip64_entries.lock().unwrap().clone();
1172 if entries.is_empty() {
1173 return Ok(());
1174 }
1175 let mut offset = 0usize;
1176 while offset < buf.len() {
1177 let window = &buf[offset..];
1178 let Some(idx) = find_subsequence(window, b"\x50\x4b\x03\x04") else {
1179 break;
1180 };
1181 let idx = idx + offset;
1182 if idx + 30 > buf.len() {
1183 break;
1184 }
1185 let filename_len = u16::from_le_bytes([buf[idx + 26], buf[idx + 27]]) as usize;
1186 if idx + 30 + filename_len > buf.len() {
1187 break;
1188 }
1189 let filename =
1190 std::str::from_utf8(&buf[idx + 30..idx + 30 + filename_len]).unwrap_or("");
1191 if in_str_slice(&entries, filename, true) != -1 {
1192 buf[idx + 4..idx + 6].copy_from_slice(&45u16.to_le_bytes());
1193 }
1194 offset = idx + 1;
1195 }
1196 Ok(())
1197 }
1198
1199 fn comments_writer(&self) {
1202 for entry in self.comments.iter() {
1203 let path = entry.key().clone();
1204 let comments = entry.value().clone();
1205 if let Ok(output) = quick_xml::se::to_string(&comments).map(|s| s.into_bytes()) {
1206 self.save_file_list(&path, &output);
1207 }
1208 }
1209 }
1210
1211 fn content_types_writer(&self) {
1212 if let Some(ct) = self.content_types.lock().unwrap().clone() {
1213 if let Ok(mut output) = xml_to_string(&ct).map(|s| s.into_bytes()) {
1214 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_CONTENT_TYPES, &mut output);
1215 self.save_file_list(DEFAULT_XML_PATH_CONTENT_TYPES, &output);
1216 }
1217 }
1218 }
1219
1220 fn drawings_writer(&self) {
1221 for entry in self.drawings.iter() {
1222 let path = entry.key().clone();
1223 let drawing = entry.value().clone();
1224 if let Ok(mut output) = xml_to_string(&drawing).map(|s| s.into_bytes()) {
1225 self.replace_namespace_bytes_if_needed(&path, &mut output);
1226 self.save_file_list(&path, &output);
1227 }
1228 }
1229 }
1230
1231 fn vml_drawing_writer(&self) {
1234 for entry in self.vml_drawing.iter() {
1235 let path = entry.key().clone();
1236 let drawing = entry.value().clone();
1237 let output = drawing.to_xml();
1238 self.pkg.insert(path, output);
1239 }
1240 }
1241
1242 fn workbook_writer(&self) {
1243 if let Some(mut wb) = self.workbook.lock().unwrap().clone() {
1244 if let Some(decode) = wb.decode_alternate_content.take() {
1248 wb.alternate_content = Some(crate::xml::workbook::XlsxAlternateContent {
1249 xmlns_mc: Some(
1250 "http://schemas.openxmlformats.org/markup-compatibility/2006".to_string(),
1251 ),
1252 content: decode.content,
1253 });
1254 }
1255 if let Ok(mut output) = xml_to_string(&wb).map(|s| s.into_bytes()) {
1256 let path = self.get_workbook_path();
1257 self.replace_namespace_bytes_if_needed(&path, &mut output);
1258 self.save_file_list(&path, &output);
1259 }
1260 }
1261 }
1262
1263 fn work_sheet_writer(&self) {
1264 for entry in self.sheet.iter() {
1265 let path = entry.key().clone();
1266 let sheet = entry.value().clone();
1267 if let Ok(mut output) = xml_to_string(&sheet).map(|s| s.into_bytes()) {
1268 self.replace_namespace_bytes_if_needed(&path, &mut output);
1269 strip_empty_attributes(&mut output);
1270 if let Some(ext_lst) = &sheet.ext_lst {
1271 let ext_xml = crate::xml::common::serialize_ext_lst(ext_lst);
1272 if !ext_xml.is_empty() {
1273 inject_ext_lst(&mut output, &ext_xml);
1274 }
1275 }
1276 self.save_file_list(&path, &output);
1277 }
1278 }
1279 }
1280
1281 fn rels_writer(&self) {
1282 for entry in self.relationships.iter() {
1283 let path = entry.key().clone();
1284 let rels = entry.value().clone();
1285 if let Ok(mut output) = xml_to_string(&rels).map(|s| s.into_bytes()) {
1286 self.replace_namespace_bytes_if_needed(&path, &mut output);
1287 if !output.windows(6).any(|w| w == b"xmlns=".as_slice()) {
1288 let _ = replace_root_namespace_attributes(
1289 &mut output,
1290 "xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"",
1291 );
1292 }
1293 strip_empty_attributes(&mut output);
1294 self.save_file_list(&path, &output);
1295 }
1296 }
1297 }
1298
1299 fn shared_strings_loader(&self) -> Result<()> {
1300 if let Some(entry) = self.temp_files.get(DEFAULT_XML_PATH_SHARED_STRINGS) {
1301 let temp_path = entry.value().clone();
1302 drop(entry);
1303 let data = self.read_bytes(DEFAULT_XML_PATH_SHARED_STRINGS);
1304 if !data.is_empty() {
1305 self.pkg
1306 .insert(DEFAULT_XML_PATH_SHARED_STRINGS.to_string(), data);
1307 }
1308 self.temp_files.remove(DEFAULT_XML_PATH_SHARED_STRINGS);
1309 let _ = fs::remove_file(temp_path);
1310 *self.shared_strings.lock().unwrap() = None;
1311 self.shared_strings_map.lock().unwrap().clear();
1312 }
1313 Ok(())
1314 }
1315
1316 fn shared_strings_registrar(&self) {
1319 let has_strings = self.shared_strings.lock().unwrap().is_some()
1320 || self.temp_files.contains_key(DEFAULT_XML_PATH_SHARED_STRINGS);
1321 if !has_strings {
1322 return;
1323 }
1324 if let Ok(mut ct) = self.content_types_reader() {
1325 let part_name = format!("/{DEFAULT_XML_PATH_SHARED_STRINGS}");
1326 let exists = ct.entries.iter().any(|e| {
1327 matches!(e, crate::xml::content_types::XlsxContentTypeEntry::Override(o) if o.part_name == part_name)
1328 });
1329 if !exists {
1330 ct.entries
1331 .push(crate::xml::content_types::XlsxContentTypeEntry::Override(
1332 crate::xml::content_types::XlsxOverride {
1333 part_name,
1334 content_type:
1335 crate::constants::CONTENT_TYPE_SPREADSHEET_ML_SHARED_STRINGS
1336 .to_string(),
1337 },
1338 ));
1339 *self.content_types.lock().unwrap() = Some(ct);
1340 }
1341 }
1342 let rels_path = self.get_workbook_rels_path();
1343 let mut rels = self.rels_reader(&rels_path).unwrap_or_default().unwrap_or_default();
1344 crate::sheet::ensure_shared_strings_rel(&mut rels);
1345 self.relationships.insert(rels_path, rels);
1346 }
1347
1348 fn shared_strings_writer(&self) {
1349 if let Some(sst) = self.shared_strings.lock().unwrap().clone() {
1350 if let Ok(output) = xml_to_string(&sst).map(|s| s.into_bytes()) {
1351 self.save_file_list(DEFAULT_XML_PATH_SHARED_STRINGS, &output);
1352 }
1353 }
1354 }
1355
1356 fn style_sheet_writer(&self) {
1357 if let Some(st) = self.styles.lock().unwrap().clone() {
1358 if let Ok(mut output) = xml_to_string(&st).map(|s| s.into_bytes()) {
1359 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_STYLES, &mut output);
1360 strip_empty_attributes(&mut output);
1361 self.save_file_list(DEFAULT_XML_PATH_STYLES, &output);
1362 }
1363 }
1364 }
1365
1366 fn theme_writer(&self) {
1367 if let Some(theme) = self.theme.lock().unwrap().clone() {
1368 let serialized = decode_theme_to_xlsx_theme(&theme);
1369 if let Ok(mut output) = xml_to_string(&serialized).map(|s| s.into_bytes()) {
1370 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_THEME, &mut output);
1371 self.save_file_list(DEFAULT_XML_PATH_THEME, &output);
1372 }
1373 }
1374 }
1375
1376 pub(crate) fn replace_namespace_bytes_if_needed(&self, path: &str, content: &mut Vec<u8>) {
1381 if let Some(attrs) = self.xml_attr.get(path) {
1382 let _ = replace_root_namespace_attributes(content, attrs.value());
1383 }
1384 }
1385
1386 pub fn add_name_spaces(&self, path: &str, ns: &str) {
1393 let prefix = match ns {
1394 crate::constants::SOURCE_RELATIONSHIP => "r",
1395 crate::constants::NAMESPACE_SPREADSHEET_X14 => "x14",
1396 crate::constants::NAMESPACE_SPREADSHEET_X15 => "x15",
1397 crate::constants::NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN => "xm",
1398 crate::constants::NAMESPACE_DRAWING_ML_MAIN => "a",
1399 crate::constants::NAMESPACE_DRAWING_ML_CHART => "c",
1400 crate::constants::NAMESPACE_DRAWING_ML_SPREADSHEET => "xdr",
1401 crate::constants::NAMESPACE_DRAWING_ML_A14 => "a14",
1402 crate::constants::NAMESPACE_DRAWING_2016_SVG => "asvg",
1403 crate::constants::NAMESPACE_SPREADSHEET => return,
1405 _ => return,
1406 };
1407 if prefix.is_empty() {
1408 return;
1409 }
1410
1411 let attr_name = format!("xmlns:{prefix}");
1412 let mut attrs = self
1413 .xml_attr
1414 .get(path)
1415 .map(|a| a.clone())
1416 .unwrap_or_default();
1417 if !attrs.contains(&attr_name) {
1418 if !attrs.is_empty() {
1419 attrs.push(' ');
1420 }
1421 attrs.push_str(&format!("{attr_name}=\"{ns}\""));
1422 }
1423
1424 if self.needs_ignorable_prefix(prefix) {
1427 let mc_ns = "http://schemas.openxmlformats.org/markup-compatibility/2006";
1428 if !attrs.contains("xmlns:mc") {
1429 if !attrs.is_empty() && !attrs.ends_with(' ') {
1430 attrs.push(' ');
1431 }
1432 attrs.push_str(&format!("xmlns:mc=\"{mc_ns}\""));
1433 }
1434 self.set_ignorable_name_space(path, prefix, &mut attrs);
1435 }
1436
1437 self.xml_attr.insert(path.to_string(), attrs);
1438 }
1439
1440 fn needs_ignorable_prefix(&self, prefix: &str) -> bool {
1441 const IGNORABLE_NS: &[&str] = &[
1442 "c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14",
1443 "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9",
1444 "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x16", "x16r2", "mo", "mx", "mv", "o",
1445 "v",
1446 ];
1447 IGNORABLE_NS.contains(&prefix)
1448 }
1449
1450 fn set_ignorable_name_space(&self, _path: &str, prefix: &str, attrs: &mut String) {
1451 let marker = "mc:Ignorable=\"";
1452 if let Some(start) = attrs.find(marker) {
1453 let value_start = start + marker.len();
1454 if let Some(end) = attrs[value_start..].find('"') {
1455 let value = &attrs[value_start..value_start + end];
1456 let parts: Vec<&str> = value.split_whitespace().collect();
1457 if !parts.contains(&prefix) {
1458 let new_value = format!("{} {}", value, prefix);
1459 attrs.replace_range(value_start..value_start + end, &new_value);
1460 }
1461 return;
1462 }
1463 }
1464 if !attrs.is_empty() && !attrs.ends_with(' ') {
1465 attrs.push(' ');
1466 }
1467 attrs.push_str(&format!("mc:Ignorable=\"{prefix}\""));
1468 }
1469
1470 pub(crate) fn add_sheet_name_space(&self, sheet: &str, ns: &str) {
1472 if let Some(path) = self.get_sheet_xml_path(sheet) {
1473 self.add_name_spaces(&path, ns);
1474 }
1475 }
1476
1477 pub fn get_sheet_id(&self, sheet: &str) -> i32 {
1479 if let Ok(wb) = self.workbook_reader() {
1480 for s in &wb.sheets.sheet {
1481 if s.name.as_deref().unwrap_or("").eq_ignore_ascii_case(sheet) {
1482 return s.sheet_id.unwrap_or(0) as i32;
1483 }
1484 }
1485 }
1486 -1
1487 }
1488
1489 pub fn expand_3d_sheet_range(&self, sheet1: &str, sheet2: &str) -> Result<Vec<String>> {
1492 let mut idx1 = self.get_sheet_index(sheet1)?;
1493 let mut idx2 = self.get_sheet_index(sheet2)?;
1494 if idx1 > idx2 {
1495 std::mem::swap(&mut idx1, &mut idx2);
1496 }
1497 let list = self.get_sheet_list();
1498 Ok(list[(idx1 - 1) as usize..idx2 as usize].to_vec())
1499 }
1500
1501 pub fn clear_calc_cache(&self) {
1503 let _ = self.calc_chain.lock().unwrap().take();
1504 self.calc_cache.lock().unwrap().clear();
1505 self.calc_raw_cache.lock().unwrap().clear();
1506 self.formula_arg_cache.lock().unwrap().clear();
1507 }
1508
1509 pub fn add_content_type_part(&self, id: i32, part: &str) -> Result<()> {
1511 match part {
1512 "comments" => self.set_content_type_part_vml_extensions()?,
1513 "drawings" => crate::sheet::set_content_type_part_image_extensions(self)?,
1514 _ => {}
1515 }
1516 let mut ct = self.content_types_reader()?;
1517 let content_type = match part {
1518 "table" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_TABLE,
1519 "pivotTable" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_PIVOT_TABLE,
1520 "pivotCache" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_PIVOT_CACHE_DEFINITION,
1521 "slicer" => crate::constants::CONTENT_TYPE_SLICER,
1522 "slicerCache" => crate::constants::CONTENT_TYPE_SLICER_CACHE,
1523 "drawings" => crate::constants::CONTENT_TYPE_DRAWING,
1524 "chart" => crate::constants::CONTENT_TYPE_DRAWING_ML,
1525 "chartsheet" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_CHARTSHEET,
1526 "comments" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_COMMENTS,
1527 _ => return Ok(()),
1528 };
1529 let part_name = match part {
1530 "table" => format!("/xl/tables/table{id}.xml"),
1531 "pivotTable" => format!("/xl/pivotTables/pivotTable{id}.xml"),
1532 "pivotCache" => format!("/xl/pivotCache/pivotCacheDefinition{id}.xml"),
1533 "slicer" => format!("/xl/slicers/slicer{id}.xml"),
1534 "slicerCache" => format!("/xl/slicerCaches/slicerCache{id}.xml"),
1535 "drawings" => format!("/xl/drawings/drawing{id}.xml"),
1536 "chart" => format!("/xl/charts/chart{id}.xml"),
1537 "chartsheet" => format!("/xl/chartsheets/sheet{id}.xml"),
1538 "comments" => format!("/xl/comments{id}.xml"),
1539 _ => return Ok(()),
1540 };
1541 for entry in &ct.entries {
1542 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = entry {
1543 if o.part_name == part_name {
1544 return Ok(());
1545 }
1546 }
1547 }
1548 ct.entries
1549 .push(crate::xml::content_types::XlsxContentTypeEntry::Override(
1550 crate::xml::content_types::XlsxOverride {
1551 part_name,
1552 content_type: content_type.to_string(),
1553 },
1554 ));
1555 *self.content_types.lock().unwrap() = Some(ct);
1556 self.set_content_type_part_rels_extensions()
1557 }
1558
1559 pub(crate) fn set_content_type_part_rels_extensions(&self) -> Result<()> {
1561 let mut ct = self.content_types_reader()?;
1562 let exists = ct.entries.iter().any(|e| {
1563 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
1564 d.extension == "rels"
1565 } else {
1566 false
1567 }
1568 });
1569 if !exists {
1570 ct.entries
1571 .push(crate::xml::content_types::XlsxContentTypeEntry::Default(
1572 crate::xml::content_types::XlsxDefault {
1573 extension: "rels".to_string(),
1574 content_type: crate::constants::CONTENT_TYPE_RELATIONSHIPS.to_string(),
1575 },
1576 ));
1577 }
1578 *self.content_types.lock().unwrap() = Some(ct);
1579 Ok(())
1580 }
1581
1582 pub(crate) fn set_content_type_part_vml_extensions(&self) -> Result<()> {
1584 let mut ct = self.content_types_reader()?;
1585 let exists = ct.entries.iter().any(|e| {
1586 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
1587 d.extension == "vml"
1588 } else {
1589 false
1590 }
1591 });
1592 if !exists {
1593 ct.entries
1594 .push(crate::xml::content_types::XlsxContentTypeEntry::Default(
1595 crate::xml::content_types::XlsxDefault {
1596 extension: "vml".to_string(),
1597 content_type: crate::constants::CONTENT_TYPE_VML.to_string(),
1598 },
1599 ));
1600 }
1601 *self.content_types.lock().unwrap() = Some(ct);
1602 Ok(())
1603 }
1604
1605 pub fn remove_content_types_part(&self, content_type: &str, part_name: &str) -> Result<()> {
1607 let mut ct = self.content_types_reader()?;
1608 ct.entries.retain(|e| {
1609 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = e {
1610 !(o.content_type == content_type && o.part_name == part_name)
1611 } else {
1612 true
1613 }
1614 });
1615 *self.content_types.lock().unwrap() = Some(ct);
1616 Ok(())
1617 }
1618
1619 pub fn get_sheet_relationships_target_by_id(&self, sheet: &str, r_id: &str) -> String {
1621 let Some(sheet_xml_path) = self.get_sheet_xml_path(sheet) else {
1622 return String::new();
1623 };
1624 let sheet_rels = format!(
1625 "xl/worksheets/_rels/{}.rels",
1626 sheet_xml_path.trim_start_matches("xl/worksheets/")
1627 );
1628 if let Ok(Some(rels)) = self.rels_reader(&sheet_rels) {
1629 for rel in &rels.relationships {
1630 if rel.id == r_id {
1631 return rel.target.clone();
1632 }
1633 }
1634 }
1635 String::new()
1636 }
1637
1638 pub fn add_sheet_legacy_drawing(&self, sheet: &str, r_id: i32) -> Result<()> {
1640 let mut ws = self.work_sheet_reader(sheet)?;
1641 ws.legacy_drawing = Some(crate::xml::worksheet::XlsxLegacyDrawing {
1642 rid: Some(format!("rId{r_id}")),
1643 });
1644 if let Some(path) = self.get_sheet_xml_path(sheet) {
1645 self.sheet.insert(path, ws);
1646 }
1647 Ok(())
1648 }
1649
1650 pub fn add_sheet_legacy_drawing_hf(&self, sheet: &str, r_id: i32) -> Result<()> {
1652 let mut ws = self.work_sheet_reader(sheet)?;
1653 ws.legacy_drawing_hf = Some(crate::xml::worksheet::XlsxLegacyDrawingHF {
1654 rid: Some(format!("rId{r_id}")),
1655 });
1656 if let Some(path) = self.get_sheet_xml_path(sheet) {
1657 self.sheet.insert(path, ws);
1658 }
1659 Ok(())
1660 }
1661
1662 pub fn count_comments(&self) -> i32 {
1664 let mut count = 0;
1665 for entry in self.pkg.iter() {
1666 if entry.key().contains("xl/comments") {
1667 count += 1;
1668 }
1669 }
1670 for entry in self.comments.iter() {
1671 if entry.key().contains("xl/comments") && !self.pkg.contains_key(entry.key()) {
1672 count += 1;
1673 }
1674 }
1675 count
1676 }
1677
1678 pub fn count_vml_drawing(&self) -> i32 {
1680 let mut count = 0;
1681 for entry in self.pkg.iter() {
1682 if entry.key().contains("xl/drawings/vmlDrawing") {
1683 count += 1;
1684 }
1685 }
1686 for entry in self.vml_drawing.iter() {
1687 if entry.key().contains("xl/drawings/vmlDrawing") && !self.pkg.contains_key(entry.key())
1688 {
1689 count += 1;
1690 }
1691 }
1692 count
1693 }
1694
1695 pub fn comments_reader(
1697 &self,
1698 path: &str,
1699 ) -> Result<Option<crate::xml::comments::XlsxComments>> {
1700 if let Some(cmts) = self.comments.get(path) {
1701 return Ok(Some(cmts.clone()));
1702 }
1703 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
1704 let data = self.apply_charset_transcoder(&self.read_bytes(path))?;
1705 let data = crate::file::namespace_strict_to_transitional(&data);
1706 let mut cmts: crate::xml::comments::XlsxComments =
1707 quick_xml::de::from_reader(data.as_slice()).unwrap_or_default();
1708 for cmt in &cmts.comment_list.comment {
1709 cmts.cells.push(cmt.r#ref.clone());
1710 }
1711 self.comments.insert(path.to_string(), cmts.clone());
1712 return Ok(Some(cmts));
1713 }
1714 Ok(None)
1715 }
1716
1717 pub fn vml_drawing_reader(&self, path: &str) -> Result<Option<crate::xml::vml::VmlDrawing>> {
1719 if let Some(vml) = self.vml_drawing.get(path) {
1720 return Ok(Some(vml.clone()));
1721 }
1722 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
1723 let data = self.apply_charset_transcoder(&self.read_bytes(path))?;
1724 let data = crate::file::namespace_strict_to_transitional(&data);
1725 let data = String::from_utf8_lossy(&data)
1726 .replace("<br>\r\n", "<br></br>\r\n")
1727 .into_bytes();
1728 let vml = crate::xml::vml::VmlDrawing::from_xml(&data).unwrap_or_default();
1729 self.vml_drawing.insert(path.to_string(), vml.clone());
1730 return Ok(Some(vml));
1731 }
1732 Ok(None)
1733 }
1734
1735 pub fn delete_sheet_relationships(&self, sheet: &str, r_id: &str) {
1737 let Some(sheet_xml_path) = self.get_sheet_xml_path(sheet) else {
1738 return;
1739 };
1740 let sheet_rels = format!(
1741 "xl/worksheets/_rels/{}.rels",
1742 sheet_xml_path.trim_start_matches("xl/worksheets/")
1743 );
1744 if let Ok(Some(mut rels)) = self.rels_reader(&sheet_rels) {
1745 rels.relationships.retain(|r| r.id != r_id);
1746 self.relationships.insert(sheet_rels, rels);
1747 }
1748 }
1749
1750 pub fn delete_workbook_rels(&self, rel_type: &str, target: &str) -> Result<String> {
1752 let rel_path = self.get_workbook_rels_path();
1753 let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
1754 let mut r_id = String::new();
1755 rels.relationships.retain(|r| {
1756 if r.r#type == rel_type && (r.target == target || r.target == format!("/{target}")) {
1757 r_id = r.id.clone();
1758 false
1759 } else {
1760 true
1761 }
1762 });
1763 self.relationships.insert(rel_path, rels);
1764 Ok(r_id)
1765 }
1766
1767 pub fn add_sheet_drawing(&self, sheet: &str, r_id: i32) -> Result<()> {
1769 let mut ws = self.work_sheet_reader(sheet)?;
1770 ws.drawing = Some(crate::xml::worksheet::XlsxDrawing {
1771 rid: Some(format!("rId{r_id}")),
1772 });
1773 self.sheet
1774 .insert(self.get_sheet_xml_path(sheet).unwrap_or_default(), ws);
1775 Ok(())
1776 }
1777
1778 pub fn count_tables(&self) -> i32 {
1780 let mut count = 0i32;
1781 for entry in self.pkg.iter() {
1782 let k = entry.key();
1783 if k.contains("xl/tables/tableSingleCells") {
1784 if let Ok(data) = self.apply_charset_transcoder(entry.value()) {
1785 let data = namespace_strict_to_transitional(&data);
1786 match xml_from_reader::<_, XlsxSingleXmlCells>(data.as_slice()) {
1787 Ok(cells) => {
1788 for cell in cells.single_xml_cell {
1789 if count < cell.id as i32 {
1790 count = cell.id as i32;
1791 }
1792 }
1793 }
1794 Err(_) => count += 1,
1795 }
1796 }
1797 }
1798 if k.contains("xl/tables/table") {
1799 if let Ok(data) = self.apply_charset_transcoder(entry.value()) {
1800 let data = namespace_strict_to_transitional(&data);
1801 match xml_from_reader::<_, XlsxTable>(data.as_slice()) {
1802 Ok(t) => {
1803 if count < t.id as i32 {
1804 count = t.id as i32;
1805 }
1806 }
1807 Err(_) => count += 1,
1808 }
1809 }
1810 }
1811 }
1812 count
1813 }
1814
1815 pub fn count_drawings(&self) -> i32 {
1817 let mut count = 0;
1818 for entry in self.pkg.iter() {
1819 if entry.key().contains("xl/drawings/drawing") {
1820 count += 1;
1821 }
1822 }
1823 for entry in self.drawings.iter() {
1824 if entry.key().contains("xl/drawings/drawing") && !self.pkg.contains_key(entry.key()) {
1825 count += 1;
1826 }
1827 }
1828 count
1829 }
1830
1831 pub fn count_charts(&self) -> i32 {
1833 let mut count = 0;
1834 for entry in self.pkg.iter() {
1835 if entry.key().contains("xl/charts/chart") {
1836 count += 1;
1837 }
1838 }
1839 count
1840 }
1841
1842 pub fn count_pivot_tables(&self) -> i32 {
1844 let mut count = 0;
1845 for entry in self.pkg.iter() {
1846 if entry.key().contains("xl/pivotTables/pivotTable") {
1847 count += 1;
1848 }
1849 }
1850 count
1851 }
1852
1853 pub fn count_pivot_cache(&self) -> i32 {
1855 let mut count = 0;
1856 for entry in self.pkg.iter() {
1857 if entry.key().contains("xl/pivotCache/pivotCacheDefinition") {
1858 count += 1;
1859 }
1860 }
1861 count
1862 }
1863
1864 pub fn count_slicers(&self) -> i32 {
1866 let mut count = 0;
1867 for entry in self.pkg.iter() {
1868 if entry.key().contains("xl/slicers/slicer") {
1869 count += 1;
1870 }
1871 }
1872 count
1873 }
1874
1875 pub fn count_slicer_cache(&self) -> i32 {
1877 let mut count = 0;
1878 for entry in self.pkg.iter() {
1879 if entry.key().contains("xl/slicerCaches/slicerCache") {
1880 count += 1;
1881 }
1882 }
1883 count
1884 }
1885
1886 pub fn get_defined_names(&self) -> Result<Vec<crate::xml::workbook::DefinedName>> {
1888 let mut out = Vec::new();
1889 if let Ok(wb) = self.workbook_reader() {
1890 if let Some(dns) = wb.defined_names {
1891 for dn in dns.defined_name {
1892 out.push(crate::xml::workbook::DefinedName {
1893 name: dn.name.clone().unwrap_or_default(),
1894 comment: dn.comment.clone().unwrap_or_default(),
1895 refers_to: dn.data.clone(),
1896 scope: dn
1897 .local_sheet_id
1898 .map(|id| {
1899 if let Ok(wb2) = self.workbook_reader() {
1900 if let Some(s) = wb2.sheets.sheet.get(id as usize) {
1901 return s.name.clone().unwrap_or_default();
1902 }
1903 }
1904 String::new()
1905 })
1906 .unwrap_or_else(|| "Workbook".to_string()),
1907 });
1908 }
1909 }
1910 }
1911 Ok(out)
1912 }
1913
1914 pub fn set_defined_name(&self, dn: &crate::xml::workbook::DefinedName) -> Result<()> {
1916 let mut wb = self.workbook_reader()?;
1917 if wb.defined_names.is_none() {
1918 wb.defined_names = Some(crate::xml::workbook::XlsxDefinedNames::default());
1919 }
1920 let names = wb.defined_names.as_mut().unwrap();
1921 let local_sheet_id = if dn.scope.is_empty() || dn.scope == "Workbook" {
1922 None
1923 } else {
1924 Some((self.get_sheet_index(&dn.scope)? - 1) as i64)
1925 };
1926 for existing in &names.defined_name {
1927 if existing.name.as_deref().unwrap_or("") == dn.name
1928 && existing.local_sheet_id == local_sheet_id
1929 {
1930 return Err(Box::new(ErrDefinedNameDuplicate));
1931 }
1932 }
1933 names
1934 .defined_name
1935 .push(crate::xml::workbook::XlsxDefinedName {
1936 name: Some(dn.name.clone()),
1937 data: dn.refers_to.clone(),
1938 hidden: Some(false),
1939 local_sheet_id,
1940 ..Default::default()
1941 });
1942 *self.workbook.lock().unwrap() = Some(wb);
1943 Ok(())
1944 }
1945
1946 pub fn delete_defined_name(&self, dn: &crate::xml::workbook::DefinedName) -> Result<()> {
1948 let mut wb = self.workbook_reader()?;
1949 let Some(names) = wb.defined_names.as_mut() else {
1950 return Err(Box::new(ErrDefinedNameScope));
1951 };
1952 let local_sheet_id = if dn.scope.is_empty() || dn.scope == "Workbook" {
1953 None
1954 } else {
1955 Some((self.get_sheet_index(&dn.scope)? - 1) as i64)
1956 };
1957 let before = names.defined_name.len();
1958 names.defined_name.retain(|e| {
1959 !(e.name.as_deref().unwrap_or("") == dn.name && e.local_sheet_id == local_sheet_id)
1960 });
1961 if names.defined_name.len() == before {
1962 return Err(Box::new(ErrDefinedNameScope));
1963 }
1964 if names.defined_name.is_empty() {
1965 wb.defined_names = None;
1966 }
1967 *self.workbook.lock().unwrap() = Some(wb);
1968 Ok(())
1969 }
1970
1971 pub fn get_defined_name_ref_to(&self, name: &str, current_sheet: &str) -> String {
1973 let mut workbook_ref = String::new();
1974 let mut sheet_ref = String::new();
1975 if let Ok(names) = self.get_defined_names() {
1976 for dn in &names {
1977 if dn.name == name {
1978 if dn.scope == "Workbook" {
1979 workbook_ref = dn.refers_to.clone();
1980 }
1981 if dn.scope == current_sheet {
1982 sheet_ref = dn.refers_to.clone();
1983 }
1984 }
1985 }
1986 }
1987 if !sheet_ref.is_empty() {
1988 sheet_ref
1989 } else {
1990 workbook_ref
1991 }
1992 }
1993
1994 pub fn get_defined_name(&self) -> Result<Vec<crate::xml::workbook::DefinedName>> {
1997 self.get_defined_names()
1998 }
1999}
2000
2001impl File {
2006 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
2008 self.write_to(writer)
2009 }
2010
2011 pub fn save_with_options(&mut self, opts: Options) -> Result<()> {
2013 {
2014 let mut o = self.options.lock().unwrap();
2015 *o = opts;
2016 }
2017 self.save()
2018 }
2019
2020 pub fn save_as_with_options(&mut self, path: &str, opts: Options) -> Result<()> {
2022 {
2023 let mut o = self.options.lock().unwrap();
2024 *o = opts;
2025 }
2026 self.save_as(path)
2027 }
2028
2029 pub fn update_linked_value(&self) -> Result<()> {
2034 let mut wb = self.workbook_reader()?;
2035 wb.calc_pr = None;
2036 *self.workbook.lock().unwrap() = Some(wb);
2037 for name in self.get_sheet_list() {
2038 if let Ok(mut ws) = self.work_sheet_reader(&name) {
2039 let path = self.get_sheet_xml_path(&name).unwrap_or_default();
2040 let mut changed = false;
2041 for row in &mut ws.sheet_data.row {
2042 for cell in &mut row.c {
2043 if cell.f.is_some() && cell.v.is_some() {
2044 cell.v = None;
2045 cell.t = None;
2046 changed = true;
2047 }
2048 }
2049 }
2050 if changed {
2051 self.sheet.insert(path, ws);
2052 }
2053 }
2054 }
2055 Ok(())
2056 }
2057
2058 pub fn read_zip_reader<R: Read + Seek>(
2062 &self,
2063 archive: &mut ZipArchive<R>,
2064 ) -> Result<(HashMap<String, Vec<u8>>, i32)> {
2065 self.read_zip_archive(archive)
2066 }
2067
2068 pub fn set_workbook_props(&self, opts: &WorkbookPropsOptions) -> Result<()> {
2070 let mut wb = self.workbook_reader()?;
2071 if wb.workbook_pr.is_none() {
2072 wb.workbook_pr = Some(XlsxWorkbookPr::default());
2073 }
2074 let pr = wb.workbook_pr.as_mut().unwrap();
2075 if let Some(v) = opts.date1904 {
2076 pr.date1904 = Some(v);
2077 }
2078 if let Some(v) = opts.filter_privacy {
2079 pr.filter_privacy = Some(v);
2080 }
2081 if let Some(ref v) = opts.code_name {
2082 pr.code_name = Some(v.clone());
2083 }
2084 *self.workbook.lock().unwrap() = Some(wb);
2085 Ok(())
2086 }
2087
2088 pub fn get_workbook_props(&self) -> Result<WorkbookPropsOptions> {
2090 let mut opts = WorkbookPropsOptions::default();
2091 let wb = self.workbook_reader()?;
2092 if let Some(pr) = &wb.workbook_pr {
2093 opts.date1904 = pr.date1904;
2094 opts.filter_privacy = pr.filter_privacy;
2095 opts.code_name = pr.code_name.clone();
2096 }
2097 Ok(opts)
2098 }
2099
2100 pub fn set_calc_props(&self, opts: &CalcPropsOptions) -> Result<()> {
2102 if let Some(ref mode) = opts.calc_mode {
2103 if in_str_slice(SUPPORTED_CALC_MODE, mode, true) == -1 {
2104 return Err(Box::new(ErrParameterInvalid));
2105 }
2106 }
2107 if let Some(ref mode) = opts.ref_mode {
2108 if in_str_slice(SUPPORTED_REF_MODE, mode, true) == -1 {
2109 return Err(Box::new(ErrParameterInvalid));
2110 }
2111 }
2112
2113 let mut wb = self.workbook_reader()?;
2114 if wb.calc_pr.is_none() {
2115 wb.calc_pr = Some(XlsxCalcPr::default());
2116 }
2117 let pr = wb.calc_pr.as_mut().unwrap();
2118 if let Some(v) = opts.calc_completed {
2119 pr.calc_completed = Some(v);
2120 }
2121 if let Some(v) = opts.calc_on_save {
2122 pr.calc_on_save = Some(v);
2123 }
2124 if let Some(v) = opts.force_full_calc {
2125 pr.force_full_calc = Some(v);
2126 }
2127 if let Some(v) = opts.full_calc_on_load {
2128 pr.full_calc_on_load = Some(v);
2129 }
2130 if let Some(v) = opts.full_precision {
2131 pr.full_precision = Some(v);
2132 }
2133 if let Some(v) = opts.iterate {
2134 pr.iterate = Some(v);
2135 }
2136 if let Some(v) = opts.iterate_delta {
2137 pr.iterate_delta = Some(v);
2138 }
2139 if let Some(ref v) = opts.calc_mode {
2140 pr.calc_mode = Some(v.clone());
2141 }
2142 if let Some(ref v) = opts.ref_mode {
2143 pr.ref_mode = Some(v.clone());
2144 }
2145 if let Some(v) = opts.calc_id {
2146 pr.calc_id = Some(v as i64);
2147 }
2148 if let Some(v) = opts.concurrent_manual_count {
2149 pr.concurrent_manual_count = Some(v as i64);
2150 }
2151 if let Some(v) = opts.iterate_count {
2152 pr.iterate_count = Some(v as i64);
2153 }
2154 pr.concurrent_calc = opts.concurrent_calc;
2155 *self.workbook.lock().unwrap() = Some(wb);
2156 Ok(())
2157 }
2158
2159 pub fn get_calc_props(&self) -> Result<CalcPropsOptions> {
2161 let mut opts = CalcPropsOptions::default();
2162 let wb = self.workbook_reader()?;
2163 if let Some(pr) = &wb.calc_pr {
2164 opts.calc_completed = pr.calc_completed;
2165 opts.calc_on_save = pr.calc_on_save;
2166 opts.force_full_calc = pr.force_full_calc;
2167 opts.full_calc_on_load = pr.full_calc_on_load;
2168 opts.full_precision = pr.full_precision;
2169 opts.iterate = pr.iterate;
2170 opts.iterate_delta = pr.iterate_delta;
2171 opts.calc_mode = pr.calc_mode.clone();
2172 opts.ref_mode = pr.ref_mode.clone();
2173 opts.calc_id = pr.calc_id.map(|v| v as u64);
2174 opts.concurrent_manual_count = pr.concurrent_manual_count.map(|v| v as u64);
2175 opts.iterate_count = pr.iterate_count.map(|v| v as u64);
2176 opts.concurrent_calc = pr.concurrent_calc;
2177 }
2178 Ok(opts)
2179 }
2180
2181 pub fn protect_workbook(&self, opts: &WorkbookProtectionOptions) -> Result<()> {
2183 let mut wb = self.workbook_reader()?;
2184 let mut protection = XlsxWorkbookProtection {
2185 lock_structure: Some(opts.lock_structure),
2186 lock_windows: Some(opts.lock_windows),
2187 ..Default::default()
2188 };
2189 if !opts.password.is_empty() {
2190 let algorithm_name = if opts.algorithm_name.is_empty() {
2191 "SHA-512"
2192 } else {
2193 &opts.algorithm_name
2194 };
2195 let (hash_value, salt_value) = crypt::gen_iso_passwd_hash(
2196 &opts.password,
2197 algorithm_name,
2198 "",
2199 WORKBOOK_PROTECTION_SPIN_COUNT,
2200 )?;
2201 protection.workbook_algorithm_name = Some(algorithm_name.to_string());
2202 protection.workbook_hash_value = Some(hash_value);
2203 protection.workbook_salt_value = Some(salt_value);
2204 protection.workbook_spin_count = Some(WORKBOOK_PROTECTION_SPIN_COUNT as i64);
2205 }
2206 wb.workbook_protection = Some(protection);
2207 *self.workbook.lock().unwrap() = Some(wb);
2208 Ok(())
2209 }
2210
2211 pub fn unprotect_workbook(&self, password: Option<&str>) -> Result<()> {
2213 let mut wb = self.workbook_reader()?;
2214 if let Some(pwd) = password {
2215 let protection = wb.workbook_protection.as_ref().ok_or_else(|| {
2216 Box::new(ErrUnprotectWorkbook) as Box<dyn std::error::Error + Send + Sync>
2217 })?;
2218 if let Some(ref algorithm_name) = protection.workbook_algorithm_name {
2219 let salt = protection.workbook_salt_value.as_deref().unwrap_or("");
2220 let spin_count = protection.workbook_spin_count.unwrap_or(0) as i32;
2221 let (hash_value, _) =
2222 crypt::gen_iso_passwd_hash(pwd, algorithm_name, salt, spin_count)?;
2223 if protection.workbook_hash_value.as_deref().unwrap_or("") != hash_value {
2224 return Err(Box::new(ErrUnprotectWorkbookPassword));
2225 }
2226 }
2227 }
2228 wb.workbook_protection = None;
2229 *self.workbook.lock().unwrap() = Some(wb);
2230 Ok(())
2231 }
2232
2233 pub fn add_vba_project(&self, data: &[u8]) -> Result<()> {
2240 if data.len() < 8 || &data[..8] != OLE_IDENTIFIER {
2241 return Err(Box::new(crate::errors::ErrAddVBAProject));
2242 }
2243 let rel_path = self.get_workbook_rels_path();
2244 let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
2245 let mut existing = false;
2246 let mut r_id = 0;
2247 for rel in &rels.relationships {
2248 if rel.target == "vbaProject.bin" && rel.r#type == SOURCE_RELATIONSHIP_VBA_PROJECT {
2249 existing = true;
2250 }
2251 let id: i32 = rel.id.trim_start_matches("rId").parse().unwrap_or(0);
2252 if id > r_id {
2253 r_id = id;
2254 }
2255 }
2256 if !existing {
2257 r_id += 1;
2258 rels.relationships.push(XlsxRelationship {
2259 id: format!("rId{r_id}"),
2260 r#type: SOURCE_RELATIONSHIP_VBA_PROJECT.to_string(),
2261 target: "vbaProject.bin".to_string(),
2262 target_mode: None,
2263 });
2264 self.relationships.insert(rel_path, rels);
2265 }
2266 self.pkg
2267 .insert("xl/vbaProject.bin".to_string(), data.to_vec());
2268 Ok(())
2269 }
2270
2271 pub fn set_content_type_part_project_extensions(&self, content_type: &str) -> Result<()> {
2273 let mut ct = self.content_types_reader()?;
2274 let mut bin_ok = false;
2275 let mut entries = ct.entries.clone();
2276 for entry in &entries {
2277 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = entry {
2278 if d.extension == "bin" {
2279 bin_ok = true;
2280 }
2281 }
2282 }
2283 for entry in &mut entries {
2284 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = entry {
2285 if o.part_name == "/xl/workbook.xml" {
2286 o.content_type = content_type.to_string();
2287 }
2288 }
2289 }
2290 if !bin_ok {
2291 entries.push(crate::xml::content_types::XlsxContentTypeEntry::Default(
2292 XlsxDefault {
2293 extension: "bin".to_string(),
2294 content_type: CONTENT_TYPE_VBA.to_string(),
2295 },
2296 ));
2297 }
2298 ct.entries = entries;
2299 *self.content_types.lock().unwrap() = Some(ct);
2300 Ok(())
2301 }
2302}
2303
2304fn normalize_options(mut opts: Options) -> Options {
2309 if opts.unzip_size_limit == 0 {
2310 opts.unzip_size_limit = UNZIP_SIZE_LIMIT;
2311 }
2312 if opts.unzip_xml_size_limit == 0 {
2313 opts.unzip_xml_size_limit = STREAM_CHUNK_SIZE;
2314 }
2315 if opts.culture_info == 0 && opts.short_date_pattern.is_empty() {
2316 opts.culture_info = CULTURE_NAME_UNKNOWN;
2317 }
2318 opts
2319}
2320
2321pub fn namespace_strict_to_transitional(content: &[u8]) -> Vec<u8> {
2323 if content.windows(13).all(|w| w != b"purl.oclc.org") {
2324 return content.to_vec();
2325 }
2326 let mut result = content.to_vec();
2327 let translations: &[(&[u8], &[u8])] = &[
2328 (
2329 STRICT_NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES.as_bytes(),
2330 NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES.as_bytes(),
2331 ),
2332 (
2333 STRICT_NAMESPACE_DRAWING_ML_MAIN.as_bytes(),
2334 NAMESPACE_DRAWING_ML_MAIN.as_bytes(),
2335 ),
2336 (
2337 STRICT_NAMESPACE_EXTENDED_PROPERTIES.as_bytes(),
2338 NAMESPACE_EXTENDED_PROPERTIES.as_bytes(),
2339 ),
2340 (
2341 STRICT_NAMESPACE_SPREADSHEET.as_bytes(),
2342 NAMESPACE_SPREADSHEET.as_bytes(),
2343 ),
2344 (
2345 STRICT_SOURCE_RELATIONSHIP.as_bytes(),
2346 SOURCE_RELATIONSHIP.as_bytes(),
2347 ),
2348 (
2349 STRICT_SOURCE_RELATIONSHIP_CHART.as_bytes(),
2350 SOURCE_RELATIONSHIP_CHART.as_bytes(),
2351 ),
2352 (
2353 STRICT_SOURCE_RELATIONSHIP_COMMENTS.as_bytes(),
2354 SOURCE_RELATIONSHIP_COMMENTS.as_bytes(),
2355 ),
2356 (
2357 STRICT_SOURCE_RELATIONSHIP_EXTEND_PROPERTIES.as_bytes(),
2358 SOURCE_RELATIONSHIP_EXTEND_PROPERTIES.as_bytes(),
2359 ),
2360 (
2361 STRICT_SOURCE_RELATIONSHIP_IMAGE.as_bytes(),
2362 SOURCE_RELATIONSHIP_IMAGE.as_bytes(),
2363 ),
2364 (
2365 STRICT_SOURCE_RELATIONSHIP_OFFICE_DOCUMENT.as_bytes(),
2366 SOURCE_RELATIONSHIP_OFFICE_DOCUMENT.as_bytes(),
2367 ),
2368 ];
2369 for (from, to) in translations {
2370 result = bytes_replace(&result, from, to);
2371 }
2372 result
2373}
2374
2375fn bytes_replace(content: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
2376 let mut result = Vec::with_capacity(content.len());
2377 let mut start = 0;
2378 while let Some(pos) = content[start..].windows(from.len()).position(|w| w == from) {
2379 let pos = start + pos;
2380 result.extend_from_slice(&content[start..pos]);
2381 result.extend_from_slice(to);
2382 start = pos + from.len();
2383 }
2384 result.extend_from_slice(&content[start..]);
2385 result
2386}
2387
2388fn detect_xml_encoding(content: &[u8]) -> Option<&str> {
2392 let start = content.windows(5).position(|w| w == b"<?xml")?;
2393 let rest = &content[start..];
2394 let end = rest.windows(2).position(|w| w == b"?>")? + 2;
2395 let decl = &rest[..end];
2396 let key = b"encoding=";
2397 let pos = decl.windows(key.len()).position(|w| w == key)?;
2398 let rest = &decl[pos + key.len()..];
2399 let quote = *rest.first()?;
2400 let close = rest[1..].iter().position(|&b| b == quote)?;
2401 std::str::from_utf8(&rest[1..1 + close]).ok()
2402}
2403
2404fn strip_xml_element(content: &[u8], name: &str) -> Vec<u8> {
2408 let s = String::from_utf8_lossy(content);
2409 let pattern = format!(
2410 r"(?s)<{}\b[^>]*>.*?</{}>",
2411 regex::escape(name),
2412 regex::escape(name)
2413 );
2414 if let Ok(re) = regex::Regex::new(&pattern) {
2415 return re.replace_all(&s, "").into_owned().into_bytes();
2416 }
2417 s.into_owned().into_bytes()
2418}
2419
2420pub(crate) fn extract_root_namespace_attributes(content: &[u8]) -> Option<String> {
2422 let s = std::str::from_utf8(content).ok()?;
2423 let mut pos = 0usize;
2425 while pos < s.len() {
2426 let c = s[pos..].chars().next()?;
2427 if c == '<' {
2428 if s[pos..].starts_with("<?") {
2429 if let Some(end) = s[pos..].find("?>") {
2430 pos += end + 2;
2431 continue;
2432 }
2433 return None;
2434 }
2435 break;
2436 }
2437 pos += c.len_utf8();
2438 }
2439 if pos >= s.len() {
2440 return None;
2441 }
2442 let start = pos;
2443 let close = s[start..].find('>')?;
2444 let tag = &s[start..start + close + 1];
2445 let name_end = tag
2447 .find(|c: char| c.is_whitespace())
2448 .unwrap_or(tag.len() - 1);
2449 let after_name = &tag[name_end..tag.len() - 1];
2450 let trimmed = after_name.trim();
2451 if trimmed.is_empty() {
2452 None
2453 } else {
2454 Some(trimmed.to_string())
2455 }
2456}
2457
2458pub(crate) fn replace_root_namespace_attributes(content: &mut Vec<u8>, attrs: &str) -> Result<()> {
2460 let s = std::str::from_utf8(content)
2461 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
2462 let mut pos = 0usize;
2463 while pos < s.len() {
2464 let c = s[pos..]
2465 .chars()
2466 .next()
2467 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "empty"))?;
2468 if c == '<' {
2469 if s[pos..].starts_with("<?") {
2470 if let Some(end) = s[pos..].find("?>") {
2471 pos += end + 2;
2472 continue;
2473 }
2474 return Err(Box::new(io::Error::new(
2475 io::ErrorKind::InvalidData,
2476 "bad xml decl",
2477 )));
2478 }
2479 break;
2480 }
2481 pos += c.len_utf8();
2482 }
2483 let start = pos;
2484 let close = s[start..]
2485 .find('>')
2486 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no root close"))?;
2487 let tag = &s[start..start + close + 1];
2488 let name_end = tag
2489 .find(|c: char| c.is_whitespace())
2490 .unwrap_or(tag.len() - 1);
2491 let tag_name = &tag[1..name_end];
2492 let end_tag = start + close + 1;
2493 let new_start = format!("<{tag_name} {attrs}>");
2494 let tail = content.split_off(end_tag);
2495 content.truncate(start);
2496 content.extend_from_slice(new_start.as_bytes());
2497 content.extend_from_slice(&tail);
2498 Ok(())
2499}
2500
2501fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
2502 haystack
2503 .windows(needle.len())
2504 .position(|window| window == needle)
2505}
2506
2507pub(crate) fn strip_empty_attributes(content: &mut Vec<u8>) {
2514 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
2515 let re = RE.get_or_init(|| regex::Regex::new(r#" ([a-zA-Z_][a-zA-Z0-9_:\-]*)="""#).unwrap());
2516 let s = String::from_utf8_lossy(content);
2517 let cleaned = re.replace_all(&s, "");
2518 *content = cleaned.into_owned().into_bytes();
2519}
2520
2521fn decode_theme_to_xlsx_theme(theme: &DecodeTheme) -> XlsxTheme {
2522 fn convert_color(c: &crate::xml::theme::DecodeCtColor) -> XlsxCtColor {
2523 XlsxCtColor {
2524 scrgb_clr: c.scrgb_clr.clone(),
2525 srgb_clr: c.srgb_clr.clone(),
2526 hsl_clr: c.hsl_clr.clone(),
2527 sys_clr: c.sys_clr.clone().map(|s| XlsxSysClr {
2528 val: s.val,
2529 last_clr: s.last_clr,
2530 }),
2531 scheme_clr: c.scheme_clr.clone(),
2532 prst_clr: c.prst_clr.clone(),
2533 }
2534 }
2535 fn convert_font(c: &crate::xml::theme::DecodeFontCollection) -> XlsxFontCollection {
2536 XlsxFontCollection {
2537 latin: c.latin.clone(),
2538 ea: c.ea.clone(),
2539 cs: c.cs.clone(),
2540 font: c.font.clone(),
2541 ext_lst: c.ext_lst.clone(),
2542 }
2543 }
2544 XlsxTheme {
2545 xmlns_a: None,
2546 xmlns_r: None,
2547 name: theme.name.clone(),
2548 theme_elements: XlsxBaseStyles {
2549 clr_scheme: crate::xml::theme::XlsxColorScheme {
2550 name: theme.theme_elements.clr_scheme.name.clone(),
2551 dk1: convert_color(&theme.theme_elements.clr_scheme.dk1),
2552 lt1: convert_color(&theme.theme_elements.clr_scheme.lt1),
2553 dk2: convert_color(&theme.theme_elements.clr_scheme.dk2),
2554 lt2: convert_color(&theme.theme_elements.clr_scheme.lt2),
2555 accent1: convert_color(&theme.theme_elements.clr_scheme.accent1),
2556 accent2: convert_color(&theme.theme_elements.clr_scheme.accent2),
2557 accent3: convert_color(&theme.theme_elements.clr_scheme.accent3),
2558 accent4: convert_color(&theme.theme_elements.clr_scheme.accent4),
2559 accent5: convert_color(&theme.theme_elements.clr_scheme.accent5),
2560 accent6: convert_color(&theme.theme_elements.clr_scheme.accent6),
2561 hlink: convert_color(&theme.theme_elements.clr_scheme.hlink),
2562 fol_hlink: convert_color(&theme.theme_elements.clr_scheme.fol_hlink),
2563 ext_lst: theme.theme_elements.clr_scheme.ext_lst.clone(),
2564 },
2565 font_scheme: crate::xml::theme::XlsxFontScheme {
2566 name: theme.theme_elements.font_scheme.name.clone(),
2567 major_font: convert_font(&theme.theme_elements.font_scheme.major_font),
2568 minor_font: convert_font(&theme.theme_elements.font_scheme.minor_font),
2569 ext_lst: theme.theme_elements.font_scheme.ext_lst.clone(),
2570 },
2571 fmt_scheme: crate::xml::theme::XlsxStyleMatrix {
2572 name: theme.theme_elements.fmt_scheme.name.clone(),
2573 fill_style_lst: theme.theme_elements.fmt_scheme.fill_style_lst.clone(),
2574 ln_style_lst: theme.theme_elements.fmt_scheme.ln_style_lst.clone(),
2575 effect_style_lst: theme.theme_elements.fmt_scheme.effect_style_lst.clone(),
2576 bg_fill_style_lst: theme.theme_elements.fmt_scheme.bg_fill_style_lst.clone(),
2577 },
2578 ext_lst: theme.theme_elements.ext_lst.clone(),
2579 },
2580 object_defaults: theme.object_defaults.clone(),
2581 extra_clr_scheme_lst: theme.extra_clr_scheme_lst.clone(),
2582 cust_clr_lst: theme.cust_clr_lst.clone(),
2583 ext_lst: theme.ext_lst.clone(),
2584 }
2585}
2586
2587pub fn set_content_type_part_project_extensions(file: &File, content_type: &str) -> Result<()> {
2593 file.set_content_type_part_project_extensions(content_type)
2594}
2595
2596pub fn add_vba_project(file: &File, data: &[u8]) -> Result<()> {
2598 file.add_vba_project(data)
2599}
2600
2601fn extract_ext_lst(data: &[u8]) -> Option<String> {
2607 let s = String::from_utf8_lossy(data);
2608 let start_key = "<extLst";
2609 let start = s.find(start_key)?;
2610 let close_bracket = s[start..].find('>')? + start + 1;
2611 let end_key = "</extLst>";
2612 let end = s.find(end_key)? + end_key.len();
2613 Some(s[close_bracket..end - end_key.len()].to_string())
2614}
2615
2616fn remove_ext_lst(data: &mut Vec<u8>) {
2619 let s = String::from_utf8_lossy(data);
2620 let Some(start) = s.find("<extLst") else {
2621 return;
2622 };
2623 let Some(end) = s.find("</extLst>") else {
2624 return;
2625 };
2626 let end = end + "</extLst>".len();
2627 let mut result = s[..start].as_bytes().to_vec();
2628 result.extend_from_slice(s[end..].as_bytes());
2629 *data = result;
2630}
2631
2632fn inject_ext_lst(output: &mut Vec<u8>, ext_xml: &str) {
2635 let s = String::from_utf8_lossy(output);
2636 let Some(pos) = s.rfind("</worksheet>") else {
2637 return;
2638 };
2639 let mut result = s[..pos].as_bytes().to_vec();
2640 result.extend_from_slice(b"<extLst>");
2641 result.extend_from_slice(ext_xml.as_bytes());
2642 result.extend_from_slice(b"</extLst>");
2643 result.extend_from_slice(s[pos..].as_bytes());
2644 *output = result;
2645}
2646
2647impl Drop for File {
2648 fn drop(&mut self) {
2649 for entry in self.temp_files.iter() {
2652 let _ = fs::remove_file(entry.value());
2653 }
2654 self.temp_files.clear();
2655 for state in self.streams.borrow().values() {
2656 let _ = fs::remove_file(&state.tmp_path);
2657 }
2658 self.streams.borrow_mut().clear();
2659 }
2660}
2661
2662#[cfg(test)]
2663mod tests {
2664 use super::*;
2665
2666 #[test]
2667 fn new_file_round_trip() {
2668 let mut f = File::new_with_options(Options::default());
2669 let tmp = std::env::temp_dir().join("excelize_rust_test.xlsx");
2670 f.save_as(tmp.to_str().unwrap()).unwrap();
2671
2672 let file = fs::File::open(&tmp).unwrap();
2673 let archive = ZipArchive::new(file).unwrap();
2674 let names: Vec<String> = archive.file_names().map(|s| s.to_string()).collect();
2675 assert!(names.contains(&"[Content_Types].xml".to_string()));
2676 assert!(names.contains(&"xl/workbook.xml".to_string()));
2677 assert!(names.contains(&"xl/worksheets/sheet1.xml".to_string()));
2678 assert!(names.contains(&"xl/styles.xml".to_string()));
2679 assert!(names.contains(&"xl/theme/theme1.xml".to_string()));
2680 drop(archive);
2681 let _ = fs::remove_file(&tmp);
2682 }
2683
2684 #[test]
2685 fn save_and_reopen() {
2686 let mut f = File::new_with_options(Options::default());
2687 let tmp = std::env::temp_dir().join("excelize_rust_reopen_test.xlsx");
2688 f.save_as(tmp.to_str().unwrap()).unwrap();
2689
2690 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
2691 assert_eq!(*f2.sheet_count.lock().unwrap(), 1);
2692 assert_eq!(f2.get_sheet_list(), vec!["Sheet1"]);
2693 let _ = fs::remove_file(&tmp);
2694 }
2695
2696 #[test]
2697 fn open_existing_xlsx() {
2698 let path = "test/Book1.xlsx";
2700 let f = File::open_file(path, Options::default()).unwrap();
2701 assert!(*f.sheet_count.lock().unwrap() >= 1);
2702 let list = f.get_sheet_list();
2703 assert!(!list.is_empty());
2704 }
2705
2706 #[test]
2707 fn workbook_props_round_trip() {
2708 let f = File::new();
2709 let mut opts = WorkbookPropsOptions::default();
2710 opts.date1904 = Some(true);
2711 opts.filter_privacy = Some(false);
2712 opts.code_name = Some("Workbook1".to_string());
2713 f.set_workbook_props(&opts).unwrap();
2714
2715 let got = f.get_workbook_props().unwrap();
2716 assert_eq!(got.date1904, Some(true));
2717 assert_eq!(got.filter_privacy, Some(false));
2718 assert_eq!(got.code_name, Some("Workbook1".to_string()));
2719 }
2720
2721 #[test]
2722 fn calc_props_round_trip() {
2723 let f = File::new();
2724 let mut opts = CalcPropsOptions::default();
2725 opts.calc_mode = Some("manual".to_string());
2726 opts.ref_mode = Some("R1C1".to_string());
2727 opts.full_calc_on_load = Some(true);
2728 opts.calc_id = Some(152511);
2729 opts.iterate_count = Some(100);
2730 f.set_calc_props(&opts).unwrap();
2731
2732 let got = f.get_calc_props().unwrap();
2733 assert_eq!(got.calc_mode, Some("manual".to_string()));
2734 assert_eq!(got.ref_mode, Some("R1C1".to_string()));
2735 assert_eq!(got.full_calc_on_load, Some(true));
2736 assert_eq!(got.calc_id, Some(152511));
2737 assert_eq!(got.iterate_count, Some(100));
2738 }
2739
2740 #[test]
2741 fn calc_props_rejects_invalid_mode() {
2742 let f = File::new();
2743 let mut opts = CalcPropsOptions::default();
2744 opts.calc_mode = Some("invalid".to_string());
2745 assert!(f.set_calc_props(&opts).is_err());
2746
2747 opts.calc_mode = None;
2748 opts.ref_mode = Some("B3".to_string());
2749 assert!(f.set_calc_props(&opts).is_err());
2750 }
2751
2752 #[test]
2753 fn protect_workbook_round_trip() {
2754 let f = File::new();
2755 let opts = WorkbookProtectionOptions {
2756 password: "password".to_string(),
2757 lock_structure: true,
2758 lock_windows: false,
2759 ..Default::default()
2760 };
2761 f.protect_workbook(&opts).unwrap();
2762 let wb = f.workbook_reader().unwrap();
2763 let protection = wb.workbook_protection.as_ref().unwrap();
2764 assert_eq!(protection.lock_structure, Some(true));
2765 assert!(protection.workbook_hash_value.is_some());
2766
2767 assert!(f.unprotect_workbook(Some("wrong")).is_err());
2768 f.unprotect_workbook(Some("password")).unwrap();
2769 assert!(f.workbook_reader().unwrap().workbook_protection.is_none());
2770 }
2771
2772 #[test]
2773 fn add_vba_project() {
2774 let f = File::new();
2775 let mut sheet_opts = crate::sheet::SheetPropsOptions::default();
2776 sheet_opts.code_name = Some("Sheet1".to_string());
2777 f.set_sheet_props("Sheet1", &sheet_opts).unwrap();
2778
2779 let bad = fs::read("test/Book1.xlsx").unwrap();
2780 assert!(f.add_vba_project(&bad).is_err());
2781
2782 let data = fs::read("test/vbaProject.bin").unwrap();
2783 f.add_vba_project(&data).unwrap();
2784 f.add_vba_project(&data).unwrap();
2786
2787 let rels = f
2788 .rels_reader("xl/_rels/workbook.xml.rels")
2789 .unwrap()
2790 .unwrap();
2791 let vba_rel = rels
2792 .relationships
2793 .iter()
2794 .find(|r| r.target == "vbaProject.bin" && r.r#type == SOURCE_RELATIONSHIP_VBA_PROJECT);
2795 assert!(vba_rel.is_some());
2796
2797 let tmp = std::env::temp_dir().join("excelize_rust_vba.xlsm");
2798 let mut f2 = File::new();
2799 f2.set_sheet_props("Sheet1", &sheet_opts).unwrap();
2800 f2.add_vba_project(&data).unwrap();
2801 f2.save_as(tmp.to_str().unwrap()).unwrap();
2802
2803 let f3 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
2804 assert!(f3.pkg.contains_key("xl/vbaProject.bin"));
2805 let _ = fs::remove_file(&tmp);
2806 }
2807
2808 #[test]
2809 fn charset_transcoder_is_used_for_non_utf8_encoding() {
2810 use crate::constants::DEFAULT_XML_PATH_CALC_CHAIN;
2811 use std::io::Read;
2812 use std::sync::{Arc, Mutex};
2813
2814 let f = File::new();
2815 let xml = br#"<?xml version="1.0" encoding="X-TEST" standalone="yes"?>
2816<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><c r="A1" i="1"/></calcChain>"#;
2817 f.pkg
2818 .insert(DEFAULT_XML_PATH_CALC_CHAIN.to_string(), xml.to_vec());
2819 *f.calc_chain.lock().unwrap() = None;
2820
2821 let called = Arc::new(Mutex::new(String::new()));
2822 let called_clone = called.clone();
2823 f.charset_transcoder(move |charset, mut input| {
2824 *called_clone.lock().unwrap() = charset.to_string();
2825 let mut buf = Vec::new();
2826 input.read_to_end(&mut buf).unwrap();
2827 Ok(Box::new(Cursor::new(buf)) as Box<dyn Read>)
2828 });
2829
2830 let cc = f.calc_chain_reader().unwrap();
2831 assert_eq!(cc.c.len(), 1);
2832 assert_eq!(cc.c[0].r, "A1");
2833 assert_eq!(called.lock().unwrap().as_str(), "X-TEST");
2834 }
2835
2836 #[test]
2837 fn charset_transcoder_handles_invalid_utf8_bytes() {
2838 use crate::constants::DEFAULT_XML_PATH_CALC_CHAIN;
2839 use std::io::Read;
2840 use std::sync::{Arc, Mutex};
2841
2842 let f = File::new();
2843 let mut xml = br#"<?xml version="1.0" encoding="X-BAD" standalone="yes"?>
2844<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><c r="A1" i="1"/></calcChain>"#
2845 .to_vec();
2846 xml.push(0xFF); f.pkg.insert(DEFAULT_XML_PATH_CALC_CHAIN.to_string(), xml);
2848 *f.calc_chain.lock().unwrap() = None;
2849
2850 let called = Arc::new(Mutex::new(false));
2851 let called_clone = called.clone();
2852 f.charset_transcoder(move |_charset, mut input| {
2853 *called_clone.lock().unwrap() = true;
2854 let mut buf = Vec::new();
2855 input.read_to_end(&mut buf).unwrap();
2856 buf.pop(); Ok(Box::new(Cursor::new(buf)) as Box<dyn Read>)
2858 });
2859
2860 let cc = f.calc_chain_reader().unwrap();
2861 assert_eq!(cc.c.len(), 1);
2862 assert_eq!(cc.c[0].r, "A1");
2863 assert!(*called.lock().unwrap());
2864 }
2865
2866 #[test]
2867 fn set_zip_writer_uses_custom_factory() {
2868 use std::sync::{Arc, Mutex};
2869
2870 struct MockZipWriter {
2871 calls: Arc<Mutex<Vec<String>>>,
2872 }
2873
2874 impl ZipWriter for MockZipWriter {
2875 fn start_file(&mut self, name: &str, _options: SimpleFileOptions) -> Result<()> {
2876 self.calls.lock().unwrap().push(format!("start:{name}"));
2877 Ok(())
2878 }
2879 fn write_all(&mut self, _buf: &[u8]) -> Result<()> {
2880 self.calls.lock().unwrap().push("write".to_string());
2881 Ok(())
2882 }
2883 fn finish(self: Box<Self>) -> Result<()> {
2884 self.calls.lock().unwrap().push("finish".to_string());
2885 Ok(())
2886 }
2887 }
2888
2889 let f = File::new();
2890 let calls = Arc::new(Mutex::new(Vec::new()));
2891 let calls_clone = calls.clone();
2892 f.set_zip_writer(move |_writer| {
2893 Box::new(MockZipWriter {
2894 calls: calls_clone.clone(),
2895 })
2896 });
2897
2898 let _ = f.write_to_buffer().unwrap();
2899 let calls = calls.lock().unwrap();
2900 assert!(calls.iter().any(|c| c.starts_with("start:")));
2901 assert!(calls.contains(&"write".to_string()));
2902 assert!(calls.contains(&"finish".to_string()));
2903 }
2904
2905 #[test]
2906 fn get_defined_name_alias() {
2907 let f = File::new();
2908 let names = f.get_defined_name().unwrap();
2909 assert!(names.is_empty());
2910 assert_eq!(f.get_defined_names().unwrap(), names);
2911 }
2912
2913 #[test]
2914 fn update_linked_value_clears_formula_cache() {
2915 let f = File::new_with_options(Options::default());
2916 f.set_cell_int("Sheet1", "A1", 1).unwrap();
2917 f.set_cell_int("Sheet1", "A2", 2).unwrap();
2918 f.set_cell_formula("Sheet1", "A3", "A1+A2").unwrap();
2919 f.calc_cell_value("Sheet1", "A3").unwrap();
2920 f.update_linked_value().unwrap();
2921 assert!(f.workbook_reader().unwrap().calc_pr.is_none());
2922 }
2923
2924 #[test]
2925 fn read_zip_reader_extracts_parts() {
2926 let mut f = File::new_with_options(Options::default());
2927 let tmp = std::env::temp_dir().join("excelize_rust_read_zip_reader.xlsx");
2928 f.save_as(tmp.to_str().unwrap()).unwrap();
2929
2930 let file = fs::File::open(&tmp).unwrap();
2931 let mut archive = ZipArchive::new(file).unwrap();
2932 let f2 = File::new_with_options(Options::default());
2933 let (parts, count) = f2.read_zip_reader(&mut archive).unwrap();
2934 assert!(parts.contains_key(DEFAULT_XML_PATH_WORKBOOK));
2935 assert_eq!(count, 1);
2936 let _ = fs::remove_file(&tmp);
2937 }
2938
2939 #[test]
2940 fn set_content_type_part_rels_extensions_adds_default() {
2941 let f = File::new();
2942 f.set_content_type_part_rels_extensions().unwrap();
2943 let ct = f.content_types_reader().unwrap();
2944 assert!(ct.entries.iter().any(|e| {
2945 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2946 d.extension == "rels" && d.content_type == CONTENT_TYPE_RELATIONSHIPS
2947 } else {
2948 false
2949 }
2950 }));
2951 }
2952
2953 #[test]
2954 fn add_content_type_part_adds_rels_default() {
2955 let f = File::new();
2956 f.add_content_type_part(1, "table").unwrap();
2957 let ct = f.content_types_reader().unwrap();
2958 assert!(ct.entries.iter().any(|e| {
2959 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2960 d.extension == "rels"
2961 } else {
2962 false
2963 }
2964 }));
2965 }
2966
2967 #[test]
2968 fn set_content_type_part_image_extensions_uses_defaults() {
2969 let f = File::new();
2970 crate::sheet::set_content_type_part_image_extensions(&f).unwrap();
2971 let ct = f.content_types_reader().unwrap();
2972 assert!(ct.entries.iter().any(|e| {
2973 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2974 d.extension == "png" && d.content_type == "image/png"
2975 } else {
2976 false
2977 }
2978 }));
2979 assert!(ct.entries.iter().any(|e| {
2980 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2981 d.extension == "jpeg" && d.content_type == "image/jpeg"
2982 } else {
2983 false
2984 }
2985 }));
2986 assert!(!ct.entries.iter().any(|e| {
2988 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = e {
2989 o.part_name.contains("image1")
2990 } else {
2991 false
2992 }
2993 }));
2994 }
2995
2996 #[test]
2997 fn add_name_spaces_registers_namespace() {
2998 let f = File::new();
2999 let path = f.get_sheet_xml_path("Sheet1").unwrap();
3000 f.add_name_spaces(&path, SOURCE_RELATIONSHIP);
3001 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
3002 assert!(attrs.contains("xmlns:r=\""));
3003 assert!(attrs.contains(SOURCE_RELATIONSHIP));
3004 }
3005
3006 #[test]
3007 fn add_name_spaces_marks_extension_namespace_ignorable() {
3008 let f = File::new();
3009 let path = f.get_sheet_xml_path("Sheet1").unwrap();
3010 f.add_name_spaces(&path, NAMESPACE_SPREADSHEET_X14);
3011 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
3012 assert!(attrs.contains("xmlns:x14=\""));
3013 assert!(attrs.contains("xmlns:mc=\""));
3014 assert!(attrs.contains("mc:Ignorable=\"x14\""));
3015 }
3016
3017 #[test]
3018 fn add_sheet_name_space_resolves_path() {
3019 let f = File::new();
3020 f.add_sheet_name_space("Sheet1", SOURCE_RELATIONSHIP);
3021 let path = f.get_sheet_xml_path("Sheet1").unwrap();
3022 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
3023 assert!(attrs.contains("xmlns:r=\""));
3024 }
3025
3026 #[test]
3027 fn workbook_writer_preserves_alternate_content() {
3028 let f = File::new();
3029 let mut wb = f.workbook_reader().unwrap();
3030 wb.decode_alternate_content = Some(crate::xml::common::XlsxInnerXml {
3031 content: "<mc:Choice Requires=\"a14\" xmlns:a14=\"http://schemas.microsoft.com/office/drawing/2010/main\"><foo/></mc:Choice>".to_string(),
3032 });
3033 *f.workbook.lock().unwrap() = Some(wb);
3034
3035 f.workbook_writer();
3036
3037 let path = f.get_workbook_path();
3038 let bytes = f.read_xml(&path);
3039 let output = String::from_utf8_lossy(&bytes);
3040 assert!(output.contains("mc:AlternateContent"));
3041 assert!(output.contains("mc:Choice"));
3042 }
3043}