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("ed_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("ed_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("ed_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}