#![warn(missing_docs)]
mod tests;
use std::collections::{HashMap, HashSet};
use std::fs::{read_to_string, File};
use std::io::{BufReader, Cursor, Read, Write};
use std::path::Path;
use std::sync::{Arc, Mutex, RwLock};
use zip::ZipArchive;
#[cfg(feature = "constant_memory")]
use tempfile::tempfile_in;
#[cfg(feature = "constant_memory")]
use std::io::BufWriter;
#[cfg(feature = "constant_memory")]
use std::path::PathBuf;
use crate::error::XlsxError;
use crate::feature_property_bag::FeaturePropertyBagTypes;
use crate::format::Format;
use crate::packager::Packager;
use crate::packager::PackagerOptions;
use crate::shared_strings_table::SharedStringsTable;
use crate::theme::{THEME_XML_2007, THEME_XML_2023};
use crate::worksheet::Worksheet;
use crate::{
utility, Border, Chart, ChartRange, ChartRangeCacheData, ColNum, Color, DefinedName,
DefinedNameType, DocProperties, Fill, Font, FontScheme, FormatPattern, Image, RowNum, Visible,
NUM_IMAGE_FORMATS, UNPARSED_SHEET_RANGE,
};
use crate::xmlwriter::{
self, xml_data_element, xml_declaration, xml_empty_tag, xml_end_tag, xml_start_tag,
xml_start_tag_only,
};
pub struct Workbook {
pub(crate) writer: Cursor<Vec<u8>>,
pub(crate) properties: DocProperties,
pub(crate) worksheets: Vec<Worksheet>,
pub(crate) xf_formats: Vec<Format>,
pub(crate) dxf_formats: Vec<Format>,
pub(crate) font_count: u16,
pub(crate) fill_count: u16,
pub(crate) border_count: u16,
pub(crate) num_formats: Vec<String>,
pub(crate) has_hyperlink_style: bool,
pub(crate) embedded_images: Vec<Image>,
pub(crate) vba_project: Vec<u8>,
pub(crate) vba_signature: Vec<u8>,
pub(crate) vba_codename: Option<String>,
pub(crate) is_xlsm_file: bool,
pub(crate) has_comments: bool,
pub(crate) string_table: Arc<Mutex<SharedStringsTable>>,
pub(crate) feature_property_bags: HashSet<FeaturePropertyBagTypes>,
pub(crate) theme_xml: String,
xf_indices: Arc<RwLock<HashMap<Format, u32>>>,
dxf_indices: HashMap<Format, u32>,
active_tab: u16,
first_sheet: u16,
defined_names: Vec<DefinedName>,
user_defined_names: Vec<DefinedName>,
read_only_mode: u8,
num_worksheets: u16,
num_chartsheets: u16,
use_large_file: bool,
default_format: Format,
default_row_height: u32,
default_col_width: u32,
max_digit_width: u32,
max_col_width: u32,
cell_padding: u32,
default_theme_version: String,
#[cfg(feature = "constant_memory")]
tempdir: Option<PathBuf>,
}
impl Default for Workbook {
fn default() -> Self {
Self::new()
}
}
impl Workbook {
pub fn new() -> Workbook {
let writer = Cursor::new(Vec::with_capacity(2048));
Workbook {
writer,
properties: DocProperties::new(),
font_count: 0,
active_tab: 0,
fill_count: 0,
first_sheet: 0,
border_count: 0,
num_formats: vec![],
read_only_mode: 0,
has_hyperlink_style: false,
worksheets: vec![],
xf_formats: vec![],
dxf_formats: vec![],
defined_names: vec![],
user_defined_names: vec![],
xf_indices: Arc::new(RwLock::new(HashMap::from([(Format::default(), 0)]))),
string_table: Arc::new(Mutex::new(SharedStringsTable::new())),
dxf_indices: HashMap::new(),
embedded_images: vec![],
is_xlsm_file: false,
vba_project: vec![],
vba_signature: vec![],
vba_codename: None,
has_comments: false,
num_worksheets: 0,
num_chartsheets: 0,
use_large_file: false,
feature_property_bags: HashSet::new(),
default_format: Format::default(),
default_row_height: 20,
default_col_width: 64,
cell_padding: 5,
max_digit_width: 7,
max_col_width: 1790,
theme_xml: String::from(THEME_XML_2007),
default_theme_version: String::from("124226"),
#[cfg(feature = "constant_memory")]
tempdir: None,
}
}
pub fn add_worksheet(&mut self) -> &mut Worksheet {
let name = format!("Sheet{}", self.num_worksheets + 1);
self.num_worksheets += 1;
let mut worksheet = Worksheet::new();
worksheet.set_name(&name).unwrap();
self.initialize_default_format(&mut worksheet);
self.worksheets.push(worksheet);
let worksheet = self.worksheets.last_mut().unwrap();
worksheet
}
#[cfg(feature = "constant_memory")]
#[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
pub fn add_worksheet_with_constant_memory(&mut self) -> &mut Worksheet {
let name = format!("Sheet{}", self.num_worksheets + 1);
self.num_worksheets += 1;
let mut worksheet = Worksheet::new();
worksheet.set_name(&name).unwrap();
self.initialize_default_format(&mut worksheet);
if let Some(tempdir) = &self.tempdir {
worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
}
worksheet.use_inline_strings = true;
worksheet.use_constant_memory = true;
worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
worksheet.has_workbook_global_xfs = true;
self.worksheets.push(worksheet);
let worksheet = self.worksheets.last_mut().unwrap();
worksheet
}
#[cfg(feature = "constant_memory")]
#[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
pub fn add_worksheet_with_low_memory(&mut self) -> &mut Worksheet {
let name = format!("Sheet{}", self.num_worksheets + 1);
self.num_worksheets += 1;
let mut worksheet = Worksheet::new();
worksheet.set_name(&name).unwrap();
self.initialize_default_format(&mut worksheet);
if let Some(tempdir) = &self.tempdir {
worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
}
worksheet.use_inline_strings = false;
worksheet.use_constant_memory = true;
worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
worksheet.has_workbook_global_xfs = true;
worksheet.string_table = Arc::clone(&self.string_table);
worksheet.has_workbook_global_sst = true;
self.worksheets.push(worksheet);
let worksheet = self.worksheets.last_mut().unwrap();
worksheet
}
#[cfg(feature = "constant_memory")]
#[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
pub fn new_worksheet_with_constant_memory(&mut self) -> Worksheet {
let mut worksheet = Worksheet::new();
if let Some(tempdir) = &self.tempdir {
worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
}
worksheet.use_inline_strings = true;
worksheet.use_constant_memory = true;
worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
worksheet.has_workbook_global_xfs = true;
worksheet
}
#[cfg(feature = "constant_memory")]
#[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
pub fn new_worksheet_with_low_memory(&mut self) -> Worksheet {
let mut worksheet = Worksheet::new();
if let Some(tempdir) = &self.tempdir {
worksheet.file_writer = BufWriter::new(tempfile_in(tempdir).unwrap());
}
worksheet.use_inline_strings = false;
worksheet.use_constant_memory = true;
worksheet.workbook_xf_indices = Arc::clone(&self.xf_indices);
worksheet.has_workbook_global_xfs = true;
worksheet.string_table = Arc::clone(&self.string_table);
worksheet.has_workbook_global_sst = true;
worksheet
}
#[cfg(feature = "constant_memory")]
#[cfg_attr(docsrs, doc(cfg(feature = "constant_memory")))]
pub fn set_tempdir<P: AsRef<Path>>(&mut self, dir: P) -> Result<&mut Workbook, XlsxError> {
tempfile_in(&dir)?;
self.tempdir = Some(dir.as_ref().to_path_buf());
Ok(self)
}
pub fn add_chartsheet(&mut self) -> &mut Worksheet {
let name = format!("Chart{}", self.num_chartsheets + 1);
self.num_chartsheets += 1;
let mut worksheet = Worksheet::new_chartsheet();
worksheet.set_name(&name).unwrap();
self.worksheets.push(worksheet);
let worksheet = self.worksheets.last_mut().unwrap();
worksheet
}
pub fn worksheet_from_index(&mut self, index: usize) -> Result<&mut Worksheet, XlsxError> {
match self.worksheets.get_mut(index) {
Some(worksheet) => Ok(worksheet),
_ => Err(XlsxError::UnknownWorksheetNameOrIndex(index.to_string())),
}
}
pub fn worksheet_from_name(&mut self, sheetname: &str) -> Result<&mut Worksheet, XlsxError> {
for (index, worksheet) in self.worksheets.iter_mut().enumerate() {
if sheetname == worksheet.name {
return self.worksheet_from_index(index);
}
}
Err(XlsxError::UnknownWorksheetNameOrIndex(
sheetname.to_string(),
))
}
pub fn worksheets_mut(&mut self) -> &mut Vec<Worksheet> {
&mut self.worksheets
}
pub fn worksheets(&mut self) -> &Vec<Worksheet> {
&self.worksheets
}
pub fn push_worksheet(&mut self, mut worksheet: Worksheet) {
if worksheet.name().is_empty() {
let name = format!("Sheet{}", self.worksheets.len() + 1);
worksheet.set_name(&name).unwrap();
}
self.worksheets.push(worksheet);
}
pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(), XlsxError> {
#[cfg(feature = "test-resave")]
{
let file = std::fs::File::create(<&std::path::Path>::clone(&path.as_ref()))?;
self.save_internal(file)?;
}
let file = std::fs::File::create(path)?;
self.save_internal(file)?;
Ok(())
}
pub fn save_to_buffer(&mut self) -> Result<Vec<u8>, XlsxError> {
let mut buf = vec![];
let cursor = Cursor::new(&mut buf);
self.save_internal(cursor)?;
Ok(buf)
}
pub fn save_to_writer<W>(&mut self, writer: W) -> Result<(), XlsxError>
where
W: Write + Send,
{
self.save_internal(writer)?;
Ok(())
}
pub fn define_name(
&mut self,
name: impl Into<String>,
formula: &str,
) -> Result<&mut Workbook, XlsxError> {
let mut defined_name = DefinedName::new();
let name = name.into();
match name.find('!') {
Some(position) => {
defined_name.quoted_sheet_name = name[0..position].to_string();
defined_name.name = name[position + 1..].to_string();
defined_name.name_type = DefinedNameType::Local;
}
None => {
defined_name.name.clone_from(&name);
defined_name.name_type = DefinedNameType::Global;
}
}
if !defined_name.name.chars().next().unwrap().is_alphabetic()
&& !defined_name.name.starts_with('_')
&& !defined_name.name.starts_with('\\')
{
let error = format!(
"Name '{}' must start with a letter or underscore in Excel",
defined_name.name
);
return Err(XlsxError::ParameterError(error));
}
if defined_name
.name
.contains([' ', ',', '/', '*', '[', ']', ':', '"', '\''])
{
let error = format!(
"Name '{}' cannot contain any of the characters `,/*[]:\"'` or `space` in Excel",
defined_name.name
);
return Err(XlsxError::ParameterError(error));
}
defined_name.range = utility::formula_to_string(formula);
defined_name.set_sort_name();
self.user_defined_names.push(defined_name);
Ok(self)
}
pub fn set_properties(&mut self, properties: &DocProperties) -> &mut Workbook {
self.properties = properties.clone();
self
}
pub fn set_default_format(
&mut self,
format: &Format,
row_height: u32,
col_width: u32,
) -> Result<&mut Workbook, XlsxError> {
if !self.worksheets.is_empty() {
return Err(XlsxError::DefaultFormatError(
"Default format must be set before adding worksheets.".to_string(),
));
}
let Some((max_digit_width, padding, max_col_width)) =
utility::default_column_metrics(col_width)
else {
return Err(XlsxError::DefaultFormatError(format!(
"Unsupported default column width: {col_width}"
)));
};
self.xf_indices = Arc::new(RwLock::new(HashMap::from([(format.clone(), 0)])));
self.default_format = format.clone();
self.cell_padding = padding;
self.max_col_width = max_col_width;
self.max_digit_width = max_digit_width;
self.default_col_width = col_width;
self.default_row_height = row_height;
Ok(self)
}
pub fn use_excel_2023_theme(&mut self) -> Result<&mut Workbook, XlsxError> {
self.theme_xml = THEME_XML_2023.to_string();
self.default_theme_version = String::from("202300");
let format = Format::new()
.set_font_name("Aptos Narrow")
.set_font_size(11)
.set_font_scheme(FontScheme::Body);
self.set_default_format(&format, 20, 64)
}
pub fn use_custom_theme<P: AsRef<Path>>(
&mut self,
path: P,
) -> Result<&mut Workbook, XlsxError> {
let theme_xml = Self::read_theme_from_path(&path)?;
if !theme_xml.starts_with("<?xml") {
return Err(XlsxError::ThemeError(format!(
"Invalid XML theme file: '{}'",
path.as_ref().display()
)));
}
if theme_xml.contains("<a:blipFill>") {
return Err(XlsxError::ThemeError(format!(
"Theme file contains image fills which aren't currently supported: '{}'",
path.as_ref().display()
)));
}
self.theme_xml = theme_xml;
self.default_theme_version = String::new();
Ok(self)
}
pub fn add_vba_project<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Workbook, XlsxError> {
let path_buf = path.as_ref().to_path_buf();
let file = File::open(path_buf)?;
let mut reader = BufReader::new(file);
let mut data = vec![];
reader.read_to_end(&mut data)?;
self.vba_project = data;
self.is_xlsm_file = true;
if self.vba_codename.is_none() {
self.vba_codename = Some("ThisWorkbook".to_string());
}
Ok(self)
}
pub fn use_zip_large_file(&mut self, enable: bool) -> &mut Workbook {
self.use_large_file = enable;
self
}
pub fn add_vba_project_with_signature<P: AsRef<Path>>(
&mut self,
project: P,
signature: P,
) -> Result<&mut Workbook, XlsxError> {
self.add_vba_project(project)?;
let path_buf = signature.as_ref().to_path_buf();
let file = File::open(path_buf)?;
let mut reader = BufReader::new(file);
let mut data = vec![];
reader.read_to_end(&mut data)?;
self.vba_signature = data;
Ok(self)
}
pub fn set_vba_name(&mut self, name: impl Into<String>) -> Result<&mut Workbook, XlsxError> {
let name = name.into();
utility::validate_vba_name(&name)?;
self.vba_codename = Some(name);
Ok(self)
}
pub fn read_only_recommended(&mut self) -> &mut Workbook {
self.read_only_mode = 2;
self
}
#[doc(hidden)]
pub fn register_format(&mut self, format: &Format) {
self.format_xf_index(format);
}
#[doc(hidden)]
pub fn populate_string_table(&mut self, string: &str, index: u32) {
let mut string_table = self.string_table.lock().unwrap();
string_table.populate_string_index(Arc::from(string), index);
}
fn reset(&mut self) {
xmlwriter::reset(&mut self.writer);
for worksheet in &mut self.worksheets {
worksheet.reset();
}
}
fn initialize_default_format(&mut self, worksheet: &mut Worksheet) {
worksheet.initialize_default_format(
&self.default_format,
self.default_row_height,
self.default_col_width,
self.max_digit_width,
self.cell_padding,
self.max_col_width,
);
}
fn read_theme_from_path<P: AsRef<Path>>(path: P) -> Result<String, XlsxError> {
let file = File::open(&path)?;
let reader = BufReader::new(file);
if let Ok(mut archive) = ZipArchive::new(reader) {
let possible_paths = [
"theme/theme/theme1.xml",
"xl/theme/theme1.xml",
];
for theme_path in &possible_paths {
if let Ok(mut theme_file) = archive.by_name(theme_path) {
let mut theme_xml = String::new();
theme_file.read_to_string(&mut theme_xml)?;
return Ok(theme_xml);
}
}
return Err(XlsxError::ThemeError(format!(
"No theme1.xml found in file: '{}'",
path.as_ref().display()
)));
}
read_to_string(&path).map_err(XlsxError::IoError)
}
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
fn save_internal<W: Write + Send>(&mut self, writer: W) -> Result<(), XlsxError> {
self.reset();
if self.worksheets.is_empty() {
self.add_worksheet();
}
self.set_active_worksheets();
for worksheet in &self.worksheets {
if worksheet.has_hyperlink_style {
let format = self.default_format.clone().set_hyperlink();
self.format_xf_index(&format);
self.has_hyperlink_style = true;
break;
}
}
let xf_indices = self.xf_indices.read().expect("RwLock poisoned");
for format in xf_indices.keys() {
if format.font.is_hyperlink {
self.has_hyperlink_style = true;
break;
}
}
drop(xf_indices);
let mut unique_worksheet_names = HashSet::new();
for worksheet in &self.worksheets {
let worksheet_name = worksheet.name.to_lowercase();
if unique_worksheet_names.contains(&worksheet_name) {
return Err(XlsxError::SheetnameReused(worksheet_name));
}
unique_worksheet_names.insert(worksheet_name);
}
for worksheet in &self.worksheets {
if worksheet.is_chartsheet && worksheet.charts.is_empty() {
return Err(XlsxError::ChartError(format!(
"Chartsheet '{}' doesn't contain a chart",
worksheet.name
)));
}
}
#[cfg(feature = "serde")]
for worksheet in &mut self.worksheets {
worksheet.store_serialized_tables()?;
}
let mut worksheet_xf_formats: Vec<Vec<Format>> = vec![];
let mut worksheet_dxf_formats: Vec<Vec<Format>> = vec![];
for worksheet in &self.worksheets {
let formats = worksheet.xf_formats.clone();
worksheet_xf_formats.push(formats);
let formats = worksheet.dxf_formats.clone();
worksheet_dxf_formats.push(formats);
}
let mut worksheet_xf_indices: Vec<Vec<u32>> = vec![];
for formats in &worksheet_xf_formats {
let mut indices = vec![];
for format in formats {
let index = self.format_xf_index(format);
indices.push(index);
}
worksheet_xf_indices.push(indices);
}
let mut worksheet_dxf_indices: Vec<Vec<u32>> = vec![];
for formats in &worksheet_dxf_formats {
let mut indices = vec![];
for format in formats {
let index = self.format_dxf_index(format);
indices.push(index);
}
worksheet_dxf_indices.push(indices);
}
let xf_indices = self.xf_indices.read().expect("RwLock poisoned");
let mut xf_indices_vec: Vec<(&Format, &u32)> = xf_indices.iter().collect();
xf_indices_vec.sort_by(|a, b| a.1.cmp(b.1));
let xf_formats: Vec<Format> = xf_indices_vec.iter().map(|x| x.0.clone()).collect();
self.xf_formats = xf_formats;
drop(xf_indices);
for (i, worksheet) in self.worksheets.iter_mut().enumerate() {
worksheet.set_global_xf_indices(&worksheet_xf_indices[i]);
worksheet.set_global_dxf_indices(&worksheet_dxf_indices[i]);
worksheet.hide_autofilter_rows();
worksheet.sheet_index = i;
if self.is_xlsm_file {
let codename = format!("Sheet{}", i + 1);
if worksheet.vba_codename.is_none() {
worksheet.vba_codename = Some(codename);
}
}
if worksheet.is_chartsheet {
worksheet.use_constant_memory = false;
}
}
self.prepare_embedded_images();
self.prepare_drawings();
self.prepare_vml();
self.prepare_chart_cache_data()?;
self.prepare_format_properties();
self.prepare_tables()?;
for worksheet in &mut self.worksheets {
if !worksheet.has_workbook_global_sst {
let string_table = self.string_table.clone();
worksheet.update_string_table_ids(string_table);
}
}
let mut package_options = PackagerOptions::new();
package_options = self.set_package_options(package_options)?;
let packager = Packager::new(writer, self.use_large_file);
packager.assemble_file(self, &package_options)?;
Ok(())
}
fn set_active_worksheets(&mut self) {
let mut active_index = 0;
for (i, worksheet) in self.worksheets.iter().enumerate() {
if worksheet.active {
active_index = i;
}
if worksheet.first_sheet {
self.first_sheet = i as u16;
}
}
self.worksheets[active_index].set_active(true);
self.active_tab = active_index as u16;
}
fn prepare_vml(&mut self) {
let mut comment_id = 1;
let mut vml_drawing_id = 1;
let mut vml_data_id = 1;
let mut vml_shape_id = 1024;
for worksheet in &mut self.worksheets {
if worksheet.has_vml {
let note_count = worksheet.prepare_vml_objects(vml_data_id, vml_shape_id);
worksheet.add_vml_drawing_rel_link(vml_drawing_id);
vml_drawing_id += 1;
if !worksheet.notes.is_empty() {
worksheet.add_comment_rel_link(comment_id);
comment_id += 1;
self.has_comments = true;
}
vml_data_id += (1024 + note_count) / 1024;
vml_shape_id += 1024 * ((1024 + note_count) / 1024);
}
if worksheet.has_header_footer_images() {
worksheet.add_vml_drawing_rel_link(vml_drawing_id);
vml_drawing_id += 1;
}
}
}
fn prepare_embedded_images(&mut self) {
let mut embedded_images = vec![];
let mut image_ids: HashMap<String, u32> = HashMap::new();
let mut global_image_id = 0;
for worksheet in &mut self.worksheets {
if worksheet.embedded_images.is_empty() {
continue;
}
let mut global_embedded_image_ids = vec![];
for image in &worksheet.embedded_images {
let image_id = match image_ids.get(&image.hash) {
Some(image_id) => *image_id,
None => {
global_image_id += 1;
embedded_images.push(image.clone());
image_ids.insert(image.hash.clone(), global_image_id);
global_image_id
}
};
global_embedded_image_ids.push(image_id);
}
worksheet.global_embedded_image_indices = global_embedded_image_ids;
}
self.embedded_images = embedded_images;
}
fn prepare_drawings(&mut self) {
let mut chart_id = 1;
let mut drawing_id = 1;
let mut shape_id = 1;
let mut image_id = self.embedded_images.len() as u32;
let mut worksheet_image_ids: HashMap<String, u32> = HashMap::new();
let mut header_footer_image_ids: HashMap<String, u32> = HashMap::new();
let mut background_image_ids: HashMap<String, u32> = HashMap::new();
for worksheet in &mut self.worksheets {
if let Some(image) = &worksheet.background_image {
let image = image.clone();
let background_image_id = match background_image_ids.get(&image.hash) {
Some(image_id) => *image_id,
None => {
image_id += 1;
background_image_ids.insert(image.hash.clone(), image_id);
image_id
}
};
worksheet.prepare_background_image(background_image_id, &image);
}
if !worksheet.images.is_empty() {
worksheet.prepare_worksheet_images(
&mut worksheet_image_ids,
&mut image_id,
drawing_id,
);
}
if !worksheet.charts.is_empty() {
worksheet.prepare_worksheet_charts(chart_id, drawing_id);
chart_id += worksheet.charts.len() as u32;
}
if !worksheet.shapes.is_empty() {
worksheet.prepare_worksheet_shapes(shape_id, drawing_id);
shape_id += worksheet.shapes.len() as u32;
}
if !worksheet.images.is_empty()
|| !worksheet.charts.is_empty()
|| !worksheet.shapes.is_empty()
{
drawing_id += 1;
}
if worksheet.has_header_footer_images() {
worksheet.prepare_header_footer_images(&mut header_footer_image_ids, &mut image_id);
}
}
}
fn prepare_tables(&mut self) -> Result<(), XlsxError> {
let mut table_id = 1;
let mut seen_table_names = HashSet::new();
for worksheet in &mut self.worksheets {
if !worksheet.tables.is_empty() {
table_id = worksheet.prepare_worksheet_tables(table_id);
}
}
for worksheet in &self.worksheets {
for table in &worksheet.tables {
if seen_table_names.contains(&table.name.to_lowercase()) {
return Err(XlsxError::TableNameReused(table.name.clone()));
}
seen_table_names.insert(table.name.to_lowercase());
}
}
Ok(())
}
fn prepare_chart_cache_data(&mut self) -> Result<(), XlsxError> {
let mut chart_caches: HashMap<
(String, RowNum, ColNum, RowNum, ColNum),
ChartRangeCacheData,
> = HashMap::new();
for worksheet in &self.worksheets {
if !worksheet.charts.is_empty() {
for chart in worksheet.charts.values() {
Self::insert_chart_ranges_to_cache(chart, &mut chart_caches);
if let Some(chart) = &chart.combined_chart {
Self::insert_chart_ranges_to_cache(chart, &mut chart_caches);
}
}
}
}
for (key, cache) in &mut chart_caches {
if let Ok(worksheet) = self.worksheet_from_name(&key.0) {
*cache = worksheet.get_cache_data(key.1, key.2, key.3, key.4);
} else {
let sheet_name = key.0.clone();
if sheet_name != UNPARSED_SHEET_RANGE {
let range = utility::chart_range_abs(&key.0, key.1, key.2, key.3, key.4);
let error =
format!("Unknown worksheet name '{sheet_name}' in chart range '{range}'");
return Err(XlsxError::UnknownWorksheetNameOrIndex(error));
}
}
}
for worksheet in &mut self.worksheets {
if !worksheet.charts.is_empty() {
for chart in worksheet.charts.values_mut() {
Self::update_chart_ranges_from_cache(chart, &mut chart_caches);
if let Some(chart) = &mut chart.combined_chart {
Self::update_chart_ranges_from_cache(chart, &mut chart_caches);
}
}
}
}
Ok(())
}
fn insert_chart_ranges_to_cache(
chart: &Chart,
chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
) {
Self::insert_to_chart_cache(&chart.title.range, chart_caches);
Self::insert_to_chart_cache(&chart.x_axis.title.range, chart_caches);
Self::insert_to_chart_cache(&chart.y_axis.title.range, chart_caches);
for series in &chart.series {
Self::insert_to_chart_cache(&series.title.range, chart_caches);
Self::insert_to_chart_cache(&series.value_range, chart_caches);
Self::insert_to_chart_cache(&series.category_range, chart_caches);
for data_label in &series.custom_data_labels {
Self::insert_to_chart_cache(&data_label.title.range, chart_caches);
}
if let Some(error_bars) = &series.y_error_bars {
Self::insert_to_chart_cache(&error_bars.plus_range, chart_caches);
Self::insert_to_chart_cache(&error_bars.minus_range, chart_caches);
}
if let Some(error_bars) = &series.x_error_bars {
Self::insert_to_chart_cache(&error_bars.plus_range, chart_caches);
Self::insert_to_chart_cache(&error_bars.minus_range, chart_caches);
}
}
}
fn update_chart_ranges_from_cache(
chart: &mut Chart,
chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
) {
Self::update_range_cache(&mut chart.title.range, chart_caches);
Self::update_range_cache(&mut chart.x_axis.title.range, chart_caches);
Self::update_range_cache(&mut chart.y_axis.title.range, chart_caches);
for series in &mut chart.series {
Self::update_range_cache(&mut series.title.range, chart_caches);
Self::update_range_cache(&mut series.value_range, chart_caches);
Self::update_range_cache(&mut series.category_range, chart_caches);
for data_label in &mut series.custom_data_labels {
if let Some(cache) = chart_caches.get(&data_label.title.range.key()) {
data_label.title.range.cache = cache.clone();
}
}
if let Some(error_bars) = &mut series.y_error_bars {
Self::update_range_cache(&mut error_bars.plus_range, chart_caches);
Self::update_range_cache(&mut error_bars.minus_range, chart_caches);
}
if let Some(error_bars) = &mut series.x_error_bars {
Self::update_range_cache(&mut error_bars.plus_range, chart_caches);
Self::update_range_cache(&mut error_bars.minus_range, chart_caches);
}
}
}
fn insert_to_chart_cache(
range: &ChartRange,
chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
) {
if range.has_data() {
chart_caches.insert(range.key(), ChartRangeCacheData::new());
}
}
fn update_range_cache(
range: &mut ChartRange,
chart_caches: &mut HashMap<(String, RowNum, ColNum, RowNum, ColNum), ChartRangeCacheData>,
) {
if let Some(cache) = chart_caches.get(&range.key()) {
range.cache = cache.clone();
}
}
fn format_xf_index(&mut self, format: &Format) -> u32 {
let xf_indices = self.xf_indices.read().expect("RwLock poisoned");
if let Some(xf_index) = xf_indices.get(format) {
return *xf_index;
}
drop(xf_indices);
let mut xf_indices = self.xf_indices.write().expect("RwLock poisoned");
let xf_index = xf_indices.len() as u32;
xf_indices.insert(format.clone(), xf_index);
xf_index
}
fn format_dxf_index(&mut self, format: &Format) -> u32 {
match self.dxf_indices.get_mut(format) {
Some(dxf_index) => *dxf_index,
None => {
let dxf_index = self.dxf_formats.len() as u32;
self.dxf_formats.push(format.clone());
self.dxf_indices.insert(format.clone(), dxf_index);
dxf_index
}
}
}
fn prepare_format_properties(&mut self) {
self.prepare_fonts();
self.prepare_fills();
self.prepare_borders();
self.prepare_num_formats();
self.prepare_feature_property_bags();
}
fn prepare_fonts(&mut self) {
let mut font_count: u16 = 0;
let mut font_indices: HashMap<Font, u16> = HashMap::new();
for xf_format in &mut self.xf_formats {
match font_indices.get(&xf_format.font) {
Some(font_index) => {
xf_format.set_font_index(*font_index, false);
}
None => {
font_indices.insert(xf_format.font.clone(), font_count);
xf_format.set_font_index(font_count, true);
font_count += 1;
}
}
}
self.font_count = font_count;
}
fn prepare_fills(&mut self) {
let mut fill_count: u16 = 2;
let mut fill_indices = HashMap::from([
(Fill::default(), 0),
(
Fill {
pattern: crate::FormatPattern::Gray125,
..Default::default()
},
1,
),
]);
for xf_format in &mut self.xf_formats {
let fill = &mut xf_format.fill;
if (fill.pattern == FormatPattern::None || fill.pattern == FormatPattern::Solid)
&& fill.background_color != Color::Default
&& fill.foreground_color == Color::Default
{
fill.foreground_color = fill.background_color;
fill.background_color = Color::Default;
fill.pattern = FormatPattern::Solid;
}
if (fill.pattern == FormatPattern::None || fill.pattern == FormatPattern::Solid)
&& fill.background_color == Color::Default
&& fill.foreground_color != Color::Default
{
fill.background_color = Color::Default;
fill.pattern = FormatPattern::Solid;
}
match fill_indices.get(fill) {
Some(fill_index) => {
xf_format.set_fill_index(*fill_index, false);
}
None => {
fill_indices.insert(fill.clone(), fill_count);
xf_format.set_fill_index(fill_count, true);
fill_count += 1;
}
}
}
self.fill_count = fill_count;
}
fn prepare_borders(&mut self) {
let mut border_count: u16 = 0;
let mut border_indices: HashMap<Border, u16> = HashMap::new();
for xf_format in &mut self.xf_formats {
match border_indices.get(&xf_format.borders) {
Some(border_index) => {
xf_format.set_border_index(*border_index, false);
}
None => {
border_indices.insert(xf_format.borders.clone(), border_count);
xf_format.set_border_index(border_count, true);
border_count += 1;
}
}
}
self.border_count = border_count;
for dxf_format in &mut self.dxf_formats {
dxf_format.has_border = !dxf_format.borders.is_default();
}
}
fn prepare_num_formats(&mut self) {
let mut unique_num_formats: HashMap<String, u16> = HashMap::new();
let mut index = 164;
let xf_formats = [&mut self.xf_formats, &mut self.dxf_formats];
self.num_formats = vec![];
for xf_format in xf_formats.into_iter().flatten() {
if xf_format.num_format_index > 0 {
continue;
}
if xf_format.num_format.is_empty() {
continue;
}
let num_format_string = xf_format.num_format.clone();
match unique_num_formats.get(&num_format_string) {
Some(index) => {
xf_format.set_num_format_index_u16(*index);
}
None => {
unique_num_formats.insert(num_format_string.clone(), index);
xf_format.set_num_format_index_u16(index);
index += 1;
if !xf_format.is_dxf_format {
self.num_formats.push(num_format_string);
}
}
}
}
}
fn prepare_feature_property_bags(&mut self) {
for xf_format in &self.xf_formats {
if xf_format.has_checkbox() {
self.feature_property_bags
.insert(FeaturePropertyBagTypes::XFComplements);
break;
}
}
for dxf_format in &self.dxf_formats {
if dxf_format.has_checkbox() {
self.feature_property_bags
.insert(FeaturePropertyBagTypes::DXFComplements);
break;
}
}
}
fn set_package_options(
&mut self,
mut package_options: PackagerOptions,
) -> Result<PackagerOptions, XlsxError> {
package_options.doc_security = self.read_only_mode;
package_options.num_embedded_images = self.embedded_images.len() as u32;
let mut defined_names = self.user_defined_names.clone();
let mut sheet_names: HashMap<String, u16> = HashMap::new();
package_options.is_xlsm_file = self.is_xlsm_file;
package_options.has_vba_signature = !self.vba_signature.is_empty();
package_options
.feature_property_bags
.clone_from(&self.feature_property_bags);
for (sheet_index, worksheet) in self.worksheets.iter().enumerate() {
let sheet_name = worksheet.name.clone();
let quoted_sheet_name = utility::quote_sheet_name(&sheet_name);
sheet_names.insert(sheet_name.clone(), sheet_index as u16);
if worksheet.is_chartsheet {
package_options.num_chartsheets += 1;
} else {
package_options.num_worksheets += 1;
}
if worksheet.visible == Visible::VeryHidden {
package_options.worksheet_names.push(String::new());
} else {
package_options.worksheet_names.push(sheet_name.clone());
}
package_options.properties = self.properties.clone();
package_options.has_sst_table = self.string_table.lock().unwrap().count > 0;
if worksheet.has_dynamic_arrays {
package_options.has_metadata = true;
package_options.has_dynamic_functions = true;
}
if !worksheet.embedded_images.is_empty() {
package_options.has_metadata = true;
package_options.has_embedded_images = true;
if worksheet.has_embedded_image_descriptions {
package_options.has_embedded_image_descriptions = true;
}
}
if worksheet.has_vml || worksheet.has_header_footer_images() {
package_options.has_vml = true;
}
if !worksheet.drawing.drawings.is_empty() {
package_options.num_drawings += 1;
}
if !worksheet.charts.is_empty() {
package_options.num_charts += worksheet.charts.len() as u16;
}
if !worksheet.tables.is_empty() {
package_options.num_tables += worksheet.tables.len() as u16;
}
if !worksheet.notes.is_empty() {
package_options.num_comments += 1;
}
if worksheet.autofilter_defined_name.in_use {
let mut defined_name = worksheet.autofilter_defined_name.clone();
defined_name.initialize("ed_sheet_name);
defined_names.push(defined_name);
}
if worksheet.print_area_defined_name.in_use {
let mut defined_name = worksheet.print_area_defined_name.clone();
defined_name.initialize("ed_sheet_name);
defined_names.push(defined_name);
}
if worksheet.repeat_row_cols_defined_name.in_use {
let mut defined_name = worksheet.repeat_row_cols_defined_name.clone();
defined_name.initialize("ed_sheet_name);
defined_names.push(defined_name);
}
for i in 0..NUM_IMAGE_FORMATS {
if worksheet.image_types[i] {
package_options.image_types[i] = true;
}
}
}
for defined_name in &mut defined_names {
let sheet_name = utility::unquote_sheetname(&defined_name.quoted_sheet_name);
if !sheet_name.is_empty() {
match sheet_names.get(&sheet_name) {
Some(index) => defined_name.index = *index,
None => {
let error = format!(
"Unknown worksheet name '{}' in defined name '{}'",
sheet_name, defined_name.name
);
return Err(XlsxError::ParameterError(error));
}
}
}
}
defined_names.sort_by_key(|n| (n.sort_name.clone(), n.range.clone()));
for defined_name in &defined_names {
let app_name = defined_name.app_name();
if !app_name.is_empty() {
package_options.defined_names.push(app_name);
}
}
self.defined_names = defined_names;
Ok(package_options)
}
pub(crate) fn assemble_xml_file(&mut self) {
xml_declaration(&mut self.writer);
self.write_workbook();
self.write_file_version();
if self.read_only_mode == 2 {
self.write_file_sharing();
}
self.write_workbook_pr();
self.write_book_views();
self.write_sheets();
if !self.defined_names.is_empty() {
self.write_defined_names();
}
self.write_calc_pr();
xml_end_tag(&mut self.writer, "workbook");
}
fn write_workbook(&mut self) {
let xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
let xmlns_r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
let attributes = [("xmlns", xmlns), ("xmlns:r", xmlns_r)];
xml_start_tag(&mut self.writer, "workbook", &attributes);
}
fn write_file_version(&mut self) {
let mut attributes = vec![
("appName", "xl"),
("lastEdited", "4"),
("lowestEdited", "4"),
("rupBuild", "4505"),
];
if self.is_xlsm_file {
attributes.push(("codeName", "{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}"));
}
xml_empty_tag(&mut self.writer, "fileVersion", &attributes);
}
fn write_file_sharing(&mut self) {
let attributes = [("readOnlyRecommended", "1")];
xml_empty_tag(&mut self.writer, "fileSharing", &attributes);
}
fn write_workbook_pr(&mut self) {
let mut attributes = vec![];
if let Some(codename) = &self.vba_codename {
attributes.push(("codeName", codename.clone()));
}
if !self.default_theme_version.is_empty() {
attributes.push(("defaultThemeVersion", self.default_theme_version.clone()));
}
xml_empty_tag(&mut self.writer, "workbookPr", &attributes);
}
fn write_book_views(&mut self) {
xml_start_tag_only(&mut self.writer, "bookViews");
self.write_workbook_view();
xml_end_tag(&mut self.writer, "bookViews");
}
fn write_workbook_view(&mut self) {
let mut attributes = vec![
("xWindow", "240".to_string()),
("yWindow", "15".to_string()),
("windowWidth", "16095".to_string()),
("windowHeight", "9660".to_string()),
];
if self.first_sheet > 0 {
let first_sheet = self.first_sheet + 1;
attributes.push(("firstSheet", first_sheet.to_string()));
}
if self.active_tab > 0 {
attributes.push(("activeTab", self.active_tab.to_string()));
}
xml_empty_tag(&mut self.writer, "workbookView", &attributes);
}
fn write_sheets(&mut self) {
xml_start_tag_only(&mut self.writer, "sheets");
let mut worksheet_data = vec![];
for worksheet in &self.worksheets {
worksheet_data.push((worksheet.name.clone(), worksheet.visible));
}
for (index, data) in worksheet_data.iter().enumerate() {
self.write_sheet(&data.0, data.1, (index + 1) as u16);
}
xml_end_tag(&mut self.writer, "sheets");
}
fn write_sheet(&mut self, name: &str, visible: Visible, index: u16) {
let sheet_id = format!("{index}");
let rel_id = format!("rId{index}");
let mut attributes = vec![("name", name.to_string()), ("sheetId", sheet_id)];
match visible {
Visible::Default => {}
Visible::Hidden => attributes.push(("state", "hidden".to_string())),
Visible::VeryHidden => attributes.push(("state", "veryHidden".to_string())),
}
attributes.push(("r:id", rel_id));
xml_empty_tag(&mut self.writer, "sheet", &attributes);
}
fn write_defined_names(&mut self) {
xml_start_tag_only(&mut self.writer, "definedNames");
for defined_name in &self.defined_names {
let mut attributes = vec![("name", defined_name.name())];
match defined_name.name_type {
DefinedNameType::Global => {}
_ => {
attributes.push(("localSheetId", defined_name.index.to_string()));
}
}
if let DefinedNameType::Autofilter = defined_name.name_type {
attributes.push(("hidden", "1".to_string()));
}
xml_data_element(
&mut self.writer,
"definedName",
&defined_name.range,
&attributes,
);
}
xml_end_tag(&mut self.writer, "definedNames");
}
fn write_calc_pr(&mut self) {
let attributes = [("calcId", "124519"), ("fullCalcOnLoad", "1")];
xml_empty_tag(&mut self.writer, "calcPr", &attributes);
}
}