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