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 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 *self.workbook.lock().unwrap() = Some(wb);
679 }
680 Ok(self.workbook.lock().unwrap().clone().unwrap())
681 }
682
683 pub fn theme_reader(&self) -> Result<Option<DecodeTheme>> {
687 if self.theme.lock().unwrap().is_none() {
688 if self.pkg.contains_key(DEFAULT_XML_PATH_THEME)
689 || self.temp_files.contains_key(DEFAULT_XML_PATH_THEME)
690 {
691 let data = self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_THEME))?;
692 let data = namespace_strict_to_transitional(&data);
693 if !data.is_empty() {
694 if let Some(attrs) = extract_root_namespace_attributes(&data) {
695 self.xml_attr
696 .insert(DEFAULT_XML_PATH_THEME.to_string(), attrs);
697 }
698 if let Ok(theme) = xml_from_reader::<_, DecodeTheme>(data.as_slice()) {
699 *self.theme.lock().unwrap() = Some(theme);
700 }
701 }
702 }
703 }
704 Ok(self.theme.lock().unwrap().clone())
705 }
706
707 pub fn shared_strings_reader(&self) -> Result<crate::xml::shared_strings::XlsxSst> {
709 if self.shared_strings.lock().unwrap().is_none() {
710 let mut sst = crate::xml::shared_strings::XlsxSst::default();
711 if self.pkg.contains_key(DEFAULT_XML_PATH_SHARED_STRINGS)
712 || self
713 .temp_files
714 .contains_key(DEFAULT_XML_PATH_SHARED_STRINGS)
715 {
716 let data = self
717 .apply_charset_transcoder(&self.read_bytes(DEFAULT_XML_PATH_SHARED_STRINGS))?;
718 let data = namespace_strict_to_transitional(&data);
719 if !data.is_empty() {
720 sst = xml_from_reader(data.as_slice()).unwrap_or_default();
721 }
722 }
723 let mut map = self.shared_strings_map.lock().unwrap();
724 map.clear();
725 for (i, si) in sst.si.iter().enumerate() {
726 let text = if let Some(t) = &si.t {
727 t.val.clone()
728 } else {
729 si.r.iter()
730 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
731 .collect()
732 };
733 map.insert(text, i as i32);
734 }
735 *self.shared_strings.lock().unwrap() = Some(sst);
736 }
737 Ok(self.shared_strings.lock().unwrap().clone().unwrap())
738 }
739
740 pub fn rels_reader(&self, path: &str) -> Result<Option<XlsxRelationships>> {
742 if let Some(rels) = self.relationships.get(path) {
743 return Ok(Some(rels.clone()));
744 }
745 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
746 let data = self.apply_charset_transcoder(&self.read_xml(path))?;
747 let data = namespace_strict_to_transitional(&data);
748 let rels = if data.is_empty() {
749 XlsxRelationships::default()
750 } else {
751 if let Some(attrs) = extract_root_namespace_attributes(&data) {
752 self.xml_attr.insert(path.to_string(), attrs);
753 }
754 xml_from_reader(data.as_slice()).unwrap_or_default()
755 };
756 self.relationships.insert(path.to_string(), rels.clone());
757 Ok(Some(rels))
758 } else {
759 Ok(None)
760 }
761 }
762
763 pub fn metadata_reader(&self) -> Result<crate::xml::metadata::XlsxMetadata> {
765 let data = namespace_strict_to_transitional(
766 &self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_METADATA))?,
767 );
768 if data.is_empty() {
769 return Ok(crate::xml::metadata::XlsxMetadata::default());
770 }
771 Ok(xml_from_reader(data.as_slice())?)
772 }
773
774 pub fn rich_value_reader(&self) -> Result<crate::xml::metadata::XlsxRichValueData> {
776 let data = namespace_strict_to_transitional(
777 &self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE))?,
778 );
779 if data.is_empty() {
780 return Ok(crate::xml::metadata::XlsxRichValueData::default());
781 }
782 Ok(xml_from_reader(data.as_slice())?)
783 }
784
785 pub fn rich_value_rel_reader(&self) -> Result<crate::xml::metadata::XlsxRichValueRels> {
787 let data = namespace_strict_to_transitional(
788 &self.apply_charset_transcoder(&self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_REL))?,
789 );
790 if data.is_empty() {
791 return Ok(crate::xml::metadata::XlsxRichValueRels::default());
792 }
793 Ok(xml_from_reader(data.as_slice())?)
794 }
795
796 pub fn rich_value_structures_reader(
798 &self,
799 ) -> Result<crate::xml::metadata::XlsxRichValueStructures> {
800 let data =
801 namespace_strict_to_transitional(&self.apply_charset_transcoder(
802 &self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_STRUCTURE),
803 )?);
804 if data.is_empty() {
805 return Ok(crate::xml::metadata::XlsxRichValueStructures::default());
806 }
807 Ok(xml_from_reader(data.as_slice())?)
808 }
809
810 pub fn rich_value_web_image_reader(
812 &self,
813 ) -> Result<crate::xml::metadata::XlsxWebImagesSupportingRichData> {
814 let data =
815 namespace_strict_to_transitional(&self.apply_charset_transcoder(
816 &self.read_xml(DEFAULT_XML_PATH_RD_RICH_VALUE_WEB_IMAGE),
817 )?);
818 if data.is_empty() {
819 return Ok(crate::xml::metadata::XlsxWebImagesSupportingRichData::default());
820 }
821 Ok(xml_from_reader(data.as_slice())?)
822 }
823
824 pub(crate) fn get_rich_data_rich_value_rel_relationship(
826 &self,
827 r_id: &str,
828 ) -> Option<XlsxRelationship> {
829 self.rels_reader(DEFAULT_XML_PATH_RD_RICH_VALUE_REL_RELS)
830 .ok()
831 .flatten()
832 .and_then(|rels| rels.relationships.into_iter().find(|rel| rel.id == r_id))
833 }
834
835 pub(crate) fn get_rich_value_web_image_relationship(
837 &self,
838 r_id: &str,
839 ) -> Option<XlsxRelationship> {
840 self.rels_reader(DEFAULT_XML_PATH_RD_RICH_VALUE_WEB_IMAGE_RELS)
841 .ok()
842 .flatten()
843 .and_then(|rels| rels.relationships.into_iter().find(|rel| rel.id == r_id))
844 }
845
846 pub fn set_rels(
848 &self,
849 r_id: &str,
850 rel_path: &str,
851 rel_type: &str,
852 target: &str,
853 target_mode: &str,
854 ) -> i32 {
855 if r_id.is_empty() {
856 return self.add_rels(rel_path, rel_type, target, target_mode);
857 }
858 let mut rels = self
859 .rels_reader(rel_path)
860 .unwrap_or_default()
861 .unwrap_or_default();
862 let mut out_id = 0;
863 for rel in &mut rels.relationships {
864 if rel.id == r_id {
865 rel.r#type = rel_type.to_string();
866 rel.target = target.to_string();
867 rel.target_mode = Some(target_mode.to_string());
868 out_id = r_id.trim_start_matches("rId").parse().unwrap_or(0);
869 break;
870 }
871 }
872 self.relationships.insert(rel_path.to_string(), rels);
873 out_id
874 }
875
876 pub fn add_rels(&self, rel_path: &str, rel_type: &str, target: &str, target_mode: &str) -> i32 {
878 let uniq_part: HashMap<String, String> = [
879 (
880 SOURCE_RELATIONSHIP_CUSTOM_PROPERTIES.to_string(),
881 "/docProps/custom.xml".to_string(),
882 ),
883 (
884 SOURCE_RELATIONSHIP_SHARED_STRINGS.to_string(),
885 "/xl/sharedStrings.xml".to_string(),
886 ),
887 ]
888 .into_iter()
889 .collect();
890
891 let mut rels = self
892 .rels_reader(rel_path)
893 .unwrap_or_default()
894 .unwrap_or_default();
895 let mut r_id = 0;
896 for rel in &rels.relationships {
897 let id: i32 = rel.id.trim_start_matches("rId").parse().unwrap_or(0);
898 if id > r_id {
899 r_id = id;
900 }
901 if rel.r#type == rel_type {
902 if let Some(part_name) = uniq_part.get(&rel.r#type) {
903 let _ = part_name;
905 }
906 }
907 }
908 r_id += 1;
909 let new_id = format!("rId{r_id}");
910 rels.relationships.push(XlsxRelationship {
911 id: new_id,
912 r#type: rel_type.to_string(),
913 target: target.to_string(),
914 target_mode: Some(target_mode.to_string()),
915 });
916 self.relationships.insert(rel_path.to_string(), rels);
917 r_id
918 }
919
920 pub fn get_workbook_path(&self) -> String {
922 if let Ok(Some(rels)) = self.rels_reader(DEFAULT_XML_PATH_RELS) {
923 for rel in &rels.relationships {
924 if rel.r#type == crate::constants::SOURCE_RELATIONSHIP_OFFICE_DOCUMENT {
925 return rel.target.trim_start_matches('/').to_string();
926 }
927 }
928 }
929 DEFAULT_XML_PATH_WORKBOOK.to_string()
930 }
931
932 pub fn get_workbook_rels_path(&self) -> String {
934 let wb = self.get_workbook_path();
935 let wb_dir = Path::new(&wb).parent().unwrap_or(Path::new("."));
936 let wb_base = Path::new(&wb).file_name().unwrap_or_default();
937 if wb_dir == Path::new(".") {
938 return format!("_rels/{}", wb_base.to_string_lossy()) + ".rels";
939 }
940 format!(
941 "{}/_rels/{}.rels",
942 wb_dir.to_string_lossy(),
943 wb_base.to_string_lossy()
944 )
945 .trim_start_matches('/')
946 .to_string()
947 }
948
949 pub fn get_worksheet_path(&self, rel_target: &str) -> String {
951 let wb_path = self.get_workbook_path();
952 let wb_dir = Path::new(&wb_path).parent().unwrap_or(Path::new("."));
953 let mut combined = wb_dir.to_path_buf();
954 for c in rel_target.split('/') {
955 if c == ".." {
956 combined.pop();
957 } else if !c.is_empty() && c != "." {
958 combined.push(c);
959 }
960 }
961 let mut s = combined.to_string_lossy().replace('\\', "/");
962 s = s.trim_start_matches('/').to_string();
963 if rel_target.starts_with('/') {
964 s = Path::new(rel_target)
965 .to_string_lossy()
966 .replace('\\', "/")
967 .trim_start_matches('/')
968 .to_string();
969 }
970 s
971 }
972
973 pub fn get_sheet_xml_path(&self, sheet: &str) -> Option<String> {
975 let map = self.sheet_map.lock().unwrap();
976 for (name, path) in map.iter() {
977 if name.eq_ignore_ascii_case(sheet) {
978 return Some(path.clone());
979 }
980 }
981 None
982 }
983
984 pub fn work_sheet_reader(&self, sheet: &str) -> Result<XlsxWorksheet> {
986 crate::excelize::check_sheet_name(sheet)?;
987 let name = self.get_sheet_xml_path(sheet).ok_or_else(|| {
988 Box::new(crate::errors::ErrSheetNotExist {
989 sheet_name: sheet.to_string(),
990 }) as Box<dyn std::error::Error + Send + Sync>
991 })?;
992 if let Some(ws) = self.sheet.get(&name) {
993 return Ok(ws.clone());
994 }
995 let data = self.apply_charset_transcoder(&self.read_bytes(&name))?;
996 let data = namespace_strict_to_transitional(&data);
997 let mut data = data;
998 let ext_lst = extract_ext_lst(&data);
999 if ext_lst.is_some() {
1000 remove_ext_lst(&mut data);
1001 }
1002 let mut ws: XlsxWorksheet = xml_from_reader(data.as_slice())?;
1003 if let Some(xml) = ext_lst {
1004 ws.ext_lst = Some(crate::xml::common::parse_ext_lst_content(&xml)?);
1005 }
1006 if !self.checked.contains_key(&name) {
1007 self.checked.insert(name.clone(), true);
1009 }
1010 self.sheet.insert(name.clone(), ws.clone());
1011 Ok(ws)
1012 }
1013
1014 pub(crate) fn check_open_reader_options(&self) -> Result<()> {
1019 let mut opts = self.options.lock().unwrap();
1020 if opts.unzip_size_limit == 0 {
1021 opts.unzip_size_limit = opts.unzip_xml_size_limit.max(UNZIP_SIZE_LIMIT);
1022 }
1023 if opts.unzip_xml_size_limit == 0 {
1024 opts.unzip_xml_size_limit = opts.unzip_size_limit.min(STREAM_CHUNK_SIZE);
1025 }
1026 if opts.unzip_xml_size_limit > opts.unzip_size_limit {
1027 return Err(Box::new(ErrOptionsUnzipSizeLimit));
1028 }
1029 let patterns = [
1030 opts.short_date_pattern.clone(),
1031 opts.long_date_pattern.clone(),
1032 opts.long_time_pattern.clone(),
1033 ];
1034 drop(opts);
1035 self.check_date_time_pattern(&patterns)
1036 }
1037
1038 fn check_date_time_pattern(&self, patterns: &[String]) -> Result<()> {
1039 for pattern in patterns {
1040 if !pattern.is_empty() && !numfmt::is_date_time_pattern(pattern) {
1041 return Err(Box::new(crate::errors::ErrUnsupportedNumberFormat));
1042 }
1043 }
1044 Ok(())
1045 }
1046
1047 pub fn write_to_buffer(&self) -> Result<Vec<u8>> {
1053 let mut buf = Cursor::new(Vec::new());
1054 let factory = self.zip_writer_factory.lock().unwrap().clone();
1055 if let Some(factory) = factory {
1056 let mut zw = factory(&mut buf);
1057 self.write_to_zip(&mut *zw)?;
1058 zw.finish()?;
1059 } else {
1060 let mut zw = Box::new(zip::ZipWriter::new(&mut buf));
1061 self.write_to_zip(&mut *zw)?;
1062 zw.finish()?;
1063 }
1064 let mut inner = buf.into_inner();
1065 self.write_zip64_lfh(&mut inner)?;
1066 let opts = self.options.lock().unwrap().clone();
1067 if !opts.password.is_empty() {
1068 inner = crypt::encrypt(&inner, &opts)?;
1069 }
1070 Ok(inner)
1071 }
1072
1073 fn write_to_zip(&self, zw: &mut dyn ZipWriter) -> Result<()> {
1074 self.calc_chain_writer();
1075 self.comments_writer();
1076 self.shared_strings_registrar();
1077 self.content_types_writer();
1078 self.drawings_writer();
1079 self.volatile_deps_writer();
1080 self.vml_drawing_writer();
1081 self.workbook_writer();
1082 self.work_sheet_writer();
1083 self.rels_writer();
1084 let _ = self.shared_strings_loader();
1085 self.shared_strings_writer();
1086 self.style_sheet_writer();
1087 self.theme_writer();
1088
1089 let mut files: Vec<String> = self.pkg.iter().map(|e| e.key().clone()).collect();
1091 files.sort_unstable_by(|a, b| b.cmp(a));
1092 for path in files {
1093 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1094 zw.start_file(&path, opts)?;
1095 let content = self.read_xml(&path);
1096 zw.write_all(&content)?;
1097 if content.len() as u64 > u32::MAX as u64 {
1098 self.zip64_entries.lock().unwrap().push(path.clone());
1099 }
1100 }
1101
1102 let mut temp_files: Vec<String> = self
1104 .temp_files
1105 .iter()
1106 .filter(|e| !self.pkg.contains_key(e.key()))
1107 .map(|e| e.key().clone())
1108 .collect();
1109 temp_files.sort_unstable_by(|a, b| b.cmp(a));
1110 for path in temp_files {
1111 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1112 zw.start_file(&path, opts)?;
1113 let content = self.read_bytes(&path);
1114 zw.write_all(&content)?;
1115 if content.len() as u64 > u32::MAX as u64 {
1116 self.zip64_entries.lock().unwrap().push(path.clone());
1117 }
1118 }
1119
1120 let mut streams: Vec<(String, PathBuf)> = self
1123 .streams
1124 .borrow_mut()
1125 .drain()
1126 .map(|(path, state)| (path, state.tmp_path))
1127 .collect();
1128 streams.sort_unstable_by(|a, b| b.0.cmp(&a.0));
1129 for (path, tmp_path) in streams {
1130 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1131 zw.start_file(&path, opts)?;
1132 let mut file = fs::File::open(&tmp_path)?;
1133 let size = file.metadata()?.len();
1134 let mut buf = [0u8; 8192];
1135 loop {
1136 let n = file.read(&mut buf)?;
1137 if n == 0 {
1138 break;
1139 }
1140 zw.write_all(&buf[..n])?;
1141 }
1142 if size > u32::MAX as u64 {
1143 self.zip64_entries.lock().unwrap().push(path.clone());
1144 }
1145 let _ = fs::remove_file(&tmp_path);
1146 }
1147 Ok(())
1148 }
1149
1150 fn write_zip64_lfh(&self, buf: &mut [u8]) -> Result<()> {
1151 let entries = self.zip64_entries.lock().unwrap().clone();
1152 if entries.is_empty() {
1153 return Ok(());
1154 }
1155 let mut offset = 0usize;
1156 while offset < buf.len() {
1157 let window = &buf[offset..];
1158 let Some(idx) = find_subsequence(window, b"\x50\x4b\x03\x04") else {
1159 break;
1160 };
1161 let idx = idx + offset;
1162 if idx + 30 > buf.len() {
1163 break;
1164 }
1165 let filename_len = u16::from_le_bytes([buf[idx + 26], buf[idx + 27]]) as usize;
1166 if idx + 30 + filename_len > buf.len() {
1167 break;
1168 }
1169 let filename =
1170 std::str::from_utf8(&buf[idx + 30..idx + 30 + filename_len]).unwrap_or("");
1171 if in_str_slice(&entries, filename, true) != -1 {
1172 buf[idx + 4..idx + 6].copy_from_slice(&45u16.to_le_bytes());
1173 }
1174 offset = idx + 1;
1175 }
1176 Ok(())
1177 }
1178
1179 fn comments_writer(&self) {
1182 for entry in self.comments.iter() {
1183 let path = entry.key().clone();
1184 let comments = entry.value().clone();
1185 if let Ok(output) = quick_xml::se::to_string(&comments).map(|s| s.into_bytes()) {
1186 self.save_file_list(&path, &output);
1187 }
1188 }
1189 }
1190
1191 fn content_types_writer(&self) {
1192 if let Some(ct) = self.content_types.lock().unwrap().clone() {
1193 if let Ok(mut output) = xml_to_string(&ct).map(|s| s.into_bytes()) {
1194 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_CONTENT_TYPES, &mut output);
1195 self.save_file_list(DEFAULT_XML_PATH_CONTENT_TYPES, &output);
1196 }
1197 }
1198 }
1199
1200 fn drawings_writer(&self) {
1201 for entry in self.drawings.iter() {
1202 let path = entry.key().clone();
1203 let drawing = entry.value().clone();
1204 if let Ok(mut output) = xml_to_string(&drawing).map(|s| s.into_bytes()) {
1205 self.replace_namespace_bytes_if_needed(&path, &mut output);
1206 self.save_file_list(&path, &output);
1207 }
1208 }
1209 }
1210
1211 fn vml_drawing_writer(&self) {
1214 for entry in self.vml_drawing.iter() {
1215 let path = entry.key().clone();
1216 let drawing = entry.value().clone();
1217 let output = drawing.to_xml();
1218 self.pkg.insert(path, output);
1219 }
1220 }
1221
1222 fn workbook_writer(&self) {
1223 if let Some(mut wb) = self.workbook.lock().unwrap().clone() {
1224 if let Some(decode) = wb.decode_alternate_content.take() {
1228 wb.alternate_content = Some(crate::xml::workbook::XlsxAlternateContent {
1229 xmlns_mc: Some(
1230 "http://schemas.openxmlformats.org/markup-compatibility/2006".to_string(),
1231 ),
1232 content: decode.content,
1233 });
1234 }
1235 if let Ok(mut output) = xml_to_string(&wb).map(|s| s.into_bytes()) {
1236 let path = self.get_workbook_path();
1237 self.replace_namespace_bytes_if_needed(&path, &mut output);
1238 self.save_file_list(&path, &output);
1239 }
1240 }
1241 }
1242
1243 fn work_sheet_writer(&self) {
1244 for entry in self.sheet.iter() {
1245 let path = entry.key().clone();
1246 let sheet = entry.value().clone();
1247 if let Ok(mut output) = xml_to_string(&sheet).map(|s| s.into_bytes()) {
1248 self.replace_namespace_bytes_if_needed(&path, &mut output);
1249 strip_empty_attributes(&mut output);
1250 if let Some(ext_lst) = &sheet.ext_lst {
1251 let ext_xml = crate::xml::common::serialize_ext_lst(ext_lst);
1252 if !ext_xml.is_empty() {
1253 inject_ext_lst(&mut output, &ext_xml);
1254 }
1255 }
1256 self.save_file_list(&path, &output);
1257 }
1258 }
1259 }
1260
1261 fn rels_writer(&self) {
1262 for entry in self.relationships.iter() {
1263 let path = entry.key().clone();
1264 let rels = entry.value().clone();
1265 if let Ok(mut output) = xml_to_string(&rels).map(|s| s.into_bytes()) {
1266 self.replace_namespace_bytes_if_needed(&path, &mut output);
1267 self.save_file_list(&path, &output);
1268 }
1269 }
1270 }
1271
1272 fn shared_strings_loader(&self) -> Result<()> {
1273 if let Some(entry) = self.temp_files.get(DEFAULT_XML_PATH_SHARED_STRINGS) {
1274 let temp_path = entry.value().clone();
1275 drop(entry);
1276 let data = self.read_bytes(DEFAULT_XML_PATH_SHARED_STRINGS);
1277 if !data.is_empty() {
1278 self.pkg
1279 .insert(DEFAULT_XML_PATH_SHARED_STRINGS.to_string(), data);
1280 }
1281 self.temp_files.remove(DEFAULT_XML_PATH_SHARED_STRINGS);
1282 let _ = fs::remove_file(temp_path);
1283 *self.shared_strings.lock().unwrap() = None;
1284 self.shared_strings_map.lock().unwrap().clear();
1285 }
1286 Ok(())
1287 }
1288
1289 fn shared_strings_registrar(&self) {
1292 let has_strings = self.shared_strings.lock().unwrap().is_some()
1293 || self.temp_files.contains_key(DEFAULT_XML_PATH_SHARED_STRINGS);
1294 if !has_strings {
1295 return;
1296 }
1297 if let Ok(mut ct) = self.content_types_reader() {
1298 let part_name = format!("/{DEFAULT_XML_PATH_SHARED_STRINGS}");
1299 let exists = ct.entries.iter().any(|e| {
1300 matches!(e, crate::xml::content_types::XlsxContentTypeEntry::Override(o) if o.part_name == part_name)
1301 });
1302 if !exists {
1303 ct.entries
1304 .push(crate::xml::content_types::XlsxContentTypeEntry::Override(
1305 crate::xml::content_types::XlsxOverride {
1306 part_name,
1307 content_type:
1308 crate::constants::CONTENT_TYPE_SPREADSHEET_ML_SHARED_STRINGS
1309 .to_string(),
1310 },
1311 ));
1312 *self.content_types.lock().unwrap() = Some(ct);
1313 }
1314 }
1315 let rels_path = self.get_workbook_rels_path();
1316 let mut rels = self.rels_reader(&rels_path).unwrap_or_default().unwrap_or_default();
1317 crate::sheet::ensure_shared_strings_rel(&mut rels);
1318 self.relationships.insert(rels_path, rels);
1319 }
1320
1321 fn shared_strings_writer(&self) {
1322 if let Some(sst) = self.shared_strings.lock().unwrap().clone() {
1323 if let Ok(output) = xml_to_string(&sst).map(|s| s.into_bytes()) {
1324 self.save_file_list(DEFAULT_XML_PATH_SHARED_STRINGS, &output);
1325 }
1326 }
1327 }
1328
1329 fn style_sheet_writer(&self) {
1330 if let Some(st) = self.styles.lock().unwrap().clone() {
1331 if let Ok(mut output) = xml_to_string(&st).map(|s| s.into_bytes()) {
1332 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_STYLES, &mut output);
1333 strip_empty_attributes(&mut output);
1334 self.save_file_list(DEFAULT_XML_PATH_STYLES, &output);
1335 }
1336 }
1337 }
1338
1339 fn theme_writer(&self) {
1340 if let Some(theme) = self.theme.lock().unwrap().clone() {
1341 let serialized = decode_theme_to_xlsx_theme(&theme);
1342 if let Ok(mut output) = xml_to_string(&serialized).map(|s| s.into_bytes()) {
1343 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_THEME, &mut output);
1344 self.save_file_list(DEFAULT_XML_PATH_THEME, &output);
1345 }
1346 }
1347 }
1348
1349 pub(crate) fn replace_namespace_bytes_if_needed(&self, path: &str, content: &mut Vec<u8>) {
1354 if let Some(attrs) = self.xml_attr.get(path) {
1355 let _ = replace_root_namespace_attributes(content, attrs.value());
1356 }
1357 }
1358
1359 pub fn add_name_spaces(&self, path: &str, ns: &str) {
1366 let prefix = match ns {
1367 crate::constants::SOURCE_RELATIONSHIP => "r",
1368 crate::constants::NAMESPACE_SPREADSHEET_X14 => "x14",
1369 crate::constants::NAMESPACE_SPREADSHEET_X15 => "x15",
1370 crate::constants::NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN => "xm",
1371 crate::constants::NAMESPACE_DRAWING_ML_MAIN => "a",
1372 crate::constants::NAMESPACE_DRAWING_ML_CHART => "c",
1373 crate::constants::NAMESPACE_DRAWING_ML_SPREADSHEET => "xdr",
1374 crate::constants::NAMESPACE_DRAWING_ML_A14 => "a14",
1375 crate::constants::NAMESPACE_DRAWING_2016_SVG => "asvg",
1376 crate::constants::NAMESPACE_SPREADSHEET => return,
1378 _ => return,
1379 };
1380 if prefix.is_empty() {
1381 return;
1382 }
1383
1384 let attr_name = format!("xmlns:{prefix}");
1385 let mut attrs = self
1386 .xml_attr
1387 .get(path)
1388 .map(|a| a.clone())
1389 .unwrap_or_default();
1390 if !attrs.contains(&attr_name) {
1391 if !attrs.is_empty() {
1392 attrs.push(' ');
1393 }
1394 attrs.push_str(&format!("{attr_name}=\"{ns}\""));
1395 }
1396
1397 if self.needs_ignorable_prefix(prefix) {
1400 let mc_ns = "http://schemas.openxmlformats.org/markup-compatibility/2006";
1401 if !attrs.contains("xmlns:mc") {
1402 if !attrs.is_empty() && !attrs.ends_with(' ') {
1403 attrs.push(' ');
1404 }
1405 attrs.push_str(&format!("xmlns:mc=\"{mc_ns}\""));
1406 }
1407 self.set_ignorable_name_space(path, prefix, &mut attrs);
1408 }
1409
1410 self.xml_attr.insert(path.to_string(), attrs);
1411 }
1412
1413 fn needs_ignorable_prefix(&self, prefix: &str) -> bool {
1414 const IGNORABLE_NS: &[&str] = &[
1415 "c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14",
1416 "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9",
1417 "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x16", "x16r2", "mo", "mx", "mv", "o",
1418 "v",
1419 ];
1420 IGNORABLE_NS.contains(&prefix)
1421 }
1422
1423 fn set_ignorable_name_space(&self, _path: &str, prefix: &str, attrs: &mut String) {
1424 let marker = "mc:Ignorable=\"";
1425 if let Some(start) = attrs.find(marker) {
1426 let value_start = start + marker.len();
1427 if let Some(end) = attrs[value_start..].find('"') {
1428 let value = &attrs[value_start..value_start + end];
1429 let parts: Vec<&str> = value.split_whitespace().collect();
1430 if !parts.contains(&prefix) {
1431 let new_value = format!("{} {}", value, prefix);
1432 attrs.replace_range(value_start..value_start + end, &new_value);
1433 }
1434 return;
1435 }
1436 }
1437 if !attrs.is_empty() && !attrs.ends_with(' ') {
1438 attrs.push(' ');
1439 }
1440 attrs.push_str(&format!("mc:Ignorable=\"{prefix}\""));
1441 }
1442
1443 pub(crate) fn add_sheet_name_space(&self, sheet: &str, ns: &str) {
1445 if let Some(path) = self.get_sheet_xml_path(sheet) {
1446 self.add_name_spaces(&path, ns);
1447 }
1448 }
1449
1450 pub fn get_sheet_id(&self, sheet: &str) -> i32 {
1452 if let Ok(wb) = self.workbook_reader() {
1453 for s in &wb.sheets.sheet {
1454 if s.name.as_deref().unwrap_or("").eq_ignore_ascii_case(sheet) {
1455 return s.sheet_id.unwrap_or(0) as i32;
1456 }
1457 }
1458 }
1459 -1
1460 }
1461
1462 pub fn expand_3d_sheet_range(&self, sheet1: &str, sheet2: &str) -> Result<Vec<String>> {
1465 let mut idx1 = self.get_sheet_index(sheet1)?;
1466 let mut idx2 = self.get_sheet_index(sheet2)?;
1467 if idx1 > idx2 {
1468 std::mem::swap(&mut idx1, &mut idx2);
1469 }
1470 let list = self.get_sheet_list();
1471 Ok(list[(idx1 - 1) as usize..idx2 as usize].to_vec())
1472 }
1473
1474 pub fn clear_calc_cache(&self) {
1476 let _ = self.calc_chain.lock().unwrap().take();
1477 self.calc_cache.lock().unwrap().clear();
1478 self.calc_raw_cache.lock().unwrap().clear();
1479 self.formula_arg_cache.lock().unwrap().clear();
1480 }
1481
1482 pub fn add_content_type_part(&self, id: i32, part: &str) -> Result<()> {
1484 match part {
1485 "comments" => self.set_content_type_part_vml_extensions()?,
1486 "drawings" => crate::sheet::set_content_type_part_image_extensions(self)?,
1487 _ => {}
1488 }
1489 let mut ct = self.content_types_reader()?;
1490 let content_type = match part {
1491 "table" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_TABLE,
1492 "pivotTable" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_PIVOT_TABLE,
1493 "pivotCache" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_PIVOT_CACHE_DEFINITION,
1494 "slicer" => crate::constants::CONTENT_TYPE_SLICER,
1495 "slicerCache" => crate::constants::CONTENT_TYPE_SLICER_CACHE,
1496 "drawings" => crate::constants::CONTENT_TYPE_DRAWING,
1497 "chart" => crate::constants::CONTENT_TYPE_DRAWING_ML,
1498 "chartsheet" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_CHARTSHEET,
1499 "comments" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_COMMENTS,
1500 _ => return Ok(()),
1501 };
1502 let part_name = match part {
1503 "table" => format!("/xl/tables/table{id}.xml"),
1504 "pivotTable" => format!("/xl/pivotTables/pivotTable{id}.xml"),
1505 "pivotCache" => format!("/xl/pivotCache/pivotCacheDefinition{id}.xml"),
1506 "slicer" => format!("/xl/slicers/slicer{id}.xml"),
1507 "slicerCache" => format!("/xl/slicerCaches/slicerCache{id}.xml"),
1508 "drawings" => format!("/xl/drawings/drawing{id}.xml"),
1509 "chart" => format!("/xl/charts/chart{id}.xml"),
1510 "chartsheet" => format!("/xl/chartsheets/sheet{id}.xml"),
1511 "comments" => format!("/xl/comments{id}.xml"),
1512 _ => return Ok(()),
1513 };
1514 for entry in &ct.entries {
1515 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = entry {
1516 if o.part_name == part_name {
1517 return Ok(());
1518 }
1519 }
1520 }
1521 ct.entries
1522 .push(crate::xml::content_types::XlsxContentTypeEntry::Override(
1523 crate::xml::content_types::XlsxOverride {
1524 part_name,
1525 content_type: content_type.to_string(),
1526 },
1527 ));
1528 *self.content_types.lock().unwrap() = Some(ct);
1529 self.set_content_type_part_rels_extensions()
1530 }
1531
1532 pub(crate) fn set_content_type_part_rels_extensions(&self) -> Result<()> {
1534 let mut ct = self.content_types_reader()?;
1535 let exists = ct.entries.iter().any(|e| {
1536 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
1537 d.extension == "rels"
1538 } else {
1539 false
1540 }
1541 });
1542 if !exists {
1543 ct.entries
1544 .push(crate::xml::content_types::XlsxContentTypeEntry::Default(
1545 crate::xml::content_types::XlsxDefault {
1546 extension: "rels".to_string(),
1547 content_type: crate::constants::CONTENT_TYPE_RELATIONSHIPS.to_string(),
1548 },
1549 ));
1550 }
1551 *self.content_types.lock().unwrap() = Some(ct);
1552 Ok(())
1553 }
1554
1555 pub(crate) fn set_content_type_part_vml_extensions(&self) -> Result<()> {
1557 let mut ct = self.content_types_reader()?;
1558 let exists = ct.entries.iter().any(|e| {
1559 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
1560 d.extension == "vml"
1561 } else {
1562 false
1563 }
1564 });
1565 if !exists {
1566 ct.entries
1567 .push(crate::xml::content_types::XlsxContentTypeEntry::Default(
1568 crate::xml::content_types::XlsxDefault {
1569 extension: "vml".to_string(),
1570 content_type: crate::constants::CONTENT_TYPE_VML.to_string(),
1571 },
1572 ));
1573 }
1574 *self.content_types.lock().unwrap() = Some(ct);
1575 Ok(())
1576 }
1577
1578 pub fn remove_content_types_part(&self, content_type: &str, part_name: &str) -> Result<()> {
1580 let mut ct = self.content_types_reader()?;
1581 ct.entries.retain(|e| {
1582 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = e {
1583 !(o.content_type == content_type && o.part_name == part_name)
1584 } else {
1585 true
1586 }
1587 });
1588 *self.content_types.lock().unwrap() = Some(ct);
1589 Ok(())
1590 }
1591
1592 pub fn get_sheet_relationships_target_by_id(&self, sheet: &str, r_id: &str) -> String {
1594 let Some(sheet_xml_path) = self.get_sheet_xml_path(sheet) else {
1595 return String::new();
1596 };
1597 let sheet_rels = format!(
1598 "xl/worksheets/_rels/{}.rels",
1599 sheet_xml_path.trim_start_matches("xl/worksheets/")
1600 );
1601 if let Ok(Some(rels)) = self.rels_reader(&sheet_rels) {
1602 for rel in &rels.relationships {
1603 if rel.id == r_id {
1604 return rel.target.clone();
1605 }
1606 }
1607 }
1608 String::new()
1609 }
1610
1611 pub fn add_sheet_legacy_drawing(&self, sheet: &str, r_id: i32) -> Result<()> {
1613 let mut ws = self.work_sheet_reader(sheet)?;
1614 ws.legacy_drawing = Some(crate::xml::worksheet::XlsxLegacyDrawing {
1615 rid: Some(format!("rId{r_id}")),
1616 });
1617 if let Some(path) = self.get_sheet_xml_path(sheet) {
1618 self.sheet.insert(path, ws);
1619 }
1620 Ok(())
1621 }
1622
1623 pub fn add_sheet_legacy_drawing_hf(&self, sheet: &str, r_id: i32) -> Result<()> {
1625 let mut ws = self.work_sheet_reader(sheet)?;
1626 ws.legacy_drawing_hf = Some(crate::xml::worksheet::XlsxLegacyDrawingHF {
1627 rid: Some(format!("rId{r_id}")),
1628 });
1629 if let Some(path) = self.get_sheet_xml_path(sheet) {
1630 self.sheet.insert(path, ws);
1631 }
1632 Ok(())
1633 }
1634
1635 pub fn count_comments(&self) -> i32 {
1637 let mut count = 0;
1638 for entry in self.pkg.iter() {
1639 if entry.key().contains("xl/comments") {
1640 count += 1;
1641 }
1642 }
1643 for entry in self.comments.iter() {
1644 if entry.key().contains("xl/comments") && !self.pkg.contains_key(entry.key()) {
1645 count += 1;
1646 }
1647 }
1648 count
1649 }
1650
1651 pub fn count_vml_drawing(&self) -> i32 {
1653 let mut count = 0;
1654 for entry in self.pkg.iter() {
1655 if entry.key().contains("xl/drawings/vmlDrawing") {
1656 count += 1;
1657 }
1658 }
1659 for entry in self.vml_drawing.iter() {
1660 if entry.key().contains("xl/drawings/vmlDrawing") && !self.pkg.contains_key(entry.key())
1661 {
1662 count += 1;
1663 }
1664 }
1665 count
1666 }
1667
1668 pub fn comments_reader(
1670 &self,
1671 path: &str,
1672 ) -> Result<Option<crate::xml::comments::XlsxComments>> {
1673 if let Some(cmts) = self.comments.get(path) {
1674 return Ok(Some(cmts.clone()));
1675 }
1676 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
1677 let data = self.apply_charset_transcoder(&self.read_bytes(path))?;
1678 let data = crate::file::namespace_strict_to_transitional(&data);
1679 let mut cmts: crate::xml::comments::XlsxComments =
1680 quick_xml::de::from_reader(data.as_slice()).unwrap_or_default();
1681 for cmt in &cmts.comment_list.comment {
1682 cmts.cells.push(cmt.r#ref.clone());
1683 }
1684 self.comments.insert(path.to_string(), cmts.clone());
1685 return Ok(Some(cmts));
1686 }
1687 Ok(None)
1688 }
1689
1690 pub fn vml_drawing_reader(&self, path: &str) -> Result<Option<crate::xml::vml::VmlDrawing>> {
1692 if let Some(vml) = self.vml_drawing.get(path) {
1693 return Ok(Some(vml.clone()));
1694 }
1695 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
1696 let data = self.apply_charset_transcoder(&self.read_bytes(path))?;
1697 let data = crate::file::namespace_strict_to_transitional(&data);
1698 let data = String::from_utf8_lossy(&data)
1699 .replace("<br>\r\n", "<br></br>\r\n")
1700 .into_bytes();
1701 let vml = crate::xml::vml::VmlDrawing::from_xml(&data).unwrap_or_default();
1702 self.vml_drawing.insert(path.to_string(), vml.clone());
1703 return Ok(Some(vml));
1704 }
1705 Ok(None)
1706 }
1707
1708 pub fn delete_sheet_relationships(&self, sheet: &str, r_id: &str) {
1710 let Some(sheet_xml_path) = self.get_sheet_xml_path(sheet) else {
1711 return;
1712 };
1713 let sheet_rels = format!(
1714 "xl/worksheets/_rels/{}.rels",
1715 sheet_xml_path.trim_start_matches("xl/worksheets/")
1716 );
1717 if let Ok(Some(mut rels)) = self.rels_reader(&sheet_rels) {
1718 rels.relationships.retain(|r| r.id != r_id);
1719 self.relationships.insert(sheet_rels, rels);
1720 }
1721 }
1722
1723 pub fn delete_workbook_rels(&self, rel_type: &str, target: &str) -> Result<String> {
1725 let rel_path = self.get_workbook_rels_path();
1726 let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
1727 let mut r_id = String::new();
1728 rels.relationships.retain(|r| {
1729 if r.r#type == rel_type && (r.target == target || r.target == format!("/{target}")) {
1730 r_id = r.id.clone();
1731 false
1732 } else {
1733 true
1734 }
1735 });
1736 self.relationships.insert(rel_path, rels);
1737 Ok(r_id)
1738 }
1739
1740 pub fn add_sheet_drawing(&self, sheet: &str, r_id: i32) -> Result<()> {
1742 let mut ws = self.work_sheet_reader(sheet)?;
1743 ws.drawing = Some(crate::xml::worksheet::XlsxDrawing {
1744 rid: Some(format!("rId{r_id}")),
1745 });
1746 self.sheet
1747 .insert(self.get_sheet_xml_path(sheet).unwrap_or_default(), ws);
1748 Ok(())
1749 }
1750
1751 pub fn count_tables(&self) -> i32 {
1753 let mut count = 0i32;
1754 for entry in self.pkg.iter() {
1755 let k = entry.key();
1756 if k.contains("xl/tables/tableSingleCells") {
1757 if let Ok(data) = self.apply_charset_transcoder(entry.value()) {
1758 let data = namespace_strict_to_transitional(&data);
1759 match xml_from_reader::<_, XlsxSingleXmlCells>(data.as_slice()) {
1760 Ok(cells) => {
1761 for cell in cells.single_xml_cell {
1762 if count < cell.id as i32 {
1763 count = cell.id as i32;
1764 }
1765 }
1766 }
1767 Err(_) => count += 1,
1768 }
1769 }
1770 }
1771 if k.contains("xl/tables/table") {
1772 if let Ok(data) = self.apply_charset_transcoder(entry.value()) {
1773 let data = namespace_strict_to_transitional(&data);
1774 match xml_from_reader::<_, XlsxTable>(data.as_slice()) {
1775 Ok(t) => {
1776 if count < t.id as i32 {
1777 count = t.id as i32;
1778 }
1779 }
1780 Err(_) => count += 1,
1781 }
1782 }
1783 }
1784 }
1785 count
1786 }
1787
1788 pub fn count_drawings(&self) -> i32 {
1790 let mut count = 0;
1791 for entry in self.pkg.iter() {
1792 if entry.key().contains("xl/drawings/drawing") {
1793 count += 1;
1794 }
1795 }
1796 for entry in self.drawings.iter() {
1797 if entry.key().contains("xl/drawings/drawing") && !self.pkg.contains_key(entry.key()) {
1798 count += 1;
1799 }
1800 }
1801 count
1802 }
1803
1804 pub fn count_charts(&self) -> i32 {
1806 let mut count = 0;
1807 for entry in self.pkg.iter() {
1808 if entry.key().contains("xl/charts/chart") {
1809 count += 1;
1810 }
1811 }
1812 count
1813 }
1814
1815 pub fn count_pivot_tables(&self) -> i32 {
1817 let mut count = 0;
1818 for entry in self.pkg.iter() {
1819 if entry.key().contains("xl/pivotTables/pivotTable") {
1820 count += 1;
1821 }
1822 }
1823 count
1824 }
1825
1826 pub fn count_pivot_cache(&self) -> i32 {
1828 let mut count = 0;
1829 for entry in self.pkg.iter() {
1830 if entry.key().contains("xl/pivotCache/pivotCacheDefinition") {
1831 count += 1;
1832 }
1833 }
1834 count
1835 }
1836
1837 pub fn count_slicers(&self) -> i32 {
1839 let mut count = 0;
1840 for entry in self.pkg.iter() {
1841 if entry.key().contains("xl/slicers/slicer") {
1842 count += 1;
1843 }
1844 }
1845 count
1846 }
1847
1848 pub fn count_slicer_cache(&self) -> i32 {
1850 let mut count = 0;
1851 for entry in self.pkg.iter() {
1852 if entry.key().contains("xl/slicerCaches/slicerCache") {
1853 count += 1;
1854 }
1855 }
1856 count
1857 }
1858
1859 pub fn get_defined_names(&self) -> Result<Vec<crate::xml::workbook::DefinedName>> {
1861 let mut out = Vec::new();
1862 if let Ok(wb) = self.workbook_reader() {
1863 if let Some(dns) = wb.defined_names {
1864 for dn in dns.defined_name {
1865 out.push(crate::xml::workbook::DefinedName {
1866 name: dn.name.clone().unwrap_or_default(),
1867 comment: dn.comment.clone().unwrap_or_default(),
1868 refers_to: dn.data.clone(),
1869 scope: dn
1870 .local_sheet_id
1871 .map(|id| {
1872 if let Ok(wb2) = self.workbook_reader() {
1873 if let Some(s) = wb2.sheets.sheet.get(id as usize) {
1874 return s.name.clone().unwrap_or_default();
1875 }
1876 }
1877 String::new()
1878 })
1879 .unwrap_or_else(|| "Workbook".to_string()),
1880 });
1881 }
1882 }
1883 }
1884 Ok(out)
1885 }
1886
1887 pub fn set_defined_name(&self, dn: &crate::xml::workbook::DefinedName) -> Result<()> {
1889 let mut wb = self.workbook_reader()?;
1890 if wb.defined_names.is_none() {
1891 wb.defined_names = Some(crate::xml::workbook::XlsxDefinedNames::default());
1892 }
1893 let names = wb.defined_names.as_mut().unwrap();
1894 let local_sheet_id = if dn.scope.is_empty() || dn.scope == "Workbook" {
1895 None
1896 } else {
1897 Some((self.get_sheet_index(&dn.scope)? - 1) as i64)
1898 };
1899 for existing in &names.defined_name {
1900 if existing.name.as_deref().unwrap_or("") == dn.name
1901 && existing.local_sheet_id == local_sheet_id
1902 {
1903 return Err(Box::new(ErrDefinedNameDuplicate));
1904 }
1905 }
1906 names
1907 .defined_name
1908 .push(crate::xml::workbook::XlsxDefinedName {
1909 name: Some(dn.name.clone()),
1910 data: dn.refers_to.clone(),
1911 hidden: Some(false),
1912 local_sheet_id,
1913 ..Default::default()
1914 });
1915 *self.workbook.lock().unwrap() = Some(wb);
1916 Ok(())
1917 }
1918
1919 pub fn delete_defined_name(&self, dn: &crate::xml::workbook::DefinedName) -> Result<()> {
1921 let mut wb = self.workbook_reader()?;
1922 let Some(names) = wb.defined_names.as_mut() else {
1923 return Err(Box::new(ErrDefinedNameScope));
1924 };
1925 let local_sheet_id = if dn.scope.is_empty() || dn.scope == "Workbook" {
1926 None
1927 } else {
1928 Some((self.get_sheet_index(&dn.scope)? - 1) as i64)
1929 };
1930 let before = names.defined_name.len();
1931 names.defined_name.retain(|e| {
1932 !(e.name.as_deref().unwrap_or("") == dn.name && e.local_sheet_id == local_sheet_id)
1933 });
1934 if names.defined_name.len() == before {
1935 return Err(Box::new(ErrDefinedNameScope));
1936 }
1937 if names.defined_name.is_empty() {
1938 wb.defined_names = None;
1939 }
1940 *self.workbook.lock().unwrap() = Some(wb);
1941 Ok(())
1942 }
1943
1944 pub fn get_defined_name_ref_to(&self, name: &str, current_sheet: &str) -> String {
1946 let mut workbook_ref = String::new();
1947 let mut sheet_ref = String::new();
1948 if let Ok(names) = self.get_defined_names() {
1949 for dn in &names {
1950 if dn.name == name {
1951 if dn.scope == "Workbook" {
1952 workbook_ref = dn.refers_to.clone();
1953 }
1954 if dn.scope == current_sheet {
1955 sheet_ref = dn.refers_to.clone();
1956 }
1957 }
1958 }
1959 }
1960 if !sheet_ref.is_empty() {
1961 sheet_ref
1962 } else {
1963 workbook_ref
1964 }
1965 }
1966
1967 pub fn get_defined_name(&self) -> Result<Vec<crate::xml::workbook::DefinedName>> {
1970 self.get_defined_names()
1971 }
1972}
1973
1974impl File {
1979 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
1981 self.write_to(writer)
1982 }
1983
1984 pub fn save_with_options(&mut self, opts: Options) -> Result<()> {
1986 {
1987 let mut o = self.options.lock().unwrap();
1988 *o = opts;
1989 }
1990 self.save()
1991 }
1992
1993 pub fn save_as_with_options(&mut self, path: &str, opts: Options) -> Result<()> {
1995 {
1996 let mut o = self.options.lock().unwrap();
1997 *o = opts;
1998 }
1999 self.save_as(path)
2000 }
2001
2002 pub fn update_linked_value(&self) -> Result<()> {
2007 let mut wb = self.workbook_reader()?;
2008 wb.calc_pr = None;
2009 *self.workbook.lock().unwrap() = Some(wb);
2010 for name in self.get_sheet_list() {
2011 if let Ok(mut ws) = self.work_sheet_reader(&name) {
2012 let path = self.get_sheet_xml_path(&name).unwrap_or_default();
2013 let mut changed = false;
2014 for row in &mut ws.sheet_data.row {
2015 for cell in &mut row.c {
2016 if cell.f.is_some() && cell.v.is_some() {
2017 cell.v = None;
2018 cell.t = None;
2019 changed = true;
2020 }
2021 }
2022 }
2023 if changed {
2024 self.sheet.insert(path, ws);
2025 }
2026 }
2027 }
2028 Ok(())
2029 }
2030
2031 pub fn read_zip_reader<R: Read + Seek>(
2035 &self,
2036 archive: &mut ZipArchive<R>,
2037 ) -> Result<(HashMap<String, Vec<u8>>, i32)> {
2038 self.read_zip_archive(archive)
2039 }
2040
2041 pub fn set_workbook_props(&self, opts: &WorkbookPropsOptions) -> Result<()> {
2043 let mut wb = self.workbook_reader()?;
2044 if wb.workbook_pr.is_none() {
2045 wb.workbook_pr = Some(XlsxWorkbookPr::default());
2046 }
2047 let pr = wb.workbook_pr.as_mut().unwrap();
2048 if let Some(v) = opts.date1904 {
2049 pr.date1904 = Some(v);
2050 }
2051 if let Some(v) = opts.filter_privacy {
2052 pr.filter_privacy = Some(v);
2053 }
2054 if let Some(ref v) = opts.code_name {
2055 pr.code_name = Some(v.clone());
2056 }
2057 *self.workbook.lock().unwrap() = Some(wb);
2058 Ok(())
2059 }
2060
2061 pub fn get_workbook_props(&self) -> Result<WorkbookPropsOptions> {
2063 let mut opts = WorkbookPropsOptions::default();
2064 let wb = self.workbook_reader()?;
2065 if let Some(pr) = &wb.workbook_pr {
2066 opts.date1904 = pr.date1904;
2067 opts.filter_privacy = pr.filter_privacy;
2068 opts.code_name = pr.code_name.clone();
2069 }
2070 Ok(opts)
2071 }
2072
2073 pub fn set_calc_props(&self, opts: &CalcPropsOptions) -> Result<()> {
2075 if let Some(ref mode) = opts.calc_mode {
2076 if in_str_slice(SUPPORTED_CALC_MODE, mode, true) == -1 {
2077 return Err(Box::new(ErrParameterInvalid));
2078 }
2079 }
2080 if let Some(ref mode) = opts.ref_mode {
2081 if in_str_slice(SUPPORTED_REF_MODE, mode, true) == -1 {
2082 return Err(Box::new(ErrParameterInvalid));
2083 }
2084 }
2085
2086 let mut wb = self.workbook_reader()?;
2087 if wb.calc_pr.is_none() {
2088 wb.calc_pr = Some(XlsxCalcPr::default());
2089 }
2090 let pr = wb.calc_pr.as_mut().unwrap();
2091 if let Some(v) = opts.calc_completed {
2092 pr.calc_completed = Some(v);
2093 }
2094 if let Some(v) = opts.calc_on_save {
2095 pr.calc_on_save = Some(v);
2096 }
2097 if let Some(v) = opts.force_full_calc {
2098 pr.force_full_calc = Some(v);
2099 }
2100 if let Some(v) = opts.full_calc_on_load {
2101 pr.full_calc_on_load = Some(v);
2102 }
2103 if let Some(v) = opts.full_precision {
2104 pr.full_precision = Some(v);
2105 }
2106 if let Some(v) = opts.iterate {
2107 pr.iterate = Some(v);
2108 }
2109 if let Some(v) = opts.iterate_delta {
2110 pr.iterate_delta = Some(v);
2111 }
2112 if let Some(ref v) = opts.calc_mode {
2113 pr.calc_mode = Some(v.clone());
2114 }
2115 if let Some(ref v) = opts.ref_mode {
2116 pr.ref_mode = Some(v.clone());
2117 }
2118 if let Some(v) = opts.calc_id {
2119 pr.calc_id = Some(v as i64);
2120 }
2121 if let Some(v) = opts.concurrent_manual_count {
2122 pr.concurrent_manual_count = Some(v as i64);
2123 }
2124 if let Some(v) = opts.iterate_count {
2125 pr.iterate_count = Some(v as i64);
2126 }
2127 pr.concurrent_calc = opts.concurrent_calc;
2128 *self.workbook.lock().unwrap() = Some(wb);
2129 Ok(())
2130 }
2131
2132 pub fn get_calc_props(&self) -> Result<CalcPropsOptions> {
2134 let mut opts = CalcPropsOptions::default();
2135 let wb = self.workbook_reader()?;
2136 if let Some(pr) = &wb.calc_pr {
2137 opts.calc_completed = pr.calc_completed;
2138 opts.calc_on_save = pr.calc_on_save;
2139 opts.force_full_calc = pr.force_full_calc;
2140 opts.full_calc_on_load = pr.full_calc_on_load;
2141 opts.full_precision = pr.full_precision;
2142 opts.iterate = pr.iterate;
2143 opts.iterate_delta = pr.iterate_delta;
2144 opts.calc_mode = pr.calc_mode.clone();
2145 opts.ref_mode = pr.ref_mode.clone();
2146 opts.calc_id = pr.calc_id.map(|v| v as u64);
2147 opts.concurrent_manual_count = pr.concurrent_manual_count.map(|v| v as u64);
2148 opts.iterate_count = pr.iterate_count.map(|v| v as u64);
2149 opts.concurrent_calc = pr.concurrent_calc;
2150 }
2151 Ok(opts)
2152 }
2153
2154 pub fn protect_workbook(&self, opts: &WorkbookProtectionOptions) -> Result<()> {
2156 let mut wb = self.workbook_reader()?;
2157 let mut protection = XlsxWorkbookProtection {
2158 lock_structure: Some(opts.lock_structure),
2159 lock_windows: Some(opts.lock_windows),
2160 ..Default::default()
2161 };
2162 if !opts.password.is_empty() {
2163 let algorithm_name = if opts.algorithm_name.is_empty() {
2164 "SHA-512"
2165 } else {
2166 &opts.algorithm_name
2167 };
2168 let (hash_value, salt_value) = crypt::gen_iso_passwd_hash(
2169 &opts.password,
2170 algorithm_name,
2171 "",
2172 WORKBOOK_PROTECTION_SPIN_COUNT,
2173 )?;
2174 protection.workbook_algorithm_name = Some(algorithm_name.to_string());
2175 protection.workbook_hash_value = Some(hash_value);
2176 protection.workbook_salt_value = Some(salt_value);
2177 protection.workbook_spin_count = Some(WORKBOOK_PROTECTION_SPIN_COUNT as i64);
2178 }
2179 wb.workbook_protection = Some(protection);
2180 *self.workbook.lock().unwrap() = Some(wb);
2181 Ok(())
2182 }
2183
2184 pub fn unprotect_workbook(&self, password: Option<&str>) -> Result<()> {
2186 let mut wb = self.workbook_reader()?;
2187 if let Some(pwd) = password {
2188 let protection = wb.workbook_protection.as_ref().ok_or_else(|| {
2189 Box::new(ErrUnprotectWorkbook) as Box<dyn std::error::Error + Send + Sync>
2190 })?;
2191 if let Some(ref algorithm_name) = protection.workbook_algorithm_name {
2192 let salt = protection.workbook_salt_value.as_deref().unwrap_or("");
2193 let spin_count = protection.workbook_spin_count.unwrap_or(0) as i32;
2194 let (hash_value, _) =
2195 crypt::gen_iso_passwd_hash(pwd, algorithm_name, salt, spin_count)?;
2196 if protection.workbook_hash_value.as_deref().unwrap_or("") != hash_value {
2197 return Err(Box::new(ErrUnprotectWorkbookPassword));
2198 }
2199 }
2200 }
2201 wb.workbook_protection = None;
2202 *self.workbook.lock().unwrap() = Some(wb);
2203 Ok(())
2204 }
2205
2206 pub fn add_vba_project(&self, data: &[u8]) -> Result<()> {
2213 if data.len() < 8 || &data[..8] != OLE_IDENTIFIER {
2214 return Err(Box::new(crate::errors::ErrAddVBAProject));
2215 }
2216 let rel_path = self.get_workbook_rels_path();
2217 let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
2218 let mut existing = false;
2219 let mut r_id = 0;
2220 for rel in &rels.relationships {
2221 if rel.target == "vbaProject.bin" && rel.r#type == SOURCE_RELATIONSHIP_VBA_PROJECT {
2222 existing = true;
2223 }
2224 let id: i32 = rel.id.trim_start_matches("rId").parse().unwrap_or(0);
2225 if id > r_id {
2226 r_id = id;
2227 }
2228 }
2229 if !existing {
2230 r_id += 1;
2231 rels.relationships.push(XlsxRelationship {
2232 id: format!("rId{r_id}"),
2233 r#type: SOURCE_RELATIONSHIP_VBA_PROJECT.to_string(),
2234 target: "vbaProject.bin".to_string(),
2235 target_mode: None,
2236 });
2237 self.relationships.insert(rel_path, rels);
2238 }
2239 self.pkg
2240 .insert("xl/vbaProject.bin".to_string(), data.to_vec());
2241 Ok(())
2242 }
2243
2244 pub fn set_content_type_part_project_extensions(&self, content_type: &str) -> Result<()> {
2246 let mut ct = self.content_types_reader()?;
2247 let mut bin_ok = false;
2248 let mut entries = ct.entries.clone();
2249 for entry in &entries {
2250 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = entry {
2251 if d.extension == "bin" {
2252 bin_ok = true;
2253 }
2254 }
2255 }
2256 for entry in &mut entries {
2257 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = entry {
2258 if o.part_name == "/xl/workbook.xml" {
2259 o.content_type = content_type.to_string();
2260 }
2261 }
2262 }
2263 if !bin_ok {
2264 entries.push(crate::xml::content_types::XlsxContentTypeEntry::Default(
2265 XlsxDefault {
2266 extension: "bin".to_string(),
2267 content_type: CONTENT_TYPE_VBA.to_string(),
2268 },
2269 ));
2270 }
2271 ct.entries = entries;
2272 *self.content_types.lock().unwrap() = Some(ct);
2273 Ok(())
2274 }
2275}
2276
2277fn normalize_options(mut opts: Options) -> Options {
2282 if opts.unzip_size_limit == 0 {
2283 opts.unzip_size_limit = UNZIP_SIZE_LIMIT;
2284 }
2285 if opts.unzip_xml_size_limit == 0 {
2286 opts.unzip_xml_size_limit = STREAM_CHUNK_SIZE;
2287 }
2288 if opts.culture_info == 0 && opts.short_date_pattern.is_empty() {
2289 opts.culture_info = CULTURE_NAME_UNKNOWN;
2290 }
2291 opts
2292}
2293
2294pub fn namespace_strict_to_transitional(content: &[u8]) -> Vec<u8> {
2296 if content.windows(13).all(|w| w != b"purl.oclc.org") {
2297 return content.to_vec();
2298 }
2299 let mut result = content.to_vec();
2300 let translations: &[(&[u8], &[u8])] = &[
2301 (
2302 STRICT_NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES.as_bytes(),
2303 NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES.as_bytes(),
2304 ),
2305 (
2306 STRICT_NAMESPACE_DRAWING_ML_MAIN.as_bytes(),
2307 NAMESPACE_DRAWING_ML_MAIN.as_bytes(),
2308 ),
2309 (
2310 STRICT_NAMESPACE_EXTENDED_PROPERTIES.as_bytes(),
2311 NAMESPACE_EXTENDED_PROPERTIES.as_bytes(),
2312 ),
2313 (
2314 STRICT_NAMESPACE_SPREADSHEET.as_bytes(),
2315 NAMESPACE_SPREADSHEET.as_bytes(),
2316 ),
2317 (
2318 STRICT_SOURCE_RELATIONSHIP.as_bytes(),
2319 SOURCE_RELATIONSHIP.as_bytes(),
2320 ),
2321 (
2322 STRICT_SOURCE_RELATIONSHIP_CHART.as_bytes(),
2323 SOURCE_RELATIONSHIP_CHART.as_bytes(),
2324 ),
2325 (
2326 STRICT_SOURCE_RELATIONSHIP_COMMENTS.as_bytes(),
2327 SOURCE_RELATIONSHIP_COMMENTS.as_bytes(),
2328 ),
2329 (
2330 STRICT_SOURCE_RELATIONSHIP_EXTEND_PROPERTIES.as_bytes(),
2331 SOURCE_RELATIONSHIP_EXTEND_PROPERTIES.as_bytes(),
2332 ),
2333 (
2334 STRICT_SOURCE_RELATIONSHIP_IMAGE.as_bytes(),
2335 SOURCE_RELATIONSHIP_IMAGE.as_bytes(),
2336 ),
2337 (
2338 STRICT_SOURCE_RELATIONSHIP_OFFICE_DOCUMENT.as_bytes(),
2339 SOURCE_RELATIONSHIP_OFFICE_DOCUMENT.as_bytes(),
2340 ),
2341 ];
2342 for (from, to) in translations {
2343 result = bytes_replace(&result, from, to);
2344 }
2345 result
2346}
2347
2348fn bytes_replace(content: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
2349 let mut result = Vec::with_capacity(content.len());
2350 let mut start = 0;
2351 while let Some(pos) = content[start..].windows(from.len()).position(|w| w == from) {
2352 let pos = start + pos;
2353 result.extend_from_slice(&content[start..pos]);
2354 result.extend_from_slice(to);
2355 start = pos + from.len();
2356 }
2357 result.extend_from_slice(&content[start..]);
2358 result
2359}
2360
2361fn detect_xml_encoding(content: &[u8]) -> Option<&str> {
2365 let start = content.windows(5).position(|w| w == b"<?xml")?;
2366 let rest = &content[start..];
2367 let end = rest.windows(2).position(|w| w == b"?>")? + 2;
2368 let decl = &rest[..end];
2369 let key = b"encoding=";
2370 let pos = decl.windows(key.len()).position(|w| w == key)?;
2371 let rest = &decl[pos + key.len()..];
2372 let quote = *rest.first()?;
2373 let close = rest[1..].iter().position(|&b| b == quote)?;
2374 std::str::from_utf8(&rest[1..1 + close]).ok()
2375}
2376
2377fn strip_xml_element(content: &[u8], name: &str) -> Vec<u8> {
2381 let s = String::from_utf8_lossy(content);
2382 let pattern = format!(
2383 r"(?s)<{}\b[^>]*>.*?</{}>",
2384 regex::escape(name),
2385 regex::escape(name)
2386 );
2387 if let Ok(re) = regex::Regex::new(&pattern) {
2388 return re.replace_all(&s, "").into_owned().into_bytes();
2389 }
2390 s.into_owned().into_bytes()
2391}
2392
2393pub(crate) fn extract_root_namespace_attributes(content: &[u8]) -> Option<String> {
2395 let s = std::str::from_utf8(content).ok()?;
2396 let mut pos = 0usize;
2398 while pos < s.len() {
2399 let c = s[pos..].chars().next()?;
2400 if c == '<' {
2401 if s[pos..].starts_with("<?") {
2402 if let Some(end) = s[pos..].find("?>") {
2403 pos += end + 2;
2404 continue;
2405 }
2406 return None;
2407 }
2408 break;
2409 }
2410 pos += c.len_utf8();
2411 }
2412 if pos >= s.len() {
2413 return None;
2414 }
2415 let start = pos;
2416 let close = s[start..].find('>')?;
2417 let tag = &s[start..start + close + 1];
2418 let name_end = tag
2420 .find(|c: char| c.is_whitespace())
2421 .unwrap_or(tag.len() - 1);
2422 let after_name = &tag[name_end..tag.len() - 1];
2423 let trimmed = after_name.trim();
2424 if trimmed.is_empty() {
2425 None
2426 } else {
2427 Some(trimmed.to_string())
2428 }
2429}
2430
2431pub(crate) fn replace_root_namespace_attributes(content: &mut Vec<u8>, attrs: &str) -> Result<()> {
2433 let s = std::str::from_utf8(content)
2434 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
2435 let mut pos = 0usize;
2436 while pos < s.len() {
2437 let c = s[pos..]
2438 .chars()
2439 .next()
2440 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "empty"))?;
2441 if c == '<' {
2442 if s[pos..].starts_with("<?") {
2443 if let Some(end) = s[pos..].find("?>") {
2444 pos += end + 2;
2445 continue;
2446 }
2447 return Err(Box::new(io::Error::new(
2448 io::ErrorKind::InvalidData,
2449 "bad xml decl",
2450 )));
2451 }
2452 break;
2453 }
2454 pos += c.len_utf8();
2455 }
2456 let start = pos;
2457 let close = s[start..]
2458 .find('>')
2459 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no root close"))?;
2460 let tag = &s[start..start + close + 1];
2461 let name_end = tag
2462 .find(|c: char| c.is_whitespace())
2463 .unwrap_or(tag.len() - 1);
2464 let tag_name = &tag[1..name_end];
2465 let end_tag = start + close + 1;
2466 let new_start = format!("<{tag_name} {attrs}>");
2467 let tail = content.split_off(end_tag);
2468 content.truncate(start);
2469 content.extend_from_slice(new_start.as_bytes());
2470 content.extend_from_slice(&tail);
2471 Ok(())
2472}
2473
2474fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
2475 haystack
2476 .windows(needle.len())
2477 .position(|window| window == needle)
2478}
2479
2480pub(crate) fn strip_empty_attributes(content: &mut Vec<u8>) {
2487 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
2488 let re = RE.get_or_init(|| regex::Regex::new(r#" ([a-zA-Z_][a-zA-Z0-9_:\-]*)="""#).unwrap());
2489 let s = String::from_utf8_lossy(content);
2490 let cleaned = re.replace_all(&s, "");
2491 *content = cleaned.into_owned().into_bytes();
2492}
2493
2494fn decode_theme_to_xlsx_theme(theme: &DecodeTheme) -> XlsxTheme {
2495 fn convert_color(c: &crate::xml::theme::DecodeCtColor) -> XlsxCtColor {
2496 XlsxCtColor {
2497 scrgb_clr: c.scrgb_clr.clone(),
2498 srgb_clr: c.srgb_clr.clone(),
2499 hsl_clr: c.hsl_clr.clone(),
2500 sys_clr: c.sys_clr.clone().map(|s| XlsxSysClr {
2501 val: s.val,
2502 last_clr: s.last_clr,
2503 }),
2504 scheme_clr: c.scheme_clr.clone(),
2505 prst_clr: c.prst_clr.clone(),
2506 }
2507 }
2508 fn convert_font(c: &crate::xml::theme::DecodeFontCollection) -> XlsxFontCollection {
2509 XlsxFontCollection {
2510 latin: c.latin.clone(),
2511 ea: c.ea.clone(),
2512 cs: c.cs.clone(),
2513 font: c.font.clone(),
2514 ext_lst: c.ext_lst.clone(),
2515 }
2516 }
2517 XlsxTheme {
2518 xmlns_a: None,
2519 xmlns_r: None,
2520 name: theme.name.clone(),
2521 theme_elements: XlsxBaseStyles {
2522 clr_scheme: crate::xml::theme::XlsxColorScheme {
2523 name: theme.theme_elements.clr_scheme.name.clone(),
2524 dk1: convert_color(&theme.theme_elements.clr_scheme.dk1),
2525 lt1: convert_color(&theme.theme_elements.clr_scheme.lt1),
2526 dk2: convert_color(&theme.theme_elements.clr_scheme.dk2),
2527 lt2: convert_color(&theme.theme_elements.clr_scheme.lt2),
2528 accent1: convert_color(&theme.theme_elements.clr_scheme.accent1),
2529 accent2: convert_color(&theme.theme_elements.clr_scheme.accent2),
2530 accent3: convert_color(&theme.theme_elements.clr_scheme.accent3),
2531 accent4: convert_color(&theme.theme_elements.clr_scheme.accent4),
2532 accent5: convert_color(&theme.theme_elements.clr_scheme.accent5),
2533 accent6: convert_color(&theme.theme_elements.clr_scheme.accent6),
2534 hlink: convert_color(&theme.theme_elements.clr_scheme.hlink),
2535 fol_hlink: convert_color(&theme.theme_elements.clr_scheme.fol_hlink),
2536 ext_lst: theme.theme_elements.clr_scheme.ext_lst.clone(),
2537 },
2538 font_scheme: crate::xml::theme::XlsxFontScheme {
2539 name: theme.theme_elements.font_scheme.name.clone(),
2540 major_font: convert_font(&theme.theme_elements.font_scheme.major_font),
2541 minor_font: convert_font(&theme.theme_elements.font_scheme.minor_font),
2542 ext_lst: theme.theme_elements.font_scheme.ext_lst.clone(),
2543 },
2544 fmt_scheme: crate::xml::theme::XlsxStyleMatrix {
2545 name: theme.theme_elements.fmt_scheme.name.clone(),
2546 fill_style_lst: theme.theme_elements.fmt_scheme.fill_style_lst.clone(),
2547 ln_style_lst: theme.theme_elements.fmt_scheme.ln_style_lst.clone(),
2548 effect_style_lst: theme.theme_elements.fmt_scheme.effect_style_lst.clone(),
2549 bg_fill_style_lst: theme.theme_elements.fmt_scheme.bg_fill_style_lst.clone(),
2550 },
2551 ext_lst: theme.theme_elements.ext_lst.clone(),
2552 },
2553 object_defaults: theme.object_defaults.clone(),
2554 extra_clr_scheme_lst: theme.extra_clr_scheme_lst.clone(),
2555 cust_clr_lst: theme.cust_clr_lst.clone(),
2556 ext_lst: theme.ext_lst.clone(),
2557 }
2558}
2559
2560pub fn set_content_type_part_project_extensions(file: &File, content_type: &str) -> Result<()> {
2566 file.set_content_type_part_project_extensions(content_type)
2567}
2568
2569pub fn add_vba_project(file: &File, data: &[u8]) -> Result<()> {
2571 file.add_vba_project(data)
2572}
2573
2574fn extract_ext_lst(data: &[u8]) -> Option<String> {
2580 let s = String::from_utf8_lossy(data);
2581 let start_key = "<extLst";
2582 let start = s.find(start_key)?;
2583 let close_bracket = s[start..].find('>')? + start + 1;
2584 let end_key = "</extLst>";
2585 let end = s.find(end_key)? + end_key.len();
2586 Some(s[close_bracket..end - end_key.len()].to_string())
2587}
2588
2589fn remove_ext_lst(data: &mut Vec<u8>) {
2592 let s = String::from_utf8_lossy(data);
2593 let Some(start) = s.find("<extLst") else {
2594 return;
2595 };
2596 let Some(end) = s.find("</extLst>") else {
2597 return;
2598 };
2599 let end = end + "</extLst>".len();
2600 let mut result = s[..start].as_bytes().to_vec();
2601 result.extend_from_slice(s[end..].as_bytes());
2602 *data = result;
2603}
2604
2605fn inject_ext_lst(output: &mut Vec<u8>, ext_xml: &str) {
2608 let s = String::from_utf8_lossy(output);
2609 let Some(pos) = s.rfind("</worksheet>") else {
2610 return;
2611 };
2612 let mut result = s[..pos].as_bytes().to_vec();
2613 result.extend_from_slice(b"<extLst>");
2614 result.extend_from_slice(ext_xml.as_bytes());
2615 result.extend_from_slice(b"</extLst>");
2616 result.extend_from_slice(s[pos..].as_bytes());
2617 *output = result;
2618}
2619
2620impl Drop for File {
2621 fn drop(&mut self) {
2622 for entry in self.temp_files.iter() {
2625 let _ = fs::remove_file(entry.value());
2626 }
2627 self.temp_files.clear();
2628 for state in self.streams.borrow().values() {
2629 let _ = fs::remove_file(&state.tmp_path);
2630 }
2631 self.streams.borrow_mut().clear();
2632 }
2633}
2634
2635#[cfg(test)]
2636mod tests {
2637 use super::*;
2638
2639 #[test]
2640 fn new_file_round_trip() {
2641 let mut f = File::new_with_options(Options::default());
2642 let tmp = std::env::temp_dir().join("excelize_rust_test.xlsx");
2643 f.save_as(tmp.to_str().unwrap()).unwrap();
2644
2645 let file = fs::File::open(&tmp).unwrap();
2646 let archive = ZipArchive::new(file).unwrap();
2647 let names: Vec<String> = archive.file_names().map(|s| s.to_string()).collect();
2648 assert!(names.contains(&"[Content_Types].xml".to_string()));
2649 assert!(names.contains(&"xl/workbook.xml".to_string()));
2650 assert!(names.contains(&"xl/worksheets/sheet1.xml".to_string()));
2651 assert!(names.contains(&"xl/styles.xml".to_string()));
2652 assert!(names.contains(&"xl/theme/theme1.xml".to_string()));
2653 drop(archive);
2654 let _ = fs::remove_file(&tmp);
2655 }
2656
2657 #[test]
2658 fn save_and_reopen() {
2659 let mut f = File::new_with_options(Options::default());
2660 let tmp = std::env::temp_dir().join("excelize_rust_reopen_test.xlsx");
2661 f.save_as(tmp.to_str().unwrap()).unwrap();
2662
2663 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
2664 assert_eq!(*f2.sheet_count.lock().unwrap(), 1);
2665 assert_eq!(f2.get_sheet_list(), vec!["Sheet1"]);
2666 let _ = fs::remove_file(&tmp);
2667 }
2668
2669 #[test]
2670 fn open_existing_xlsx() {
2671 let path = "test/Book1.xlsx";
2673 let f = File::open_file(path, Options::default()).unwrap();
2674 assert!(*f.sheet_count.lock().unwrap() >= 1);
2675 let list = f.get_sheet_list();
2676 assert!(!list.is_empty());
2677 }
2678
2679 #[test]
2680 fn workbook_props_round_trip() {
2681 let f = File::new();
2682 let mut opts = WorkbookPropsOptions::default();
2683 opts.date1904 = Some(true);
2684 opts.filter_privacy = Some(false);
2685 opts.code_name = Some("Workbook1".to_string());
2686 f.set_workbook_props(&opts).unwrap();
2687
2688 let got = f.get_workbook_props().unwrap();
2689 assert_eq!(got.date1904, Some(true));
2690 assert_eq!(got.filter_privacy, Some(false));
2691 assert_eq!(got.code_name, Some("Workbook1".to_string()));
2692 }
2693
2694 #[test]
2695 fn calc_props_round_trip() {
2696 let f = File::new();
2697 let mut opts = CalcPropsOptions::default();
2698 opts.calc_mode = Some("manual".to_string());
2699 opts.ref_mode = Some("R1C1".to_string());
2700 opts.full_calc_on_load = Some(true);
2701 opts.calc_id = Some(152511);
2702 opts.iterate_count = Some(100);
2703 f.set_calc_props(&opts).unwrap();
2704
2705 let got = f.get_calc_props().unwrap();
2706 assert_eq!(got.calc_mode, Some("manual".to_string()));
2707 assert_eq!(got.ref_mode, Some("R1C1".to_string()));
2708 assert_eq!(got.full_calc_on_load, Some(true));
2709 assert_eq!(got.calc_id, Some(152511));
2710 assert_eq!(got.iterate_count, Some(100));
2711 }
2712
2713 #[test]
2714 fn calc_props_rejects_invalid_mode() {
2715 let f = File::new();
2716 let mut opts = CalcPropsOptions::default();
2717 opts.calc_mode = Some("invalid".to_string());
2718 assert!(f.set_calc_props(&opts).is_err());
2719
2720 opts.calc_mode = None;
2721 opts.ref_mode = Some("B3".to_string());
2722 assert!(f.set_calc_props(&opts).is_err());
2723 }
2724
2725 #[test]
2726 fn protect_workbook_round_trip() {
2727 let f = File::new();
2728 let opts = WorkbookProtectionOptions {
2729 password: "password".to_string(),
2730 lock_structure: true,
2731 lock_windows: false,
2732 ..Default::default()
2733 };
2734 f.protect_workbook(&opts).unwrap();
2735 let wb = f.workbook_reader().unwrap();
2736 let protection = wb.workbook_protection.as_ref().unwrap();
2737 assert_eq!(protection.lock_structure, Some(true));
2738 assert!(protection.workbook_hash_value.is_some());
2739
2740 assert!(f.unprotect_workbook(Some("wrong")).is_err());
2741 f.unprotect_workbook(Some("password")).unwrap();
2742 assert!(f.workbook_reader().unwrap().workbook_protection.is_none());
2743 }
2744
2745 #[test]
2746 fn add_vba_project() {
2747 let f = File::new();
2748 let mut sheet_opts = crate::sheet::SheetPropsOptions::default();
2749 sheet_opts.code_name = Some("Sheet1".to_string());
2750 f.set_sheet_props("Sheet1", &sheet_opts).unwrap();
2751
2752 let bad = fs::read("test/Book1.xlsx").unwrap();
2753 assert!(f.add_vba_project(&bad).is_err());
2754
2755 let data = fs::read("test/vbaProject.bin").unwrap();
2756 f.add_vba_project(&data).unwrap();
2757 f.add_vba_project(&data).unwrap();
2759
2760 let rels = f
2761 .rels_reader("xl/_rels/workbook.xml.rels")
2762 .unwrap()
2763 .unwrap();
2764 let vba_rel = rels
2765 .relationships
2766 .iter()
2767 .find(|r| r.target == "vbaProject.bin" && r.r#type == SOURCE_RELATIONSHIP_VBA_PROJECT);
2768 assert!(vba_rel.is_some());
2769
2770 let tmp = std::env::temp_dir().join("excelize_rust_vba.xlsm");
2771 let mut f2 = File::new();
2772 f2.set_sheet_props("Sheet1", &sheet_opts).unwrap();
2773 f2.add_vba_project(&data).unwrap();
2774 f2.save_as(tmp.to_str().unwrap()).unwrap();
2775
2776 let f3 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
2777 assert!(f3.pkg.contains_key("xl/vbaProject.bin"));
2778 let _ = fs::remove_file(&tmp);
2779 }
2780
2781 #[test]
2782 fn charset_transcoder_is_used_for_non_utf8_encoding() {
2783 use crate::constants::DEFAULT_XML_PATH_CALC_CHAIN;
2784 use std::io::Read;
2785 use std::sync::{Arc, Mutex};
2786
2787 let f = File::new();
2788 let xml = br#"<?xml version="1.0" encoding="X-TEST" standalone="yes"?>
2789<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><c r="A1" i="1"/></calcChain>"#;
2790 f.pkg
2791 .insert(DEFAULT_XML_PATH_CALC_CHAIN.to_string(), xml.to_vec());
2792 *f.calc_chain.lock().unwrap() = None;
2793
2794 let called = Arc::new(Mutex::new(String::new()));
2795 let called_clone = called.clone();
2796 f.charset_transcoder(move |charset, mut input| {
2797 *called_clone.lock().unwrap() = charset.to_string();
2798 let mut buf = Vec::new();
2799 input.read_to_end(&mut buf).unwrap();
2800 Ok(Box::new(Cursor::new(buf)) as Box<dyn Read>)
2801 });
2802
2803 let cc = f.calc_chain_reader().unwrap();
2804 assert_eq!(cc.c.len(), 1);
2805 assert_eq!(cc.c[0].r, "A1");
2806 assert_eq!(called.lock().unwrap().as_str(), "X-TEST");
2807 }
2808
2809 #[test]
2810 fn charset_transcoder_handles_invalid_utf8_bytes() {
2811 use crate::constants::DEFAULT_XML_PATH_CALC_CHAIN;
2812 use std::io::Read;
2813 use std::sync::{Arc, Mutex};
2814
2815 let f = File::new();
2816 let mut xml = br#"<?xml version="1.0" encoding="X-BAD" standalone="yes"?>
2817<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><c r="A1" i="1"/></calcChain>"#
2818 .to_vec();
2819 xml.push(0xFF); f.pkg.insert(DEFAULT_XML_PATH_CALC_CHAIN.to_string(), xml);
2821 *f.calc_chain.lock().unwrap() = None;
2822
2823 let called = Arc::new(Mutex::new(false));
2824 let called_clone = called.clone();
2825 f.charset_transcoder(move |_charset, mut input| {
2826 *called_clone.lock().unwrap() = true;
2827 let mut buf = Vec::new();
2828 input.read_to_end(&mut buf).unwrap();
2829 buf.pop(); Ok(Box::new(Cursor::new(buf)) as Box<dyn Read>)
2831 });
2832
2833 let cc = f.calc_chain_reader().unwrap();
2834 assert_eq!(cc.c.len(), 1);
2835 assert_eq!(cc.c[0].r, "A1");
2836 assert!(*called.lock().unwrap());
2837 }
2838
2839 #[test]
2840 fn set_zip_writer_uses_custom_factory() {
2841 use std::sync::{Arc, Mutex};
2842
2843 struct MockZipWriter {
2844 calls: Arc<Mutex<Vec<String>>>,
2845 }
2846
2847 impl ZipWriter for MockZipWriter {
2848 fn start_file(&mut self, name: &str, _options: SimpleFileOptions) -> Result<()> {
2849 self.calls.lock().unwrap().push(format!("start:{name}"));
2850 Ok(())
2851 }
2852 fn write_all(&mut self, _buf: &[u8]) -> Result<()> {
2853 self.calls.lock().unwrap().push("write".to_string());
2854 Ok(())
2855 }
2856 fn finish(self: Box<Self>) -> Result<()> {
2857 self.calls.lock().unwrap().push("finish".to_string());
2858 Ok(())
2859 }
2860 }
2861
2862 let f = File::new();
2863 let calls = Arc::new(Mutex::new(Vec::new()));
2864 let calls_clone = calls.clone();
2865 f.set_zip_writer(move |_writer| {
2866 Box::new(MockZipWriter {
2867 calls: calls_clone.clone(),
2868 })
2869 });
2870
2871 let _ = f.write_to_buffer().unwrap();
2872 let calls = calls.lock().unwrap();
2873 assert!(calls.iter().any(|c| c.starts_with("start:")));
2874 assert!(calls.contains(&"write".to_string()));
2875 assert!(calls.contains(&"finish".to_string()));
2876 }
2877
2878 #[test]
2879 fn get_defined_name_alias() {
2880 let f = File::new();
2881 let names = f.get_defined_name().unwrap();
2882 assert!(names.is_empty());
2883 assert_eq!(f.get_defined_names().unwrap(), names);
2884 }
2885
2886 #[test]
2887 fn update_linked_value_clears_formula_cache() {
2888 let f = File::new_with_options(Options::default());
2889 f.set_cell_int("Sheet1", "A1", 1).unwrap();
2890 f.set_cell_int("Sheet1", "A2", 2).unwrap();
2891 f.set_cell_formula("Sheet1", "A3", "A1+A2").unwrap();
2892 f.calc_cell_value("Sheet1", "A3").unwrap();
2893 f.update_linked_value().unwrap();
2894 assert!(f.workbook_reader().unwrap().calc_pr.is_none());
2895 }
2896
2897 #[test]
2898 fn read_zip_reader_extracts_parts() {
2899 let mut f = File::new_with_options(Options::default());
2900 let tmp = std::env::temp_dir().join("excelize_rust_read_zip_reader.xlsx");
2901 f.save_as(tmp.to_str().unwrap()).unwrap();
2902
2903 let file = fs::File::open(&tmp).unwrap();
2904 let mut archive = ZipArchive::new(file).unwrap();
2905 let f2 = File::new_with_options(Options::default());
2906 let (parts, count) = f2.read_zip_reader(&mut archive).unwrap();
2907 assert!(parts.contains_key(DEFAULT_XML_PATH_WORKBOOK));
2908 assert_eq!(count, 1);
2909 let _ = fs::remove_file(&tmp);
2910 }
2911
2912 #[test]
2913 fn set_content_type_part_rels_extensions_adds_default() {
2914 let f = File::new();
2915 f.set_content_type_part_rels_extensions().unwrap();
2916 let ct = f.content_types_reader().unwrap();
2917 assert!(ct.entries.iter().any(|e| {
2918 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2919 d.extension == "rels" && d.content_type == CONTENT_TYPE_RELATIONSHIPS
2920 } else {
2921 false
2922 }
2923 }));
2924 }
2925
2926 #[test]
2927 fn add_content_type_part_adds_rels_default() {
2928 let f = File::new();
2929 f.add_content_type_part(1, "table").unwrap();
2930 let ct = f.content_types_reader().unwrap();
2931 assert!(ct.entries.iter().any(|e| {
2932 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2933 d.extension == "rels"
2934 } else {
2935 false
2936 }
2937 }));
2938 }
2939
2940 #[test]
2941 fn set_content_type_part_image_extensions_uses_defaults() {
2942 let f = File::new();
2943 crate::sheet::set_content_type_part_image_extensions(&f).unwrap();
2944 let ct = f.content_types_reader().unwrap();
2945 assert!(ct.entries.iter().any(|e| {
2946 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2947 d.extension == "png" && d.content_type == "image/png"
2948 } else {
2949 false
2950 }
2951 }));
2952 assert!(ct.entries.iter().any(|e| {
2953 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2954 d.extension == "jpeg" && d.content_type == "image/jpeg"
2955 } else {
2956 false
2957 }
2958 }));
2959 assert!(!ct.entries.iter().any(|e| {
2961 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = e {
2962 o.part_name.contains("image1")
2963 } else {
2964 false
2965 }
2966 }));
2967 }
2968
2969 #[test]
2970 fn add_name_spaces_registers_namespace() {
2971 let f = File::new();
2972 let path = f.get_sheet_xml_path("Sheet1").unwrap();
2973 f.add_name_spaces(&path, SOURCE_RELATIONSHIP);
2974 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
2975 assert!(attrs.contains("xmlns:r=\""));
2976 assert!(attrs.contains(SOURCE_RELATIONSHIP));
2977 }
2978
2979 #[test]
2980 fn add_name_spaces_marks_extension_namespace_ignorable() {
2981 let f = File::new();
2982 let path = f.get_sheet_xml_path("Sheet1").unwrap();
2983 f.add_name_spaces(&path, NAMESPACE_SPREADSHEET_X14);
2984 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
2985 assert!(attrs.contains("xmlns:x14=\""));
2986 assert!(attrs.contains("xmlns:mc=\""));
2987 assert!(attrs.contains("mc:Ignorable=\"x14\""));
2988 }
2989
2990 #[test]
2991 fn add_sheet_name_space_resolves_path() {
2992 let f = File::new();
2993 f.add_sheet_name_space("Sheet1", SOURCE_RELATIONSHIP);
2994 let path = f.get_sheet_xml_path("Sheet1").unwrap();
2995 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
2996 assert!(attrs.contains("xmlns:r=\""));
2997 }
2998
2999 #[test]
3000 fn workbook_writer_preserves_alternate_content() {
3001 let f = File::new();
3002 let mut wb = f.workbook_reader().unwrap();
3003 wb.decode_alternate_content = Some(crate::xml::common::XlsxInnerXml {
3004 content: "<mc:Choice Requires=\"a14\" xmlns:a14=\"http://schemas.microsoft.com/office/drawing/2010/main\"><foo/></mc:Choice>".to_string(),
3005 });
3006 *f.workbook.lock().unwrap() = Some(wb);
3007
3008 f.workbook_writer();
3009
3010 let path = f.get_workbook_path();
3011 let bytes = f.read_xml(&path);
3012 let output = String::from_utf8_lossy(&bytes);
3013 assert!(output.contains("mc:AlternateContent"));
3014 assert!(output.contains("mc:Choice"));
3015 }
3016}