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.content_types_writer();
1077 self.drawings_writer();
1078 self.volatile_deps_writer();
1079 self.vml_drawing_writer();
1080 self.workbook_writer();
1081 self.work_sheet_writer();
1082 self.rels_writer();
1083 let _ = self.shared_strings_loader();
1084 self.shared_strings_writer();
1085 self.style_sheet_writer();
1086 self.theme_writer();
1087
1088 let mut files: Vec<String> = self.pkg.iter().map(|e| e.key().clone()).collect();
1090 files.sort_unstable_by(|a, b| b.cmp(a));
1091 for path in files {
1092 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1093 zw.start_file(&path, opts)?;
1094 let content = self.read_xml(&path);
1095 zw.write_all(&content)?;
1096 if content.len() as u64 > u32::MAX as u64 {
1097 self.zip64_entries.lock().unwrap().push(path.clone());
1098 }
1099 }
1100
1101 let mut temp_files: Vec<String> = self
1103 .temp_files
1104 .iter()
1105 .filter(|e| !self.pkg.contains_key(e.key()))
1106 .map(|e| e.key().clone())
1107 .collect();
1108 temp_files.sort_unstable_by(|a, b| b.cmp(a));
1109 for path in temp_files {
1110 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1111 zw.start_file(&path, opts)?;
1112 let content = self.read_bytes(&path);
1113 zw.write_all(&content)?;
1114 if content.len() as u64 > u32::MAX as u64 {
1115 self.zip64_entries.lock().unwrap().push(path.clone());
1116 }
1117 }
1118
1119 let mut streams: Vec<(String, PathBuf)> = self
1122 .streams
1123 .borrow_mut()
1124 .drain()
1125 .map(|(path, state)| (path, state.tmp_path))
1126 .collect();
1127 streams.sort_unstable_by(|a, b| b.0.cmp(&a.0));
1128 for (path, tmp_path) in streams {
1129 let opts = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
1130 zw.start_file(&path, opts)?;
1131 let mut file = fs::File::open(&tmp_path)?;
1132 let size = file.metadata()?.len();
1133 let mut buf = [0u8; 8192];
1134 loop {
1135 let n = file.read(&mut buf)?;
1136 if n == 0 {
1137 break;
1138 }
1139 zw.write_all(&buf[..n])?;
1140 }
1141 if size > u32::MAX as u64 {
1142 self.zip64_entries.lock().unwrap().push(path.clone());
1143 }
1144 let _ = fs::remove_file(&tmp_path);
1145 }
1146 Ok(())
1147 }
1148
1149 fn write_zip64_lfh(&self, buf: &mut [u8]) -> Result<()> {
1150 let entries = self.zip64_entries.lock().unwrap().clone();
1151 if entries.is_empty() {
1152 return Ok(());
1153 }
1154 let mut offset = 0usize;
1155 while offset < buf.len() {
1156 let window = &buf[offset..];
1157 let Some(idx) = find_subsequence(window, b"\x50\x4b\x03\x04") else {
1158 break;
1159 };
1160 let idx = idx + offset;
1161 if idx + 30 > buf.len() {
1162 break;
1163 }
1164 let filename_len = u16::from_le_bytes([buf[idx + 26], buf[idx + 27]]) as usize;
1165 if idx + 30 + filename_len > buf.len() {
1166 break;
1167 }
1168 let filename =
1169 std::str::from_utf8(&buf[idx + 30..idx + 30 + filename_len]).unwrap_or("");
1170 if in_str_slice(&entries, filename, true) != -1 {
1171 buf[idx + 4..idx + 6].copy_from_slice(&45u16.to_le_bytes());
1172 }
1173 offset = idx + 1;
1174 }
1175 Ok(())
1176 }
1177
1178 fn comments_writer(&self) {
1181 for entry in self.comments.iter() {
1182 let path = entry.key().clone();
1183 let comments = entry.value().clone();
1184 if let Ok(output) = quick_xml::se::to_string(&comments).map(|s| s.into_bytes()) {
1185 self.save_file_list(&path, &output);
1186 }
1187 }
1188 }
1189
1190 fn content_types_writer(&self) {
1191 if let Some(ct) = self.content_types.lock().unwrap().clone() {
1192 if let Ok(mut output) = xml_to_string(&ct).map(|s| s.into_bytes()) {
1193 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_CONTENT_TYPES, &mut output);
1194 self.save_file_list(DEFAULT_XML_PATH_CONTENT_TYPES, &output);
1195 }
1196 }
1197 }
1198
1199 fn drawings_writer(&self) {
1200 for entry in self.drawings.iter() {
1201 let path = entry.key().clone();
1202 let drawing = entry.value().clone();
1203 if let Ok(mut output) = xml_to_string(&drawing).map(|s| s.into_bytes()) {
1204 self.replace_namespace_bytes_if_needed(&path, &mut output);
1205 self.save_file_list(&path, &output);
1206 }
1207 }
1208 }
1209
1210 fn vml_drawing_writer(&self) {
1213 for entry in self.vml_drawing.iter() {
1214 let path = entry.key().clone();
1215 let drawing = entry.value().clone();
1216 let output = drawing.to_xml();
1217 self.pkg.insert(path, output);
1218 }
1219 }
1220
1221 fn workbook_writer(&self) {
1222 if let Some(mut wb) = self.workbook.lock().unwrap().clone() {
1223 if let Some(decode) = wb.decode_alternate_content.take() {
1227 wb.alternate_content = Some(crate::xml::workbook::XlsxAlternateContent {
1228 xmlns_mc: Some(
1229 "http://schemas.openxmlformats.org/markup-compatibility/2006".to_string(),
1230 ),
1231 content: decode.content,
1232 });
1233 }
1234 if let Ok(mut output) = xml_to_string(&wb).map(|s| s.into_bytes()) {
1235 let path = self.get_workbook_path();
1236 self.replace_namespace_bytes_if_needed(&path, &mut output);
1237 self.save_file_list(&path, &output);
1238 }
1239 }
1240 }
1241
1242 fn work_sheet_writer(&self) {
1243 for entry in self.sheet.iter() {
1244 let path = entry.key().clone();
1245 let sheet = entry.value().clone();
1246 if let Ok(mut output) = xml_to_string(&sheet).map(|s| s.into_bytes()) {
1247 self.replace_namespace_bytes_if_needed(&path, &mut output);
1248 strip_empty_attributes(&mut output);
1249 if let Some(ext_lst) = &sheet.ext_lst {
1250 let ext_xml = crate::xml::common::serialize_ext_lst(ext_lst);
1251 if !ext_xml.is_empty() {
1252 inject_ext_lst(&mut output, &ext_xml);
1253 }
1254 }
1255 self.save_file_list(&path, &output);
1256 }
1257 }
1258 }
1259
1260 fn rels_writer(&self) {
1261 for entry in self.relationships.iter() {
1262 let path = entry.key().clone();
1263 let rels = entry.value().clone();
1264 if let Ok(mut output) = xml_to_string(&rels).map(|s| s.into_bytes()) {
1265 self.replace_namespace_bytes_if_needed(&path, &mut output);
1266 self.save_file_list(&path, &output);
1267 }
1268 }
1269 }
1270
1271 fn shared_strings_loader(&self) -> Result<()> {
1272 if let Some(entry) = self.temp_files.get(DEFAULT_XML_PATH_SHARED_STRINGS) {
1273 let temp_path = entry.value().clone();
1274 drop(entry);
1275 let data = self.read_bytes(DEFAULT_XML_PATH_SHARED_STRINGS);
1276 if !data.is_empty() {
1277 self.pkg
1278 .insert(DEFAULT_XML_PATH_SHARED_STRINGS.to_string(), data);
1279 }
1280 self.temp_files.remove(DEFAULT_XML_PATH_SHARED_STRINGS);
1281 let _ = fs::remove_file(temp_path);
1282 *self.shared_strings.lock().unwrap() = None;
1283 self.shared_strings_map.lock().unwrap().clear();
1284 }
1285 Ok(())
1286 }
1287
1288 fn shared_strings_writer(&self) {
1289 if let Some(sst) = self.shared_strings.lock().unwrap().clone() {
1290 if let Ok(output) = xml_to_string(&sst).map(|s| s.into_bytes()) {
1291 self.save_file_list(DEFAULT_XML_PATH_SHARED_STRINGS, &output);
1292 }
1293 }
1294 }
1295
1296 fn style_sheet_writer(&self) {
1297 if let Some(st) = self.styles.lock().unwrap().clone() {
1298 if let Ok(mut output) = xml_to_string(&st).map(|s| s.into_bytes()) {
1299 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_STYLES, &mut output);
1300 strip_empty_attributes(&mut output);
1301 self.save_file_list(DEFAULT_XML_PATH_STYLES, &output);
1302 }
1303 }
1304 }
1305
1306 fn theme_writer(&self) {
1307 if let Some(theme) = self.theme.lock().unwrap().clone() {
1308 let serialized = decode_theme_to_xlsx_theme(&theme);
1309 if let Ok(mut output) = xml_to_string(&serialized).map(|s| s.into_bytes()) {
1310 self.replace_namespace_bytes_if_needed(DEFAULT_XML_PATH_THEME, &mut output);
1311 self.save_file_list(DEFAULT_XML_PATH_THEME, &output);
1312 }
1313 }
1314 }
1315
1316 pub(crate) fn replace_namespace_bytes_if_needed(&self, path: &str, content: &mut Vec<u8>) {
1321 if let Some(attrs) = self.xml_attr.get(path) {
1322 let _ = replace_root_namespace_attributes(content, attrs.value());
1323 }
1324 }
1325
1326 pub fn add_name_spaces(&self, path: &str, ns: &str) {
1333 let prefix = match ns {
1334 crate::constants::SOURCE_RELATIONSHIP => "r",
1335 crate::constants::NAMESPACE_SPREADSHEET_X14 => "x14",
1336 crate::constants::NAMESPACE_SPREADSHEET_X15 => "x15",
1337 crate::constants::NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN => "xm",
1338 crate::constants::NAMESPACE_DRAWING_ML_MAIN => "a",
1339 crate::constants::NAMESPACE_DRAWING_ML_CHART => "c",
1340 crate::constants::NAMESPACE_DRAWING_ML_SPREADSHEET => "xdr",
1341 crate::constants::NAMESPACE_DRAWING_ML_A14 => "a14",
1342 crate::constants::NAMESPACE_DRAWING_2016_SVG => "asvg",
1343 crate::constants::NAMESPACE_SPREADSHEET => return,
1345 _ => return,
1346 };
1347 if prefix.is_empty() {
1348 return;
1349 }
1350
1351 let attr_name = format!("xmlns:{prefix}");
1352 let mut attrs = self
1353 .xml_attr
1354 .get(path)
1355 .map(|a| a.clone())
1356 .unwrap_or_default();
1357 if !attrs.contains(&attr_name) {
1358 if !attrs.is_empty() {
1359 attrs.push(' ');
1360 }
1361 attrs.push_str(&format!("{attr_name}=\"{ns}\""));
1362 }
1363
1364 if self.needs_ignorable_prefix(prefix) {
1367 let mc_ns = "http://schemas.openxmlformats.org/markup-compatibility/2006";
1368 if !attrs.contains("xmlns:mc") {
1369 if !attrs.is_empty() && !attrs.ends_with(' ') {
1370 attrs.push(' ');
1371 }
1372 attrs.push_str(&format!("xmlns:mc=\"{mc_ns}\""));
1373 }
1374 self.set_ignorable_name_space(path, prefix, &mut attrs);
1375 }
1376
1377 self.xml_attr.insert(path.to_string(), attrs);
1378 }
1379
1380 fn needs_ignorable_prefix(&self, prefix: &str) -> bool {
1381 const IGNORABLE_NS: &[&str] = &[
1382 "c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14",
1383 "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9",
1384 "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x16", "x16r2", "mo", "mx", "mv", "o",
1385 "v",
1386 ];
1387 IGNORABLE_NS.contains(&prefix)
1388 }
1389
1390 fn set_ignorable_name_space(&self, _path: &str, prefix: &str, attrs: &mut String) {
1391 let marker = "mc:Ignorable=\"";
1392 if let Some(start) = attrs.find(marker) {
1393 let value_start = start + marker.len();
1394 if let Some(end) = attrs[value_start..].find('"') {
1395 let value = &attrs[value_start..value_start + end];
1396 let parts: Vec<&str> = value.split_whitespace().collect();
1397 if !parts.contains(&prefix) {
1398 let new_value = format!("{} {}", value, prefix);
1399 attrs.replace_range(value_start..value_start + end, &new_value);
1400 }
1401 return;
1402 }
1403 }
1404 if !attrs.is_empty() && !attrs.ends_with(' ') {
1405 attrs.push(' ');
1406 }
1407 attrs.push_str(&format!("mc:Ignorable=\"{prefix}\""));
1408 }
1409
1410 pub(crate) fn add_sheet_name_space(&self, sheet: &str, ns: &str) {
1412 if let Some(path) = self.get_sheet_xml_path(sheet) {
1413 self.add_name_spaces(&path, ns);
1414 }
1415 }
1416
1417 pub fn get_sheet_id(&self, sheet: &str) -> i32 {
1419 if let Ok(wb) = self.workbook_reader() {
1420 for s in &wb.sheets.sheet {
1421 if s.name.as_deref().unwrap_or("").eq_ignore_ascii_case(sheet) {
1422 return s.sheet_id.unwrap_or(0) as i32;
1423 }
1424 }
1425 }
1426 -1
1427 }
1428
1429 pub fn expand_3d_sheet_range(&self, sheet1: &str, sheet2: &str) -> Result<Vec<String>> {
1432 let mut idx1 = self.get_sheet_index(sheet1)?;
1433 let mut idx2 = self.get_sheet_index(sheet2)?;
1434 if idx1 > idx2 {
1435 std::mem::swap(&mut idx1, &mut idx2);
1436 }
1437 let list = self.get_sheet_list();
1438 Ok(list[(idx1 - 1) as usize..idx2 as usize].to_vec())
1439 }
1440
1441 pub fn clear_calc_cache(&self) {
1443 let _ = self.calc_chain.lock().unwrap().take();
1444 self.calc_cache.lock().unwrap().clear();
1445 self.calc_raw_cache.lock().unwrap().clear();
1446 self.formula_arg_cache.lock().unwrap().clear();
1447 }
1448
1449 pub fn add_content_type_part(&self, id: i32, part: &str) -> Result<()> {
1451 match part {
1452 "comments" => self.set_content_type_part_vml_extensions()?,
1453 "drawings" => crate::sheet::set_content_type_part_image_extensions(self)?,
1454 _ => {}
1455 }
1456 let mut ct = self.content_types_reader()?;
1457 let content_type = match part {
1458 "table" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_TABLE,
1459 "pivotTable" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_PIVOT_TABLE,
1460 "pivotCache" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_PIVOT_CACHE_DEFINITION,
1461 "slicer" => crate::constants::CONTENT_TYPE_SLICER,
1462 "slicerCache" => crate::constants::CONTENT_TYPE_SLICER_CACHE,
1463 "drawings" => crate::constants::CONTENT_TYPE_DRAWING,
1464 "chart" => crate::constants::CONTENT_TYPE_DRAWING_ML,
1465 "chartsheet" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_CHARTSHEET,
1466 "comments" => crate::constants::CONTENT_TYPE_SPREADSHEET_ML_COMMENTS,
1467 _ => return Ok(()),
1468 };
1469 let part_name = match part {
1470 "table" => format!("/xl/tables/table{id}.xml"),
1471 "pivotTable" => format!("/xl/pivotTables/pivotTable{id}.xml"),
1472 "pivotCache" => format!("/xl/pivotCache/pivotCacheDefinition{id}.xml"),
1473 "slicer" => format!("/xl/slicers/slicer{id}.xml"),
1474 "slicerCache" => format!("/xl/slicerCaches/slicerCache{id}.xml"),
1475 "drawings" => format!("/xl/drawings/drawing{id}.xml"),
1476 "chart" => format!("/xl/charts/chart{id}.xml"),
1477 "chartsheet" => format!("/xl/chartsheets/sheet{id}.xml"),
1478 "comments" => format!("/xl/comments{id}.xml"),
1479 _ => return Ok(()),
1480 };
1481 for entry in &ct.entries {
1482 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = entry {
1483 if o.part_name == part_name {
1484 return Ok(());
1485 }
1486 }
1487 }
1488 ct.entries
1489 .push(crate::xml::content_types::XlsxContentTypeEntry::Override(
1490 crate::xml::content_types::XlsxOverride {
1491 part_name,
1492 content_type: content_type.to_string(),
1493 },
1494 ));
1495 *self.content_types.lock().unwrap() = Some(ct);
1496 self.set_content_type_part_rels_extensions()
1497 }
1498
1499 pub(crate) fn set_content_type_part_rels_extensions(&self) -> Result<()> {
1501 let mut ct = self.content_types_reader()?;
1502 let exists = ct.entries.iter().any(|e| {
1503 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
1504 d.extension == "rels"
1505 } else {
1506 false
1507 }
1508 });
1509 if !exists {
1510 ct.entries
1511 .push(crate::xml::content_types::XlsxContentTypeEntry::Default(
1512 crate::xml::content_types::XlsxDefault {
1513 extension: "rels".to_string(),
1514 content_type: crate::constants::CONTENT_TYPE_RELATIONSHIPS.to_string(),
1515 },
1516 ));
1517 }
1518 *self.content_types.lock().unwrap() = Some(ct);
1519 Ok(())
1520 }
1521
1522 pub(crate) fn set_content_type_part_vml_extensions(&self) -> Result<()> {
1524 let mut ct = self.content_types_reader()?;
1525 let exists = ct.entries.iter().any(|e| {
1526 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
1527 d.extension == "vml"
1528 } else {
1529 false
1530 }
1531 });
1532 if !exists {
1533 ct.entries
1534 .push(crate::xml::content_types::XlsxContentTypeEntry::Default(
1535 crate::xml::content_types::XlsxDefault {
1536 extension: "vml".to_string(),
1537 content_type: crate::constants::CONTENT_TYPE_VML.to_string(),
1538 },
1539 ));
1540 }
1541 *self.content_types.lock().unwrap() = Some(ct);
1542 Ok(())
1543 }
1544
1545 pub fn remove_content_types_part(&self, content_type: &str, part_name: &str) -> Result<()> {
1547 let mut ct = self.content_types_reader()?;
1548 ct.entries.retain(|e| {
1549 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = e {
1550 !(o.content_type == content_type && o.part_name == part_name)
1551 } else {
1552 true
1553 }
1554 });
1555 *self.content_types.lock().unwrap() = Some(ct);
1556 Ok(())
1557 }
1558
1559 pub fn get_sheet_relationships_target_by_id(&self, sheet: &str, r_id: &str) -> String {
1561 let Some(sheet_xml_path) = self.get_sheet_xml_path(sheet) else {
1562 return String::new();
1563 };
1564 let sheet_rels = format!(
1565 "xl/worksheets/_rels/{}.rels",
1566 sheet_xml_path.trim_start_matches("xl/worksheets/")
1567 );
1568 if let Ok(Some(rels)) = self.rels_reader(&sheet_rels) {
1569 for rel in &rels.relationships {
1570 if rel.id == r_id {
1571 return rel.target.clone();
1572 }
1573 }
1574 }
1575 String::new()
1576 }
1577
1578 pub fn add_sheet_legacy_drawing(&self, sheet: &str, r_id: i32) -> Result<()> {
1580 let mut ws = self.work_sheet_reader(sheet)?;
1581 ws.legacy_drawing = Some(crate::xml::worksheet::XlsxLegacyDrawing {
1582 rid: Some(format!("rId{r_id}")),
1583 });
1584 if let Some(path) = self.get_sheet_xml_path(sheet) {
1585 self.sheet.insert(path, ws);
1586 }
1587 Ok(())
1588 }
1589
1590 pub fn add_sheet_legacy_drawing_hf(&self, sheet: &str, r_id: i32) -> Result<()> {
1592 let mut ws = self.work_sheet_reader(sheet)?;
1593 ws.legacy_drawing_hf = Some(crate::xml::worksheet::XlsxLegacyDrawingHF {
1594 rid: Some(format!("rId{r_id}")),
1595 });
1596 if let Some(path) = self.get_sheet_xml_path(sheet) {
1597 self.sheet.insert(path, ws);
1598 }
1599 Ok(())
1600 }
1601
1602 pub fn count_comments(&self) -> i32 {
1604 let mut count = 0;
1605 for entry in self.pkg.iter() {
1606 if entry.key().contains("xl/comments") {
1607 count += 1;
1608 }
1609 }
1610 for entry in self.comments.iter() {
1611 if entry.key().contains("xl/comments") && !self.pkg.contains_key(entry.key()) {
1612 count += 1;
1613 }
1614 }
1615 count
1616 }
1617
1618 pub fn count_vml_drawing(&self) -> i32 {
1620 let mut count = 0;
1621 for entry in self.pkg.iter() {
1622 if entry.key().contains("xl/drawings/vmlDrawing") {
1623 count += 1;
1624 }
1625 }
1626 for entry in self.vml_drawing.iter() {
1627 if entry.key().contains("xl/drawings/vmlDrawing") && !self.pkg.contains_key(entry.key())
1628 {
1629 count += 1;
1630 }
1631 }
1632 count
1633 }
1634
1635 pub fn comments_reader(
1637 &self,
1638 path: &str,
1639 ) -> Result<Option<crate::xml::comments::XlsxComments>> {
1640 if let Some(cmts) = self.comments.get(path) {
1641 return Ok(Some(cmts.clone()));
1642 }
1643 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
1644 let data = self.apply_charset_transcoder(&self.read_bytes(path))?;
1645 let data = crate::file::namespace_strict_to_transitional(&data);
1646 let mut cmts: crate::xml::comments::XlsxComments =
1647 quick_xml::de::from_reader(data.as_slice()).unwrap_or_default();
1648 for cmt in &cmts.comment_list.comment {
1649 cmts.cells.push(cmt.r#ref.clone());
1650 }
1651 self.comments.insert(path.to_string(), cmts.clone());
1652 return Ok(Some(cmts));
1653 }
1654 Ok(None)
1655 }
1656
1657 pub fn vml_drawing_reader(&self, path: &str) -> Result<Option<crate::xml::vml::VmlDrawing>> {
1659 if let Some(vml) = self.vml_drawing.get(path) {
1660 return Ok(Some(vml.clone()));
1661 }
1662 if self.pkg.contains_key(path) || self.temp_files.contains_key(path) {
1663 let data = self.apply_charset_transcoder(&self.read_bytes(path))?;
1664 let data = crate::file::namespace_strict_to_transitional(&data);
1665 let data = String::from_utf8_lossy(&data)
1666 .replace("<br>\r\n", "<br></br>\r\n")
1667 .into_bytes();
1668 let vml = crate::xml::vml::VmlDrawing::from_xml(&data).unwrap_or_default();
1669 self.vml_drawing.insert(path.to_string(), vml.clone());
1670 return Ok(Some(vml));
1671 }
1672 Ok(None)
1673 }
1674
1675 pub fn delete_sheet_relationships(&self, sheet: &str, r_id: &str) {
1677 let Some(sheet_xml_path) = self.get_sheet_xml_path(sheet) else {
1678 return;
1679 };
1680 let sheet_rels = format!(
1681 "xl/worksheets/_rels/{}.rels",
1682 sheet_xml_path.trim_start_matches("xl/worksheets/")
1683 );
1684 if let Ok(Some(mut rels)) = self.rels_reader(&sheet_rels) {
1685 rels.relationships.retain(|r| r.id != r_id);
1686 self.relationships.insert(sheet_rels, rels);
1687 }
1688 }
1689
1690 pub fn delete_workbook_rels(&self, rel_type: &str, target: &str) -> Result<String> {
1692 let rel_path = self.get_workbook_rels_path();
1693 let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
1694 let mut r_id = String::new();
1695 rels.relationships.retain(|r| {
1696 if r.r#type == rel_type && (r.target == target || r.target == format!("/{target}")) {
1697 r_id = r.id.clone();
1698 false
1699 } else {
1700 true
1701 }
1702 });
1703 self.relationships.insert(rel_path, rels);
1704 Ok(r_id)
1705 }
1706
1707 pub fn add_sheet_drawing(&self, sheet: &str, r_id: i32) -> Result<()> {
1709 let mut ws = self.work_sheet_reader(sheet)?;
1710 ws.drawing = Some(crate::xml::worksheet::XlsxDrawing {
1711 rid: Some(format!("rId{r_id}")),
1712 });
1713 self.sheet
1714 .insert(self.get_sheet_xml_path(sheet).unwrap_or_default(), ws);
1715 Ok(())
1716 }
1717
1718 pub fn count_tables(&self) -> i32 {
1720 let mut count = 0i32;
1721 for entry in self.pkg.iter() {
1722 let k = entry.key();
1723 if k.contains("xl/tables/tableSingleCells") {
1724 if let Ok(data) = self.apply_charset_transcoder(entry.value()) {
1725 let data = namespace_strict_to_transitional(&data);
1726 match xml_from_reader::<_, XlsxSingleXmlCells>(data.as_slice()) {
1727 Ok(cells) => {
1728 for cell in cells.single_xml_cell {
1729 if count < cell.id as i32 {
1730 count = cell.id as i32;
1731 }
1732 }
1733 }
1734 Err(_) => count += 1,
1735 }
1736 }
1737 }
1738 if k.contains("xl/tables/table") {
1739 if let Ok(data) = self.apply_charset_transcoder(entry.value()) {
1740 let data = namespace_strict_to_transitional(&data);
1741 match xml_from_reader::<_, XlsxTable>(data.as_slice()) {
1742 Ok(t) => {
1743 if count < t.id as i32 {
1744 count = t.id as i32;
1745 }
1746 }
1747 Err(_) => count += 1,
1748 }
1749 }
1750 }
1751 }
1752 count
1753 }
1754
1755 pub fn count_drawings(&self) -> i32 {
1757 let mut count = 0;
1758 for entry in self.pkg.iter() {
1759 if entry.key().contains("xl/drawings/drawing") {
1760 count += 1;
1761 }
1762 }
1763 for entry in self.drawings.iter() {
1764 if entry.key().contains("xl/drawings/drawing") && !self.pkg.contains_key(entry.key()) {
1765 count += 1;
1766 }
1767 }
1768 count
1769 }
1770
1771 pub fn count_charts(&self) -> i32 {
1773 let mut count = 0;
1774 for entry in self.pkg.iter() {
1775 if entry.key().contains("xl/charts/chart") {
1776 count += 1;
1777 }
1778 }
1779 count
1780 }
1781
1782 pub fn count_pivot_tables(&self) -> i32 {
1784 let mut count = 0;
1785 for entry in self.pkg.iter() {
1786 if entry.key().contains("xl/pivotTables/pivotTable") {
1787 count += 1;
1788 }
1789 }
1790 count
1791 }
1792
1793 pub fn count_pivot_cache(&self) -> i32 {
1795 let mut count = 0;
1796 for entry in self.pkg.iter() {
1797 if entry.key().contains("xl/pivotCache/pivotCacheDefinition") {
1798 count += 1;
1799 }
1800 }
1801 count
1802 }
1803
1804 pub fn count_slicers(&self) -> i32 {
1806 let mut count = 0;
1807 for entry in self.pkg.iter() {
1808 if entry.key().contains("xl/slicers/slicer") {
1809 count += 1;
1810 }
1811 }
1812 count
1813 }
1814
1815 pub fn count_slicer_cache(&self) -> i32 {
1817 let mut count = 0;
1818 for entry in self.pkg.iter() {
1819 if entry.key().contains("xl/slicerCaches/slicerCache") {
1820 count += 1;
1821 }
1822 }
1823 count
1824 }
1825
1826 pub fn get_defined_names(&self) -> Result<Vec<crate::xml::workbook::DefinedName>> {
1828 let mut out = Vec::new();
1829 if let Ok(wb) = self.workbook_reader() {
1830 if let Some(dns) = wb.defined_names {
1831 for dn in dns.defined_name {
1832 out.push(crate::xml::workbook::DefinedName {
1833 name: dn.name.clone().unwrap_or_default(),
1834 comment: dn.comment.clone().unwrap_or_default(),
1835 refers_to: dn.data.clone(),
1836 scope: dn
1837 .local_sheet_id
1838 .map(|id| {
1839 if let Ok(wb2) = self.workbook_reader() {
1840 if let Some(s) = wb2.sheets.sheet.get(id as usize) {
1841 return s.name.clone().unwrap_or_default();
1842 }
1843 }
1844 String::new()
1845 })
1846 .unwrap_or_else(|| "Workbook".to_string()),
1847 });
1848 }
1849 }
1850 }
1851 Ok(out)
1852 }
1853
1854 pub fn set_defined_name(&self, dn: &crate::xml::workbook::DefinedName) -> Result<()> {
1856 let mut wb = self.workbook_reader()?;
1857 if wb.defined_names.is_none() {
1858 wb.defined_names = Some(crate::xml::workbook::XlsxDefinedNames::default());
1859 }
1860 let names = wb.defined_names.as_mut().unwrap();
1861 let local_sheet_id = if dn.scope.is_empty() || dn.scope == "Workbook" {
1862 None
1863 } else {
1864 Some((self.get_sheet_index(&dn.scope)? - 1) as i64)
1865 };
1866 for existing in &names.defined_name {
1867 if existing.name.as_deref().unwrap_or("") == dn.name
1868 && existing.local_sheet_id == local_sheet_id
1869 {
1870 return Err(Box::new(ErrDefinedNameDuplicate));
1871 }
1872 }
1873 names
1874 .defined_name
1875 .push(crate::xml::workbook::XlsxDefinedName {
1876 name: Some(dn.name.clone()),
1877 data: dn.refers_to.clone(),
1878 hidden: Some(false),
1879 local_sheet_id,
1880 ..Default::default()
1881 });
1882 *self.workbook.lock().unwrap() = Some(wb);
1883 Ok(())
1884 }
1885
1886 pub fn delete_defined_name(&self, dn: &crate::xml::workbook::DefinedName) -> Result<()> {
1888 let mut wb = self.workbook_reader()?;
1889 let Some(names) = wb.defined_names.as_mut() else {
1890 return Err(Box::new(ErrDefinedNameScope));
1891 };
1892 let local_sheet_id = if dn.scope.is_empty() || dn.scope == "Workbook" {
1893 None
1894 } else {
1895 Some((self.get_sheet_index(&dn.scope)? - 1) as i64)
1896 };
1897 let before = names.defined_name.len();
1898 names.defined_name.retain(|e| {
1899 !(e.name.as_deref().unwrap_or("") == dn.name && e.local_sheet_id == local_sheet_id)
1900 });
1901 if names.defined_name.len() == before {
1902 return Err(Box::new(ErrDefinedNameScope));
1903 }
1904 if names.defined_name.is_empty() {
1905 wb.defined_names = None;
1906 }
1907 *self.workbook.lock().unwrap() = Some(wb);
1908 Ok(())
1909 }
1910
1911 pub fn get_defined_name_ref_to(&self, name: &str, current_sheet: &str) -> String {
1913 let mut workbook_ref = String::new();
1914 let mut sheet_ref = String::new();
1915 if let Ok(names) = self.get_defined_names() {
1916 for dn in &names {
1917 if dn.name == name {
1918 if dn.scope == "Workbook" {
1919 workbook_ref = dn.refers_to.clone();
1920 }
1921 if dn.scope == current_sheet {
1922 sheet_ref = dn.refers_to.clone();
1923 }
1924 }
1925 }
1926 }
1927 if !sheet_ref.is_empty() {
1928 sheet_ref
1929 } else {
1930 workbook_ref
1931 }
1932 }
1933
1934 pub fn get_defined_name(&self) -> Result<Vec<crate::xml::workbook::DefinedName>> {
1937 self.get_defined_names()
1938 }
1939}
1940
1941impl File {
1946 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
1948 self.write_to(writer)
1949 }
1950
1951 pub fn save_with_options(&mut self, opts: Options) -> Result<()> {
1953 {
1954 let mut o = self.options.lock().unwrap();
1955 *o = opts;
1956 }
1957 self.save()
1958 }
1959
1960 pub fn save_as_with_options(&mut self, path: &str, opts: Options) -> Result<()> {
1962 {
1963 let mut o = self.options.lock().unwrap();
1964 *o = opts;
1965 }
1966 self.save_as(path)
1967 }
1968
1969 pub fn update_linked_value(&self) -> Result<()> {
1974 let mut wb = self.workbook_reader()?;
1975 wb.calc_pr = None;
1976 *self.workbook.lock().unwrap() = Some(wb);
1977 for name in self.get_sheet_list() {
1978 if let Ok(mut ws) = self.work_sheet_reader(&name) {
1979 let path = self.get_sheet_xml_path(&name).unwrap_or_default();
1980 let mut changed = false;
1981 for row in &mut ws.sheet_data.row {
1982 for cell in &mut row.c {
1983 if cell.f.is_some() && cell.v.is_some() {
1984 cell.v = None;
1985 cell.t = None;
1986 changed = true;
1987 }
1988 }
1989 }
1990 if changed {
1991 self.sheet.insert(path, ws);
1992 }
1993 }
1994 }
1995 Ok(())
1996 }
1997
1998 pub fn read_zip_reader<R: Read + Seek>(
2002 &self,
2003 archive: &mut ZipArchive<R>,
2004 ) -> Result<(HashMap<String, Vec<u8>>, i32)> {
2005 self.read_zip_archive(archive)
2006 }
2007
2008 pub fn set_workbook_props(&self, opts: &WorkbookPropsOptions) -> Result<()> {
2010 let mut wb = self.workbook_reader()?;
2011 if wb.workbook_pr.is_none() {
2012 wb.workbook_pr = Some(XlsxWorkbookPr::default());
2013 }
2014 let pr = wb.workbook_pr.as_mut().unwrap();
2015 if let Some(v) = opts.date1904 {
2016 pr.date1904 = Some(v);
2017 }
2018 if let Some(v) = opts.filter_privacy {
2019 pr.filter_privacy = Some(v);
2020 }
2021 if let Some(ref v) = opts.code_name {
2022 pr.code_name = Some(v.clone());
2023 }
2024 *self.workbook.lock().unwrap() = Some(wb);
2025 Ok(())
2026 }
2027
2028 pub fn get_workbook_props(&self) -> Result<WorkbookPropsOptions> {
2030 let mut opts = WorkbookPropsOptions::default();
2031 let wb = self.workbook_reader()?;
2032 if let Some(pr) = &wb.workbook_pr {
2033 opts.date1904 = pr.date1904;
2034 opts.filter_privacy = pr.filter_privacy;
2035 opts.code_name = pr.code_name.clone();
2036 }
2037 Ok(opts)
2038 }
2039
2040 pub fn set_calc_props(&self, opts: &CalcPropsOptions) -> Result<()> {
2042 if let Some(ref mode) = opts.calc_mode {
2043 if in_str_slice(SUPPORTED_CALC_MODE, mode, true) == -1 {
2044 return Err(Box::new(ErrParameterInvalid));
2045 }
2046 }
2047 if let Some(ref mode) = opts.ref_mode {
2048 if in_str_slice(SUPPORTED_REF_MODE, mode, true) == -1 {
2049 return Err(Box::new(ErrParameterInvalid));
2050 }
2051 }
2052
2053 let mut wb = self.workbook_reader()?;
2054 if wb.calc_pr.is_none() {
2055 wb.calc_pr = Some(XlsxCalcPr::default());
2056 }
2057 let pr = wb.calc_pr.as_mut().unwrap();
2058 if let Some(v) = opts.calc_completed {
2059 pr.calc_completed = Some(v);
2060 }
2061 if let Some(v) = opts.calc_on_save {
2062 pr.calc_on_save = Some(v);
2063 }
2064 if let Some(v) = opts.force_full_calc {
2065 pr.force_full_calc = Some(v);
2066 }
2067 if let Some(v) = opts.full_calc_on_load {
2068 pr.full_calc_on_load = Some(v);
2069 }
2070 if let Some(v) = opts.full_precision {
2071 pr.full_precision = Some(v);
2072 }
2073 if let Some(v) = opts.iterate {
2074 pr.iterate = Some(v);
2075 }
2076 if let Some(v) = opts.iterate_delta {
2077 pr.iterate_delta = Some(v);
2078 }
2079 if let Some(ref v) = opts.calc_mode {
2080 pr.calc_mode = Some(v.clone());
2081 }
2082 if let Some(ref v) = opts.ref_mode {
2083 pr.ref_mode = Some(v.clone());
2084 }
2085 if let Some(v) = opts.calc_id {
2086 pr.calc_id = Some(v as i64);
2087 }
2088 if let Some(v) = opts.concurrent_manual_count {
2089 pr.concurrent_manual_count = Some(v as i64);
2090 }
2091 if let Some(v) = opts.iterate_count {
2092 pr.iterate_count = Some(v as i64);
2093 }
2094 pr.concurrent_calc = opts.concurrent_calc;
2095 *self.workbook.lock().unwrap() = Some(wb);
2096 Ok(())
2097 }
2098
2099 pub fn get_calc_props(&self) -> Result<CalcPropsOptions> {
2101 let mut opts = CalcPropsOptions::default();
2102 let wb = self.workbook_reader()?;
2103 if let Some(pr) = &wb.calc_pr {
2104 opts.calc_completed = pr.calc_completed;
2105 opts.calc_on_save = pr.calc_on_save;
2106 opts.force_full_calc = pr.force_full_calc;
2107 opts.full_calc_on_load = pr.full_calc_on_load;
2108 opts.full_precision = pr.full_precision;
2109 opts.iterate = pr.iterate;
2110 opts.iterate_delta = pr.iterate_delta;
2111 opts.calc_mode = pr.calc_mode.clone();
2112 opts.ref_mode = pr.ref_mode.clone();
2113 opts.calc_id = pr.calc_id.map(|v| v as u64);
2114 opts.concurrent_manual_count = pr.concurrent_manual_count.map(|v| v as u64);
2115 opts.iterate_count = pr.iterate_count.map(|v| v as u64);
2116 opts.concurrent_calc = pr.concurrent_calc;
2117 }
2118 Ok(opts)
2119 }
2120
2121 pub fn protect_workbook(&self, opts: &WorkbookProtectionOptions) -> Result<()> {
2123 let mut wb = self.workbook_reader()?;
2124 let mut protection = XlsxWorkbookProtection {
2125 lock_structure: Some(opts.lock_structure),
2126 lock_windows: Some(opts.lock_windows),
2127 ..Default::default()
2128 };
2129 if !opts.password.is_empty() {
2130 let algorithm_name = if opts.algorithm_name.is_empty() {
2131 "SHA-512"
2132 } else {
2133 &opts.algorithm_name
2134 };
2135 let (hash_value, salt_value) = crypt::gen_iso_passwd_hash(
2136 &opts.password,
2137 algorithm_name,
2138 "",
2139 WORKBOOK_PROTECTION_SPIN_COUNT,
2140 )?;
2141 protection.workbook_algorithm_name = Some(algorithm_name.to_string());
2142 protection.workbook_hash_value = Some(hash_value);
2143 protection.workbook_salt_value = Some(salt_value);
2144 protection.workbook_spin_count = Some(WORKBOOK_PROTECTION_SPIN_COUNT as i64);
2145 }
2146 wb.workbook_protection = Some(protection);
2147 *self.workbook.lock().unwrap() = Some(wb);
2148 Ok(())
2149 }
2150
2151 pub fn unprotect_workbook(&self, password: Option<&str>) -> Result<()> {
2153 let mut wb = self.workbook_reader()?;
2154 if let Some(pwd) = password {
2155 let protection = wb.workbook_protection.as_ref().ok_or_else(|| {
2156 Box::new(ErrUnprotectWorkbook) as Box<dyn std::error::Error + Send + Sync>
2157 })?;
2158 if let Some(ref algorithm_name) = protection.workbook_algorithm_name {
2159 let salt = protection.workbook_salt_value.as_deref().unwrap_or("");
2160 let spin_count = protection.workbook_spin_count.unwrap_or(0) as i32;
2161 let (hash_value, _) =
2162 crypt::gen_iso_passwd_hash(pwd, algorithm_name, salt, spin_count)?;
2163 if protection.workbook_hash_value.as_deref().unwrap_or("") != hash_value {
2164 return Err(Box::new(ErrUnprotectWorkbookPassword));
2165 }
2166 }
2167 }
2168 wb.workbook_protection = None;
2169 *self.workbook.lock().unwrap() = Some(wb);
2170 Ok(())
2171 }
2172
2173 pub fn add_vba_project(&self, data: &[u8]) -> Result<()> {
2180 if data.len() < 8 || &data[..8] != OLE_IDENTIFIER {
2181 return Err(Box::new(crate::errors::ErrAddVBAProject));
2182 }
2183 let rel_path = self.get_workbook_rels_path();
2184 let mut rels = self.rels_reader(&rel_path)?.unwrap_or_default();
2185 let mut existing = false;
2186 let mut r_id = 0;
2187 for rel in &rels.relationships {
2188 if rel.target == "vbaProject.bin" && rel.r#type == SOURCE_RELATIONSHIP_VBA_PROJECT {
2189 existing = true;
2190 }
2191 let id: i32 = rel.id.trim_start_matches("rId").parse().unwrap_or(0);
2192 if id > r_id {
2193 r_id = id;
2194 }
2195 }
2196 if !existing {
2197 r_id += 1;
2198 rels.relationships.push(XlsxRelationship {
2199 id: format!("rId{r_id}"),
2200 r#type: SOURCE_RELATIONSHIP_VBA_PROJECT.to_string(),
2201 target: "vbaProject.bin".to_string(),
2202 target_mode: None,
2203 });
2204 self.relationships.insert(rel_path, rels);
2205 }
2206 self.pkg
2207 .insert("xl/vbaProject.bin".to_string(), data.to_vec());
2208 Ok(())
2209 }
2210
2211 pub fn set_content_type_part_project_extensions(&self, content_type: &str) -> Result<()> {
2213 let mut ct = self.content_types_reader()?;
2214 let mut bin_ok = false;
2215 let mut entries = ct.entries.clone();
2216 for entry in &entries {
2217 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = entry {
2218 if d.extension == "bin" {
2219 bin_ok = true;
2220 }
2221 }
2222 }
2223 for entry in &mut entries {
2224 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = entry {
2225 if o.part_name == "/xl/workbook.xml" {
2226 o.content_type = content_type.to_string();
2227 }
2228 }
2229 }
2230 if !bin_ok {
2231 entries.push(crate::xml::content_types::XlsxContentTypeEntry::Default(
2232 XlsxDefault {
2233 extension: "bin".to_string(),
2234 content_type: CONTENT_TYPE_VBA.to_string(),
2235 },
2236 ));
2237 }
2238 ct.entries = entries;
2239 *self.content_types.lock().unwrap() = Some(ct);
2240 Ok(())
2241 }
2242}
2243
2244fn normalize_options(mut opts: Options) -> Options {
2249 if opts.unzip_size_limit == 0 {
2250 opts.unzip_size_limit = UNZIP_SIZE_LIMIT;
2251 }
2252 if opts.unzip_xml_size_limit == 0 {
2253 opts.unzip_xml_size_limit = STREAM_CHUNK_SIZE;
2254 }
2255 if opts.culture_info == 0 && opts.short_date_pattern.is_empty() {
2256 opts.culture_info = CULTURE_NAME_UNKNOWN;
2257 }
2258 opts
2259}
2260
2261pub fn namespace_strict_to_transitional(content: &[u8]) -> Vec<u8> {
2263 if content.windows(13).all(|w| w != b"purl.oclc.org") {
2264 return content.to_vec();
2265 }
2266 let mut result = content.to_vec();
2267 let translations: &[(&[u8], &[u8])] = &[
2268 (
2269 STRICT_NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES.as_bytes(),
2270 NAMESPACE_DOCUMENT_PROPERTIES_VARIANT_TYPES.as_bytes(),
2271 ),
2272 (
2273 STRICT_NAMESPACE_DRAWING_ML_MAIN.as_bytes(),
2274 NAMESPACE_DRAWING_ML_MAIN.as_bytes(),
2275 ),
2276 (
2277 STRICT_NAMESPACE_EXTENDED_PROPERTIES.as_bytes(),
2278 NAMESPACE_EXTENDED_PROPERTIES.as_bytes(),
2279 ),
2280 (
2281 STRICT_NAMESPACE_SPREADSHEET.as_bytes(),
2282 NAMESPACE_SPREADSHEET.as_bytes(),
2283 ),
2284 (
2285 STRICT_SOURCE_RELATIONSHIP.as_bytes(),
2286 SOURCE_RELATIONSHIP.as_bytes(),
2287 ),
2288 (
2289 STRICT_SOURCE_RELATIONSHIP_CHART.as_bytes(),
2290 SOURCE_RELATIONSHIP_CHART.as_bytes(),
2291 ),
2292 (
2293 STRICT_SOURCE_RELATIONSHIP_COMMENTS.as_bytes(),
2294 SOURCE_RELATIONSHIP_COMMENTS.as_bytes(),
2295 ),
2296 (
2297 STRICT_SOURCE_RELATIONSHIP_EXTEND_PROPERTIES.as_bytes(),
2298 SOURCE_RELATIONSHIP_EXTEND_PROPERTIES.as_bytes(),
2299 ),
2300 (
2301 STRICT_SOURCE_RELATIONSHIP_IMAGE.as_bytes(),
2302 SOURCE_RELATIONSHIP_IMAGE.as_bytes(),
2303 ),
2304 (
2305 STRICT_SOURCE_RELATIONSHIP_OFFICE_DOCUMENT.as_bytes(),
2306 SOURCE_RELATIONSHIP_OFFICE_DOCUMENT.as_bytes(),
2307 ),
2308 ];
2309 for (from, to) in translations {
2310 result = bytes_replace(&result, from, to);
2311 }
2312 result
2313}
2314
2315fn bytes_replace(content: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
2316 let mut result = Vec::with_capacity(content.len());
2317 let mut start = 0;
2318 while let Some(pos) = content[start..].windows(from.len()).position(|w| w == from) {
2319 let pos = start + pos;
2320 result.extend_from_slice(&content[start..pos]);
2321 result.extend_from_slice(to);
2322 start = pos + from.len();
2323 }
2324 result.extend_from_slice(&content[start..]);
2325 result
2326}
2327
2328fn detect_xml_encoding(content: &[u8]) -> Option<&str> {
2332 let start = content.windows(5).position(|w| w == b"<?xml")?;
2333 let rest = &content[start..];
2334 let end = rest.windows(2).position(|w| w == b"?>")? + 2;
2335 let decl = &rest[..end];
2336 let key = b"encoding=";
2337 let pos = decl.windows(key.len()).position(|w| w == key)?;
2338 let rest = &decl[pos + key.len()..];
2339 let quote = *rest.first()?;
2340 let close = rest[1..].iter().position(|&b| b == quote)?;
2341 std::str::from_utf8(&rest[1..1 + close]).ok()
2342}
2343
2344fn strip_xml_element(content: &[u8], name: &str) -> Vec<u8> {
2348 let s = String::from_utf8_lossy(content);
2349 let pattern = format!(
2350 r"(?s)<{}\b[^>]*>.*?</{}>",
2351 regex::escape(name),
2352 regex::escape(name)
2353 );
2354 if let Ok(re) = regex::Regex::new(&pattern) {
2355 return re.replace_all(&s, "").into_owned().into_bytes();
2356 }
2357 s.into_owned().into_bytes()
2358}
2359
2360pub(crate) fn extract_root_namespace_attributes(content: &[u8]) -> Option<String> {
2362 let s = std::str::from_utf8(content).ok()?;
2363 let mut pos = 0usize;
2365 while pos < s.len() {
2366 let c = s[pos..].chars().next()?;
2367 if c == '<' {
2368 if s[pos..].starts_with("<?") {
2369 if let Some(end) = s[pos..].find("?>") {
2370 pos += end + 2;
2371 continue;
2372 }
2373 return None;
2374 }
2375 break;
2376 }
2377 pos += c.len_utf8();
2378 }
2379 if pos >= s.len() {
2380 return None;
2381 }
2382 let start = pos;
2383 let close = s[start..].find('>')?;
2384 let tag = &s[start..start + close + 1];
2385 let name_end = tag
2387 .find(|c: char| c.is_whitespace())
2388 .unwrap_or(tag.len() - 1);
2389 let after_name = &tag[name_end..tag.len() - 1];
2390 let trimmed = after_name.trim();
2391 if trimmed.is_empty() {
2392 None
2393 } else {
2394 Some(trimmed.to_string())
2395 }
2396}
2397
2398pub(crate) fn replace_root_namespace_attributes(content: &mut Vec<u8>, attrs: &str) -> Result<()> {
2400 let s = std::str::from_utf8(content)
2401 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
2402 let mut pos = 0usize;
2403 while pos < s.len() {
2404 let c = s[pos..]
2405 .chars()
2406 .next()
2407 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "empty"))?;
2408 if c == '<' {
2409 if s[pos..].starts_with("<?") {
2410 if let Some(end) = s[pos..].find("?>") {
2411 pos += end + 2;
2412 continue;
2413 }
2414 return Err(Box::new(io::Error::new(
2415 io::ErrorKind::InvalidData,
2416 "bad xml decl",
2417 )));
2418 }
2419 break;
2420 }
2421 pos += c.len_utf8();
2422 }
2423 let start = pos;
2424 let close = s[start..]
2425 .find('>')
2426 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no root close"))?;
2427 let tag = &s[start..start + close + 1];
2428 let name_end = tag
2429 .find(|c: char| c.is_whitespace())
2430 .unwrap_or(tag.len() - 1);
2431 let tag_name = &tag[1..name_end];
2432 let end_tag = start + close + 1;
2433 let new_start = format!("<{tag_name} {attrs}>");
2434 let tail = content.split_off(end_tag);
2435 content.truncate(start);
2436 content.extend_from_slice(new_start.as_bytes());
2437 content.extend_from_slice(&tail);
2438 Ok(())
2439}
2440
2441fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
2442 haystack
2443 .windows(needle.len())
2444 .position(|window| window == needle)
2445}
2446
2447pub(crate) fn strip_empty_attributes(content: &mut Vec<u8>) {
2454 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
2455 let re = RE.get_or_init(|| regex::Regex::new(r#" ([a-zA-Z_][a-zA-Z0-9_:\-]*)="""#).unwrap());
2456 let s = String::from_utf8_lossy(content);
2457 let cleaned = re.replace_all(&s, "");
2458 *content = cleaned.into_owned().into_bytes();
2459}
2460
2461fn decode_theme_to_xlsx_theme(theme: &DecodeTheme) -> XlsxTheme {
2462 fn convert_color(c: &crate::xml::theme::DecodeCtColor) -> XlsxCtColor {
2463 XlsxCtColor {
2464 scrgb_clr: c.scrgb_clr.clone(),
2465 srgb_clr: c.srgb_clr.clone(),
2466 hsl_clr: c.hsl_clr.clone(),
2467 sys_clr: c.sys_clr.clone().map(|s| XlsxSysClr {
2468 val: s.val,
2469 last_clr: s.last_clr,
2470 }),
2471 scheme_clr: c.scheme_clr.clone(),
2472 prst_clr: c.prst_clr.clone(),
2473 }
2474 }
2475 fn convert_font(c: &crate::xml::theme::DecodeFontCollection) -> XlsxFontCollection {
2476 XlsxFontCollection {
2477 latin: c.latin.clone(),
2478 ea: c.ea.clone(),
2479 cs: c.cs.clone(),
2480 font: c.font.clone(),
2481 ext_lst: c.ext_lst.clone(),
2482 }
2483 }
2484 XlsxTheme {
2485 xmlns_a: None,
2486 xmlns_r: None,
2487 name: theme.name.clone(),
2488 theme_elements: XlsxBaseStyles {
2489 clr_scheme: crate::xml::theme::XlsxColorScheme {
2490 name: theme.theme_elements.clr_scheme.name.clone(),
2491 dk1: convert_color(&theme.theme_elements.clr_scheme.dk1),
2492 lt1: convert_color(&theme.theme_elements.clr_scheme.lt1),
2493 dk2: convert_color(&theme.theme_elements.clr_scheme.dk2),
2494 lt2: convert_color(&theme.theme_elements.clr_scheme.lt2),
2495 accent1: convert_color(&theme.theme_elements.clr_scheme.accent1),
2496 accent2: convert_color(&theme.theme_elements.clr_scheme.accent2),
2497 accent3: convert_color(&theme.theme_elements.clr_scheme.accent3),
2498 accent4: convert_color(&theme.theme_elements.clr_scheme.accent4),
2499 accent5: convert_color(&theme.theme_elements.clr_scheme.accent5),
2500 accent6: convert_color(&theme.theme_elements.clr_scheme.accent6),
2501 hlink: convert_color(&theme.theme_elements.clr_scheme.hlink),
2502 fol_hlink: convert_color(&theme.theme_elements.clr_scheme.fol_hlink),
2503 ext_lst: theme.theme_elements.clr_scheme.ext_lst.clone(),
2504 },
2505 font_scheme: crate::xml::theme::XlsxFontScheme {
2506 name: theme.theme_elements.font_scheme.name.clone(),
2507 major_font: convert_font(&theme.theme_elements.font_scheme.major_font),
2508 minor_font: convert_font(&theme.theme_elements.font_scheme.minor_font),
2509 ext_lst: theme.theme_elements.font_scheme.ext_lst.clone(),
2510 },
2511 fmt_scheme: crate::xml::theme::XlsxStyleMatrix {
2512 name: theme.theme_elements.fmt_scheme.name.clone(),
2513 fill_style_lst: theme.theme_elements.fmt_scheme.fill_style_lst.clone(),
2514 ln_style_lst: theme.theme_elements.fmt_scheme.ln_style_lst.clone(),
2515 effect_style_lst: theme.theme_elements.fmt_scheme.effect_style_lst.clone(),
2516 bg_fill_style_lst: theme.theme_elements.fmt_scheme.bg_fill_style_lst.clone(),
2517 },
2518 ext_lst: theme.theme_elements.ext_lst.clone(),
2519 },
2520 object_defaults: theme.object_defaults.clone(),
2521 extra_clr_scheme_lst: theme.extra_clr_scheme_lst.clone(),
2522 cust_clr_lst: theme.cust_clr_lst.clone(),
2523 ext_lst: theme.ext_lst.clone(),
2524 }
2525}
2526
2527pub fn set_content_type_part_project_extensions(file: &File, content_type: &str) -> Result<()> {
2533 file.set_content_type_part_project_extensions(content_type)
2534}
2535
2536pub fn add_vba_project(file: &File, data: &[u8]) -> Result<()> {
2538 file.add_vba_project(data)
2539}
2540
2541fn extract_ext_lst(data: &[u8]) -> Option<String> {
2547 let s = String::from_utf8_lossy(data);
2548 let start_key = "<extLst";
2549 let start = s.find(start_key)?;
2550 let close_bracket = s[start..].find('>')? + start + 1;
2551 let end_key = "</extLst>";
2552 let end = s.find(end_key)? + end_key.len();
2553 Some(s[close_bracket..end - end_key.len()].to_string())
2554}
2555
2556fn remove_ext_lst(data: &mut Vec<u8>) {
2559 let s = String::from_utf8_lossy(data);
2560 let Some(start) = s.find("<extLst") else {
2561 return;
2562 };
2563 let Some(end) = s.find("</extLst>") else {
2564 return;
2565 };
2566 let end = end + "</extLst>".len();
2567 let mut result = s[..start].as_bytes().to_vec();
2568 result.extend_from_slice(s[end..].as_bytes());
2569 *data = result;
2570}
2571
2572fn inject_ext_lst(output: &mut Vec<u8>, ext_xml: &str) {
2575 let s = String::from_utf8_lossy(output);
2576 let Some(pos) = s.rfind("</worksheet>") else {
2577 return;
2578 };
2579 let mut result = s[..pos].as_bytes().to_vec();
2580 result.extend_from_slice(b"<extLst>");
2581 result.extend_from_slice(ext_xml.as_bytes());
2582 result.extend_from_slice(b"</extLst>");
2583 result.extend_from_slice(s[pos..].as_bytes());
2584 *output = result;
2585}
2586
2587impl Drop for File {
2588 fn drop(&mut self) {
2589 for entry in self.temp_files.iter() {
2592 let _ = fs::remove_file(entry.value());
2593 }
2594 self.temp_files.clear();
2595 for state in self.streams.borrow().values() {
2596 let _ = fs::remove_file(&state.tmp_path);
2597 }
2598 self.streams.borrow_mut().clear();
2599 }
2600}
2601
2602#[cfg(test)]
2603mod tests {
2604 use super::*;
2605
2606 #[test]
2607 fn new_file_round_trip() {
2608 let mut f = File::new_with_options(Options::default());
2609 let tmp = std::env::temp_dir().join("excelize_rust_test.xlsx");
2610 f.save_as(tmp.to_str().unwrap()).unwrap();
2611
2612 let file = fs::File::open(&tmp).unwrap();
2613 let archive = ZipArchive::new(file).unwrap();
2614 let names: Vec<String> = archive.file_names().map(|s| s.to_string()).collect();
2615 assert!(names.contains(&"[Content_Types].xml".to_string()));
2616 assert!(names.contains(&"xl/workbook.xml".to_string()));
2617 assert!(names.contains(&"xl/worksheets/sheet1.xml".to_string()));
2618 assert!(names.contains(&"xl/styles.xml".to_string()));
2619 assert!(names.contains(&"xl/theme/theme1.xml".to_string()));
2620 drop(archive);
2621 let _ = fs::remove_file(&tmp);
2622 }
2623
2624 #[test]
2625 fn save_and_reopen() {
2626 let mut f = File::new_with_options(Options::default());
2627 let tmp = std::env::temp_dir().join("excelize_rust_reopen_test.xlsx");
2628 f.save_as(tmp.to_str().unwrap()).unwrap();
2629
2630 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
2631 assert_eq!(*f2.sheet_count.lock().unwrap(), 1);
2632 assert_eq!(f2.get_sheet_list(), vec!["Sheet1"]);
2633 let _ = fs::remove_file(&tmp);
2634 }
2635
2636 #[test]
2637 fn open_existing_xlsx() {
2638 let path = "test/Book1.xlsx";
2640 let f = File::open_file(path, Options::default()).unwrap();
2641 assert!(*f.sheet_count.lock().unwrap() >= 1);
2642 let list = f.get_sheet_list();
2643 assert!(!list.is_empty());
2644 }
2645
2646 #[test]
2647 fn workbook_props_round_trip() {
2648 let f = File::new();
2649 let mut opts = WorkbookPropsOptions::default();
2650 opts.date1904 = Some(true);
2651 opts.filter_privacy = Some(false);
2652 opts.code_name = Some("Workbook1".to_string());
2653 f.set_workbook_props(&opts).unwrap();
2654
2655 let got = f.get_workbook_props().unwrap();
2656 assert_eq!(got.date1904, Some(true));
2657 assert_eq!(got.filter_privacy, Some(false));
2658 assert_eq!(got.code_name, Some("Workbook1".to_string()));
2659 }
2660
2661 #[test]
2662 fn calc_props_round_trip() {
2663 let f = File::new();
2664 let mut opts = CalcPropsOptions::default();
2665 opts.calc_mode = Some("manual".to_string());
2666 opts.ref_mode = Some("R1C1".to_string());
2667 opts.full_calc_on_load = Some(true);
2668 opts.calc_id = Some(152511);
2669 opts.iterate_count = Some(100);
2670 f.set_calc_props(&opts).unwrap();
2671
2672 let got = f.get_calc_props().unwrap();
2673 assert_eq!(got.calc_mode, Some("manual".to_string()));
2674 assert_eq!(got.ref_mode, Some("R1C1".to_string()));
2675 assert_eq!(got.full_calc_on_load, Some(true));
2676 assert_eq!(got.calc_id, Some(152511));
2677 assert_eq!(got.iterate_count, Some(100));
2678 }
2679
2680 #[test]
2681 fn calc_props_rejects_invalid_mode() {
2682 let f = File::new();
2683 let mut opts = CalcPropsOptions::default();
2684 opts.calc_mode = Some("invalid".to_string());
2685 assert!(f.set_calc_props(&opts).is_err());
2686
2687 opts.calc_mode = None;
2688 opts.ref_mode = Some("B3".to_string());
2689 assert!(f.set_calc_props(&opts).is_err());
2690 }
2691
2692 #[test]
2693 fn protect_workbook_round_trip() {
2694 let f = File::new();
2695 let opts = WorkbookProtectionOptions {
2696 password: "password".to_string(),
2697 lock_structure: true,
2698 lock_windows: false,
2699 ..Default::default()
2700 };
2701 f.protect_workbook(&opts).unwrap();
2702 let wb = f.workbook_reader().unwrap();
2703 let protection = wb.workbook_protection.as_ref().unwrap();
2704 assert_eq!(protection.lock_structure, Some(true));
2705 assert!(protection.workbook_hash_value.is_some());
2706
2707 assert!(f.unprotect_workbook(Some("wrong")).is_err());
2708 f.unprotect_workbook(Some("password")).unwrap();
2709 assert!(f.workbook_reader().unwrap().workbook_protection.is_none());
2710 }
2711
2712 #[test]
2713 fn add_vba_project() {
2714 let f = File::new();
2715 let mut sheet_opts = crate::sheet::SheetPropsOptions::default();
2716 sheet_opts.code_name = Some("Sheet1".to_string());
2717 f.set_sheet_props("Sheet1", &sheet_opts).unwrap();
2718
2719 let bad = fs::read("test/Book1.xlsx").unwrap();
2720 assert!(f.add_vba_project(&bad).is_err());
2721
2722 let data = fs::read("test/vbaProject.bin").unwrap();
2723 f.add_vba_project(&data).unwrap();
2724 f.add_vba_project(&data).unwrap();
2726
2727 let rels = f
2728 .rels_reader("xl/_rels/workbook.xml.rels")
2729 .unwrap()
2730 .unwrap();
2731 let vba_rel = rels
2732 .relationships
2733 .iter()
2734 .find(|r| r.target == "vbaProject.bin" && r.r#type == SOURCE_RELATIONSHIP_VBA_PROJECT);
2735 assert!(vba_rel.is_some());
2736
2737 let tmp = std::env::temp_dir().join("excelize_rust_vba.xlsm");
2738 let mut f2 = File::new();
2739 f2.set_sheet_props("Sheet1", &sheet_opts).unwrap();
2740 f2.add_vba_project(&data).unwrap();
2741 f2.save_as(tmp.to_str().unwrap()).unwrap();
2742
2743 let f3 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
2744 assert!(f3.pkg.contains_key("xl/vbaProject.bin"));
2745 let _ = fs::remove_file(&tmp);
2746 }
2747
2748 #[test]
2749 fn charset_transcoder_is_used_for_non_utf8_encoding() {
2750 use crate::constants::DEFAULT_XML_PATH_CALC_CHAIN;
2751 use std::io::Read;
2752 use std::sync::{Arc, Mutex};
2753
2754 let f = File::new();
2755 let xml = br#"<?xml version="1.0" encoding="X-TEST" standalone="yes"?>
2756<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><c r="A1" i="1"/></calcChain>"#;
2757 f.pkg
2758 .insert(DEFAULT_XML_PATH_CALC_CHAIN.to_string(), xml.to_vec());
2759 *f.calc_chain.lock().unwrap() = None;
2760
2761 let called = Arc::new(Mutex::new(String::new()));
2762 let called_clone = called.clone();
2763 f.charset_transcoder(move |charset, mut input| {
2764 *called_clone.lock().unwrap() = charset.to_string();
2765 let mut buf = Vec::new();
2766 input.read_to_end(&mut buf).unwrap();
2767 Ok(Box::new(Cursor::new(buf)) as Box<dyn Read>)
2768 });
2769
2770 let cc = f.calc_chain_reader().unwrap();
2771 assert_eq!(cc.c.len(), 1);
2772 assert_eq!(cc.c[0].r, "A1");
2773 assert_eq!(called.lock().unwrap().as_str(), "X-TEST");
2774 }
2775
2776 #[test]
2777 fn charset_transcoder_handles_invalid_utf8_bytes() {
2778 use crate::constants::DEFAULT_XML_PATH_CALC_CHAIN;
2779 use std::io::Read;
2780 use std::sync::{Arc, Mutex};
2781
2782 let f = File::new();
2783 let mut xml = br#"<?xml version="1.0" encoding="X-BAD" standalone="yes"?>
2784<calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><c r="A1" i="1"/></calcChain>"#
2785 .to_vec();
2786 xml.push(0xFF); f.pkg.insert(DEFAULT_XML_PATH_CALC_CHAIN.to_string(), xml);
2788 *f.calc_chain.lock().unwrap() = None;
2789
2790 let called = Arc::new(Mutex::new(false));
2791 let called_clone = called.clone();
2792 f.charset_transcoder(move |_charset, mut input| {
2793 *called_clone.lock().unwrap() = true;
2794 let mut buf = Vec::new();
2795 input.read_to_end(&mut buf).unwrap();
2796 buf.pop(); Ok(Box::new(Cursor::new(buf)) as Box<dyn Read>)
2798 });
2799
2800 let cc = f.calc_chain_reader().unwrap();
2801 assert_eq!(cc.c.len(), 1);
2802 assert_eq!(cc.c[0].r, "A1");
2803 assert!(*called.lock().unwrap());
2804 }
2805
2806 #[test]
2807 fn set_zip_writer_uses_custom_factory() {
2808 use std::sync::{Arc, Mutex};
2809
2810 struct MockZipWriter {
2811 calls: Arc<Mutex<Vec<String>>>,
2812 }
2813
2814 impl ZipWriter for MockZipWriter {
2815 fn start_file(&mut self, name: &str, _options: SimpleFileOptions) -> Result<()> {
2816 self.calls.lock().unwrap().push(format!("start:{name}"));
2817 Ok(())
2818 }
2819 fn write_all(&mut self, _buf: &[u8]) -> Result<()> {
2820 self.calls.lock().unwrap().push("write".to_string());
2821 Ok(())
2822 }
2823 fn finish(self: Box<Self>) -> Result<()> {
2824 self.calls.lock().unwrap().push("finish".to_string());
2825 Ok(())
2826 }
2827 }
2828
2829 let f = File::new();
2830 let calls = Arc::new(Mutex::new(Vec::new()));
2831 let calls_clone = calls.clone();
2832 f.set_zip_writer(move |_writer| {
2833 Box::new(MockZipWriter {
2834 calls: calls_clone.clone(),
2835 })
2836 });
2837
2838 let _ = f.write_to_buffer().unwrap();
2839 let calls = calls.lock().unwrap();
2840 assert!(calls.iter().any(|c| c.starts_with("start:")));
2841 assert!(calls.contains(&"write".to_string()));
2842 assert!(calls.contains(&"finish".to_string()));
2843 }
2844
2845 #[test]
2846 fn get_defined_name_alias() {
2847 let f = File::new();
2848 let names = f.get_defined_name().unwrap();
2849 assert!(names.is_empty());
2850 assert_eq!(f.get_defined_names().unwrap(), names);
2851 }
2852
2853 #[test]
2854 fn update_linked_value_clears_formula_cache() {
2855 let f = File::new_with_options(Options::default());
2856 f.set_cell_int("Sheet1", "A1", 1).unwrap();
2857 f.set_cell_int("Sheet1", "A2", 2).unwrap();
2858 f.set_cell_formula("Sheet1", "A3", "A1+A2").unwrap();
2859 f.calc_cell_value("Sheet1", "A3").unwrap();
2860 f.update_linked_value().unwrap();
2861 assert!(f.workbook_reader().unwrap().calc_pr.is_none());
2862 }
2863
2864 #[test]
2865 fn read_zip_reader_extracts_parts() {
2866 let mut f = File::new_with_options(Options::default());
2867 let tmp = std::env::temp_dir().join("excelize_rust_read_zip_reader.xlsx");
2868 f.save_as(tmp.to_str().unwrap()).unwrap();
2869
2870 let file = fs::File::open(&tmp).unwrap();
2871 let mut archive = ZipArchive::new(file).unwrap();
2872 let f2 = File::new_with_options(Options::default());
2873 let (parts, count) = f2.read_zip_reader(&mut archive).unwrap();
2874 assert!(parts.contains_key(DEFAULT_XML_PATH_WORKBOOK));
2875 assert_eq!(count, 1);
2876 let _ = fs::remove_file(&tmp);
2877 }
2878
2879 #[test]
2880 fn set_content_type_part_rels_extensions_adds_default() {
2881 let f = File::new();
2882 f.set_content_type_part_rels_extensions().unwrap();
2883 let ct = f.content_types_reader().unwrap();
2884 assert!(ct.entries.iter().any(|e| {
2885 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2886 d.extension == "rels" && d.content_type == CONTENT_TYPE_RELATIONSHIPS
2887 } else {
2888 false
2889 }
2890 }));
2891 }
2892
2893 #[test]
2894 fn add_content_type_part_adds_rels_default() {
2895 let f = File::new();
2896 f.add_content_type_part(1, "table").unwrap();
2897 let ct = f.content_types_reader().unwrap();
2898 assert!(ct.entries.iter().any(|e| {
2899 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2900 d.extension == "rels"
2901 } else {
2902 false
2903 }
2904 }));
2905 }
2906
2907 #[test]
2908 fn set_content_type_part_image_extensions_uses_defaults() {
2909 let f = File::new();
2910 crate::sheet::set_content_type_part_image_extensions(&f).unwrap();
2911 let ct = f.content_types_reader().unwrap();
2912 assert!(ct.entries.iter().any(|e| {
2913 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2914 d.extension == "png" && d.content_type == "image/png"
2915 } else {
2916 false
2917 }
2918 }));
2919 assert!(ct.entries.iter().any(|e| {
2920 if let crate::xml::content_types::XlsxContentTypeEntry::Default(d) = e {
2921 d.extension == "jpeg" && d.content_type == "image/jpeg"
2922 } else {
2923 false
2924 }
2925 }));
2926 assert!(!ct.entries.iter().any(|e| {
2928 if let crate::xml::content_types::XlsxContentTypeEntry::Override(o) = e {
2929 o.part_name.contains("image1")
2930 } else {
2931 false
2932 }
2933 }));
2934 }
2935
2936 #[test]
2937 fn add_name_spaces_registers_namespace() {
2938 let f = File::new();
2939 let path = f.get_sheet_xml_path("Sheet1").unwrap();
2940 f.add_name_spaces(&path, SOURCE_RELATIONSHIP);
2941 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
2942 assert!(attrs.contains("xmlns:r=\""));
2943 assert!(attrs.contains(SOURCE_RELATIONSHIP));
2944 }
2945
2946 #[test]
2947 fn add_name_spaces_marks_extension_namespace_ignorable() {
2948 let f = File::new();
2949 let path = f.get_sheet_xml_path("Sheet1").unwrap();
2950 f.add_name_spaces(&path, NAMESPACE_SPREADSHEET_X14);
2951 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
2952 assert!(attrs.contains("xmlns:x14=\""));
2953 assert!(attrs.contains("xmlns:mc=\""));
2954 assert!(attrs.contains("mc:Ignorable=\"x14\""));
2955 }
2956
2957 #[test]
2958 fn add_sheet_name_space_resolves_path() {
2959 let f = File::new();
2960 f.add_sheet_name_space("Sheet1", SOURCE_RELATIONSHIP);
2961 let path = f.get_sheet_xml_path("Sheet1").unwrap();
2962 let attrs = f.xml_attr.get(&path).map(|a| a.clone()).unwrap_or_default();
2963 assert!(attrs.contains("xmlns:r=\""));
2964 }
2965
2966 #[test]
2967 fn workbook_writer_preserves_alternate_content() {
2968 let f = File::new();
2969 let mut wb = f.workbook_reader().unwrap();
2970 wb.decode_alternate_content = Some(crate::xml::common::XlsxInnerXml {
2971 content: "<mc:Choice Requires=\"a14\" xmlns:a14=\"http://schemas.microsoft.com/office/drawing/2010/main\"><foo/></mc:Choice>".to_string(),
2972 });
2973 *f.workbook.lock().unwrap() = Some(wb);
2974
2975 f.workbook_writer();
2976
2977 let path = f.get_workbook_path();
2978 let bytes = f.read_xml(&path);
2979 let output = String::from_utf8_lossy(&bytes);
2980 assert!(output.contains("mc:AlternateContent"));
2981 assert!(output.contains("mc:Choice"));
2982 }
2983}