Skip to main content

rust_xlsxwriter/
workbook.rs

1// workbook - A module for creating the Excel workbook.xml file.
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4//
5// Copyright 2022-2026, John McNamara, jmcnamara@cpan.org
6
7//! # Working with Workbooks
8//!
9//! The [`Workbook`] struct represents an Excel file in its entirety. It is the
10//! starting point for creating a new Excel xlsx file.
11//!
12//!
13//! ```
14//! # // This code is available in examples/doc_workbook_new.rs
15//! #
16//! use rust_xlsxwriter::{Workbook, XlsxError};
17//!
18//! fn main() -> Result<(), XlsxError> {
19//!     let mut workbook = Workbook::new();
20//!
21//!     let _worksheet = workbook.add_worksheet();
22//!
23//!     workbook.save("workbook.xlsx")?;
24//!
25//!     Ok(())
26//! }
27//! ```
28//!
29//! <img src="https://rustxlsxwriter.github.io/images/workbook_new.png">
30//!
31//!
32//! For more details on the Worksheet APIs, see the [`Worksheet`]
33//! documentation and the sections below.
34//!
35//! # Contents
36//!
37//! - [Creating and saving an xlsx file](#creating-and-saving-an-xlsx-file)
38//! - [Checksum of a saved file](#checksum-of-a-saved-file)
39//!
40//!
41//! # Creating and saving an xlsx file
42//!
43//! Creating a  [`Workbook`] struct instance to represent an Excel xlsx file is
44//! done via the [`Workbook::new()`] method:
45//!
46//!
47//! ```
48//! # // This code is available in examples/doc_workbook_new.rs
49//! #
50//! # use rust_xlsxwriter::{Workbook, XlsxError};
51//! #
52//! # fn main() -> Result<(), XlsxError> {
53//!     let mut workbook = Workbook::new();
54//!
55//! #     let _worksheet = workbook.add_worksheet();
56//! #
57//! #     workbook.save("workbook.xlsx")?;
58//! #
59//! #     Ok(())
60//! # }
61//! ```
62//!
63//! Once you are finished writing data via a worksheet you can save it with the
64//! [`Workbook::save()`] method:
65//!
66//! ```
67//! # // This code is available in examples/doc_workbook_new.rs
68//! #
69//! use rust_xlsxwriter::{Workbook, XlsxError};
70//!
71//! fn main() -> Result<(), XlsxError> {
72//!     let mut workbook = Workbook::new();
73//!
74//!     let _worksheet = workbook.add_worksheet();
75//!
76//!     workbook.save("workbook.xlsx")?;
77//!
78//!     Ok(())
79//! }
80//! ```
81//!
82//! This will give you a simple output file like the following.
83//!
84//! <img src="https://rustxlsxwriter.github.io/images/workbook_new.png">
85//!
86//! The  `save()` method takes a [`std::path`] or path/filename string. You can
87//! also save the xlsx file data to a `Vec<u8>` buffer via the
88//! [`Workbook::save_to_buffer()`] method:
89//!
90//! ```
91//! # // This code is available in examples/doc_workbook_save_to_buffer.rs
92//! #
93//! use rust_xlsxwriter::{Workbook, XlsxError};
94//!
95//! fn main() -> Result<(), XlsxError> {
96//!     let mut workbook = Workbook::new();
97//!
98//!     let worksheet = workbook.add_worksheet();
99//!     worksheet.write_string(0, 0, "Hello")?;
100//!
101//!     let buf = workbook.save_to_buffer()?;
102//!
103//!     println!("File size: {}", buf.len());
104//!
105//!     Ok(())
106//! }
107//! ```
108//!
109//! This can be useful if you intend to stream the data.
110//!
111//!
112//! # Checksum of a saved file
113//!
114//!
115//! A common issue that occurs with `rust_xlsxwriter`, but also with Excel, is
116//! that running the same program twice doesn't generate the same file, byte for
117//! byte. This can cause issues with applications that do checksumming for
118//! testing purposes.
119//!
120//! For example consider the following simple `rust_xlsxwriter` program:
121//!
122//! ```
123//! # // This code is available in examples/doc_properties_checksum1.rs
124//! #
125//! use rust_xlsxwriter::{Workbook, XlsxError};
126//!
127//! fn main() -> Result<(), XlsxError> {
128//!     let mut workbook = Workbook::new();
129//!     let worksheet = workbook.add_worksheet();
130//!
131//!     worksheet.write_string(0, 0, "Hello")?;
132//!
133//!     workbook.save("properties.xlsx")?;
134//!
135//!     Ok(())
136//! }
137//! ```
138//!
139//! If we run this several times, with a small delay, we will get different
140//! checksums as shown below:
141//!
142//! ```bash
143//! $ cargo run --example doc_properties_checksum1
144//!
145//! $ sum properties.xlsx
146//! 62457 6 properties.xlsx
147//!
148//! $ sleep 2
149//!
150//! $ cargo run --example doc_properties_checksum1
151//!
152//! $ sum properties.xlsx
153//! 56692 6 properties.xlsx # Different to previous.
154//! ```
155//!
156//! This is due to a file creation datetime that is included in the file and
157//! which changes each time a new file is created.
158//!
159//! The relevant section of the `docProps/core.xml` sub-file in the xlsx format
160//! looks like this:
161//!
162//! ```xml
163//! <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
164//! <cp:coreProperties>
165//!   <dc:creator/>
166//!   <cp:lastModifiedBy/>
167//!   <dcterms:created xsi:type="dcterms:W3CDTF">2023-01-08T00:23:58Z</dcterms:created>
168//!   <dcterms:modified xsi:type="dcterms:W3CDTF">2023-01-08T00:23:58Z</dcterms:modified>
169//! </cp:coreProperties>
170//! ```
171//!
172//! If required, this can be avoided by setting a constant creation date in the
173//! document properties metadata:
174//!
175//!
176//! ```
177//! # // This code is available in examples/doc_properties_checksum2.rs
178//! #
179//! use rust_xlsxwriter::{DocProperties, ExcelDateTime, Workbook, XlsxError};
180//!
181//! fn main() -> Result<(), XlsxError> {
182//!     let mut workbook = Workbook::new();
183//!
184//!     // Create a file creation date for the file.
185//!     let date = ExcelDateTime::from_ymd(2023, 1, 1)?;
186//!
187//!     // Add it to the document metadata.
188//!     let properties = DocProperties::new().set_creation_datetime(&date);
189//!     workbook.set_properties(&properties);
190//!
191//!     let worksheet = workbook.add_worksheet();
192//!     worksheet.write_string(0, 0, "Hello")?;
193//!
194//!     workbook.save("properties.xlsx")?;
195//!
196//!     Ok(())
197//! }
198//! ```
199//!
200//! Then we will get the same checksum for the same output every time:
201//!
202//! ```bash
203//! $ cargo run --example doc_properties_checksum2
204//!
205//! $ sum properties.xlsx 8914 6 properties.xlsx
206//!
207//! $ sleep 2
208//!
209//! $ cargo run --example doc_properties_checksum2
210//!
211//! $ sum properties.xlsx 8914 6 properties.xlsx # Same as previous
212//! ```
213//!
214//! For more details see [`DocProperties`] and [`Workbook::set_properties()`].
215//!
216#![warn(missing_docs)]
217
218mod tests;
219
220use std::collections::{HashMap, HashSet};
221use std::fs::{read_to_string, File};
222use std::io::{BufReader, Cursor, Read, Seek, Write};
223use std::path::Path;
224use std::sync::{Arc, Mutex, RwLock};
225use zip::ZipArchive;
226
227#[cfg(feature = "constant_memory")]
228use tempfile::tempfile_in;
229
230#[cfg(feature = "constant_memory")]
231use std::io::BufWriter;
232
233#[cfg(feature = "constant_memory")]
234use std::path::PathBuf;
235
236use crate::error::XlsxError;
237use crate::feature_property_bag::FeaturePropertyBagTypes;
238use crate::format::Format;
239use crate::packager::Packager;
240use crate::packager::PackagerOptions;
241use crate::shared_strings_table::SharedStringsTable;
242use crate::theme::{THEME_XML_2007, THEME_XML_2023};
243use crate::worksheet::Worksheet;
244
245use crate::{
246    utility, Border, Chart, ChartRange, ChartRangeCacheData, ColNum, Color, DefinedName,
247    DefinedNameType, DocProperties, Fill, Font, FontScheme, FormatPattern, Image, RowNum, Visible,
248    NUM_IMAGE_FORMATS, UNPARSED_SHEET_RANGE,
249};
250
251use crate::xmlwriter::{
252    self, xml_data_element, xml_declaration, xml_empty_tag, xml_end_tag, xml_start_tag,
253    xml_start_tag_only,
254};
255
256/// The `Workbook` struct represents an Excel file in its entirety. It is the
257/// starting point for creating a new Excel xlsx file.
258///
259/// <img src="https://rustxlsxwriter.github.io/images/demo.png">
260///
261/// # Examples
262///
263/// Sample code to generate the Excel file shown above.
264///
265/// ```rust
266/// # // This code is available in examples/app_demo.rs
267/// #
268/// use rust_xlsxwriter::*;
269///
270/// fn main() -> Result<(), XlsxError> {
271///     // Create a new Excel file object.
272///     let mut workbook = Workbook::new();
273///
274///     // Create some formats to use in the worksheet.
275///     let bold_format = Format::new().set_bold();
276///     let decimal_format = Format::new().set_num_format("0.000");
277///     let date_format = Format::new().set_num_format("yyyy-mm-dd");
278///     let merge_format = Format::new()
279///         .set_border(FormatBorder::Thin)
280///         .set_align(FormatAlign::Center);
281///
282///     // Add a worksheet to the workbook.
283///     let worksheet = workbook.add_worksheet();
284///
285///     // Set the column width for clarity.
286///     worksheet.set_column_width(0, 22)?;
287///
288///     // Write a string without formatting.
289///     worksheet.write(0, 0, "Hello")?;
290///
291///     // Write a string with the bold format defined above.
292///     worksheet.write_with_format(1, 0, "World", &bold_format)?;
293///
294///     // Write some numbers.
295///     worksheet.write(2, 0, 1)?;
296///     worksheet.write(3, 0, 2.34)?;
297///
298///     // Write a number with formatting.
299///     worksheet.write_with_format(4, 0, 3.00, &decimal_format)?;
300///
301///     // Write a formula.
302///     worksheet.write(5, 0, Formula::new("=SIN(PI()/4)"))?;
303///
304///     // Write a date.
305///     let date = ExcelDateTime::from_ymd(2023, 1, 25)?;
306///     worksheet.write_with_format(6, 0, &date, &date_format)?;
307///
308///     // Write some links.
309///     worksheet.write(7, 0, Url::new("https://www.rust-lang.org"))?;
310///     worksheet.write(8, 0, Url::new("https://www.rust-lang.org").set_text("Rust"))?;
311///
312///     // Write some merged cells.
313///     worksheet.merge_range(9, 0, 9, 1, "Merged cells", &merge_format)?;
314///
315///     // Insert an image.
316///     let image = Image::new("examples/rust_logo.png")?;
317///     worksheet.insert_image(1, 2, &image)?;
318///
319///     // Save the file to disk.
320///     workbook.save("demo.xlsx")?;
321///
322///     Ok(())
323/// }
324/// ```
325pub struct Workbook {
326    pub(crate) writer: Cursor<Vec<u8>>,
327    pub(crate) properties: DocProperties,
328    pub(crate) worksheets: Vec<Worksheet>,
329    pub(crate) xf_formats: Vec<Format>,
330    pub(crate) dxf_formats: Vec<Format>,
331    pub(crate) font_count: u16,
332    pub(crate) fill_count: u16,
333    pub(crate) border_count: u16,
334    pub(crate) num_formats: Vec<String>,
335    pub(crate) has_hyperlink_style: bool,
336    pub(crate) embedded_images: Vec<Image>,
337    pub(crate) vba_project: Vec<u8>,
338    pub(crate) vba_signature: Vec<u8>,
339    pub(crate) vba_codename: Option<String>,
340    pub(crate) is_xlsm_file: bool,
341    pub(crate) has_comments: bool,
342    pub(crate) string_table: Arc<Mutex<SharedStringsTable>>,
343    pub(crate) feature_property_bags: HashSet<FeaturePropertyBagTypes>,
344    pub(crate) theme_xml: String,
345
346    xf_indices: Arc<RwLock<HashMap<Format, u32>>>,
347    dxf_indices: HashMap<Format, u32>,
348    active_tab: u16,
349    first_sheet: u16,
350    defined_names: Vec<DefinedName>,
351    user_defined_names: Vec<DefinedName>,
352    read_only_mode: u8,
353    num_worksheets: u16,
354    num_chartsheets: u16,
355    use_large_file: bool,
356    default_format: Format,
357    default_row_height: u32,
358    default_col_width: u32,
359    max_digit_width: u32,
360    max_col_width: u32,
361    cell_padding: u32,
362    default_theme_version: String,
363
364    #[cfg(feature = "constant_memory")]
365    tempdir: Option<PathBuf>,
366}
367
368impl Default for Workbook {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374impl Workbook {
375    // -----------------------------------------------------------------------
376    // Public (and crate public) methods.
377    // -----------------------------------------------------------------------
378
379    /// Create a new Workbook object to represent an Excel spreadsheet file.
380    ///
381    /// The `Workbook::new()` constructor is used to create a new Excel workbook
382    /// object. This is used to create worksheets and add data prior to saving
383    /// everything to an xlsx file with [`Workbook::save()`], or
384    /// [`Workbook::save_to_buffer()`].
385    ///
386    /// **Note**: `rust_xlsxwriter` can only create new files. It cannot read or
387    /// modify existing files.
388    ///
389    /// # Examples
390    ///
391    /// The following example demonstrates creating a simple workbook, with one
392    /// unused worksheet.
393    ///
394    /// ```
395    /// # // This code is available in examples/doc_workbook_new.rs
396    /// #
397    /// # use rust_xlsxwriter::{Workbook, XlsxError};
398    /// #
399    /// fn main() -> Result<(), XlsxError> {
400    ///     let mut workbook = Workbook::new();
401    ///
402    ///     let _worksheet = workbook.add_worksheet();
403    ///
404    ///     workbook.save("workbook.xlsx")?;
405    ///
406    ///     Ok(())
407    /// }
408    /// ```
409    ///
410    /// Output file:
411    ///
412    /// <img src="https://rustxlsxwriter.github.io/images/workbook_new.png">
413    ///
414    pub fn new() -> Workbook {
415        let writer = Cursor::new(Vec::with_capacity(2048));
416
417        Workbook {
418            writer,
419            properties: DocProperties::new(),
420            font_count: 0,
421            active_tab: 0,
422            fill_count: 0,
423            first_sheet: 0,
424            border_count: 0,
425            num_formats: vec![],
426            read_only_mode: 0,
427            has_hyperlink_style: false,
428            worksheets: vec![],
429            xf_formats: vec![],
430            dxf_formats: vec![],
431            defined_names: vec![],
432            user_defined_names: vec![],
433            xf_indices: Arc::new(RwLock::new(HashMap::from([(Format::default(), 0)]))),
434            string_table: Arc::new(Mutex::new(SharedStringsTable::new())),
435            dxf_indices: HashMap::new(),
436            embedded_images: vec![],
437            is_xlsm_file: false,
438            vba_project: vec![],
439            vba_signature: vec![],
440            vba_codename: None,
441            has_comments: false,
442            num_worksheets: 0,
443            num_chartsheets: 0,
444            use_large_file: false,
445            feature_property_bags: HashSet::new(),
446            default_format: Format::default(),
447            default_row_height: 20,
448            default_col_width: 64,
449            cell_padding: 5,
450            max_digit_width: 7,
451            max_col_width: 1790,
452            theme_xml: String::from(THEME_XML_2007),
453            default_theme_version: String::from("124226"),
454
455            #[cfg(feature = "constant_memory")]
456            tempdir: None,
457        }
458    }
459
460    /// Add a new worksheet to a workbook.
461    ///
462    /// The `add_worksheet()` method adds a new [`Worksheet`] to a workbook.
463    ///
464    /// The worksheets will be given standard Excel names like `Sheet1`,
465    /// `Sheet2`, etc. Alternatively, the name can be set using
466    /// `worksheet.set_name()`, see the example below and the docs for
467    /// [`Worksheet::set_name()`].
468    ///
469    /// The `add_worksheet()` method returns a borrowed mutable reference to a
470    /// Worksheet instance owned by the Workbook, so only one worksheet can be in
471    /// existence at a time, see the example below. This limitation can be
472    /// avoided, if necessary, by creating standalone Worksheet objects via
473    /// [`Worksheet::new()`] and then later adding them to the workbook with
474    /// [`Workbook::push_worksheet`].
475    ///
476    /// See also the documentation on [Creating worksheets] and working with the
477    /// borrow checker.
478    ///
479    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
480    ///
481    /// # Examples
482    ///
483    /// The following example demonstrates creating adding worksheets to a
484    /// workbook.
485    ///
486    /// ```
487    /// # // This code is available in examples/doc_workbook_add_worksheet.rs
488    /// #
489    /// # use rust_xlsxwriter::{Workbook, XlsxError};
490    /// #
491    /// fn main() -> Result<(), XlsxError> {
492    ///     let mut workbook = Workbook::new();
493    ///
494    ///     let worksheet = workbook.add_worksheet(); // Sheet1
495    ///     worksheet.write_string(0, 0, "Hello")?;
496    ///
497    ///     let worksheet = workbook.add_worksheet().set_name("Foglio2")?;
498    ///     worksheet.write_string(0, 0, "Hello")?;
499    ///
500    ///     let worksheet = workbook.add_worksheet(); // Sheet3
501    ///     worksheet.write_string(0, 0, "Hello")?;
502    ///
503    ///     workbook.save("workbook.xlsx")?;
504    ///
505    ///     Ok(())
506    /// }
507    /// ```
508    ///
509    /// Output file:
510    ///
511    /// <img
512    /// src="https://rustxlsxwriter.github.io/images/workbook_add_worksheet.png">
513    ///
514    pub fn add_worksheet(&mut self) -> &mut Worksheet {
515        let name = format!("Sheet{}", self.num_worksheets + 1);
516        self.num_worksheets += 1;
517
518        let mut worksheet = Worksheet::new();
519        worksheet.set_name(&name).unwrap();
520
521        self.initialize_default_format(&mut worksheet);
522
523        self.worksheets.push(worksheet);
524        let worksheet = self.worksheets.last_mut().unwrap();
525
526        worksheet
527    }
528
529    /// Add a new worksheet that supports "constant memory" mode.
530    ///
531    /// This method adds a new [`Worksheet`] that is optimizes to reduce memory
532    /// usage when writing large files. See the documentation on [Constant
533    /// memory mode](../performance/index.html#constant-memory-mode).
534    ///
535    /// Constant memory mode requires the `rust_xlsxwriter` `constant_memory`
536    /// feature flag.
537    ///
538    /// The [`Worksheet`] reference returned by this method behaves like any
539    /// other worksheet, see [`Workbook::add_worksheet()`] above. However there
540    /// are some
541    /// [restrictions](../performance/index.html#restrictions-when-using-constant-memory-mode)
542    /// on its usage.
543    ///
544    /// # Examples
545    ///
546    /// The following example demonstrates adding worksheets in "standard" and
547    /// "constant memory" modes.
548    ///
549    /// ```
550    /// # // This code is available in examples/doc_workbook_add_worksheet_with_constant_memory.rs
551    /// #
552    /// # use rust_xlsxwriter::{Workbook, XlsxError};
553    /// #
554    /// # fn main() -> Result<(), XlsxError> {
555    /// #     // Create a new Excel file object.
556    /// #     let mut workbook = Workbook::new();
557    ///
558    ///     // Add a worksheet in standard mode.
559    ///     let worksheet = workbook.add_worksheet();
560    ///     worksheet.write(0, 0, "Standard")?;
561    ///
562    ///     // Add a worksheet in "constant memory" mode.
563    ///     let worksheet = workbook.add_worksheet_with_constant_memory();
564    ///     worksheet.write(0, 0, "Constant memory")?;
565    ///
566    /// #     workbook.save("workbook.xlsx")?;
567    /// #
568    /// #     Ok(())
569    /// # }
570    /// ```
571    ///
572    /// Output file:
573    ///
574    /// <img
575    /// src="https://rustxlsxwriter.github.io/images/workbook_add_worksheet_with_constant_memory.png">
576    ///
577    #[cfg(feature = "constant_memory")]
578    #[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
579    pub fn add_worksheet_with_constant_memory(&mut self) -> &mut Worksheet {
580        let name = format!("Sheet{}", self.num_worksheets + 1);
581        self.num_worksheets += 1;
582
583        let mut worksheet = Worksheet::new();
584        worksheet.set_name(&name).unwrap();
585
586        self.initialize_default_format(&mut worksheet);
587
588        if let Some(tempdir) = &self.tempdir {
589            worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
590        }
591
592        worksheet.use_inline_strings = true;
593        worksheet.use_constant_memory = true;
594
595        worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
596        worksheet.has_workbook_global_xfs = true;
597
598        self.worksheets.push(worksheet);
599        let worksheet = self.worksheets.last_mut().unwrap();
600
601        worksheet
602    }
603
604    /// Add a new worksheet that supports "low memory" mode.
605    ///
606    /// This method adds a new [`Worksheet`] that is optimizes to reduce memory
607    /// usage when writing large files. See the documentation on [Constant
608    /// memory mode](../performance/index.html#constant-memory-mode).
609    ///
610    /// Constant memory mode requires the `rust_xlsxwriter` `constant_memory`
611    /// feature flag.
612    ///
613    /// The [`Worksheet`] reference returned by this method behaves like any
614    /// other worksheet, see [`Workbook::add_worksheet()`] above. However there
615    /// are some
616    /// [restrictions](../performance/index.html#restrictions-when-using-constant-memory-mode)
617    /// on its usage.
618    ///
619    /// # Examples
620    ///
621    /// The following example demonstrates adding worksheets in "standard" and
622    /// "low memory" modes.
623    ///
624    /// ```
625    /// # // This code is available in examples/doc_workbook_add_worksheet_with_low_memory.rs
626    /// #
627    /// # use rust_xlsxwriter::{Workbook, XlsxError};
628    /// #
629    /// # fn main() -> Result<(), XlsxError> {
630    /// #     // Create a new Excel file object.
631    /// #     let mut workbook = Workbook::new();
632    ///
633    ///     // Add a worksheet in standard mode.
634    ///     let worksheet = workbook.add_worksheet();
635    ///     worksheet.write(0, 0, "Standard")?;
636    ///
637    ///     // Add a worksheet in "low memory" mode.
638    ///     let worksheet = workbook.add_worksheet_with_low_memory();
639    ///     worksheet.write(0, 0, "Low memory")?;
640    ///
641    /// #     workbook.save("workbook.xlsx")?;
642    /// #
643    /// #     Ok(())
644    /// # }
645    /// ```
646    ///
647    /// Output file:
648    ///
649    /// <img
650    /// src="https://rustxlsxwriter.github.io/images/workbook_add_worksheet_with_low_memory.png">
651    ///
652    #[cfg(feature = "constant_memory")]
653    #[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
654    pub fn add_worksheet_with_low_memory(&mut self) -> &mut Worksheet {
655        let name = format!("Sheet{}", self.num_worksheets + 1);
656        self.num_worksheets += 1;
657
658        let mut worksheet = Worksheet::new();
659        worksheet.set_name(&name).unwrap();
660
661        self.initialize_default_format(&mut worksheet);
662
663        if let Some(tempdir) = &self.tempdir {
664            worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
665        }
666
667        worksheet.use_inline_strings = false;
668        worksheet.use_constant_memory = true;
669
670        worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
671        worksheet.has_workbook_global_xfs = true;
672
673        worksheet.string_table = Arc::clone(&self.string_table);
674        worksheet.has_workbook_global_sst = true;
675
676        self.worksheets.push(worksheet);
677        let worksheet = self.worksheets.last_mut().unwrap();
678
679        worksheet
680    }
681
682    /// Create a new worksheet that supports "constant memory" mode.
683    ///
684    /// This method returns a new standalone [`Worksheet`] that is optimizes to
685    /// reduce memory usage when writing large files. See the documentation on
686    /// [Constant memory mode](../performance/index.html#constant-memory-mode).
687    ///
688    /// The `add_worksheet_with_constant_memory()` method above returns a
689    /// borrowed mutable reference to a Worksheet instance owned by the Workbook
690    /// so only one worksheet can be in existence at a time. This limitation can
691    /// be avoided, if necessary, by creating standalone Worksheet objects and
692    /// then later adding them to the workbook with
693    /// [`Workbook::push_worksheet`]. The "constant memory" [`Worksheet`] object
694    /// is create via a [`Workbook`] in order to link it to a parent workbook.
695    /// This restriction is required so that formats can be written in "constant
696    /// memory" mode.
697    ///
698    /// See also the documentation on [Creating worksheets] and working with the
699    /// borrow checker.
700    ///
701    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
702    ///
703    /// Constant memory mode requires the `rust_xlsxwriter` `constant_memory`
704    /// feature flag.
705    ///
706    #[cfg(feature = "constant_memory")]
707    #[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
708    pub fn new_worksheet_with_constant_memory(&mut self) -> Worksheet {
709        let mut worksheet = Worksheet::new();
710
711        if let Some(tempdir) = &self.tempdir {
712            worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
713        }
714
715        worksheet.use_inline_strings = true;
716        worksheet.use_constant_memory = true;
717
718        worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
719        worksheet.has_workbook_global_xfs = true;
720
721        worksheet
722    }
723
724    /// Create a new worksheet that supports "low memory" mode.
725    ///
726    /// This method returns a new standalone [`Worksheet`] that is optimizes to
727    /// reduce memory usage when writing large files. See the documentation on
728    /// [Constant memory mode](../performance/index.html#constant-memory-mode).
729    ///
730    /// The `add_worksheet_with_constant_memory()` method above returns a
731    /// borrowed mutable reference to a Worksheet instance owned by the Workbook
732    /// so only one worksheet can be in existence at a time. This limitation can
733    /// be avoided, if necessary, by creating standalone Worksheet objects and
734    /// then later adding them to the workbook with
735    /// [`Workbook::push_worksheet`]. The "low memory" [`Worksheet`] object is
736    /// create via a [`Workbook`] in order to link it to a parent workbook. This
737    /// restriction is required so that formats and strings can be written in
738    /// "constant memory" mode.
739    ///
740    /// See also the documentation on [Creating worksheets] and working with the
741    /// borrow checker.
742    ///
743    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
744    ///
745    /// Constant memory mode requires the `rust_xlsxwriter` `constant_memory`
746    /// feature flag.
747    ///
748    #[cfg(feature = "constant_memory")]
749    #[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
750    pub fn new_worksheet_with_low_memory(&mut self) -> Worksheet {
751        let mut worksheet = Worksheet::new();
752
753        if let Some(tempdir) = &self.tempdir {
754            worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
755        }
756
757        worksheet.use_inline_strings = false;
758        worksheet.use_constant_memory = true;
759
760        worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
761        worksheet.has_workbook_global_xfs = true;
762
763        worksheet.string_table = Arc::clone(&self.string_table);
764        worksheet.has_workbook_global_sst = true;
765
766        worksheet
767    }
768
769    /// Set the temporary directory used in "constant memory" and "low memory"
770    /// mode.
771    ///
772    /// In "constant" and "low" memory" mode `rust_xlsxwriter` uses temporary
773    /// files to reduce memory usage when writing large files (see [Constant
774    /// memory mode](../performance/index.html#constant-memory-mode) for
775    /// details).
776    ///
777    /// By default these files are created in the [`std::env::temp_dir()`]
778    /// directory by the [`tempfile`] crate. However, in cases where the default
779    /// temporary directory isn't accessible to your application, doesn't
780    /// contain enough space, or is mounted in memory you can set a custom
781    /// temporary directory using the `set_tempdir()` method.
782    ///
783    /// # Parameters
784    ///
785    /// - `dir`: The path/directory to use for temporary files. It must exist
786    ///   and be writeable. You can pass the path as a `&str` or as a
787    ///   [`std::path`] `Path` or `PathBuf` instance.
788    ///
789    /// # Errors
790    ///
791    /// - [`XlsxError::IoError`] - A wrapper for various IO errors when creating
792    ///   a temporary file in the custom temporary directory.
793    ///
794    /// # Examples
795    ///
796    /// The following example demonstrates setting a custom directory for
797    /// temporary files when creating a file in "constant memory" mode.
798    ///
799    /// ```
800    /// # // This code is available in examples/doc_workbook_set_tempdir.rs
801    /// #
802    /// # use rust_xlsxwriter::{Workbook, XlsxError};
803    /// #
804    /// # fn main() -> Result<(), XlsxError> {
805    /// #     // Create a new Excel file object.
806    /// #     let mut workbook = Workbook::new();
807    /// #
808    ///     // Set a custom directory for temporary files before adding worksheets.
809    ///     workbook.set_tempdir(".")?;
810    ///
811    ///     // Add a worksheet in "constant memory" mode.
812    ///     let worksheet = workbook.add_worksheet_with_constant_memory();
813    ///     worksheet.write(0, 0, "Hello")?;
814    /// #
815    /// #     workbook.save("workbook.xlsx")?;
816    /// #
817    /// #     Ok(())
818    /// # }
819    /// ```
820    ///
821    /// Output file:
822    ///
823    /// <img
824    /// src="https://rustxlsxwriter.github.io/images/workbook_set_tempdir.png">
825    ///
826    #[cfg(feature = "constant_memory")]
827    #[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
828    pub fn set_tempdir<P: AsRef<Path>>(&mut self, dir: P) -> Result<&mut Workbook, XlsxError> {
829        // Check that the directory exists and is writable.
830        tempfile_in(&dir)?;
831
832        self.tempdir = Some(dir.as_ref().to_path_buf());
833
834        Ok(self)
835    }
836
837    /// Add a new chartsheet to a workbook.
838    ///
839    /// The `add_chartsheet()` method adds a new "chartsheet" [`Worksheet`] to a
840    /// workbook.
841    ///
842    /// A Chartsheet in Excel is a specialized type of worksheet that doesn't
843    /// have cells but instead is used to display a single chart. It supports
844    /// worksheet display options such as headers and footers, margins, tab
845    /// selection, and print properties.
846    ///
847    /// The chartsheets will be given standard Excel name like `Chart1`,
848    /// `Chart2`, etc. Alternatively, the name can be set using
849    /// [`Worksheet::set_name()`].
850    ///
851    /// The `add_worksheet()` method returns a borrowed mutable reference to a
852    /// Worksheet instance owned by the Workbook so only one worksheet can be in
853    /// existence at a time. This limitation can be avoided, if necessary, by
854    /// creating standalone Worksheet objects via [`Worksheet::new()`] and then
855    /// later adding them to the workbook with [`Workbook::push_worksheet`].
856    ///
857    /// See also the documentation on [Creating worksheets] and working with the
858    /// borrow checker.
859    ///
860    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
861    ///
862    /// # Examples
863    ///
864    /// A simple chartsheet example. A chart is placed on it own dedicated
865    /// worksheet.
866    ///
867    /// ```
868    /// # // This code is available in examples/doc_chartsheet.rs
869    /// #
870    /// # use rust_xlsxwriter::{Chart, ChartType, Workbook, XlsxError};
871    /// #
872    /// # fn main() -> Result<(), XlsxError> {
873    /// #     let mut workbook = Workbook::new();
874    /// #     let worksheet = workbook.add_worksheet();
875    /// #
876    /// #     // Add some data for the chart.
877    /// #     worksheet.write(0, 0, 10)?;
878    /// #     worksheet.write(1, 0, 60)?;
879    /// #     worksheet.write(2, 0, 30)?;
880    /// #     worksheet.write(3, 0, 10)?;
881    /// #     worksheet.write(4, 0, 50)?;
882    /// #
883    ///     // Create a new chart.
884    ///     let mut chart = Chart::new(ChartType::Column);
885    ///
886    ///     // Add a data series using Excel formula syntax to describe the range.
887    ///     chart.add_series().set_values("Sheet1!$A$1:$A$5");
888    ///
889    ///     // Create a new chartsheet.
890    ///     let chartsheet = workbook.add_chartsheet();
891    ///
892    ///     // Add the chart to the chartsheet.
893    ///     chartsheet.insert_chart(0, 0, &chart)?;
894    /// #
895    /// #     // Save the file.
896    /// #     workbook.save("chart.xlsx")?;
897    /// #
898    /// #     Ok(())
899    /// # }
900    /// ```
901    ///
902    /// Output file:
903    ///
904    /// <img src="https://rustxlsxwriter.github.io/images/chartsheet.png">
905    ///
906    pub fn add_chartsheet(&mut self) -> &mut Worksheet {
907        let name = format!("Chart{}", self.num_chartsheets + 1);
908        self.num_chartsheets += 1;
909
910        let mut worksheet = Worksheet::new_chartsheet();
911        worksheet.set_name(&name).unwrap();
912
913        self.worksheets.push(worksheet);
914        let worksheet = self.worksheets.last_mut().unwrap();
915
916        worksheet
917    }
918
919    /// Get a worksheet reference by index.
920    ///
921    /// Get a reference to a worksheet created via [`Workbook::add_worksheet()`]
922    /// using an index based on the creation order.
923    ///
924    /// Due to borrow checking rules you can only have one active reference to a
925    /// worksheet object created by `add_worksheet()` since that method always
926    /// returns a mutable reference. For a workbook with multiple worksheets
927    /// this restriction is generally workable if you can create and use the
928    /// worksheets sequentially since you will only need to have one reference
929    /// at any one time. However, if you can't structure your code to work
930    /// sequentially then you get a reference to a previously created worksheet
931    /// using `worksheet_from_index()`. The standard borrow checking rules still
932    /// apply so you will have to give up ownership of any other worksheet
933    /// reference prior to calling this method. See the example below.
934    ///
935    /// See also [`Workbook::worksheet_from_name()`] and the documentation on
936    /// [Creating worksheets] and working with the borrow checker.
937    ///
938    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
939    ///
940    /// # Parameters
941    ///
942    /// - `index`: The index of the worksheet to get a reference to.
943    ///
944    /// # Errors
945    ///
946    /// - [`XlsxError::UnknownWorksheetNameOrIndex`] - Error when trying to
947    ///   retrieve a worksheet reference by index. This is usually an index out
948    ///   of bounds error.
949    ///
950    /// # Examples
951    ///
952    /// The following example demonstrates getting worksheet reference by index.
953    ///
954    /// ```
955    /// # // This code is available in examples/doc_workbook_worksheet_from_index.rs
956    /// #
957    /// # use rust_xlsxwriter::{Workbook, XlsxError};
958    /// #
959    /// # fn main() -> Result<(), XlsxError> {
960    /// #     let mut workbook = Workbook::new();
961    /// #
962    ///     // Start with a reference to worksheet1.
963    ///     let mut worksheet1 = workbook.add_worksheet();
964    ///     worksheet1.write_string(0, 0, "Hello")?;
965    ///
966    ///     // If we don't try to use the workbook1 reference again we can switch to
967    ///     // using a reference to worksheet2.
968    ///     let mut worksheet2 = workbook.add_worksheet();
969    ///     worksheet2.write_string(0, 0, "Hello")?;
970    ///
971    ///     // Stop using worksheet2 and move back to worksheet1.
972    ///     worksheet1 = workbook.worksheet_from_index(0)?;
973    ///     worksheet1.write_string(1, 0, "Sheet1")?;
974    ///
975    ///     // Stop using worksheet1 and move back to worksheet2.
976    ///     worksheet2 = workbook.worksheet_from_index(1)?;
977    ///     worksheet2.write_string(1, 0, "Sheet2")?;
978    /// #
979    /// #     workbook.save("workbook.xlsx")?;
980    /// #
981    /// #     Ok(())
982    /// # }
983    /// ```
984    ///
985    /// Output file:
986    ///
987    /// <img
988    /// src="https://rustxlsxwriter.github.io/images/workbook_worksheet_from_index.png">
989    ///
990    pub fn worksheet_from_index(&mut self, index: usize) -> Result<&mut Worksheet, XlsxError> {
991        match self.worksheets.get_mut(index) {
992            Some(worksheet) => Ok(worksheet),
993            _ => Err(XlsxError::UnknownWorksheetNameOrIndex(index.to_string())),
994        }
995    }
996
997    /// Get a worksheet reference by name.
998    ///
999    /// Get a reference to a worksheet created via [`Workbook::add_worksheet()`]
1000    /// using the sheet name.
1001    ///
1002    /// Due to borrow checking rules you can only have one active reference to a
1003    /// worksheet object created by `add_worksheet()` since that method always
1004    /// returns a mutable reference. For a workbook with multiple worksheets
1005    /// this restriction is generally workable if you can create and use the
1006    /// worksheets sequentially since you will only need to have one reference
1007    /// at any one time. However, if you can't structure your code to work
1008    /// sequentially then you get a reference to a previously created worksheet
1009    /// using `worksheet_from_name()`. The standard borrow checking rules still
1010    /// apply so you will have to give up ownership of any other worksheet
1011    /// reference prior to calling this method. See the example below.
1012    ///
1013    /// Worksheet names are usually "Sheet1", "Sheet2", etc., or else a user
1014    /// define name that was set using [`Worksheet::set_name()`]. You can also
1015    /// use the [`Worksheet::name()`] method to get the name.
1016    ///
1017    /// See also [`Workbook::worksheet_from_index()`] and the documentation on
1018    /// [Creating worksheets] and working with the borrow checker.
1019    ///
1020    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
1021    ///
1022    /// # Parameters
1023    ///
1024    /// - `name`: The name of the worksheet to get a reference to.
1025    ///
1026    /// # Errors
1027    ///
1028    /// - [`XlsxError::UnknownWorksheetNameOrIndex`] - Error when trying to
1029    ///   retrieve a worksheet reference by index. This is usually an index out
1030    ///   of bounds error.
1031    ///
1032    /// # Examples
1033    ///
1034    /// The following example demonstrates getting worksheet reference by name.
1035    ///
1036    /// ```
1037    /// # // This code is available in examples/doc_workbook_worksheet_from_name.rs
1038    /// #
1039    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1040    /// #
1041    /// # fn main() -> Result<(), XlsxError> {
1042    /// #     let mut workbook = Workbook::new();
1043    ///
1044    ///     // Start with a reference to worksheet1.
1045    ///     let mut worksheet1 = workbook.add_worksheet();
1046    ///     let name1 = worksheet1.name(); // "Sheet1"
1047    ///     worksheet1.write_string(0, 0, "Hello")?;
1048    ///
1049    ///     // If we don't try to use the workbook1 reference again we can switch to
1050    ///     // using a reference to worksheet2.
1051    ///     let mut worksheet2 = workbook.add_worksheet().set_name("Data")?;
1052    ///     let name2 = worksheet2.name();
1053    ///     worksheet2.write_string(0, 0, "Hello")?;
1054    ///
1055    ///     // Stop using worksheet2 and move back to worksheet1.
1056    ///     worksheet1 = workbook.worksheet_from_name(&name1)?;
1057    ///     worksheet1.write_string(1, 0, "Sheet1")?;
1058    ///
1059    ///     // Stop using worksheet1 and move back to worksheet2.
1060    ///     worksheet2 = workbook.worksheet_from_name(&name2)?;
1061    ///     worksheet2.write_string(1, 0, "Sheet2")?;
1062    ///
1063    /// #     workbook.save("workbook.xlsx")?;
1064    /// #
1065    /// #     Ok(())
1066    /// # }
1067    /// ```
1068    ///
1069    /// Output file:
1070    ///
1071    /// <img
1072    /// src="https://rustxlsxwriter.github.io/images/workbook_worksheet_from_name.png">
1073    ///
1074    pub fn worksheet_from_name(&mut self, sheetname: &str) -> Result<&mut Worksheet, XlsxError> {
1075        for (index, worksheet) in self.worksheets.iter_mut().enumerate() {
1076            if sheetname == worksheet.name {
1077                return self.worksheet_from_index(index);
1078            }
1079        }
1080
1081        // If we didn't find a matching sheet name then raise
1082        Err(XlsxError::UnknownWorksheetNameOrIndex(
1083            sheetname.to_string(),
1084        ))
1085    }
1086
1087    /// Get a mutable reference to the vector of worksheets.
1088    ///
1089    /// Get a mutable reference to the vector of Worksheets used by the Workbook
1090    /// instance. This can be useful for iterating over, and performing the same
1091    /// operation, on all the worksheets in the workbook. See the example below.
1092    ///
1093    /// If you are careful you can also use some of the standard [slice]
1094    /// operations on the vector reference, see below.
1095    ///
1096    /// See also the documentation on [Creating worksheets] and working with the
1097    /// borrow checker.
1098    ///
1099    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
1100    ///
1101    /// # Examples
1102    ///
1103    /// The following example demonstrates operating on the vector of all the
1104    /// worksheets in a workbook.
1105    ///
1106    /// ```
1107    /// # // This code is available in examples/doc_workbook_worksheets_mut.rs
1108    /// #
1109    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1110    /// #
1111    /// # fn main() -> Result<(), XlsxError> {
1112    /// #     let mut workbook = Workbook::new();
1113    /// #
1114    ///     // Add three worksheets to the workbook.
1115    ///     let _ = workbook.add_worksheet();
1116    ///     let _ = workbook.add_worksheet();
1117    ///     let _ = workbook.add_worksheet();
1118    ///
1119    ///     // Write the same data to all three worksheets.
1120    ///     for worksheet in workbook.worksheets_mut() {
1121    ///         worksheet.write_string(0, 0, "Hello")?;
1122    ///         worksheet.write_number(1, 0, 12345)?;
1123    ///     }
1124    ///
1125    ///     // If you are careful you can use standard slice operations.
1126    ///     workbook.worksheets_mut().swap(0, 1);
1127    /// #
1128    /// #     workbook.save("workbook.xlsx")?;
1129    /// #
1130    /// #     Ok(())
1131    /// # }
1132    /// ```
1133    ///
1134    /// Output file, note the same data is in all three worksheets and Sheet2
1135    /// and Sheet1 have swapped position:
1136    ///
1137    /// <img
1138    /// src="https://rustxlsxwriter.github.io/images/workbook_worksheets_mut.png">
1139    ///
1140    pub fn worksheets_mut(&mut self) -> &mut Vec<Worksheet> {
1141        &mut self.worksheets
1142    }
1143
1144    /// Get a reference to the vector of worksheets.
1145    ///
1146    /// Get a reference to the vector of Worksheets used by the Workbook
1147    /// instance. This is less useful than [`Workbook::worksheets_mut`] version
1148    /// since a mutable reference is required for most worksheet operations.
1149    ///
1150    /// # Examples
1151    ///
1152    /// The following example demonstrates operating on the vector of all the
1153    /// worksheets in a workbook. The non mutable version of this method is less
1154    /// useful than `workbook.worksheets_mut()`.
1155    ///
1156    /// ```
1157    /// # // This code is available in examples/doc_workbook_worksheets.rs
1158    /// #
1159    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1160    /// #
1161    /// # fn main() -> Result<(), XlsxError> {
1162    /// #     let mut workbook = Workbook::new();
1163    /// #
1164    ///     // Add three worksheets to the workbook.
1165    ///     let _worksheet1 = workbook.add_worksheet();
1166    ///     let _worksheet2 = workbook.add_worksheet();
1167    ///     let _worksheet3 = workbook.add_worksheet();
1168    ///
1169    ///     // Get some information from all three worksheets.
1170    ///     for worksheet in workbook.worksheets() {
1171    ///         println!("{}", worksheet.name());
1172    ///     }
1173    ///
1174    /// #     workbook.save("workbook.xlsx")?;
1175    /// #
1176    /// #     Ok(())
1177    /// # }
1178    /// ```
1179    ///
1180    pub fn worksheets(&mut self) -> &Vec<Worksheet> {
1181        &self.worksheets
1182    }
1183
1184    /// Add a worksheet object to a workbook.
1185    ///
1186    /// Add a worksheet created directly using `Workbook::new()` to a workbook.
1187    ///
1188    /// There are two way of creating a worksheet object with `rust_xlsxwriter`:
1189    /// via the [`Workbook::add_worksheet()`] method and via the
1190    /// [`Worksheet::new()`] constructor. The first method ties the worksheet to
1191    /// the workbook object that will write it automatically when the file is
1192    /// saved, whereas the second method creates a worksheet that is independent
1193    /// of a workbook. This has certain advantages in keeping the worksheet free
1194    /// of the workbook borrow checking until you wish to add it.
1195    ///
1196    /// When working with the independent worksheet object you can add it to a
1197    /// workbook using `push_worksheet()`, see the example below.
1198    ///
1199    /// See also the documentation on [Creating worksheets] and working with the
1200    /// borrow checker.
1201    ///
1202    /// [Creating worksheets]: ../worksheet/index.html#creating-worksheets
1203    ///
1204    /// # Parameters
1205    ///
1206    /// - `worksheet`: The worksheet to add to the workbook.
1207    ///
1208    /// # Examples
1209    ///
1210    /// The following example demonstrates creating a standalone worksheet
1211    /// object and then adding to a workbook.
1212    ///
1213    /// ```
1214    /// # // This code is available in examples/doc_workbook_push_worksheet.rs
1215    /// #
1216    /// # use rust_xlsxwriter::{Workbook, Worksheet, XlsxError};
1217    /// #
1218    /// # fn main() -> Result<(), XlsxError> {
1219    /// #     let mut workbook = Workbook::new();
1220    ///     let mut worksheet = Worksheet::new();
1221    ///
1222    ///     // Use the worksheet object.
1223    ///     worksheet.write_string(0, 0, "Hello")?;
1224    ///
1225    ///     // Add it to the workbook.
1226    ///     workbook.push_worksheet(worksheet);
1227    ///
1228    ///     // Save the workbook.
1229    /// #     workbook.save("workbook.xlsx")?;
1230    /// #
1231    /// #     Ok(())
1232    /// # }
1233    /// ```
1234    ///
1235    /// Output file:
1236    ///
1237    /// <img
1238    /// src="https://rustxlsxwriter.github.io/images/workbook_push_worksheet.png">
1239    ///
1240    pub fn push_worksheet(&mut self, mut worksheet: Worksheet) {
1241        if worksheet.name().is_empty() {
1242            let name = format!("Sheet{}", self.worksheets.len() + 1);
1243            worksheet.set_name(&name).unwrap();
1244        }
1245
1246        self.worksheets.push(worksheet);
1247    }
1248
1249    /// Save the Workbook as an xlsx file.
1250    ///
1251    /// The workbook `save()` method writes all the Workbook data to a new xlsx
1252    /// file. It will overwrite any existing file.
1253    ///
1254    /// The `save()` method can be called multiple times so it is possible to
1255    /// get incremental files at different stages of a process, or to save the
1256    /// same Workbook object to different paths. However, `save()` is an
1257    /// expensive operation which assembles multiple files into an xlsx/zip
1258    /// container so for performance reasons you shouldn't call it
1259    /// unnecessarily.
1260    ///
1261    /// # Parameters
1262    ///
1263    /// - `path`: The path of the new Excel file to create as a `&str` or as a
1264    ///   [`std::path`] `Path` or `PathBuf` instance.
1265    ///
1266    /// # Errors
1267    ///
1268    /// - [`XlsxError::SheetnameReused`] - Worksheet name is already in use in
1269    ///   the workbook.
1270    /// - [`XlsxError::TableNameReused`] - Worksheet Table name is already in
1271    ///   use in the workbook.
1272    /// - [`XlsxError::ChartError`] - A Chartsheet doesn't contain a chart.
1273    /// - [`XlsxError::IoError`] - A wrapper for various IO errors when creating
1274    ///   the xlsx file, or its sub-files.
1275    /// - [`XlsxError::ZipError`] - A wrapper for various zip errors when
1276    ///   creating the xlsx file, or its sub-files.
1277    ///
1278    /// # Examples
1279    ///
1280    /// The following example demonstrates creating a simple workbook using a
1281    /// string path.
1282    ///
1283    /// ```
1284    /// # // This code is available in examples/doc_workbook_save.rs
1285    /// #
1286    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1287    /// #
1288    /// fn main() -> Result<(), XlsxError> {
1289    ///     let mut workbook = Workbook::new();
1290    ///
1291    ///     let _worksheet = workbook.add_worksheet();
1292    ///
1293    ///     workbook.save("workbook.xlsx")?;
1294    ///
1295    ///     Ok(())
1296    /// }
1297    /// ```
1298    ///
1299    /// The following example demonstrates creating a simple workbook using a
1300    /// Rust [`std::path`] Path.
1301    ///
1302    /// ```
1303    /// # // This code is available in examples/doc_workbook_save_to_path.rs
1304    /// #
1305    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1306    /// #
1307    /// fn main() -> Result<(), XlsxError> {
1308    ///     let mut workbook = Workbook::new();
1309    ///
1310    ///     let _worksheet = workbook.add_worksheet();
1311    ///
1312    ///     let path = std::path::Path::new("workbook.xlsx");
1313    ///     workbook.save(path)?;
1314    ///
1315    ///     Ok(())
1316    /// }
1317    /// ```
1318    ///
1319    pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(), XlsxError> {
1320        #[cfg(feature = "test-resave")]
1321        {
1322            // Some test code to test double/multiple saves.
1323            let file = std::fs::File::create(<&std::path::Path>::clone(&path.as_ref()))?;
1324            self.save_internal(file)?;
1325        }
1326
1327        let file = std::fs::File::create(path)?;
1328        self.save_internal(file)?;
1329        Ok(())
1330    }
1331
1332    /// Save the Workbook as an xlsx file and return it as a byte vector.
1333    ///
1334    /// The workbook `save_to_buffer()` method is similar to the
1335    /// [`Workbook::save()`] method except that it returns the xlsx file as a
1336    /// `Vec<u8>` buffer suitable for streaming in a web application.
1337    ///
1338    /// # Errors
1339    ///
1340    /// - [`XlsxError::SheetnameReused`] - Worksheet name is already in use in
1341    ///   the workbook.
1342    /// - [`XlsxError::IoError`] - A wrapper for various IO errors when creating
1343    ///   the xlsx file, or its sub-files.
1344    /// - [`XlsxError::ZipError`] - A wrapper for various zip errors when
1345    ///   creating the xlsx file, or its sub-files.
1346    ///
1347    /// # Examples
1348    ///
1349    /// The following example demonstrates creating a simple workbook to a
1350    /// `Vec<u8>` buffer.
1351    ///
1352    /// ```
1353    /// # // This code is available in examples/doc_workbook_save_to_buffer.rs
1354    /// #
1355    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1356    /// #
1357    /// fn main() -> Result<(), XlsxError> {
1358    ///     let mut workbook = Workbook::new();
1359    ///
1360    ///     let worksheet = workbook.add_worksheet();
1361    ///     worksheet.write_string(0, 0, "Hello")?;
1362    ///
1363    ///     let buf = workbook.save_to_buffer()?;
1364    ///
1365    ///     println!("File size: {}", buf.len());
1366    ///
1367    ///     Ok(())
1368    /// }
1369    /// ```
1370    ///
1371    pub fn save_to_buffer(&mut self) -> Result<Vec<u8>, XlsxError> {
1372        let mut buf = vec![];
1373        let cursor = Cursor::new(&mut buf);
1374        self.save_internal(cursor)?;
1375        Ok(buf)
1376    }
1377
1378    /// Save the Workbook as an xlsx file to a user supplied file/buffer.
1379    ///
1380    /// The workbook `save_to_writer()` method is similar to the
1381    /// [`Workbook::save()`] method except that it writes the xlsx file to types
1382    /// that implement the [`Write`] trait such as the [`std::fs::File`] type or
1383    /// buffers.
1384    ///
1385    /// # Parameters
1386    ///
1387    /// - `writer`: An object that implements the [`Write`] trait.
1388    ///
1389    /// # Errors
1390    ///
1391    /// - [`XlsxError::SheetnameReused`] - Worksheet name is already in use in
1392    ///   the workbook.
1393    /// - [`XlsxError::IoError`] - A wrapper for various IO errors when creating
1394    ///   the xlsx file, or its sub-files.
1395    /// - [`XlsxError::ZipError`] - A wrapper for various zip errors when
1396    ///   creating the xlsx file, or its sub-files.
1397    ///
1398    /// # Examples
1399    ///
1400    /// The following example demonstrates creating a simple workbook to some
1401    /// types that implement the `Write` trait like a file and a buffer.
1402    ///
1403    /// ```
1404    /// # // This code is available in examples/doc_workbook_save_to_writer.rs
1405    /// #
1406    /// # use std::fs::File;
1407    /// # use std::io::{Cursor, Write};
1408    /// #
1409    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1410    ///
1411    /// fn main() -> Result<(), XlsxError> {
1412    ///     let mut workbook = Workbook::new();
1413    ///
1414    ///     let worksheet = workbook.add_worksheet();
1415    ///     worksheet.write_string(0, 0, "Hello")?;
1416    ///
1417    ///     // Save the file to a File object.
1418    ///     let file = File::create("workbook1.xlsx")?;
1419    ///     workbook.save_to_writer(file)?;
1420    ///
1421    ///     // Save the file to a buffer. It is wrapped in a Cursor because it need to
1422    ///     // implement the `Seek` trait.
1423    ///     let mut cursor = Cursor::new(Vec::new());
1424    ///     workbook.save_to_writer(&mut cursor)?;
1425    ///
1426    ///     // Write the buffer to a file for the sake of the example.
1427    ///     let buf = cursor.into_inner();
1428    ///     let mut file = File::create("workbook2.xlsx")?;
1429    ///     Write::write_all(&mut file, &buf)?;
1430    ///
1431    ///     Ok(())
1432    /// }
1433    ///
1434    pub fn save_to_writer<W>(&mut self, writer: W) -> Result<(), XlsxError>
1435    where
1436        W: Write + Seek + Send,
1437    {
1438        self.save_internal(writer)?;
1439        Ok(())
1440    }
1441
1442    /// Create a defined name in the workbook to use as a variable.
1443    ///
1444    /// The `define_name()` method is used to define a variable name that can
1445    /// be used to represent a value, a single cell, or a range of cells in a
1446    /// workbook. These are sometimes referred to as "Named Ranges."
1447    ///
1448    /// Defined names are generally used to simplify or clarify formulas by
1449    /// using descriptive variable names. For example:
1450    ///
1451    /// ```text
1452    ///     // Global workbook name.
1453    ///     workbook.define_name("Exchange_rate", "=0.96")?;
1454    ///     worksheet.write_formula(0, 0, "=Exchange_rate")?;
1455    /// ```
1456    ///
1457    /// A name defined like this is "global" to the workbook and can be used in
1458    /// any worksheet in the workbook.  It is also possible to define a
1459    /// local/worksheet name by prefixing it with the sheet name using the
1460    /// syntax `"sheetname!defined_name"`:
1461    ///
1462    /// ```text
1463    ///     // Local worksheet name.
1464    ///     workbook.define_name('Sheet2!Sales', '=Sheet2!$G$1:$G$10')?;
1465    /// ```
1466    ///
1467    /// See the full example below.
1468    ///
1469    /// Note, Excel has limitations on names used in defined names. For example,
1470    /// it must start with a letter or underscore and cannot contain a space or
1471    /// any of the characters: `,/*[]:\"'`. It also cannot look like an Excel
1472    /// range such as `A1`, `XFD12345`, or `R1C1`. If in doubt, it is best to test
1473    /// the name in Excel first.
1474    ///
1475    /// For local defined names sheet name must exist (at the time of saving)
1476    /// and if the sheet name contains spaces or special characters you must
1477    /// follow the Excel convention and enclose it in single quotes:
1478    ///
1479    /// ```text
1480    ///     workbook.define_name("'New Data'!Sales", ""=Sheet2!$G$1:$G$10")?;
1481    /// ```
1482    ///
1483    /// The rules for names in Excel are explained in the Microsoft Office
1484    /// documentation on how to [Define and use names in
1485    /// formulas](https://support.microsoft.com/en-us/office/define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64)
1486    /// and subsections.
1487    ///
1488    /// # Parameters
1489    ///
1490    /// - `name`: The variable name to define.
1491    /// - `formula`: The formula, value or range that the name defines..
1492    ///
1493    /// # Errors
1494    ///
1495    /// - [`XlsxError::ParameterError`] - The following Excel error cases will
1496    ///   raise a `ParameterError` error:
1497    ///   * If the name doesn't start with a letter or underscore.
1498    ///   * If the name contains `,/*[]:\"'` or `space`.
1499    ///
1500    /// # Examples
1501    ///
1502    /// Example of how to create defined names using the `rust_xlsxwriter`
1503    /// library.
1504    ///
1505    /// This functionality is used to define user friendly variable names to
1506    /// represent a value, a single cell,  or a range of cells in a workbook.
1507    ///
1508    /// ```
1509    /// # // This code is available in examples/app_defined_name.rs
1510    /// #
1511    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1512    /// #
1513    /// # fn main() -> Result<(), XlsxError> {
1514    /// #     // Create a new Excel file object.
1515    /// #     let mut workbook = Workbook::new();
1516    /// #
1517    /// #     // Add two worksheets to the workbook.
1518    /// #     let _worksheet1 = workbook.add_worksheet();
1519    /// #     let _worksheet2 = workbook.add_worksheet();
1520    /// #
1521    ///     // Define some global/workbook names.
1522    ///     workbook.define_name("Exchange_rate", "=0.96")?;
1523    ///     workbook.define_name("Sales", "=Sheet1!$G$1:$H$10")?;
1524    ///
1525    ///     // Define a local/worksheet name. Over-rides the "Sales" name above.
1526    ///     workbook.define_name("Sheet2!Sales", "=Sheet2!$G$1:$G$10")?;
1527    /// #
1528    /// #     // Write some text in the file and one of the defined names in a formula.
1529    /// #     for worksheet in workbook.worksheets_mut() {
1530    /// #         worksheet.set_column_width(0, 45)?;
1531    /// #         worksheet.write_string(0, 0, "This worksheet contains some defined names.")?;
1532    /// #         worksheet.write_string(1, 0, "See Formulas -> Name Manager above.")?;
1533    /// #         worksheet.write_string(2, 0, "Example formula in cell B3 ->")?;
1534    /// #
1535    /// #         worksheet.write_formula(2, 1, "=Exchange_rate")?;
1536    /// #     }
1537    /// #
1538    /// #     // Save the file to disk.
1539    /// #     workbook.save("defined_name.xlsx")?;
1540    /// #
1541    /// #     Ok(())
1542    /// # }
1543    /// ```
1544    ///
1545    /// Output file:
1546    ///
1547    /// <img
1548    /// src="https://rustxlsxwriter.github.io/images/app_defined_name1.png">
1549    ///
1550    /// Here is the output in the Excel Name Manager. Note that there is a
1551    /// Global/Workbook "Sales" variable name and a Local/Worksheet version.
1552    ///
1553    /// <img
1554    /// src="https://rustxlsxwriter.github.io/images/app_defined_name2.png">
1555    ///
1556    pub fn define_name(
1557        &mut self,
1558        name: impl Into<String>,
1559        formula: &str,
1560    ) -> Result<&mut Workbook, XlsxError> {
1561        let mut defined_name = DefinedName::new();
1562        let name = name.into();
1563
1564        // Match Global/Workbook or Local/Worksheet defined names.
1565        match name.find('!') {
1566            Some(position) => {
1567                defined_name.quoted_sheet_name = name[0..position].to_string();
1568                defined_name.name = name[position + 1..].to_string();
1569                defined_name.name_type = DefinedNameType::Local;
1570            }
1571            None => {
1572                defined_name.name.clone_from(&name);
1573                defined_name.name_type = DefinedNameType::Global;
1574            }
1575        }
1576
1577        // Excel requires that the name starts with a letter or underscore.
1578        // Also, backspace is allowed but undocumented by Excel.
1579        if !defined_name.name.chars().next().unwrap().is_alphabetic()
1580            && !defined_name.name.starts_with('_')
1581            && !defined_name.name.starts_with('\\')
1582        {
1583            let error = format!(
1584                "Name '{}' must start with a letter or underscore in Excel",
1585                defined_name.name
1586            );
1587            return Err(XlsxError::ParameterError(error));
1588        }
1589
1590        // Excel also prohibits certain characters in the name.
1591        if defined_name
1592            .name
1593            .contains([' ', ',', '/', '*', '[', ']', ':', '"', '\''])
1594        {
1595            let error = format!(
1596                "Name '{}' cannot contain any of the characters `,/*[]:\"'` or `space` in Excel",
1597                defined_name.name
1598            );
1599            return Err(XlsxError::ParameterError(error));
1600        }
1601
1602        defined_name.range = utility::formula_to_string(formula);
1603        defined_name.set_sort_name();
1604
1605        self.user_defined_names.push(defined_name);
1606
1607        Ok(self)
1608    }
1609
1610    /// Set the Excel document metadata properties.
1611    ///
1612    /// Set various Excel document metadata properties such as Author or
1613    /// Creation Date. It is used in conjunction with the [`DocProperties`]
1614    /// struct.
1615    ///
1616    /// # Parameters
1617    ///
1618    /// - `properties`: A reference to a [`DocProperties`] object.
1619    ///
1620    /// # Examples
1621    ///
1622    /// An example of setting workbook document properties for a file created
1623    /// using the `rust_xlsxwriter` library.
1624    ///
1625    /// ```
1626    /// # // This code is available in examples/app_doc_properties.rs
1627    /// #
1628    /// # use rust_xlsxwriter::{DocProperties, Workbook, XlsxError};
1629    /// #
1630    /// # fn main() -> Result<(), XlsxError> {
1631    /// #     let mut workbook = Workbook::new();
1632    /// #
1633    ///     let properties = DocProperties::new()
1634    ///         .set_title("This is an example spreadsheet")
1635    ///         .set_subject("That demonstrates document properties")
1636    ///         .set_author("A. Rust User")
1637    ///         .set_manager("J. Alfred Prufrock")
1638    ///         .set_company("Rust Solutions Inc")
1639    ///         .set_category("Sample spreadsheets")
1640    ///         .set_keywords("Sample, Example, Properties")
1641    ///         .set_comment("Created with Rust and rust_xlsxwriter");
1642    ///
1643    ///     workbook.set_properties(&properties);
1644    /// #
1645    /// #     let worksheet = workbook.add_worksheet();
1646    /// #
1647    /// #     worksheet.set_column_width(0, 30)?;
1648    /// #     worksheet.write_string(0, 0, "See File -> Info -> Properties")?;
1649    /// #
1650    /// #     workbook.save("doc_properties.xlsx")?;
1651    /// #
1652    /// #     Ok(())
1653    /// # }
1654    /// ```
1655    ///
1656    /// Output file:
1657    ///
1658    /// <img
1659    /// src="https://rustxlsxwriter.github.io/images/app_doc_properties.png">
1660    ///
1661    ///
1662    /// The document properties can also be used to set a constant creation date
1663    /// so that a file generated by a `rust_xlsxwriter` program will have the
1664    /// same checksum no matter when it is created.
1665    ///
1666    ///
1667    /// ```
1668    /// # // This code is available in examples/doc_properties_checksum2.rs
1669    /// #
1670    /// use rust_xlsxwriter::{DocProperties, ExcelDateTime, Workbook, XlsxError};
1671    ///
1672    /// fn main() -> Result<(), XlsxError> {
1673    ///     let mut workbook = Workbook::new();
1674    ///
1675    ///     // Create a file creation date for the file.
1676    ///     let date = ExcelDateTime::from_ymd(2023, 1, 1)?;
1677    ///
1678    ///     // Add it to the document metadata.
1679    ///     let properties = DocProperties::new().set_creation_datetime(&date);
1680    ///     workbook.set_properties(&properties);
1681    ///
1682    ///     let worksheet = workbook.add_worksheet();
1683    ///     worksheet.write_string(0, 0, "Hello")?;
1684    ///
1685    ///     workbook.save("properties.xlsx")?;
1686    ///
1687    ///     Ok(())
1688    /// }
1689    /// ```
1690    ///
1691    ///  See also [Checksum of a saved
1692    ///  file](../workbook/index.html#checksum-of-a-saved-file).
1693    ///
1694    pub fn set_properties(&mut self, properties: &DocProperties) -> &mut Workbook {
1695        self.properties = properties.clone();
1696        self
1697    }
1698
1699    /// Set the default cell format for a workbook.
1700    ///
1701    /// The `rust_xlsxwriter` library uses the Excel 2007 default cell format of
1702    /// "Calibri 11" for all worksheets. If required, it is possible to change
1703    /// the default format, mainly the font properties, to something like the
1704    /// more recent Excel default of "Aptos Narrow 11", the older "Arial 10", or
1705    /// an international font such as "MS Pゴシック".
1706    ///
1707    /// Changing the default format, and font, changes the default column width
1708    /// and row height for a worksheet and as a result it changes the
1709    /// positioning and scaling of objects such as images and charts. As such,
1710    /// it is also necessary to set the default column pixel width and row pixel
1711    /// height when changing the default format/font. These dimensions can be
1712    /// obtained by clicking on the row and column gridlines in a sample
1713    /// worksheet in Excel. See the example below for a "Aptos Narrow 11"
1714    /// workbook where the required height and width dimensions would be 20 and
1715    /// 64:
1716    ///
1717    /// <img
1718    /// src="https://rustxlsxwriter.github.io/images/workbook_aptos_narrow.png">
1719    ///
1720    /// These dimensions should be obtained from a Windows version of Excel
1721    /// since the macOS versions use non-standard dimensions.
1722    ///
1723    /// Only fonts that have the following column pixel width are currently
1724    /// supported: 56, 64, 72, 80, 96, 104 and 120. However, this should cover
1725    /// the most common fonts that Excel uses. If you need to support a
1726    /// different font/width combination please open a feature request in the
1727    /// GitHub repository with a sample blank workbook.
1728    ///
1729    /// This method must be called before adding any worksheets to the workbook
1730    /// so that the default format can be shared with new worksheets.
1731    ///
1732    /// This method doesn't currently adapt the [`Worksheet::autofit()`] method
1733    /// for the new format font but this will hopefully be added in a future
1734    /// release.
1735    ///
1736    /// # Parameters
1737    ///
1738    /// - `format`: The new default [`Format`] property for the workbook.
1739    /// - `row_height`: The default row height in pixels.
1740    /// - `col_width`: The default column width in pixels.
1741    ///
1742    /// # Errors
1743    ///
1744    /// - [`XlsxError::DefaultFormatError`] - This error occurs if you try to
1745    ///   set the default format after a worksheet has been added to the
1746    ///   workbook, or if the pixel column width is not one of the supported
1747    ///   values shown above.
1748    ///
1749    /// # Examples
1750    ///
1751    /// The following example demonstrates changing the default format for a
1752    /// workbook.
1753    ///
1754    /// ```
1755    /// # // This code is available in examples/doc_workbook_set_default_format1.rs
1756    /// #
1757    /// # use rust_xlsxwriter::{Format, Workbook, XlsxError};
1758    /// #
1759    /// # fn main() -> Result<(), XlsxError> {
1760    /// #     let mut workbook = Workbook::new();
1761    /// #
1762    ///     // Create a new default format for the workbook.
1763    ///     let format = Format::new()
1764    ///         .set_font_name("Aptos Narrow")
1765    ///         .set_font_size(11);
1766    ///
1767    ///     // Set the default format for the workbook.
1768    ///     workbook.set_default_format(&format, 20, 64)?;
1769    ///
1770    ///     // Add a worksheet to the workbook.
1771    ///     let worksheet = workbook.add_worksheet();
1772    ///
1773    ///     // Write some text to demonstrate the changed default format.
1774    ///     worksheet.write(0, 0, "Hello")?;
1775    /// #
1776    /// #     // Save the workbook to disk.
1777    /// #     workbook.save("workbook.xlsx")?;
1778    /// #
1779    /// #     Ok(())
1780    /// # }
1781    /// ```
1782    ///
1783    /// Output file:
1784    ///
1785    /// <img
1786    /// src="https://rustxlsxwriter.github.io/images/workbook_set_default_format1.png">
1787    ///
1788    /// Here is another example:
1789    ///
1790    /// ```
1791    /// # // This code is available in examples/doc_workbook_set_default_format2.rs
1792    /// #
1793    /// # use rust_xlsxwriter::{Format, FormatAlign, Workbook, XlsxError};
1794    /// #
1795    /// # fn main() -> Result<(), XlsxError> {
1796    /// #     let mut workbook = Workbook::new();
1797    /// #
1798    ///     // Create a new default format for the workbook.
1799    ///     let format = Format::new()
1800    ///         .set_font_name("MS Pゴシック")
1801    ///         .set_font_size(11)
1802    ///         .set_font_charset(128)
1803    ///         .set_align(FormatAlign::VerticalCenter);
1804    ///
1805    ///     // Set the default format for the workbook.
1806    ///     workbook.set_default_format(&format, 18, 72)?;
1807    ///
1808    ///     // Add a worksheet to the workbook.
1809    ///     let worksheet = workbook.add_worksheet();
1810    ///
1811    ///     // Write some text to demonstrate the changed default format.
1812    ///     worksheet.write(0, 0, "結局きたよ")?;
1813    /// #
1814    /// #     // Save the workbook to disk.
1815    /// #     workbook.save("workbook.xlsx")?;
1816    /// #
1817    /// #     Ok(())
1818    /// # }
1819    /// ```
1820    ///
1821    /// Output file:
1822    ///
1823    /// <img
1824    /// src="https://rustxlsxwriter.github.io/images/workbook_set_default_format2.png">
1825    ///
1826    pub fn set_default_format(
1827        &mut self,
1828        format: &Format,
1829        row_height: u32,
1830        col_width: u32,
1831    ) -> Result<&mut Workbook, XlsxError> {
1832        if !self.worksheets.is_empty() {
1833            return Err(XlsxError::DefaultFormatError(
1834                "Default format must be set before adding worksheets.".to_string(),
1835            ));
1836        }
1837
1838        let Some((max_digit_width, padding, max_col_width)) =
1839            utility::default_column_metrics(col_width)
1840        else {
1841            return Err(XlsxError::DefaultFormatError(format!(
1842                "Unsupported default column width: {col_width}"
1843            )));
1844        };
1845
1846        self.xf_indices = Arc::new(RwLock::new(HashMap::from([(format.clone(), 0)])));
1847        self.default_format = format.clone();
1848
1849        self.cell_padding = padding;
1850        self.max_col_width = max_col_width;
1851        self.max_digit_width = max_digit_width;
1852        self.default_col_width = col_width;
1853        self.default_row_height = row_height;
1854
1855        Ok(self)
1856    }
1857
1858    /// Change the default workbook theme to the Excel 2023 Office/Aptos theme.
1859    ///
1860    /// Excel uses themes to define default fonts and colors for a workbook. The
1861    /// default theme in Excel from 2007 to 2022 was the "Office" theme with the
1862    /// Calibri 11 font and an associated color palette. In Excel 2023 and later
1863    /// the "Office" theme uses the Aptos 11 font and a different color palette.
1864    /// The older "Office" theme is now referred to as the "Office 2013-2022"
1865    /// theme.
1866    ///
1867    /// The `rust_xlsxwriter` library uses the original "Office" theme with
1868    /// Calibri 11 as the default font but, if required,
1869    /// `use_excel_2023_theme()` can be used to change to the newer Excel 2023
1870    /// "Office" theme with Aptos Narrow 11.
1871    ///
1872    /// Changing the theme and default font also changes the default row height
1873    /// and column width which in turn affects the positioning of objects such
1874    /// as images and charts. These changes are handled automatically.
1875    ///
1876    /// This method must be called before adding any worksheets to the workbook
1877    /// so that the default format can be shared with new worksheets.
1878    ///
1879    /// # Errors
1880    ///
1881    /// - [`XlsxError::DefaultFormatError`] - This error occurs if you try to
1882    ///   change the default theme, and by extension the default format, after a
1883    ///   worksheet has been added to the workbook.
1884    ///
1885    /// # Examples
1886    ///
1887    /// The following example demonstrates changing the default theme for a
1888    /// workbook. The example uses the Excel 2023 Office/Aptos theme.
1889    ///
1890    /// ```
1891    /// # // This code is available in examples/doc_workbook_use_excel_2023_theme.rs
1892    /// #
1893    /// # use rust_xlsxwriter::{Workbook, XlsxError};
1894    /// #
1895    /// # fn main() -> Result<(), XlsxError> {
1896    /// #     let mut workbook = Workbook::new();
1897    /// #
1898    ///     // Use the Excel 2023 Office/Aptos theme in the workbook.
1899    ///     workbook.use_excel_2023_theme()?;
1900    ///
1901    ///     // Add a worksheet to the workbook.
1902    ///     let worksheet = workbook.add_worksheet();
1903    ///
1904    ///     // Write some text to demonstrate the changed theme.
1905    ///     worksheet.write(0, 0, "Hello")?;
1906    ///
1907    ///     // Save the workbook to disk.
1908    /// #     workbook.save("workbook.xlsx")?;
1909    /// #
1910    /// #     Ok(())
1911    /// # }
1912    /// ```
1913    ///
1914    /// Output file:
1915    ///
1916    /// <img
1917    /// src="https://rustxlsxwriter.github.io/images/workbook_use_excel_2023_theme.png">
1918    ///
1919    pub fn use_excel_2023_theme(&mut self) -> Result<&mut Workbook, XlsxError> {
1920        self.theme_xml = THEME_XML_2023.to_string();
1921        self.default_theme_version = String::from("202300");
1922
1923        // Set the default font associated with the 2023 theme.
1924        let format = Format::new()
1925            .set_font_name("Aptos Narrow")
1926            .set_font_size(11)
1927            .set_font_scheme(FontScheme::Body);
1928
1929        // Set the default format and row/column dimensions.
1930        self.set_default_format(&format, 20, 64)
1931    }
1932
1933    /// Change the default workbook theme to a user defined custom theme.
1934    ///
1935    /// Excel uses themes to define default fonts and colors for a workbook. The
1936    /// `rust_xlsxwriter` library uses the original "Office" theme with Calibri
1937    /// 11 as the default font but, if required, `use_custom_theme()` can be
1938    /// used to change to a custom, user-supplied, theme.
1939    ///
1940    /// The theme file can be an Excel `.thmx` file or `.xlsx` file. It can also
1941    /// be a `theme1.xml` file extracted from an unzipped Excel xlsx file, where
1942    /// it is typically located at `xl/theme/theme1.xml`.
1943    ///
1944    /// In addition to supplying the theme XML file it is also necessary to set
1945    /// the default format to match the theme. This is done via the
1946    /// [`Workbook::set_default_format()`] method, see the example below. When
1947    /// setting a font that will be used as the default format it is also
1948    /// necessary to set the scheme parameter to [`FontScheme::Body`]
1949    ///
1950    /// Note, older Excel 2007 style theme files that contain image fills as
1951    /// part of the theme are not currently supported and will raise an
1952    /// [`XlsxError::ThemeError`] error on loading.
1953    ///
1954    /// # Parameters
1955    ///
1956    /// - `path`: A path to a `theme.xml` file.
1957    ///
1958    /// # Errors
1959    ///
1960    /// - [`XlsxError::IoError`] - I/O errors if the path doesn't exist or is
1961    ///   restricted.
1962    /// - [`XlsxError::ThemeError`] - File doesn't contain a `theme` xml file,
1963    ///   it isn't XML, or it contains unsupported image fills.
1964    ///
1965    /// # Examples
1966    ///
1967    /// The following example demonstrates changing the default theme for a workbook
1968    /// to a user supplied custom theme. The example uses an Excel `.thmx` file but
1969    /// it can also be an `.xlsx` file or a theme xml file extracted from an Excel
1970    /// xlsx file.
1971    ///
1972    /// ```
1973    /// # // This code is available in examples/doc_workbook_use_custom_theme.rs
1974    /// #
1975    /// # use rust_xlsxwriter::{FontScheme, Format, Workbook, XlsxError};
1976    /// #
1977    /// # fn main() -> Result<(), XlsxError> {
1978    /// #     let mut workbook = Workbook::new();
1979    /// #
1980    ///     // Add a custom theme to the workbook.
1981    ///     workbook.use_custom_theme("tests/input/themes/Technic.thmx")?;
1982    ///
1983    ///     // Create a new default format to match the custom theme. Note, that the
1984    ///     // scheme is set to "Body" to indicate that the font is part of the theme.
1985    ///     let format = Format::new()
1986    ///         .set_font_name("Arial")
1987    ///         .set_font_size(11)
1988    ///         .set_font_scheme(FontScheme::Body);
1989    ///
1990    ///     // Add the default format for the workbook.
1991    ///     workbook.set_default_format(&format, 19, 72)?;
1992    ///
1993    ///     // Add a worksheet to the workbook.
1994    ///     let worksheet = workbook.add_worksheet();
1995    ///
1996    ///     // Write some text to demonstrate the changed theme.
1997    ///     worksheet.write(0, 0, "Hello")?;
1998    /// #
1999    /// #     // Save the workbook to disk.
2000    /// #     workbook.save("workbook.xlsx")?;
2001    /// #
2002    /// #     Ok(())
2003    /// # }
2004    /// ```
2005    ///
2006    /// Output file:
2007    ///
2008    /// <img
2009    /// src="https://rustxlsxwriter.github.io/images/workbook_use_custom_theme.png">
2010    ///
2011    pub fn use_custom_theme<P: AsRef<Path>>(
2012        &mut self,
2013        path: P,
2014    ) -> Result<&mut Workbook, XlsxError> {
2015        let theme_xml = Self::read_theme_from_path(&path)?;
2016
2017        // Simple check to see if the file is text/XML.
2018        if !theme_xml.starts_with("<?xml") {
2019            return Err(XlsxError::ThemeError(format!(
2020                "Invalid XML theme file: '{}'",
2021                path.as_ref().display()
2022            )));
2023        }
2024
2025        // Check for Excel 2007 theme files that contain images as fills. These
2026        // aren't currently supported.
2027        if theme_xml.contains("<a:blipFill>") {
2028            return Err(XlsxError::ThemeError(format!(
2029                "Theme file contains image fills which aren't currently supported: '{}'",
2030                path.as_ref().display()
2031            )));
2032        }
2033
2034        self.theme_xml = theme_xml;
2035        self.default_theme_version = String::new();
2036
2037        Ok(self)
2038    }
2039
2040    /// Add a vba macro file to the workbook.
2041    ///
2042    /// The `add_vba_project()` method can be used to add macros or functions to
2043    /// a workbook using a binary VBA project file that has been extracted from
2044    /// an existing Excel `xlsm` file.
2045    ///
2046    /// An Excel `xlsm` file is structurally the same as an `xlsx` file except
2047    /// that it contains an additional `vbaProject.bin` binary file containing
2048    /// VBA functions and/or macros.
2049    ///
2050    /// The `vbaProject.bin` in a `xlsm` file is a binary OLE COM container.
2051    /// This was the format used in older `xls` versions of Excel prior to Excel
2052    /// 2007. Unlike other components of an xlsx/xlsm file the data isn't stored
2053    /// in XML format. Instead the functions and macros as stored as a
2054    /// pre-parsed binary format. As such it wouldn't be feasible to
2055    /// programmatically define macros and create a `vbaProject.bin` file from
2056    /// scratch.
2057    ///
2058    /// Instead, as a workaround, the Rust
2059    /// [`vba_extract`](https://crates.io/crates/vba_extract) utility is used to
2060    /// extract `vbaProject.bin` files from existing xlsm files which you can
2061    /// then add to `rust_xlsxwriter` files.
2062    ///
2063    /// The utility can be installed via `cargo`:
2064    ///
2065    /// ```bash
2066    /// $ cargo install vba_extract
2067    /// ```
2068    ///
2069    /// Once `vba_extract` is installed it can be used as follows:
2070    ///
2071    /// ```bash
2072    /// $ vba_extract macro_file.xlsm
2073    ///
2074    /// Extracted: vbaProject.bin
2075    /// ```
2076    ///
2077    /// If the VBA project is signed, `vba_extract` also extracts the
2078    /// `vbaProjectSignature.bin` file from the xlsm file (see below).
2079    ///
2080    /// The process is explained in detail in [Working with VBA
2081    /// macros](crate::macros).
2082    ///
2083    /// Only one `vbaProject.bin` file can be added per workbook. The name
2084    /// doesn’t have to be `vbaProject.bin`. Any suitable path/name for an
2085    /// existing VBA bin file will do.
2086    ///
2087    /// # Parameters
2088    ///
2089    /// - `path`: A path to a `vbaProject.bin` file.
2090    ///
2091    /// # Errors
2092    ///
2093    /// - [`XlsxError::IoError`] - I/O errors if the path doesn't exist or is
2094    ///   restricted.
2095    ///
2096    /// # Examples
2097    ///
2098    /// An example of adding macros to an `rust_xlsxwriter` file using a VBA
2099    /// macros file extracted from an existing Excel xlsm file.
2100    ///
2101    /// ```
2102    /// # // This code is available in examples/app_macros.rs
2103    /// #
2104    /// use rust_xlsxwriter::{Button, Workbook, XlsxError};
2105    ///
2106    /// fn main() -> Result<(), XlsxError> {
2107    ///     // Create a new Excel file object.
2108    ///     let mut workbook = Workbook::new();
2109    ///
2110    ///     // Add the VBA macro file.
2111    ///     workbook.add_vba_project("examples/vbaProject.bin")?;
2112    ///
2113    ///     // Add a worksheet and some text.
2114    ///     let worksheet = workbook.add_worksheet();
2115    ///
2116    ///     // Widen the first column for clarity.
2117    ///     worksheet.set_column_width(0, 30)?;
2118    ///
2119    ///     worksheet.write(2, 0, "Press the button to say hello:")?;
2120    ///
2121    ///     // Add a button tied to a macro in the VBA project.
2122    ///     let button = Button::new()
2123    ///         .set_caption("Press Me")
2124    ///         .set_macro("say_hello")
2125    ///         .set_width(80)
2126    ///         .set_height(30);
2127    ///
2128    ///     worksheet.insert_button(2, 1, &button)?;
2129    ///
2130    ///     // Save the file to disk. Note the `.xlsm` extension. This is required by
2131    ///     // Excel or it will raise a warning.
2132    ///     workbook.save("macros.xlsm")?;
2133    ///
2134    ///     Ok(())
2135    /// }
2136    /// ```
2137    ///
2138    /// Output file:
2139    ///
2140    /// <img src="https://rustxlsxwriter.github.io/images/app_macros.png">
2141    ///
2142    pub fn add_vba_project<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Workbook, XlsxError> {
2143        let path_buf = path.as_ref().to_path_buf();
2144        let file = File::open(path_buf)?;
2145        let mut reader = BufReader::new(file);
2146        let mut data = vec![];
2147        reader.read_to_end(&mut data)?;
2148
2149        self.vba_project = data;
2150        self.is_xlsm_file = true;
2151
2152        if self.vba_codename.is_none() {
2153            self.vba_codename = Some("ThisWorkbook".to_string());
2154        }
2155
2156        Ok(self)
2157    }
2158
2159    /// Use zip large file/ZIP64 extensions.
2160    ///
2161    /// The `rust_xlsxwriter` library uses the [zip.rs] crate to provide the zip
2162    /// container for the xlsx file that it generates. The size limit for a
2163    /// standard zip file is 4GB for the overall container or for any of the
2164    /// uncompressed files within it.  Anything greater than that requires
2165    /// [ZIP64] support. In practice this would apply to worksheets with
2166    /// approximately 150 million cells, or more.
2167    ///
2168    /// The `use_zip_large_file()` option enables ZIP64/large file support by
2169    /// enabling the `zip.rs` [`large_file()`] option. Here is what the `zip.rs`
2170    /// library says about the `large_file()` option:
2171    ///
2172    /// > If `large_file()` is set to false and the file exceeds the limit, an
2173    /// > I/O error is thrown and the file is aborted. If set to true, readers
2174    /// > will require ZIP64 support and if the file does not exceed the limit,
2175    /// > 20 B are wasted. The default is false.
2176    ///
2177    /// You can interpret this to mean that it is safe/efficient to turn on
2178    /// large file mode by default if you anticipate that your application may
2179    /// generate files that exceed the 4GB limit. At least for Excel. Other
2180    /// applications may have issues if they don't support ZIP64 extensions.
2181    ///
2182    /// [zip.rs]: https://crates.io/crates/zip
2183    /// [ZIP64]: https://en.wikipedia.org/wiki/ZIP_(file_format)#ZIP64
2184    /// [`large_file()`]:
2185    ///     https://docs.rs/zip/latest/zip/write/type.SimpleFileOptions.html#method.large_file
2186    ///
2187    /// # Parameters
2188    ///
2189    /// - `enable`: Turn the property on/off. It is off by default.
2190    ///
2191    pub fn use_zip_large_file(&mut self, enable: bool) -> &mut Workbook {
2192        self.use_large_file = enable;
2193
2194        self
2195    }
2196
2197    /// Add a signed vba macro file to the workbook.
2198    ///
2199    /// The `add_vba_project_with_signature()` method can be used to add signed
2200    /// macros or functions to a workbook using a binary VBA project file that
2201    /// has been extracted from an existing Excel `xlsm` file.
2202    ///
2203    /// VBA macros can be signed in Excel to allow for further control over
2204    /// execution. The signature part is added to the `xlsm` file in a binary
2205    /// called `vbaProjectSignature.bin` which must be used in conjunction with
2206    /// `vbaProject.bin`, see above.
2207    ///
2208    /// The Rust [`vba_extract`](https://crates.io/crates/vba_extract) utility
2209    /// will extract the `vbaProject.bin` and `vbaProjectSignature.bin` files
2210    /// from an `xlsm` file with signed macros.
2211    ///
2212    ///
2213    /// See [`Workbook::add_vba_project()`] above and [Working with VBA
2214    /// macros](crate::macros) for more details.
2215    ///
2216    /// # Parameters
2217    ///
2218    /// - `project`: A path to a `vbaProject.bin` file.
2219    /// - `signature`: A path to a `vbaProjectSignature.bin` file.
2220    ///
2221    /// # Errors
2222    ///
2223    /// - [`XlsxError::IoError`] - I/O errors if the path doesn't exist or is
2224    ///   restricted.
2225    ///
2226    /// # Examples
2227    ///
2228    /// The following example demonstrates a simple example of adding a vba
2229    /// project to an xlsm file.
2230    ///
2231    /// ```
2232    /// # // This code is available in examples/doc_macros_signed.rs
2233    /// #
2234    /// # use rust_xlsxwriter::{Workbook, XlsxError};
2235    /// #
2236    /// # #[allow(unused_variables)]
2237    /// # fn main() -> Result<(), XlsxError> {
2238    /// #     let mut workbook = Workbook::new();
2239    /// #
2240    ///     workbook.add_vba_project_with_signature(
2241    ///         "examples/vbaProject.bin",
2242    ///         "examples/vbaProjectSignature.bin",
2243    ///     )?;
2244    /// #
2245    /// #     let worksheet = workbook.add_worksheet();
2246    /// #
2247    /// #     // Note the `.xlsm` extension.
2248    /// #     workbook.save("macros.xlsm")?;
2249    /// #
2250    /// #     Ok(())
2251    /// # }
2252    /// ```
2253    ///
2254    pub fn add_vba_project_with_signature<P: AsRef<Path>>(
2255        &mut self,
2256        project: P,
2257        signature: P,
2258    ) -> Result<&mut Workbook, XlsxError> {
2259        // Add the project binary file.
2260        self.add_vba_project(project)?;
2261
2262        // Add the signature binary file.
2263        let path_buf = signature.as_ref().to_path_buf();
2264        let file = File::open(path_buf)?;
2265        let mut reader = BufReader::new(file);
2266        let mut data = vec![];
2267        reader.read_to_end(&mut data)?;
2268
2269        self.vba_signature = data;
2270
2271        Ok(self)
2272    }
2273
2274    /// Set the workbook name used in VBA macros.
2275    ///
2276    /// This method can be used to set the VBA name for the workbook. This is
2277    /// sometimes required when a VBA macro included via
2278    /// [`Workbook::add_vba_project()`] makes reference to the workbook with a
2279    /// name other than the default Excel VBA name of `ThisWorkbook`.
2280    ///
2281    /// See also the
2282    /// [`Worksheet::set_vba_name()`](crate::Worksheet::set_vba_name()) method
2283    /// for setting a worksheet VBA name.
2284    ///
2285    /// The name must be a valid Excel VBA object name as defined by the
2286    /// following rules:
2287    ///
2288    /// - The name must be less than 32 characters.
2289    /// - The name can only contain word characters: letters, numbers and
2290    ///   underscores.
2291    /// - The name must start with a letter.
2292    /// - The name cannot be blank.
2293    ///
2294    /// # Parameters
2295    ///
2296    /// - `name`: The vba name. It must follow the Excel rules, shown above.
2297    ///
2298    /// # Errors
2299    ///
2300    /// - [`XlsxError::VbaNameError`] - The name doesn't meet one of Excel's
2301    ///   criteria, shown above.
2302    ///
2303    pub fn set_vba_name(&mut self, name: impl Into<String>) -> Result<&mut Workbook, XlsxError> {
2304        let name = name.into();
2305        utility::validate_vba_name(&name)?;
2306        self.vba_codename = Some(name);
2307
2308        Ok(self)
2309    }
2310
2311    /// Add a recommendation to open the file in “read-only” mode.
2312    ///
2313    /// This method can be used to set the Excel “Read-only Recommended” option
2314    /// that is available when saving a file. This presents the user of the file
2315    /// with an option to open it in "read-only" mode. This means that any
2316    /// changes to the file can’t be saved back to the same file and must be
2317    /// saved to a new file.
2318    ///
2319    /// # Examples
2320    ///
2321    /// The following example demonstrates creating a simple workbook which opens
2322    /// with a recommendation that the file should be opened in read only mode.
2323    ///
2324    /// ```
2325    /// # // This code is available in examples/doc_workbook_read_only_recommended.rs
2326    /// #
2327    /// # use rust_xlsxwriter::{Workbook, XlsxError};
2328    /// #
2329    /// # fn main() -> Result<(), XlsxError> {
2330    ///     let mut workbook = Workbook::new();
2331    ///
2332    ///     let _worksheet = workbook.add_worksheet();
2333    ///
2334    ///     workbook.read_only_recommended();
2335    ///
2336    ///     workbook.save("workbook.xlsx")?;
2337    /// #
2338    /// #     Ok(())
2339    /// # }
2340    /// ```
2341    ///
2342    /// Alert when you open the output file:
2343    ///
2344    /// <img src="https://rustxlsxwriter.github.io/images/workbook_read_only_recommended.png">
2345    ///
2346    pub fn read_only_recommended(&mut self) -> &mut Workbook {
2347        self.read_only_mode = 2;
2348        self
2349    }
2350
2351    /// Set the order/index for the format.
2352    ///
2353    /// This is currently only used in testing to ensure the same format order
2354    /// as target Excel files. It can also be used in multi-threaded constant
2355    /// memory programs to pre-compute the format index so that all uses of the
2356    /// format only involve a `RwLock` `read()` and not a `write()`.
2357    ///
2358    /// # Parameters
2359    ///
2360    /// `format` - The [`Format`] instance to register.
2361    ///
2362    #[doc(hidden)]
2363    pub fn register_format(&mut self, format: &Format) {
2364        self.format_xf_index(format);
2365    }
2366
2367    /// Set the order/index for shared string table strings.
2368    ///
2369    /// This is currently only used in testing to ensure the same string order
2370    /// as target Excel files.
2371    ///
2372    /// # Parameters
2373    ///
2374    /// `string` - The string to add to the shared string table.
2375    /// `index` - The index in the shared string table.
2376    ///
2377    #[doc(hidden)]
2378    pub fn populate_string_table(&mut self, string: &str, index: u32) {
2379        let mut string_table = self.string_table.lock().unwrap();
2380
2381        string_table.populate_string_index(Arc::from(string), index);
2382    }
2383
2384    // -----------------------------------------------------------------------
2385    // Internal function/methods.
2386    // -----------------------------------------------------------------------
2387
2388    // Reset workbook between saves.
2389    fn reset(&mut self) {
2390        xmlwriter::reset(&mut self.writer);
2391
2392        for worksheet in &mut self.worksheets {
2393            worksheet.reset();
2394        }
2395    }
2396
2397    // Initialize the default format for a worksheet.
2398    fn initialize_default_format(&mut self, worksheet: &mut Worksheet) {
2399        worksheet.initialize_default_format(
2400            &self.default_format,
2401            self.default_row_height,
2402            self.default_col_width,
2403            self.max_digit_width,
2404            self.cell_padding,
2405            self.max_col_width,
2406        );
2407    }
2408
2409    // Read theme XML from either a zip file (thmx/xlsx) or a text file.
2410    fn read_theme_from_path<P: AsRef<Path>>(path: P) -> Result<String, XlsxError> {
2411        let file = File::open(&path)?;
2412        let reader = BufReader::new(file);
2413
2414        // Try to read as a thmx/xlsx zip file first.
2415        if let Ok(mut archive) = ZipArchive::new(reader) {
2416            let possible_paths = [
2417                // Path in .thmx file.
2418                "theme/theme/theme1.xml",
2419                // Path in .xlsx file.
2420                "xl/theme/theme1.xml",
2421            ];
2422
2423            for theme_path in &possible_paths {
2424                if let Ok(mut theme_file) = archive.by_name(theme_path) {
2425                    let mut theme_xml = String::new();
2426                    theme_file.read_to_string(&mut theme_xml)?;
2427                    return Ok(theme_xml);
2428                }
2429            }
2430
2431            return Err(XlsxError::ThemeError(format!(
2432                "No theme1.xml found in file: '{}'",
2433                path.as_ref().display()
2434            )));
2435        }
2436
2437        // Try reading as a text file.
2438        read_to_string(&path).map_err(XlsxError::IoError)
2439    }
2440
2441    // Internal function to prepare the workbook and other component files for
2442    // writing to the xlsx file.
2443    #[allow(clippy::similar_names)]
2444    #[allow(clippy::too_many_lines)]
2445    fn save_internal<W: Write + Seek + Send>(&mut self, writer: W) -> Result<(), XlsxError> {
2446        // Reset workbook and worksheet state data between saves.
2447        self.reset();
2448
2449        // Ensure that there is at least one worksheet in the workbook.
2450        if self.worksheets.is_empty() {
2451            self.add_worksheet();
2452        }
2453        // Ensure one sheet is active/selected.
2454        self.set_active_worksheets();
2455
2456        // Check for the use of hyperlink style in the worksheets and if so add
2457        // a hyperlink style to the global formats.
2458        for worksheet in &self.worksheets {
2459            if worksheet.has_hyperlink_style {
2460                let format = self.default_format.clone().set_hyperlink();
2461                self.format_xf_index(&format);
2462                self.has_hyperlink_style = true;
2463                break;
2464            }
2465        }
2466
2467        // Also check for hyperlinks in the global format table.
2468        let xf_indices = self.xf_indices.read().expect("RwLock poisoned");
2469        for format in xf_indices.keys() {
2470            if format.font.is_hyperlink {
2471                self.has_hyperlink_style = true;
2472                break;
2473            }
2474        }
2475        drop(xf_indices);
2476
2477        // Check for duplicate sheet names, which aren't allowed by Excel.
2478        let mut unique_worksheet_names = HashSet::new();
2479        for worksheet in &self.worksheets {
2480            let worksheet_name = worksheet.name.to_lowercase();
2481            if unique_worksheet_names.contains(&worksheet_name) {
2482                return Err(XlsxError::SheetnameReused(worksheet_name));
2483            }
2484
2485            unique_worksheet_names.insert(worksheet_name);
2486        }
2487
2488        // Check that chartsheets have a chart.
2489        for worksheet in &self.worksheets {
2490            if worksheet.is_chartsheet && worksheet.charts.is_empty() {
2491                return Err(XlsxError::ChartError(format!(
2492                    "Chartsheet '{}' doesn't contain a chart",
2493                    worksheet.name
2494                )));
2495            }
2496        }
2497
2498        // Write any Tables associated with serialization areas.
2499        #[cfg(feature = "serde")]
2500        for worksheet in &mut self.worksheets {
2501            worksheet.store_serialized_tables()?;
2502        }
2503
2504        // Convert any worksheet local formats to workbook/global formats. At
2505        // the worksheet level each unique format will have an index like 0, 1,
2506        // 2, etc., starting from 0 for each worksheet. However, at a workbook
2507        // level they may have an equivalent index of 1, 7, 5 or whatever
2508        // workbook order they appear in.
2509        let mut worksheet_xf_formats: Vec<Vec<Format>> = vec![];
2510        let mut worksheet_dxf_formats: Vec<Vec<Format>> = vec![];
2511        for worksheet in &self.worksheets {
2512            let formats = worksheet.xf_formats.clone();
2513            worksheet_xf_formats.push(formats);
2514            let formats = worksheet.dxf_formats.clone();
2515            worksheet_dxf_formats.push(formats);
2516        }
2517
2518        let mut worksheet_xf_indices: Vec<Vec<u32>> = vec![];
2519        for formats in &worksheet_xf_formats {
2520            let mut indices = vec![];
2521            for format in formats {
2522                let index = self.format_xf_index(format);
2523                indices.push(index);
2524            }
2525            worksheet_xf_indices.push(indices);
2526        }
2527        let mut worksheet_dxf_indices: Vec<Vec<u32>> = vec![];
2528        for formats in &worksheet_dxf_formats {
2529            let mut indices = vec![];
2530            for format in formats {
2531                let index = self.format_dxf_index(format);
2532                indices.push(index);
2533            }
2534            worksheet_dxf_indices.push(indices);
2535        }
2536
2537        // We extract the XF Formats used as keys in the index lookup to a
2538        // vector of formats sorted by their index number.
2539        let xf_indices = self.xf_indices.read().expect("RwLock poisoned");
2540
2541        let mut xf_indices_vec: Vec<(&Format, &u32)> = xf_indices.iter().collect();
2542        xf_indices_vec.sort_by(|a, b| a.1.cmp(b.1));
2543
2544        let xf_formats: Vec<Format> = xf_indices_vec.iter().map(|x| x.0.clone()).collect();
2545        self.xf_formats = xf_formats;
2546
2547        drop(xf_indices);
2548
2549        // Map worksheet/local format indices to the workbook/global values.
2550        for (i, worksheet) in self.worksheets.iter_mut().enumerate() {
2551            worksheet.set_global_xf_indices(&worksheet_xf_indices[i]);
2552            worksheet.set_global_dxf_indices(&worksheet_dxf_indices[i]);
2553
2554            // Perform the autofilter row hiding.
2555            worksheet.hide_autofilter_rows();
2556
2557            // Set the index of the worksheets.
2558            worksheet.sheet_index = i;
2559
2560            // Set a default codename for the worksheet if the overall workbook
2561            // is a xlsm file. Note that the VBA sheet naming scheme is based on
2562            // SheetN and not on the actual sheet name.
2563            if self.is_xlsm_file {
2564                let codename = format!("Sheet{}", i + 1);
2565                if worksheet.vba_codename.is_none() {
2566                    worksheet.vba_codename = Some(codename);
2567                }
2568            }
2569
2570            // Chartsheets shouldn't use constant memory mode.
2571            if worksheet.is_chartsheet {
2572                worksheet.use_constant_memory = false;
2573            }
2574        }
2575
2576        // Generate a global array of embedded images from the worksheets.
2577        self.prepare_embedded_images();
2578
2579        // Convert the images in the workbooks into drawing files and rel links.
2580        self.prepare_drawings();
2581
2582        // Prepare the worksheet VML elements such as buttons and header images.
2583        self.prepare_vml();
2584
2585        // Fill the chart data caches from worksheet data.
2586        self.prepare_chart_cache_data()?;
2587
2588        // Prepare the formats for writing with styles.rs.
2589        self.prepare_format_properties();
2590
2591        // Prepare worksheet tables.
2592        self.prepare_tables()?;
2593
2594        // Update the shared string table in each worksheet.
2595        for worksheet in &mut self.worksheets {
2596            if !worksheet.has_workbook_global_sst {
2597                let string_table = self.string_table.clone();
2598                worksheet.update_string_table_ids(string_table);
2599            }
2600        }
2601
2602        // Collect workbook level metadata to help generate the xlsx file.
2603        let mut package_options = PackagerOptions::new();
2604        package_options = self.set_package_options(package_options)?;
2605
2606        // Create the Packager object that will assemble the zip/xlsx file.
2607        let packager = Packager::new(writer, self.use_large_file);
2608        packager.assemble_file(self, &package_options)?;
2609
2610        Ok(())
2611    }
2612
2613    // Iterates through the worksheets and finds which is the user-defined Active
2614    // sheet. If none has been set, then defaults to the first sheet, like Excel.
2615    fn set_active_worksheets(&mut self) {
2616        let mut active_index = 0;
2617
2618        for (i, worksheet) in self.worksheets.iter().enumerate() {
2619            if worksheet.active {
2620                active_index = i;
2621            }
2622            if worksheet.first_sheet {
2623                self.first_sheet = i as u16;
2624            }
2625        }
2626        self.worksheets[active_index].set_active(true);
2627        self.active_tab = active_index as u16;
2628    }
2629
2630    // Prepare the worksheet VML elements such as buttons and header images.
2631    fn prepare_vml(&mut self) {
2632        let mut comment_id = 1;
2633        let mut vml_drawing_id = 1;
2634        let mut vml_data_id = 1;
2635        let mut vml_shape_id = 1024;
2636
2637        for worksheet in &mut self.worksheets {
2638            if worksheet.has_vml {
2639                let note_count = worksheet.prepare_vml_objects(vml_data_id, vml_shape_id);
2640                worksheet.add_vml_drawing_rel_link(vml_drawing_id);
2641                vml_drawing_id += 1;
2642
2643                if !worksheet.notes.is_empty() {
2644                    worksheet.add_comment_rel_link(comment_id);
2645                    comment_id += 1;
2646                    self.has_comments = true;
2647                }
2648
2649                // Each VML should start with a shape id incremented by 1024.
2650                vml_data_id += (1024 + note_count) / 1024;
2651                vml_shape_id += 1024 * ((1024 + note_count) / 1024);
2652            }
2653
2654            if worksheet.has_header_footer_images() {
2655                worksheet.add_vml_drawing_rel_link(vml_drawing_id);
2656                vml_drawing_id += 1;
2657            }
2658        }
2659    }
2660
2661    // Convert any embedded images in the worksheets to a global reference. Each
2662    // worksheet will have a local index to an embedded cell image. We need to
2663    // map these local references to a workbook/global id that takes into
2664    // account duplicate images.
2665    fn prepare_embedded_images(&mut self) {
2666        let mut embedded_images = vec![];
2667        let mut image_ids: HashMap<String, u32> = HashMap::new();
2668        let mut global_image_id = 0;
2669
2670        for worksheet in &mut self.worksheets {
2671            if worksheet.embedded_images.is_empty() {
2672                continue;
2673            }
2674
2675            let mut global_embedded_image_ids = vec![];
2676            for image in &worksheet.embedded_images {
2677                let image_id = match image_ids.get(&image.hash) {
2678                    Some(image_id) => *image_id,
2679                    None => {
2680                        global_image_id += 1;
2681                        embedded_images.push(image.clone());
2682                        image_ids.insert(image.hash.clone(), global_image_id);
2683                        global_image_id
2684                    }
2685                };
2686
2687                global_embedded_image_ids.push(image_id);
2688            }
2689
2690            worksheet.global_embedded_image_indices = global_embedded_image_ids;
2691        }
2692
2693        self.embedded_images = embedded_images;
2694    }
2695
2696    // Convert the images in the workbooks into drawing files and rel links.
2697    fn prepare_drawings(&mut self) {
2698        let mut chart_id = 1;
2699        let mut drawing_id = 1;
2700        let mut shape_id = 1;
2701        let mut image_id = self.embedded_images.len() as u32;
2702
2703        // These are the image ids for each unique image file.
2704        let mut worksheet_image_ids: HashMap<String, u32> = HashMap::new();
2705        let mut header_footer_image_ids: HashMap<String, u32> = HashMap::new();
2706        let mut background_image_ids: HashMap<String, u32> = HashMap::new();
2707
2708        for worksheet in &mut self.worksheets {
2709            if let Some(image) = &worksheet.background_image {
2710                let image = image.clone();
2711
2712                let background_image_id = match background_image_ids.get(&image.hash) {
2713                    Some(image_id) => *image_id,
2714                    None => {
2715                        image_id += 1;
2716                        background_image_ids.insert(image.hash.clone(), image_id);
2717                        image_id
2718                    }
2719                };
2720
2721                worksheet.prepare_background_image(background_image_id, &image);
2722            }
2723
2724            if !worksheet.images.is_empty() {
2725                worksheet.prepare_worksheet_images(
2726                    &mut worksheet_image_ids,
2727                    &mut image_id,
2728                    drawing_id,
2729                );
2730            }
2731
2732            if !worksheet.charts.is_empty() {
2733                worksheet.prepare_worksheet_charts(chart_id, drawing_id);
2734                chart_id += worksheet.charts.len() as u32;
2735            }
2736
2737            if !worksheet.shapes.is_empty() {
2738                worksheet.prepare_worksheet_shapes(shape_id, drawing_id);
2739                shape_id += worksheet.shapes.len() as u32;
2740            }
2741
2742            // Increase the drawing number/id for image/chart file.
2743            if !worksheet.images.is_empty()
2744                || !worksheet.charts.is_empty()
2745                || !worksheet.shapes.is_empty()
2746            {
2747                drawing_id += 1;
2748            }
2749
2750            if worksheet.has_header_footer_images() {
2751                worksheet.prepare_header_footer_images(&mut header_footer_image_ids, &mut image_id);
2752            }
2753        }
2754    }
2755
2756    // Prepare and check each table in the workbook.
2757    fn prepare_tables(&mut self) -> Result<(), XlsxError> {
2758        let mut table_id = 1;
2759        let mut seen_table_names = HashSet::new();
2760
2761        // Set a unique table id and table name and also set the .rel file
2762        // linkages.
2763        for worksheet in &mut self.worksheets {
2764            if !worksheet.tables.is_empty() {
2765                table_id = worksheet.prepare_worksheet_tables(table_id);
2766            }
2767        }
2768
2769        // Check for duplicate table names.
2770        for worksheet in &self.worksheets {
2771            for table in &worksheet.tables {
2772                if seen_table_names.contains(&table.name.to_lowercase()) {
2773                    return Err(XlsxError::TableNameReused(table.name.clone()));
2774                }
2775
2776                seen_table_names.insert(table.name.to_lowercase());
2777            }
2778        }
2779
2780        Ok(())
2781    }
2782
2783    // Add worksheet number/string cache data to chart ranges. This isn't
2784    // strictly necessary, but it helps non-Excel apps to render charts
2785    // correctly.
2786    fn prepare_chart_cache_data(&mut self) -> Result<(), XlsxError> {
2787        // First build up a hash of the chart data ranges. The data may not be
2788        // in the same worksheet as the chart so we need to do the lookup at the
2789        // workbook level.
2790        let mut chart_caches: HashMap<
2791            (String, RowNum, ColNum, RowNum, ColNum),
2792            ChartRangeCacheData,
2793        > = HashMap::new();
2794
2795        // Add the chart ranges to the cache lookup table.
2796        for worksheet in &self.worksheets {
2797            if !worksheet.charts.is_empty() {
2798                for chart in worksheet.charts.values() {
2799                    Self::insert_chart_ranges_to_cache(chart, &mut chart_caches);
2800
2801                    if let Some(chart) = &chart.combined_chart {
2802                        Self::insert_chart_ranges_to_cache(chart, &mut chart_caches);
2803                    }
2804                }
2805            }
2806        }
2807
2808        // Populate the caches with data from the worksheet ranges.
2809        for (key, cache) in &mut chart_caches {
2810            if let Ok(worksheet) = self.worksheet_from_name(&key.0) {
2811                *cache = worksheet.get_cache_data(key.1, key.2, key.3, key.4);
2812            } else {
2813                let sheet_name = key.0.clone();
2814
2815                if sheet_name != UNPARSED_SHEET_RANGE {
2816                    let range = utility::chart_range_abs(&key.0, key.1, key.2, key.3, key.4);
2817                    let error =
2818                        format!("Unknown worksheet name '{sheet_name}' in chart range '{range}'");
2819
2820                    return Err(XlsxError::UnknownWorksheetNameOrIndex(error));
2821                }
2822            }
2823        }
2824
2825        // Fill the caches back into the chart ranges.
2826        for worksheet in &mut self.worksheets {
2827            if !worksheet.charts.is_empty() {
2828                for chart in worksheet.charts.values_mut() {
2829                    Self::update_chart_ranges_from_cache(chart, &mut chart_caches);
2830
2831                    if let Some(chart) = &mut chart.combined_chart {
2832                        Self::update_chart_ranges_from_cache(chart, &mut chart_caches);
2833                    }
2834                }
2835            }
2836        }
2837
2838        Ok(())
2839    }
2840
2841    // Insert all the various chart ranges into the lookup range cache.
2842    fn insert_chart_ranges_to_cache(
2843        chart: &Chart,
2844        chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
2845    ) {
2846        Self::insert_to_chart_cache(&chart.title.range, chart_caches);
2847        Self::insert_to_chart_cache(&chart.x_axis.title.range, chart_caches);
2848        Self::insert_to_chart_cache(&chart.y_axis.title.range, chart_caches);
2849
2850        for series in &chart.series {
2851            Self::insert_to_chart_cache(&series.title.range, chart_caches);
2852            Self::insert_to_chart_cache(&series.value_range, chart_caches);
2853            Self::insert_to_chart_cache(&series.category_range, chart_caches);
2854
2855            for data_label in &series.custom_data_labels {
2856                Self::insert_to_chart_cache(&data_label.title.range, chart_caches);
2857            }
2858
2859            if let Some(error_bars) = &series.y_error_bars {
2860                Self::insert_to_chart_cache(&error_bars.plus_range, chart_caches);
2861                Self::insert_to_chart_cache(&error_bars.minus_range, chart_caches);
2862            }
2863
2864            if let Some(error_bars) = &series.x_error_bars {
2865                Self::insert_to_chart_cache(&error_bars.plus_range, chart_caches);
2866                Self::insert_to_chart_cache(&error_bars.minus_range, chart_caches);
2867            }
2868        }
2869    }
2870
2871    // Update all the various chart ranges from the lookup range cache.
2872    fn update_chart_ranges_from_cache(
2873        chart: &mut Chart,
2874        chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
2875    ) {
2876        Self::update_range_cache(&mut chart.title.range, chart_caches);
2877        Self::update_range_cache(&mut chart.x_axis.title.range, chart_caches);
2878        Self::update_range_cache(&mut chart.y_axis.title.range, chart_caches);
2879
2880        for series in &mut chart.series {
2881            Self::update_range_cache(&mut series.title.range, chart_caches);
2882            Self::update_range_cache(&mut series.value_range, chart_caches);
2883            Self::update_range_cache(&mut series.category_range, chart_caches);
2884
2885            for data_label in &mut series.custom_data_labels {
2886                if let Some(cache) = chart_caches.get(&data_label.title.range.key()) {
2887                    data_label.title.range.cache = cache.clone();
2888                }
2889            }
2890
2891            if let Some(error_bars) = &mut series.y_error_bars {
2892                Self::update_range_cache(&mut error_bars.plus_range, chart_caches);
2893                Self::update_range_cache(&mut error_bars.minus_range, chart_caches);
2894            }
2895
2896            if let Some(error_bars) = &mut series.x_error_bars {
2897                Self::update_range_cache(&mut error_bars.plus_range, chart_caches);
2898                Self::update_range_cache(&mut error_bars.minus_range, chart_caches);
2899            }
2900        }
2901    }
2902
2903    // Insert a chart range (expressed as a hash/key value) into the chart cache
2904    // for lookup later.
2905    fn insert_to_chart_cache(
2906        range: &ChartRange,
2907        chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
2908    ) {
2909        if range.has_data() {
2910            chart_caches.insert(range.key(), ChartRangeCacheData::new());
2911        }
2912    }
2913
2914    // Populate a chart range cache with data read from the worksheet.
2915    fn update_range_cache(
2916        range: &mut ChartRange,
2917        chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
2918    ) {
2919        if let Some(cache) = chart_caches.get(&range.key()) {
2920            range.cache = cache.clone();
2921        }
2922    }
2923
2924    // Evaluate a format and return its index number if already seen/used or
2925    // store it and return a new index.
2926    fn format_xf_index(&mut self, format: &Format) -> u32 {
2927        // Try a read() lock first to check if the format is known.
2928        let xf_indices = self.xf_indices.read().expect("RwLock poisoned");
2929
2930        if let Some(xf_index) = xf_indices.get(format) {
2931            return *xf_index;
2932        }
2933
2934        // Index wasn't found, so drop the read() lock and get a write() lock to
2935        // add the format and create a new index.
2936        drop(xf_indices);
2937
2938        // Add the new format and give it an index.
2939        let mut xf_indices = self.xf_indices.write().expect("RwLock poisoned");
2940        let xf_index = xf_indices.len() as u32;
2941        xf_indices.insert(format.clone(), xf_index);
2942
2943        xf_index
2944    }
2945
2946    fn format_dxf_index(&mut self, format: &Format) -> u32 {
2947        match self.dxf_indices.get_mut(format) {
2948            Some(dxf_index) => *dxf_index,
2949            None => {
2950                let dxf_index = self.dxf_formats.len() as u32;
2951                self.dxf_formats.push(format.clone());
2952                self.dxf_indices.insert(format.clone(), dxf_index);
2953                dxf_index
2954            }
2955        }
2956    }
2957
2958    // Prepare all Format properties prior to passing them to styles.rs.
2959    fn prepare_format_properties(&mut self) {
2960        // Set the font index for the format objects.
2961        self.prepare_fonts();
2962
2963        // Set the fill index for the format objects.
2964        self.prepare_fills();
2965
2966        // Set the border index for the format objects.
2967        self.prepare_borders();
2968
2969        // Set the number format index for the format objects.
2970        self.prepare_num_formats();
2971
2972        // Check for any format properties that require a feature bag. Currently,
2973        // this only applies to checkboxes.
2974        self.prepare_feature_property_bags();
2975    }
2976
2977    // Set the font index for the format objects. This only needs to be done for
2978    // XF formats. DXF formats are handled differently.
2979    fn prepare_fonts(&mut self) {
2980        let mut font_count: u16 = 0;
2981        let mut font_indices: HashMap<Font, u16> = HashMap::new();
2982
2983        for xf_format in &mut self.xf_formats {
2984            match font_indices.get(&xf_format.font) {
2985                Some(font_index) => {
2986                    xf_format.set_font_index(*font_index, false);
2987                }
2988                None => {
2989                    font_indices.insert(xf_format.font.clone(), font_count);
2990                    xf_format.set_font_index(font_count, true);
2991                    font_count += 1;
2992                }
2993            }
2994        }
2995        self.font_count = font_count;
2996    }
2997
2998    // Set the fill index for the format objects.
2999    fn prepare_fills(&mut self) {
3000        // The user defined fill properties start from 2 since there are 2
3001        // default fills: patternType="none" and patternType="gray125". The
3002        // following code adds these 2 default fills.
3003        let mut fill_count: u16 = 2;
3004
3005        let mut fill_indices = HashMap::from([
3006            (Fill::default(), 0),
3007            (
3008                Fill {
3009                    pattern: crate::FormatPattern::Gray125,
3010                    ..Default::default()
3011                },
3012                1,
3013            ),
3014        ]);
3015
3016        for xf_format in &mut self.xf_formats {
3017            let fill = &mut xf_format.fill;
3018            // If the user specifies a foreground or background color without a
3019            // pattern they probably wanted a solid fill, so we fill in the
3020            // defaults.
3021            if (fill.pattern == FormatPattern::None || fill.pattern == FormatPattern::Solid)
3022                && fill.background_color != Color::Default
3023                && fill.foreground_color == Color::Default
3024            {
3025                fill.foreground_color = fill.background_color;
3026                fill.background_color = Color::Default;
3027                fill.pattern = FormatPattern::Solid;
3028            }
3029
3030            if (fill.pattern == FormatPattern::None || fill.pattern == FormatPattern::Solid)
3031                && fill.background_color == Color::Default
3032                && fill.foreground_color != Color::Default
3033            {
3034                fill.background_color = Color::Default;
3035                fill.pattern = FormatPattern::Solid;
3036            }
3037
3038            // Find unique or repeated fill ids.
3039            match fill_indices.get(fill) {
3040                Some(fill_index) => {
3041                    xf_format.set_fill_index(*fill_index, false);
3042                }
3043                None => {
3044                    fill_indices.insert(fill.clone(), fill_count);
3045                    xf_format.set_fill_index(fill_count, true);
3046                    fill_count += 1;
3047                }
3048            }
3049        }
3050        self.fill_count = fill_count;
3051    }
3052
3053    // Set the border index for the format objects.
3054    fn prepare_borders(&mut self) {
3055        let mut border_count: u16 = 0;
3056        let mut border_indices: HashMap<Border, u16> = HashMap::new();
3057
3058        for xf_format in &mut self.xf_formats {
3059            match border_indices.get(&xf_format.borders) {
3060                Some(border_index) => {
3061                    xf_format.set_border_index(*border_index, false);
3062                }
3063                None => {
3064                    border_indices.insert(xf_format.borders.clone(), border_count);
3065                    xf_format.set_border_index(border_count, true);
3066                    border_count += 1;
3067                }
3068            }
3069        }
3070        self.border_count = border_count;
3071
3072        // For DXF borders we only need to check if any properties have changed.
3073        for dxf_format in &mut self.dxf_formats {
3074            dxf_format.has_border = !dxf_format.borders.is_default();
3075        }
3076    }
3077
3078    // Set the number format index for the format objects.
3079    fn prepare_num_formats(&mut self) {
3080        let mut unique_num_formats: HashMap<String, u16> = HashMap::new();
3081        // User defined number formats in Excel start from index 164.
3082        let mut index = 164;
3083        let xf_formats = [&mut self.xf_formats, &mut self.dxf_formats];
3084        self.num_formats = vec![];
3085
3086        for xf_format in xf_formats.into_iter().flatten() {
3087            if xf_format.num_format_index > 0 {
3088                continue;
3089            }
3090
3091            if xf_format.num_format.is_empty() {
3092                continue;
3093            }
3094
3095            let num_format_string = xf_format.num_format.clone();
3096
3097            match unique_num_formats.get(&num_format_string) {
3098                Some(index) => {
3099                    xf_format.set_num_format_index_u16(*index);
3100                }
3101                None => {
3102                    unique_num_formats.insert(num_format_string.clone(), index);
3103                    xf_format.set_num_format_index_u16(index);
3104                    index += 1;
3105
3106                    // Only store XF formats (not DXF formats).
3107                    if !xf_format.is_dxf_format {
3108                        self.num_formats.push(num_format_string);
3109                    }
3110                }
3111            }
3112        }
3113    }
3114
3115    // Check for any format properties that require a feature bag. Currently,
3116    // this only applies to checkboxes.
3117    fn prepare_feature_property_bags(&mut self) {
3118        for xf_format in &self.xf_formats {
3119            if xf_format.has_checkbox() {
3120                self.feature_property_bags
3121                    .insert(FeaturePropertyBagTypes::XFComplements);
3122                break;
3123            }
3124        }
3125
3126        for dxf_format in &self.dxf_formats {
3127            if dxf_format.has_checkbox() {
3128                self.feature_property_bags
3129                    .insert(FeaturePropertyBagTypes::DXFComplements);
3130                break;
3131            }
3132        }
3133    }
3134
3135    // Collect some workbook-level metadata to help generate the xlsx
3136    // package/file.
3137    fn set_package_options(
3138        &mut self,
3139        mut package_options: PackagerOptions,
3140    ) -> Result<PackagerOptions, XlsxError> {
3141        package_options.doc_security = self.read_only_mode;
3142        package_options.num_embedded_images = self.embedded_images.len() as u32;
3143
3144        let mut defined_names = self.user_defined_names.clone();
3145        let mut sheet_names: HashMap<String, u16> = HashMap::new();
3146
3147        package_options.is_xlsm_file = self.is_xlsm_file;
3148        package_options.has_vba_signature = !self.vba_signature.is_empty();
3149        package_options
3150            .feature_property_bags
3151            .clone_from(&self.feature_property_bags);
3152
3153        // Iterate over the worksheets to capture workbook and update the
3154        // package options metadata.
3155        for (sheet_index, worksheet) in self.worksheets.iter().enumerate() {
3156            let sheet_name = worksheet.name.clone();
3157            let quoted_sheet_name = utility::quote_sheet_name(&sheet_name);
3158            sheet_names.insert(sheet_name.clone(), sheet_index as u16);
3159
3160            if worksheet.is_chartsheet {
3161                package_options.num_chartsheets += 1;
3162            } else {
3163                package_options.num_worksheets += 1;
3164            }
3165
3166            if worksheet.visible == Visible::VeryHidden {
3167                package_options.worksheet_names.push(String::new());
3168            } else {
3169                package_options.worksheet_names.push(sheet_name.clone());
3170            }
3171
3172            package_options.properties = self.properties.clone();
3173
3174            package_options.has_sst_table = self.string_table.lock().unwrap().count > 0;
3175
3176            if worksheet.has_dynamic_arrays {
3177                package_options.has_metadata = true;
3178                package_options.has_dynamic_functions = true;
3179            }
3180
3181            if !worksheet.embedded_images.is_empty() {
3182                package_options.has_metadata = true;
3183                package_options.has_embedded_images = true;
3184                if worksheet.has_embedded_image_descriptions {
3185                    package_options.has_embedded_image_descriptions = true;
3186                }
3187            }
3188
3189            if worksheet.has_vml || worksheet.has_header_footer_images() {
3190                package_options.has_vml = true;
3191            }
3192
3193            if !worksheet.drawing.drawings.is_empty() {
3194                package_options.num_drawings += 1;
3195            }
3196
3197            if !worksheet.charts.is_empty() {
3198                package_options.num_charts += worksheet.charts.len() as u16;
3199            }
3200
3201            if !worksheet.tables.is_empty() {
3202                package_options.num_tables += worksheet.tables.len() as u16;
3203            }
3204
3205            if !worksheet.notes.is_empty() {
3206                package_options.num_comments += 1;
3207            }
3208
3209            // Store the autofilter areas which are a category of defined name.
3210            if worksheet.autofilter_defined_name.in_use {
3211                let mut defined_name = worksheet.autofilter_defined_name.clone();
3212                defined_name.initialize(&quoted_sheet_name);
3213                defined_names.push(defined_name);
3214            }
3215
3216            // Store any user defined print areas which are a category of defined name.
3217            if worksheet.print_area_defined_name.in_use {
3218                let mut defined_name = worksheet.print_area_defined_name.clone();
3219                defined_name.initialize(&quoted_sheet_name);
3220                defined_names.push(defined_name);
3221            }
3222
3223            // Store any user defined print repeat rows/columns which are a
3224            // category of defined name.
3225            if worksheet.repeat_row_cols_defined_name.in_use {
3226                let mut defined_name = worksheet.repeat_row_cols_defined_name.clone();
3227                defined_name.initialize(&quoted_sheet_name);
3228                defined_names.push(defined_name);
3229            }
3230
3231            // Set the used image types.
3232            for i in 0..NUM_IMAGE_FORMATS {
3233                if worksheet.image_types[i] {
3234                    package_options.image_types[i] = true;
3235                }
3236            }
3237        }
3238
3239        // Map the sheet name and associated index so that we can map a sheet
3240        // reference in a Local/Sheet defined name to a worksheet index.
3241        for defined_name in &mut defined_names {
3242            let sheet_name = utility::unquote_sheetname(&defined_name.quoted_sheet_name);
3243
3244            if !sheet_name.is_empty() {
3245                match sheet_names.get(&sheet_name) {
3246                    Some(index) => defined_name.index = *index,
3247                    None => {
3248                        let error = format!(
3249                            "Unknown worksheet name '{}' in defined name '{}'",
3250                            sheet_name, defined_name.name
3251                        );
3252                        return Err(XlsxError::ParameterError(error));
3253                    }
3254                }
3255            }
3256        }
3257
3258        // Excel stores defined names in a sorted order.
3259        defined_names.sort_by_key(|n| (n.sort_name.clone(), n.range.clone()));
3260
3261        // Map the non-Global defined names to App.xml entries.
3262        for defined_name in &defined_names {
3263            let app_name = defined_name.app_name();
3264            if !app_name.is_empty() {
3265                package_options.defined_names.push(app_name);
3266            }
3267        }
3268
3269        self.defined_names = defined_names;
3270
3271        Ok(package_options)
3272    }
3273
3274    // -----------------------------------------------------------------------
3275    // XML assembly methods.
3276    // -----------------------------------------------------------------------
3277
3278    // Assemble and generate the XML file.
3279    pub(crate) fn assemble_xml_file(&mut self) {
3280        xml_declaration(&mut self.writer);
3281
3282        // Write the workbook element.
3283        self.write_workbook();
3284
3285        // Write the fileVersion element.
3286        self.write_file_version();
3287
3288        // Write the fileSharing element.
3289        if self.read_only_mode == 2 {
3290            self.write_file_sharing();
3291        }
3292
3293        // Write the workbookPr element.
3294        self.write_workbook_pr();
3295
3296        // Write the bookViews element.
3297        self.write_book_views();
3298
3299        // Write the sheets element.
3300        self.write_sheets();
3301
3302        // Write the definedNames element.
3303        if !self.defined_names.is_empty() {
3304            self.write_defined_names();
3305        }
3306
3307        // Write the calcPr element.
3308        self.write_calc_pr();
3309
3310        // Close the workbook tag.
3311        xml_end_tag(&mut self.writer, "workbook");
3312    }
3313
3314    // Write the <workbook> element.
3315    fn write_workbook(&mut self) {
3316        let xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
3317        let xmlns_r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
3318
3319        let attributes = [("xmlns", xmlns), ("xmlns:r", xmlns_r)];
3320
3321        xml_start_tag(&mut self.writer, "workbook", &attributes);
3322    }
3323
3324    // Write the <fileVersion> element.
3325    fn write_file_version(&mut self) {
3326        let mut attributes = vec![
3327            ("appName", "xl"),
3328            ("lastEdited", "4"),
3329            ("lowestEdited", "4"),
3330            ("rupBuild", "4505"),
3331        ];
3332
3333        if self.is_xlsm_file {
3334            attributes.push(("codeName", "{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}"));
3335        }
3336
3337        xml_empty_tag(&mut self.writer, "fileVersion", &attributes);
3338    }
3339
3340    // Write the <fileSharing> element.
3341    fn write_file_sharing(&mut self) {
3342        let attributes = [("readOnlyRecommended", "1")];
3343
3344        xml_empty_tag(&mut self.writer, "fileSharing", &attributes);
3345    }
3346
3347    // Write the <workbookPr> element.
3348    fn write_workbook_pr(&mut self) {
3349        let mut attributes = vec![];
3350
3351        if let Some(codename) = &self.vba_codename {
3352            attributes.push(("codeName", codename.clone()));
3353        }
3354
3355        if !self.default_theme_version.is_empty() {
3356            attributes.push(("defaultThemeVersion", self.default_theme_version.clone()));
3357        }
3358
3359        xml_empty_tag(&mut self.writer, "workbookPr", &attributes);
3360    }
3361
3362    // Write the <bookViews> element.
3363    fn write_book_views(&mut self) {
3364        xml_start_tag_only(&mut self.writer, "bookViews");
3365
3366        // Write the workbookView element.
3367        self.write_workbook_view();
3368
3369        xml_end_tag(&mut self.writer, "bookViews");
3370    }
3371
3372    // Write the <workbookView> element.
3373    fn write_workbook_view(&mut self) {
3374        let mut attributes = vec![
3375            ("xWindow", "240".to_string()),
3376            ("yWindow", "15".to_string()),
3377            ("windowWidth", "16095".to_string()),
3378            ("windowHeight", "9660".to_string()),
3379        ];
3380
3381        // Store the firstSheet attribute when it isn't the first sheet.
3382        if self.first_sheet > 0 {
3383            let first_sheet = self.first_sheet + 1;
3384            attributes.push(("firstSheet", first_sheet.to_string()));
3385        }
3386
3387        // Store the activeTab attribute when it isn't the first sheet.
3388        if self.active_tab > 0 {
3389            attributes.push(("activeTab", self.active_tab.to_string()));
3390        }
3391
3392        xml_empty_tag(&mut self.writer, "workbookView", &attributes);
3393    }
3394
3395    // Write the <sheets> element.
3396    fn write_sheets(&mut self) {
3397        xml_start_tag_only(&mut self.writer, "sheets");
3398
3399        let mut worksheet_data = vec![];
3400        for worksheet in &self.worksheets {
3401            worksheet_data.push((worksheet.name.clone(), worksheet.visible));
3402        }
3403
3404        for (index, data) in worksheet_data.iter().enumerate() {
3405            // Write the sheet element.
3406            self.write_sheet(&data.0, data.1, (index + 1) as u16);
3407        }
3408
3409        xml_end_tag(&mut self.writer, "sheets");
3410    }
3411
3412    // Write the <sheet> element.
3413    fn write_sheet(&mut self, name: &str, visible: Visible, index: u16) {
3414        let sheet_id = format!("{index}");
3415        let rel_id = format!("rId{index}");
3416
3417        let mut attributes = vec![("name", name.to_string()), ("sheetId", sheet_id)];
3418
3419        match visible {
3420            Visible::Default => {}
3421            Visible::Hidden => attributes.push(("state", "hidden".to_string())),
3422            Visible::VeryHidden => attributes.push(("state", "veryHidden".to_string())),
3423        }
3424
3425        attributes.push(("r:id", rel_id));
3426
3427        xml_empty_tag(&mut self.writer, "sheet", &attributes);
3428    }
3429
3430    // Write the <definedNames> element.
3431    fn write_defined_names(&mut self) {
3432        xml_start_tag_only(&mut self.writer, "definedNames");
3433
3434        for defined_name in &self.defined_names {
3435            let mut attributes = vec![("name", defined_name.name())];
3436
3437            match defined_name.name_type {
3438                DefinedNameType::Global => {}
3439                _ => {
3440                    attributes.push(("localSheetId", defined_name.index.to_string()));
3441                }
3442            }
3443
3444            if let DefinedNameType::Autofilter = defined_name.name_type {
3445                attributes.push(("hidden", "1".to_string()));
3446            }
3447
3448            xml_data_element(
3449                &mut self.writer,
3450                "definedName",
3451                &defined_name.range,
3452                &attributes,
3453            );
3454        }
3455
3456        xml_end_tag(&mut self.writer, "definedNames");
3457    }
3458
3459    // Write the <calcPr> element.
3460    fn write_calc_pr(&mut self) {
3461        let attributes = [("calcId", "124519"), ("fullCalcOnLoad", "1")];
3462
3463        xml_empty_tag(&mut self.writer, "calcPr", &attributes);
3464    }
3465}