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