#![warn(missing_docs)]
mod tests;
use std::borrow::Cow;
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::io::Cursor;
use std::io::Write;
use std::mem;
use std::sync::{Arc, Mutex, RwLock};
use std::{cmp, fmt};
#[cfg(feature = "constant_memory")]
use tempfile::tempfile_in;
#[cfg(feature = "constant_memory")]
use std::io::BufWriter;
#[cfg(feature = "constant_memory")]
use std::fs::File;
#[cfg(feature = "rust_decimal")]
use rust_decimal::prelude::{Decimal, ToPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use crate::{
deserialize_headers, serializer::SerializerState, CustomSerializeField,
SerializationHeaderConfig, SerializeFieldOptions, SerializerHeader, TableData, XlsxSerialize,
};
#[cfg(feature = "enhanced_autofit")]
use ssfmt::{FormatOptions, NumberFormat};
use crate::drawing::{Drawing, DrawingCoordinates, DrawingInfo, DrawingObject, DrawingType};
use crate::error::XlsxError;
use crate::format::Format;
use crate::formula::Formula;
use crate::shared_strings_table::SharedStringsTable;
use crate::styles::Styles;
use crate::vml::VmlInfo;
use crate::xmlwriter::{
xml_data_element, xml_data_element_only, xml_declaration, xml_empty_tag, xml_empty_tag_only,
xml_end_tag, xml_raw_string, xml_start_tag, xml_start_tag_only, XML_WRITE_ERROR,
};
use crate::{
utility, xmlwriter, Button, Chart, ChartEmptyCells, ChartRangeCacheData,
ChartRangeCacheDataType, Color, ConditionalFormat, DataValidation, DataValidationErrorStyle,
DataValidationRuleInternal, DataValidationType, ExcelDateTime, FilterCondition, FilterCriteria,
FilterData, FilterDataType, HeaderImagePosition, HyperlinkType, Image, IntoExcelDateTime, Note,
ObjectMovement, ProtectionOptions, Shape, Sparkline, SparklineType, Table, TableFunction, Url,
};
pub type RowNum = u32;
pub type ColNum = u16;
pub(crate) const COL_MAX: ColNum = 16_384;
pub(crate) const ROW_MAX: RowNum = 1_048_576;
pub(crate) const NUM_IMAGE_FORMATS: usize = 5;
pub(crate) const MAX_PARAMETER_LEN: usize = 255;
pub(crate) const MAX_AUTOFIT_WIDTH_PIXELS: u32 = 1790;
const MAX_STRING_LEN: usize = 32_767;
const COLUMN_LETTERS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
pub struct Worksheet {
pub(crate) writer: Cursor<Vec<u8>>,
pub(crate) name: String,
pub(crate) sheet_index: usize,
pub(crate) active: bool,
pub(crate) selected: bool,
pub(crate) visible: Visible,
pub(crate) first_sheet: bool,
pub(crate) has_local_string_table: bool,
pub(crate) has_dynamic_arrays: bool,
pub(crate) print_area_defined_name: DefinedName,
pub(crate) repeat_row_cols_defined_name: DefinedName,
pub(crate) autofilter_defined_name: DefinedName,
pub(crate) autofilter_area: String,
pub(crate) default_format: Format,
pub(crate) xf_formats: Vec<Format>,
pub(crate) dxf_formats: Vec<Format>,
pub(crate) has_vml: bool,
pub(crate) has_hyperlink_style: bool,
pub(crate) images: BTreeMap<(RowNum, ColNum, u32, u32), Image>,
pub(crate) buttons_vml_info: Vec<VmlInfo>,
pub(crate) comments_vml_info: Vec<VmlInfo>,
pub(crate) drawing: Drawing,
pub(crate) image_types: [bool; NUM_IMAGE_FORMATS],
pub(crate) header_footer_images: [Option<Image>; 6],
pub(crate) charts: BTreeMap<(RowNum, ColNum, u32, u32), Chart>,
pub(crate) buttons: BTreeMap<(RowNum, ColNum, u32, u32), Button>,
pub(crate) notes: BTreeMap<RowNum, BTreeMap<ColNum, Note>>,
pub(crate) shapes: BTreeMap<(RowNum, ColNum, u32, u32), Shape>,
pub(crate) tables: Vec<Table>,
pub(crate) has_embedded_image_descriptions: bool,
pub(crate) embedded_images: Vec<Image>,
pub(crate) global_embedded_image_indices: Vec<u32>,
pub(crate) vba_codename: Option<String>,
pub(crate) note_authors: BTreeMap<String, usize>,
pub(crate) vml_data_id: String,
pub(crate) vml_shape_id: u32,
pub(crate) is_chartsheet: bool,
pub(crate) use_constant_memory: bool,
pub(crate) use_inline_strings: bool,
pub(crate) current_row: RowNum,
pub(crate) workbook_xf_indices: Arc<RwLock<HashMap<Format, u32>>>,
pub(crate) string_table: Arc<Mutex<SharedStringsTable>>,
pub(crate) has_workbook_global_xfs: bool,
pub(crate) has_workbook_global_sst: bool,
pub(crate) background_image: Option<Image>,
drawing_rel_ids: HashMap<String, u32>,
pub(crate) comment_relationships: Vec<(String, String, String)>,
pub(crate) drawing_object_relationships: Vec<(String, String, String)>,
pub(crate) drawing_relationships: Vec<(String, String, String)>,
pub(crate) header_footer_vml_info: Vec<VmlInfo>,
pub(crate) hyperlink_relationships: Vec<(String, String, String)>,
pub(crate) table_relationships: Vec<(String, String, String)>,
pub(crate) vml_drawing_relationships: Vec<(String, String, String)>,
pub(crate) background_relationships: Vec<(String, String, String)>,
data_table: BTreeMap<RowNum, BTreeMap<ColNum, CellType>>,
is_writing_ahead: bool,
merged_ranges: Vec<CellRange>,
merged_cells: HashMap<(RowNum, ColNum), usize>,
table_ranges: Vec<CellRange>,
table_cells: HashMap<(RowNum, ColNum), usize>,
dimensions: CellRange,
xf_indices: HashMap<Format, u32>,
dxf_indices: HashMap<Format, u32>,
global_xf_indices: Vec<u32>,
global_dxf_indices: Vec<u32>,
changed_rows: HashMap<RowNum, RowOptions>,
changed_cols: HashMap<ColNum, ColOptions>,
page_setup_changed: bool,
tab_color: Color,
fit_to_page: bool,
fit_width: u16,
fit_height: u16,
paper_size: u8,
default_page_order: bool,
right_to_left: bool,
is_portrait: bool,
page_view: PageView,
zoom: u16,
zoom_to_fit: bool,
print_scale: u16,
print_options_changed: bool,
center_horizontally: bool,
center_vertically: bool,
screen_gridlines: bool,
print_gridlines: bool,
print_black_and_white: bool,
print_draft: bool,
print_headings: bool,
header: String,
footer: String,
head_footer_changed: bool,
header_footer_scale_with_doc: bool,
header_footer_align_with_page: bool,
margin_left: f64,
margin_right: f64,
margin_top: f64,
margin_bottom: f64,
margin_header: f64,
margin_footer: f64,
first_page_number: u16,
default_result: Box<str>,
panes: Panes,
hyperlinks: BTreeMap<(RowNum, ColNum), Url>,
rel_count: u32,
protection_on: bool,
protection_hash: u16,
protection_options: ProtectionOptions,
unprotected_ranges: Vec<(String, String, u16)>,
selected_range: (String, String),
top_left_cell: String,
horizontal_breaks: Vec<u32>,
vertical_breaks: Vec<u32>,
filter_conditions: BTreeMap<ColNum, FilterCondition>,
filter_automatic_off: bool,
has_drawing_object_linkage: bool,
cells_with_autofilter: HashMap<(RowNum, ColNum), (FilterType, CellRange)>,
conditional_formats: BTreeMap<String, Vec<Box<dyn ConditionalFormat + Sync + Send>>>,
conditional_format_order: Vec<String>,
data_validations: BTreeMap<String, DataValidation>,
has_conditional_formats: bool,
use_x14_extensions: bool,
has_x14_conditional_formats: bool,
has_sparklines: bool,
sparklines: Vec<Sparkline>,
embedded_image_ids: HashMap<String, u32>,
show_all_notes: bool,
original_row_height: u32,
default_col_width: u32,
cell_padding: u32,
default_row_height: u32,
max_digit_width: u32,
max_col_width: u32,
hide_unused_rows: bool,
has_sheet_data: bool,
nan: String,
infinity: String,
neg_infinity: String,
ignored_errors: HashMap<IgnoreError, String>,
ignored_error_cells: HashSet<(RowNum, ColNum, RowNum, ColNum)>,
max_outline_row_level: u8,
max_outline_col_level: u8,
outline_symbols_above: bool,
outline_symbols_left: bool,
max_autofit_width: u32,
max_autofit_row: RowNum,
#[cfg(feature = "constant_memory")]
pub(crate) file_writer: BufWriter<File>,
#[cfg(feature = "constant_memory")]
write_ahead: BTreeMap<RowNum, BTreeMap<ColNum, CellType>>,
#[cfg(feature = "serde")]
pub(crate) serializer_state: SerializerState,
}
impl Default for Worksheet {
fn default() -> Self {
Self::new()
}
}
impl Worksheet {
#[allow(clippy::too_many_lines)]
pub fn new() -> Worksheet {
let writer = Cursor::new(Vec::with_capacity(2048));
let dimensions = CellRange::default();
let panes = Panes {
freeze_cell: (0, 0),
top_cell: (0, 0),
};
#[cfg(feature = "constant_memory")]
let file_writer = BufWriter::new(tempfile_in(std::env::temp_dir()).unwrap());
Worksheet {
writer,
name: String::new(),
sheet_index: 0,
active: false,
selected: false,
visible: Visible::Default,
first_sheet: false,
has_local_string_table: false,
has_vml: false,
has_dynamic_arrays: false,
print_area_defined_name: DefinedName::new(),
repeat_row_cols_defined_name: DefinedName::new(),
autofilter_defined_name: DefinedName::new(),
autofilter_area: String::new(),
data_table: BTreeMap::new(),
is_writing_ahead: false,
dimensions,
merged_ranges: vec![],
merged_cells: HashMap::new(),
tables: vec![],
table_ranges: vec![],
table_cells: HashMap::new(),
default_format: Format::default(),
xf_formats: vec![Format::default()],
dxf_formats: vec![],
xf_indices: HashMap::from([(Format::default(), 0)]),
dxf_indices: HashMap::new(),
global_xf_indices: vec![],
global_dxf_indices: vec![],
changed_rows: HashMap::new(),
changed_cols: HashMap::new(),
page_setup_changed: false,
fit_to_page: false,
tab_color: Color::Default,
fit_width: 1,
fit_height: 1,
paper_size: 0,
default_page_order: true,
right_to_left: false,
is_portrait: true,
page_view: PageView::Normal,
zoom: 100,
zoom_to_fit: false,
print_scale: 100,
print_options_changed: false,
center_horizontally: false,
center_vertically: false,
screen_gridlines: true,
print_gridlines: false,
print_black_and_white: false,
print_draft: false,
print_headings: false,
header: String::new(),
footer: String::new(),
head_footer_changed: false,
header_footer_scale_with_doc: true,
header_footer_align_with_page: true,
margin_left: 0.7,
margin_right: 0.7,
margin_top: 0.75,
margin_bottom: 0.75,
margin_header: 0.3,
margin_footer: 0.3,
first_page_number: 0,
default_result: Box::from("0"),
panes,
has_hyperlink_style: false,
hyperlinks: BTreeMap::new(),
images: BTreeMap::new(),
shapes: BTreeMap::new(),
drawing: Drawing::new(),
image_types: [false; NUM_IMAGE_FORMATS],
header_footer_images: [None, None, None, None, None, None],
buttons_vml_info: vec![],
comments_vml_info: vec![],
rel_count: 0,
protection_on: false,
protection_hash: 0,
protection_options: ProtectionOptions::new(),
unprotected_ranges: vec![],
selected_range: (String::new(), String::new()),
top_left_cell: String::new(),
horizontal_breaks: vec![],
vertical_breaks: vec![],
filter_conditions: BTreeMap::new(),
filter_automatic_off: false,
charts: BTreeMap::new(),
buttons: BTreeMap::new(),
notes: BTreeMap::new(),
has_drawing_object_linkage: false,
cells_with_autofilter: HashMap::new(),
conditional_formats: BTreeMap::new(),
conditional_format_order: vec![],
data_validations: BTreeMap::new(),
has_conditional_formats: false,
use_x14_extensions: false,
has_x14_conditional_formats: false,
embedded_images: vec![],
embedded_image_ids: HashMap::new(),
global_embedded_image_indices: vec![],
has_embedded_image_descriptions: false,
has_sparklines: false,
sparklines: vec![],
vba_codename: None,
note_authors: BTreeMap::from([("Author".to_string(), 0)]),
show_all_notes: false,
vml_data_id: String::new(),
vml_shape_id: 0,
original_row_height: 20,
default_col_width: 64,
cell_padding: 5,
default_row_height: 20,
max_digit_width: 7,
max_col_width: 1790,
hide_unused_rows: false,
nan: "NAN".to_string(),
infinity: "INF".to_string(),
neg_infinity: "-INF".to_string(),
ignored_errors: HashMap::new(),
ignored_error_cells: HashSet::new(),
max_outline_row_level: 0,
max_outline_col_level: 0,
outline_symbols_above: false,
outline_symbols_left: false,
background_image: None,
max_autofit_width: MAX_AUTOFIT_WIDTH_PIXELS,
max_autofit_row: ROW_MAX - 1,
comment_relationships: vec![],
drawing_object_relationships: vec![],
drawing_rel_ids: HashMap::new(),
drawing_relationships: vec![],
header_footer_vml_info: vec![],
hyperlink_relationships: vec![],
table_relationships: vec![],
vml_drawing_relationships: vec![],
background_relationships: vec![],
is_chartsheet: false,
use_constant_memory: false,
use_inline_strings: false,
has_sheet_data: false,
current_row: 0,
workbook_xf_indices: Arc::new(RwLock::new(HashMap::new())),
string_table: Arc::new(Mutex::new(SharedStringsTable::new())),
has_workbook_global_xfs: false,
has_workbook_global_sst: false,
#[cfg(feature = "constant_memory")]
file_writer,
#[cfg(feature = "constant_memory")]
write_ahead: BTreeMap::new(),
#[cfg(feature = "serde")]
serializer_state: SerializerState::new(),
}
}
pub fn new_chartsheet() -> Worksheet {
let protection_options = ProtectionOptions {
edit_objects: true,
..Default::default()
};
Worksheet {
is_chartsheet: true,
is_portrait: false,
protection_options,
..Default::default()
}
}
pub fn set_name(&mut self, name: impl Into<String>) -> Result<&mut Worksheet, XlsxError> {
let name = name.into();
let error_message = format!(r#"worksheet.set_name("{name}")"#);
utility::validate_sheetname(&name, &error_message)?;
self.name = name;
Ok(self)
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn write(
&mut self,
row: RowNum,
col: ColNum,
data: impl IntoExcelData,
) -> Result<&mut Worksheet, XlsxError> {
data.write(self, row, col)
}
pub fn write_with_format<'a, T>(
&'a mut self,
row: RowNum,
col: ColNum,
data: T,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError>
where
T: IntoExcelData,
{
data.write_with_format(self, row, col, format)
}
pub fn write_row<I>(
&mut self,
row: RowNum,
col: ColNum,
data: I,
) -> Result<&mut Worksheet, XlsxError>
where
I: IntoIterator,
I::Item: IntoExcelData,
{
for (col, item) in (col..).zip(data) {
self.write(row, col, item)?;
}
Ok(self)
}
pub fn write_row_with_format<I>(
&mut self,
row: RowNum,
col: ColNum,
data: I,
format: &Format,
) -> Result<&mut Worksheet, XlsxError>
where
I: IntoIterator,
I::Item: IntoExcelData,
{
for (col, item) in (col..).zip(data) {
self.write_with_format(row, col, item, format)?;
}
Ok(self)
}
pub fn write_column<I>(
&mut self,
row: RowNum,
col: ColNum,
data: I,
) -> Result<&mut Worksheet, XlsxError>
where
I: IntoIterator,
I::Item: IntoExcelData,
{
for (row, item) in (row..).zip(data) {
self.write(row, col, item)?;
}
Ok(self)
}
pub fn write_column_with_format<I>(
&mut self,
row: RowNum,
col: ColNum,
data: I,
format: &Format,
) -> Result<&mut Worksheet, XlsxError>
where
I: IntoIterator,
I::Item: IntoExcelData,
{
for (row, item) in (row..).zip(data) {
self.write_with_format(row, col, item, format)?;
}
Ok(self)
}
pub fn write_row_matrix<I, II>(
&mut self,
row: RowNum,
col: ColNum,
data: I,
) -> Result<&mut Worksheet, XlsxError>
where
I: IntoIterator,
I::Item: IntoIterator<Item = II>,
II: IntoExcelData,
{
for (row, item) in (row..).zip(data) {
self.write_row(row, col, item)?;
}
Ok(self)
}
pub fn write_column_matrix<I, II>(
&mut self,
row: RowNum,
col: ColNum,
data: I,
) -> Result<&mut Worksheet, XlsxError>
where
I: IntoIterator,
I::Item: IntoIterator<Item = II>,
II: IntoExcelData,
{
for (col, item) in (col..).zip(data) {
self.write_column(row, col, item)?;
}
Ok(self)
}
pub fn write_number(
&mut self,
row: RowNum,
col: ColNum,
number: impl Into<f64>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_number(row, col, number, None)
}
pub fn write_number_with_format(
&mut self,
row: RowNum,
col: ColNum,
number: impl Into<f64>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_number(row, col, number.into(), Some(format))
}
pub fn write_string(
&mut self,
row: RowNum,
col: ColNum,
string: impl Into<String>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_string(row, col, string.into(), None)
}
pub fn write_string_with_format(
&mut self,
row: RowNum,
col: ColNum,
string: impl Into<String>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_string(row, col, string.into(), Some(format))
}
pub fn write_rich_string(
&mut self,
row: RowNum,
col: ColNum,
rich_string: &[(&Format, &str)],
) -> Result<&mut Worksheet, XlsxError> {
let (string, raw_string) = Self::get_rich_string(rich_string)?;
self.store_rich_string(row, col, &string, &raw_string, None)
}
pub fn write_rich_string_with_format(
&mut self,
row: RowNum,
col: ColNum,
rich_string: &[(&Format, &str)],
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let (string, raw_string) = Self::get_rich_string(rich_string)?;
self.store_rich_string(row, col, &string, &raw_string, Some(format))
}
pub fn write_formula(
&mut self,
row: RowNum,
col: ColNum,
formula: impl Into<Formula>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_formula(row, col, formula.into(), None)
}
pub fn write_formula_with_format(
&mut self,
row: RowNum,
col: ColNum,
formula: impl Into<Formula>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_formula(row, col, formula.into(), Some(format))
}
pub fn write_array_formula(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
formula: impl Into<Formula>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_array_formula(
first_row,
first_col,
last_row,
last_col,
formula.into(),
None,
false,
)
}
pub fn write_array_formula_with_format(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
formula: impl Into<Formula>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_array_formula(
first_row,
first_col,
last_row,
last_col,
formula.into(),
Some(format),
false,
)
}
pub fn write_dynamic_array_formula(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
formula: impl Into<Formula>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_array_formula(
first_row,
first_col,
last_row,
last_col,
formula.into(),
None,
true,
)
}
pub fn write_dynamic_array_formula_with_format(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
formula: impl Into<Formula>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_array_formula(
first_row,
first_col,
last_row,
last_col,
formula.into(),
Some(format),
true,
)
}
pub fn write_dynamic_formula(
&mut self,
row: RowNum,
col: ColNum,
formula: impl Into<Formula>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_array_formula(row, col, row, col, formula.into(), None, true)
}
pub fn write_dynamic_formula_with_format(
&mut self,
row: RowNum,
col: ColNum,
formula: impl Into<Formula>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_array_formula(row, col, row, col, formula.into(), Some(format), true)
}
pub fn write_blank(
&mut self,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_blank(row, col, format)
}
pub fn write_url(
&mut self,
row: RowNum,
col: ColNum,
link: impl Into<Url>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_url(row, col, &link.into(), None)
}
pub fn write_url_with_text(
&mut self,
row: RowNum,
col: ColNum,
link: impl Into<Url>,
text: impl Into<String>,
) -> Result<&mut Worksheet, XlsxError> {
let link = link.into().set_text(text.into());
self.store_url(row, col, &link, None)
}
pub fn write_url_with_format(
&mut self,
row: RowNum,
col: ColNum,
link: impl Into<Url>,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_url(row, col, &link.into(), Some(format))
}
#[doc(hidden)] pub fn write_url_with_options(
&mut self,
row: RowNum,
col: ColNum,
link: impl Into<Url>,
text: impl Into<String>,
tip: impl Into<String>,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
let link = link.into().set_text(text.into()).set_tip(tip.into());
self.store_url(row, col, &link, format)
}
pub fn write_datetime_with_format(
&mut self,
row: RowNum,
col: ColNum,
datetime: impl IntoExcelDateTime,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let datetime = datetime.to_excel_serial_date();
self.store_datetime(row, col, datetime, Some(format))
}
pub fn write_datetime(
&mut self,
row: RowNum,
col: ColNum,
datetime: impl IntoExcelDateTime,
) -> Result<&mut Worksheet, XlsxError> {
let datetime = datetime.to_excel_serial_date();
self.store_datetime(row, col, datetime, None)
}
#[doc(hidden)] pub fn write_date_with_format(
&mut self,
row: RowNum,
col: ColNum,
date: impl IntoExcelDateTime,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let datetime = date.to_excel_serial_date();
self.store_datetime(row, col, datetime, Some(format))
}
#[doc(hidden)] pub fn write_time_with_format(
&mut self,
row: RowNum,
col: ColNum,
time: impl IntoExcelDateTime,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let datetime = time.to_excel_serial_date();
self.store_datetime(row, col, datetime, Some(format))
}
pub fn write_boolean(
&mut self,
row: RowNum,
col: ColNum,
boolean: bool,
) -> Result<&mut Worksheet, XlsxError> {
self.store_boolean(row, col, boolean, None)
}
pub fn write_boolean_with_format(
&mut self,
row: RowNum,
col: ColNum,
boolean: bool,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_boolean(row, col, boolean, Some(format))
}
pub fn merge_range(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
string: &str,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(first_row, first_col)
|| !self.check_dimensions(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
if first_row == last_row && first_col == last_col {
return Err(XlsxError::MergeRangeSingleCell);
}
self.write_string_with_format(first_row, first_col, string, format)?;
for col in first_col + 1..=last_col {
self.write_blank(first_row, col, format)?;
}
self.set_writing_ahead(true);
for row in first_row + 1..=last_row {
for col in first_col..=last_col {
self.write_blank(row, col, format)?;
}
}
self.set_writing_ahead(false);
let cell_range = CellRange::new(first_row, first_col, last_row, last_col);
let new_index = self.merged_ranges.len();
for row in first_row..=last_row {
for col in first_col..=last_col {
match self.merged_cells.get_mut(&(row, col)) {
Some(index) => {
let previous_cell_range = self.merged_ranges.get(*index).unwrap();
return Err(XlsxError::MergeRangeOverlaps(
cell_range.to_error_string(),
previous_cell_range.to_error_string(),
));
}
None => self.merged_cells.insert((row, col), new_index),
};
}
}
self.merged_ranges.push(cell_range);
Ok(self)
}
pub fn insert_image(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
) -> Result<&mut Worksheet, XlsxError> {
self.insert_image_with_offset(row, col, image, 0, 0)?;
Ok(self)
}
pub fn insert_image_with_offset(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
x_offset: u32,
y_offset: u32,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let mut image = image.clone();
image.x_offset = x_offset;
image.y_offset = y_offset;
self.images.insert((row, col, y_offset, x_offset), image);
Ok(self)
}
pub fn embed_image(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
) -> Result<&mut Worksheet, XlsxError> {
self.store_embedded_image(row, col, image, None)
}
pub fn embed_image_with_format(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
self.store_embedded_image(row, col, image, Some(format))
}
pub fn insert_image_fit_to_cell(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
keep_aspect_ratio: bool,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let column_width = self.column_pixel_width(col, image.object_movement);
let row_height = self.row_pixel_height(row, image.object_movement);
let image = image
.clone()
.set_scale_to_size(column_width, row_height, keep_aspect_ratio);
self.images.insert((row, col, 0, 0), image);
Ok(self)
}
pub fn insert_image_fit_to_cell_centered(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let column_width = self.column_pixel_width(col, image.object_movement);
let row_height = self.row_pixel_height(row, image.object_movement);
let mut image = image
.clone()
.set_scale_to_size(column_width, row_height, true);
let mut x_offset = 0;
let mut y_offset = 0;
let image_width = image.scaled_width().round() as u32;
let image_height = image.scaled_height().round() as u32;
if image_height == row_height {
x_offset = (column_width - image_width) / 2;
} else {
y_offset = (row_height - image_height) / 2;
}
image.x_offset = x_offset;
image.y_offset = y_offset;
self.images.insert((row, col, x_offset, y_offset), image);
Ok(self)
}
pub fn insert_background_image(&mut self, image: &Image) -> &mut Worksheet {
self.background_image = Some(image.clone());
self
}
pub fn insert_chart(
&mut self,
row: RowNum,
col: ColNum,
chart: &Chart,
) -> Result<&mut Worksheet, XlsxError> {
self.insert_chart_with_offset(row, col, chart, 0, 0)?;
Ok(self)
}
pub fn insert_chart_with_offset(
&mut self,
row: RowNum,
col: ColNum,
chart: &Chart,
x_offset: u32,
y_offset: u32,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let mut chart = chart.clone();
chart.is_chartsheet = self.is_chartsheet;
chart.validate()?;
chart.x_offset = x_offset;
chart.y_offset = y_offset;
let mut row = row;
let mut col = col;
if self.is_chartsheet {
row = 0;
col = 0;
chart.x_offset = 0;
chart.y_offset = 0;
}
self.charts.insert((row, col, y_offset, x_offset), chart);
Ok(self)
}
pub fn insert_note(
&mut self,
row: RowNum,
col: ColNum,
note: &Note,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if note.text.chars().count() > MAX_STRING_LEN - 54 {
return Err(XlsxError::MaxStringLengthExceeded);
}
let mut note = note.clone();
note.cell_row = row;
note.cell_col = col;
match self.notes.entry(row) {
Entry::Occupied(mut entry) => {
let columns = entry.get_mut();
columns.insert(col, note);
}
Entry::Vacant(entry) => {
let columns = BTreeMap::from([(col, note)]);
entry.insert(columns);
}
}
self.has_vml = true;
Ok(self)
}
pub fn insert_shape(
&mut self,
row: RowNum,
col: ColNum,
shape: &Shape,
) -> Result<&mut Worksheet, XlsxError> {
self.insert_shape_with_offset(row, col, shape, 0, 0)?;
Ok(self)
}
pub fn insert_shape_with_offset(
&mut self,
row: RowNum,
col: ColNum,
shape: &Shape,
x_offset: u32,
y_offset: u32,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let mut shape = shape.clone();
shape.x_offset = x_offset;
shape.y_offset = y_offset;
self.shapes.insert((row, col, y_offset, x_offset), shape);
Ok(self)
}
pub fn show_all_notes(&mut self, enable: bool) -> &mut Worksheet {
self.show_all_notes = enable;
self
}
pub fn set_default_note_author(&mut self, name: impl Into<String>) -> &mut Worksheet {
let name = name.into();
if name.chars().count() > 52 {
eprintln!("Author string must be less than the Excel limit of 52 characters: {name}");
return self;
}
self.note_authors = BTreeMap::from([(name, 0)]);
self
}
pub fn insert_button(
&mut self,
row: RowNum,
col: ColNum,
button: &Button,
) -> Result<&mut Worksheet, XlsxError> {
self.insert_button_with_offset(row, col, button, 0, 0)?;
Ok(self)
}
pub fn insert_button_with_offset(
&mut self,
row: RowNum,
col: ColNum,
button: &Button,
x_offset: u32,
y_offset: u32,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let mut button = button.clone();
button.x_offset = x_offset;
button.y_offset = y_offset;
self.buttons.insert((row, col, y_offset, x_offset), button);
self.has_vml = true;
Ok(self)
}
pub fn insert_checkbox(
&mut self,
row: RowNum,
col: ColNum,
boolean: bool,
) -> Result<&mut Worksheet, XlsxError> {
let format = Format::new().set_checkbox();
self.store_boolean(row, col, boolean, Some(&format))
}
pub fn insert_checkbox_with_format(
&mut self,
row: RowNum,
col: ColNum,
boolean: bool,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let format = format.clone().set_checkbox();
self.store_boolean(row, col, boolean, Some(&format))
}
pub fn set_row_height(
&mut self,
row: RowNum,
height: impl Into<f64>,
) -> Result<&mut Worksheet, XlsxError> {
let pixel_height = (height.into() * 4.0 / 3.0).round() as u32;
self.set_row_height_pixels(row, pixel_height)
}
pub fn set_row_height_pixels(
&mut self,
row: RowNum,
height: u32,
) -> Result<&mut Worksheet, XlsxError> {
if height == 0 {
return self.set_row_hidden(row);
}
let min_col = self.get_min_col();
if !self.check_dimensions(row, min_col) {
return Err(XlsxError::RowColumnLimitError);
}
match self.changed_rows.get_mut(&row) {
Some(row_options) => row_options.height = height,
None => {
let row_options = RowOptions {
height,
xf_index: 0,
hidden: false,
level: 0,
collapsed: false,
format: None,
};
self.changed_rows.insert(row, row_options);
}
}
Ok(self)
}
pub fn set_row_format(
&mut self,
row: RowNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let min_col = self.get_min_col();
if !self.check_dimensions(row, min_col) {
return Err(XlsxError::RowColumnLimitError);
}
let xf_index = self.format_xf_index(format);
match self.changed_rows.get_mut(&row) {
Some(row_options) => row_options.xf_index = xf_index,
None => {
let row_options = RowOptions {
height: self.default_row_height,
xf_index,
hidden: false,
level: 0,
collapsed: false,
format: Some(format.clone()),
};
self.changed_rows.insert(row, row_options);
}
}
Ok(self)
}
pub fn group_rows(
&mut self,
first_row: RowNum,
last_row: RowNum,
) -> Result<&mut Worksheet, XlsxError> {
self.set_grouped_rows(first_row, last_row, false)
}
pub fn group_rows_collapsed(
&mut self,
first_row: RowNum,
last_row: RowNum,
) -> Result<&mut Worksheet, XlsxError> {
self.set_collapsed_row(last_row + 1)?;
self.set_grouped_rows(first_row, last_row, true)
}
pub fn group_columns(
&mut self,
first_col: ColNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
self.set_grouped_columns(first_col, last_col, false)
}
pub fn group_columns_collapsed(
&mut self,
first_col: ColNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
self.set_collapsed_column(last_col + 1)?;
self.set_grouped_columns(first_col, last_col, true)
}
pub fn group_symbols_above(&mut self, enable: bool) -> &mut Worksheet {
self.outline_symbols_above = enable;
self
}
pub fn group_symbols_to_left(&mut self, enable: bool) -> &mut Worksheet {
self.outline_symbols_left = enable;
self
}
pub fn set_row_hidden(&mut self, row: RowNum) -> Result<&mut Worksheet, XlsxError> {
let min_col = self.get_min_col();
if !self.check_dimensions(row, min_col) {
return Err(XlsxError::RowColumnLimitError);
}
match self.changed_rows.get_mut(&row) {
Some(row_options) => row_options.hidden = true,
None => {
let row_options = RowOptions {
height: self.default_row_height,
xf_index: 0,
hidden: true,
level: 0,
collapsed: false,
format: None,
};
self.changed_rows.insert(row, row_options);
}
}
Ok(self)
}
pub fn set_row_unhidden(&mut self, row: RowNum) -> Result<&mut Worksheet, XlsxError> {
let min_col = self.get_min_col();
if !self.check_dimensions(row, min_col) {
return Err(XlsxError::RowColumnLimitError);
}
if let Some(row_options) = self.changed_rows.get_mut(&row) {
row_options.hidden = false;
}
Ok(self)
}
pub fn set_default_row_height(&mut self, height: impl Into<f64>) -> &mut Worksheet {
let height = height.into();
if height <= 0.0 {
return self;
}
let pixel_height = (height * 4.0 / 3.0).round() as u32;
self.default_row_height = pixel_height;
self
}
pub fn set_default_row_height_pixels(&mut self, height: u32) -> &mut Worksheet {
let height = 0.75 * f64::from(height);
self.set_default_row_height(height)
}
pub fn hide_unused_rows(&mut self, enable: bool) -> &mut Worksheet {
self.hide_unused_rows = enable;
self
}
pub fn set_column_width(
&mut self,
col: ColNum,
width: impl Into<f64>,
) -> Result<&mut Worksheet, XlsxError> {
let width = width.into();
let width_pixels = if width == 0.0 {
0
} else if width < 1.0 {
(width * f64::from(self.max_digit_width + self.cell_padding)).round() as u32
} else {
(width * f64::from(self.max_digit_width)).round() as u32 + self.cell_padding
};
self.set_column_width_internal(col, width_pixels, false)
}
pub fn set_column_width_pixels(
&mut self,
col: ColNum,
width: u32,
) -> Result<&mut Worksheet, XlsxError> {
self.set_column_width_internal(col, width, false)
}
pub fn set_column_autofit_width(
&mut self,
col: ColNum,
width: u32,
) -> Result<&mut Worksheet, XlsxError> {
self.set_column_width_internal(col, width, true)
}
pub fn set_column_format(
&mut self,
col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let min_row = self.get_min_row();
if !self.check_dimensions(min_row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let xf_index = self.format_xf_index(format);
match self.changed_cols.get_mut(&col) {
Some(col_options) => col_options.xf_index = xf_index,
None => {
let col_options = ColOptions {
width: self.default_col_width,
xf_index,
hidden: false,
level: 0,
collapsed: false,
autofit: false,
format: Some(format.clone()),
};
self.changed_cols.insert(col, col_options);
}
}
Ok(self)
}
pub fn set_column_hidden(&mut self, col: ColNum) -> Result<&mut Worksheet, XlsxError> {
if col >= COL_MAX {
return Err(XlsxError::RowColumnLimitError);
}
match self.changed_cols.get_mut(&col) {
Some(col_options) => col_options.hidden = true,
None => {
let col_options = ColOptions {
width: self.default_col_width,
xf_index: 0,
hidden: true,
level: 0,
collapsed: false,
autofit: false,
format: None,
};
self.changed_cols.insert(col, col_options);
}
}
Ok(self)
}
pub fn set_column_range_width(
&mut self,
first_col: ColNum,
last_col: ColNum,
width: impl Into<f64>,
) -> Result<&mut Worksheet, XlsxError> {
if first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let width = width.into();
for col_num in first_col..=last_col {
self.set_column_width(col_num, width)?;
}
Ok(self)
}
pub fn set_column_range_width_pixels(
&mut self,
first_col: ColNum,
last_col: ColNum,
width: u32,
) -> Result<&mut Worksheet, XlsxError> {
if first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
for col_num in first_col..=last_col {
self.set_column_width_pixels(col_num, width)?;
}
Ok(self)
}
pub fn set_column_range_format(
&mut self,
first_col: ColNum,
last_col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
if first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
for col_num in first_col..=last_col {
self.set_column_format(col_num, format)?;
}
Ok(self)
}
pub fn set_column_range_hidden(
&mut self,
first_col: ColNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
for col_num in first_col..=last_col {
self.set_column_hidden(col_num)?;
}
Ok(self)
}
pub fn set_default_format(
&mut self,
format: &Format,
row_height: u32,
col_width: u32,
) -> Result<&mut Worksheet, XlsxError> {
if self.xf_formats.len() > 1 {
return Err(XlsxError::DefaultFormatError(
"Default format must be set before writing with any other formats.".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.initialize_default_format(
format,
row_height,
col_width,
max_digit_width,
padding,
max_col_width,
);
Ok(self)
}
pub(crate) fn initialize_default_format(
&mut self,
format: &Format,
row_height: u32,
col_width: u32,
max_digit_width: u32,
padding: u32,
max_col_width: u32,
) -> &mut Worksheet {
self.xf_formats = vec![format.clone()];
self.xf_indices = 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.original_row_height = row_height;
self.default_row_height = row_height;
self
}
pub fn autofilter(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
self.autofilter_defined_name.in_use = true;
self.autofilter_defined_name.name_type = DefinedNameType::Autofilter;
self.autofilter_defined_name.first_row = first_row;
self.autofilter_defined_name.first_col = first_col;
self.autofilter_defined_name.last_row = last_row;
self.autofilter_defined_name.last_col = last_col;
let autofilter_area = CellRange::new(first_row, first_col, last_row, last_col);
self.filter_conditions = BTreeMap::new();
for col in first_col..=last_col {
let filter_data = self.cells_with_autofilter.get(&(first_row, col));
if let Some((filter_type, filter_range)) = filter_data {
if *filter_type == FilterType::Table {
return Err(XlsxError::AutofilterRangeOverlaps(
autofilter_area.to_error_string(),
filter_range.to_error_string(),
));
}
}
self.cells_with_autofilter.insert(
(first_row, col),
(FilterType::Worksheet, autofilter_area.clone()),
);
}
self.autofilter_area = autofilter_area.to_range_string();
Ok(self)
}
pub fn filter_column(
&mut self,
col: ColNum,
filter_condition: &FilterCondition,
) -> Result<&mut Worksheet, XlsxError> {
if col >= COL_MAX {
return Err(XlsxError::RowColumnLimitError);
}
if !self.autofilter_defined_name.in_use {
let error =
"The 'autofilter()' range must be set before a 'filter_condition' can be applied."
.to_string();
return Err(XlsxError::ParameterError(error));
}
if col < self.autofilter_defined_name.first_col
|| col > self.autofilter_defined_name.last_col
{
let error = format!(
"Col '{col}' outside user defined autofilter column range '{}-{}'",
self.autofilter_defined_name.first_col, self.autofilter_defined_name.last_col
);
return Err(XlsxError::ParameterError(error));
}
if filter_condition.list.is_empty()
&& filter_condition.custom1.is_none()
&& !filter_condition.should_match_blanks
{
let error =
"The 'filter_condition' doesn't have a data value or condition set.".to_string();
return Err(XlsxError::ParameterError(error));
}
self.filter_conditions.insert(col, filter_condition.clone());
Ok(self)
}
pub fn filter_automatic_off(&mut self) -> &mut Worksheet {
self.filter_automatic_off = true;
self
}
#[allow(clippy::too_many_lines)]
pub fn add_table(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
table: &Table,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
if first_row == last_row && table.show_header_row {
return Err(XlsxError::TableError(
"Table must have at least one row".to_string(),
));
}
if !table.name.is_empty() {
utility::check_name(&table.name)?;
}
let default_headers =
self.default_table_headers(first_row, first_col, last_col, table.show_header_row);
let mut table = table.clone();
table.cell_range = CellRange::new(first_row, first_col, last_row, last_col);
table.initialize_columns(&default_headers)?;
let first_data_row = table.first_data_row();
let last_data_row = table.last_data_row();
for (offset, column) in table.columns.iter_mut().enumerate() {
let col = first_col + offset as u16;
if table.show_header_row && !table.is_serde_table {
match &column.header_format {
Some(header_format) => {
self.write_string_with_format(first_row, col, &column.name, header_format)?;
}
None => {
self.write_string(first_row, col, &column.name)?;
}
}
}
self.set_writing_ahead(true);
let col_format = match &column.format {
Some(format) => format.clone(),
None => Format::default(),
};
if table.show_total_row {
if !column.total_label.is_empty() {
self.write_string_with_format(last_row, col, &column.total_label, &col_format)?;
} else if column.total_function != TableFunction::None {
let formula = column.total_function();
self.write_formula_with_format(last_row, col, formula, &col_format)?;
}
}
if let Some(formula) = &column.formula {
for row in first_data_row..=last_data_row {
self.write_formula_with_format(row, col, formula, &col_format)?;
}
}
if let Some(format) = column.format.as_mut() {
format.dxf_index = self.format_dxf_index(format);
let format_index = self.format_xf_index(format);
for row in first_data_row..=last_data_row {
self.update_cell_format(row, col, format_index);
}
if table.show_total_row && column.total_function != TableFunction::None {
self.update_cell_format(last_row, col, format_index);
}
}
self.set_writing_ahead(false);
}
let cell_range = CellRange::new(first_row, first_col, last_row, last_col);
let new_index = self.table_ranges.len();
for row in first_row..=last_row {
for col in first_col..=last_col {
match self.table_cells.get_mut(&(row, col)) {
Some(index) => {
let previous_cell_range = self.table_ranges.get(*index).unwrap();
return Err(XlsxError::TableRangeOverlaps(
cell_range.to_error_string(),
previous_cell_range.to_error_string(),
));
}
None => self.table_cells.insert((row, col), new_index),
};
}
}
if table.show_autofilter {
for col in first_col..=last_col {
let filter_data = self.cells_with_autofilter.get(&(first_row, col));
if let Some((filter_type, filter_range)) = filter_data {
if *filter_type == FilterType::Worksheet {
return Err(XlsxError::AutofilterRangeOverlaps(
table.cell_range.to_error_string(),
filter_range.to_error_string(),
));
}
}
self.cells_with_autofilter.insert(
(first_row, col),
(FilterType::Table, table.cell_range.clone()),
);
}
}
self.table_ranges.push(cell_range);
self.tables.push(table);
Ok(self)
}
pub fn add_conditional_format<T>(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
conditional_format: &T,
) -> Result<&mut Worksheet, XlsxError>
where
T: ConditionalFormat + Send + Sync,
{
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let mut conditional_format = conditional_format.box_clone();
let mut cell_range = utility::cell_range(first_row, first_col, last_row, last_col);
let multi_range = conditional_format.multi_range();
if !multi_range.is_empty() {
cell_range = multi_range;
}
conditional_format.validate()?;
if conditional_format.has_x14_extensions() {
self.use_x14_extensions = true;
self.has_x14_conditional_formats = true;
}
if !conditional_format.has_x14_only() {
self.has_conditional_formats = true;
}
if let Some(format) = conditional_format.format_as_mut() {
format.dxf_index = self.format_dxf_index(format);
}
match self.conditional_formats.entry(cell_range.clone()) {
Entry::Occupied(mut entry) => {
let rules = entry.get_mut();
rules.push(conditional_format);
}
Entry::Vacant(entry) => {
let rules = vec![conditional_format];
entry.insert(rules);
self.conditional_format_order.push(cell_range);
}
}
Ok(self)
}
pub fn add_data_validation(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
data_validation: &DataValidation,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let mut data_validation = data_validation.clone();
if data_validation.is_invalid_any() {
return Ok(self);
}
let mut cell_range = utility::cell_range(first_row, first_col, last_row, last_col);
if !data_validation.multi_range.is_empty() {
cell_range.clone_from(&data_validation.multi_range);
}
self.data_validations.insert(cell_range, data_validation);
Ok(self)
}
pub fn add_sparkline(
&mut self,
row: RowNum,
col: ColNum,
sparkline: &Sparkline,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if !sparkline.data_range.has_data() {
return Err(XlsxError::SparklineError(
"Sparkline data range not set".to_string(),
));
}
sparkline.data_range.validate()?;
if !sparkline.data_range.is_1d() {
let range = sparkline.data_range.error_range();
return Err(XlsxError::SparklineError(format!(
"Sparkline data range '{range}' must be a 1D range"
)));
}
let mut sparkline = sparkline.clone();
sparkline.add_cell_range(row, col);
self.sparklines.push(sparkline);
self.use_x14_extensions = true;
self.has_sparklines = true;
Ok(self)
}
pub fn add_sparkline_group(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
sparkline: &Sparkline,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
if !sparkline.data_range.has_data() {
return Err(XlsxError::SparklineError(
"Sparkline data range not set".to_string(),
));
}
sparkline.data_range.validate()?;
if sparkline.data_range.is_1d() {
let range = sparkline.data_range.error_range();
return Err(XlsxError::SparklineError(format!(
"Sparkline data range '{range}' must be a 2D range"
)));
}
let row_range = (last_row - first_row + 1) as usize;
let col_range = (last_col - first_col + 1) as usize;
let num_cells = std::cmp::max(row_range, col_range);
let (num_rows, num_cols) = sparkline.data_range.number_of_range_points();
if num_cells != num_rows && num_cells != num_cols {
let cell_range = format!("({first_row}, {first_col}, {last_row}, {last_col})");
let sparkline_range = sparkline.data_range.error_range();
return Err(XlsxError::SparklineError(format!(
"Sparkline group range '{cell_range}' doesn't match dimensions of data range '{sparkline_range}'"
)));
}
let mut sparkline = sparkline.clone();
sparkline.add_group_range(first_row, first_col, last_row, last_col);
self.sparklines.push(sparkline);
self.use_x14_extensions = true;
self.has_sparklines = true;
Ok(self)
}
pub fn protect(&mut self) -> &mut Worksheet {
self.protection_on = true;
self
}
pub fn protect_with_password(&mut self, password: &str) -> &mut Worksheet {
self.protection_on = true;
self.protection_hash = utility::hash_password(password);
self
}
pub fn protect_with_options(&mut self, options: &ProtectionOptions) -> &mut Worksheet {
self.protection_on = true;
self.protection_options = options.clone();
self
}
pub fn unprotect_range(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
self.unprotect_range_with_options(first_row, first_col, last_row, last_col, "", "")
}
pub fn unprotect_range_with_options(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
name: &str,
password: &str,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let range = utility::cell_range(first_row, first_col, last_row, last_col);
let mut name = name.to_string();
let password_hash = utility::hash_password(password);
if name.is_empty() {
name = format!("Range{}", 1 + self.unprotected_ranges.len());
}
self.unprotected_ranges.push((range, name, password_hash));
Ok(self)
}
pub fn set_selection(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
let active_cell = utility::row_col_to_cell(first_row, first_col);
let mut first_row = first_row;
let mut first_col = first_col;
let mut last_row = last_row;
let mut last_col = last_col;
if first_row > last_row {
std::mem::swap(&mut first_row, &mut last_row);
}
if first_col > last_col {
std::mem::swap(&mut first_col, &mut last_col);
}
let range = utility::cell_range(first_row, first_col, last_row, last_col);
self.selected_range = (active_cell, range);
Ok(self)
}
pub fn set_top_left_cell(
&mut self,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if row == 0 && col == 0 {
return Ok(self);
}
self.top_left_cell = utility::row_col_to_cell(row, col);
Ok(self)
}
pub fn set_formula_result(
&mut self,
row: RowNum,
col: ColNum,
result: impl Into<String>,
) -> &mut Worksheet {
let lookup_row = if self.use_constant_memory {
self.current_row
} else {
row
};
if let Some(columns) = self.data_table.get_mut(&lookup_row) {
if let Some(cell) = columns.get_mut(&col) {
match cell {
CellType::Formula {
result: cell_result,
..
}
| CellType::ArrayFormula {
result: cell_result,
..
} => {
*cell_result = Box::from(result.into());
}
_ => {
eprintln!("Cell ({row}, {col}) doesn't contain a formula.");
}
}
}
}
self
}
pub fn set_formula_result_default(&mut self, result: impl Into<String>) -> &mut Worksheet {
self.default_result = Box::from(result.into());
self
}
pub fn set_cell_format(
&mut self,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let xf_index = self.format_xf_index(format);
self.insert_cell_format(row, col, xf_index);
Ok(self)
}
pub fn set_range_format(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let xf_index = self.format_xf_index(format);
for row in first_row..=last_row {
for col in first_col..=last_col {
self.insert_cell_format(row, col, xf_index);
}
}
Ok(self)
}
pub fn set_range_format_with_border(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
cell_format: &Format,
border_format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
if first_row == last_row && first_col == last_col {
self.set_range_border_cell(first_row, first_col, cell_format, border_format)?;
} else if first_row == last_row {
self.set_range_border_row(first_row, first_col, last_col, cell_format, border_format)?;
} else if first_col == last_col {
self.set_range_border_col(first_row, last_row, first_col, cell_format, border_format)?;
} else {
self.set_range_border_range(
first_row,
last_row,
first_col,
last_col,
cell_format,
border_format,
)?;
}
Ok(self)
}
pub fn clear_cell(&mut self, row: RowNum, col: ColNum) -> &mut Worksheet {
if !self.check_dimensions_only(row, col) {
return self;
}
self.clear_cell_internal(row, col);
self
}
pub fn clear_cell_format(&mut self, row: RowNum, col: ColNum) -> &mut Worksheet {
if !self.check_dimensions_only(row, col) {
return self;
}
self.clear_cell_format_internal(row, col);
self
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn serialize<T>(&mut self, data_structure: &T) -> Result<&mut Worksheet, XlsxError>
where
T: Serialize,
{
self.serialize_data_structure(data_structure)?;
Ok(self)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn serialize_headers<T>(
&mut self,
row: RowNum,
col: ColNum,
data_structure: &T,
) -> Result<&mut Worksheet, XlsxError>
where
T: Serialize,
{
self.serialize_headers_with_format(row, col, data_structure, &Format::default())
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn serialize_headers_with_format<T>(
&mut self,
row: RowNum,
col: ColNum,
data_structure: &T,
format: &Format,
) -> Result<&mut Worksheet, XlsxError>
where
T: Serialize,
{
let mut headers = SerializerHeader {
struct_name: String::new(),
field_names: vec![],
};
data_structure.serialize(&mut headers)?;
self.store_serialization_headers(row, col, &headers, format)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn serialize_headers_with_options<T>(
&mut self,
row: RowNum,
col: ColNum,
data_structure: &T,
header_options: &SerializeFieldOptions,
) -> Result<&mut Worksheet, XlsxError>
where
T: Serialize,
{
let mut headers = SerializerHeader {
struct_name: String::new(),
field_names: vec![],
};
data_structure.serialize(&mut headers)?;
self.store_serialization_headers_with_options(row, col, &headers, header_options)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn deserialize_headers<'de, T>(
&mut self,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError>
where
T: Deserialize<'de>,
{
self.deserialize_headers_with_format::<T>(row, col, &Format::default())
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn deserialize_headers_with_format<'de, T>(
&mut self,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError>
where
T: Deserialize<'de>,
{
let headers = deserialize_headers::<T>();
self.store_serialization_headers(row, col, &headers, format)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn deserialize_headers_with_options<'de, T>(
&mut self,
row: RowNum,
col: ColNum,
header_options: &SerializeFieldOptions,
) -> Result<&mut Worksheet, XlsxError>
where
T: Deserialize<'de>,
{
let headers = deserialize_headers::<T>();
self.store_serialization_headers_with_options(row, col, &headers, header_options)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn set_serialize_headers<T>(
&mut self,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError>
where
T: XlsxSerialize + Serialize,
{
let header_options = T::to_serialize_field_options();
self.store_custom_serialization_headers(row, col, &header_options)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn get_serialize_dimensions(
&mut self,
struct_name: &str,
) -> Result<(RowNum, ColNum, RowNum, ColNum), XlsxError> {
self.serializer_state.get_dimensions(struct_name)
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn get_serialize_column_dimensions(
&mut self,
struct_name: &str,
field_name: &str,
) -> Result<(RowNum, ColNum, RowNum, ColNum), XlsxError> {
self.serializer_state
.get_column_dimensions(struct_name, field_name)
}
#[cfg(feature = "serde")]
fn store_serialization_headers_with_options(
&mut self,
row: RowNum,
col: ColNum,
headers: &SerializerHeader,
header_options: &SerializeFieldOptions,
) -> Result<&mut Worksheet, XlsxError> {
let field_names: HashSet<String> = HashSet::from_iter(headers.field_names.clone());
for custom_header in &header_options.custom_headers {
if !field_names.contains(&custom_header.field_name) {
return Err(XlsxError::ParameterError(format!(
"No custom field name '{}' found for struct '{}'",
custom_header.field_name, headers.struct_name
)));
}
}
let mut custom_fields: HashMap<&String, &CustomSerializeField> = HashMap::new();
for custom_header in &header_options.custom_headers {
custom_fields.insert(&custom_header.field_name, custom_header);
}
let mut header_options = header_options.clone();
header_options.struct_name.clone_from(&headers.struct_name);
if !header_options.use_custom_headers_only {
let mut custom_headers: Vec<CustomSerializeField> = vec![];
for field_name in &headers.field_names {
match custom_fields.get(field_name) {
Some(custom_field) => {
if !custom_field.skip {
custom_headers.push((*custom_field).clone());
}
}
None => custom_headers.push(CustomSerializeField::new(field_name)),
}
}
header_options.custom_headers = custom_headers;
}
self.store_custom_serialization_headers(row, col, &header_options)
}
#[cfg(feature = "serde")]
fn store_serialization_headers(
&mut self,
row: RowNum,
col: ColNum,
headers: &SerializerHeader,
header_format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let custom_headers: Vec<CustomSerializeField> = headers
.field_names
.iter()
.map(CustomSerializeField::new)
.collect();
let header_options = SerializeFieldOptions {
struct_name: headers.struct_name.clone(),
header_format: Some(header_format.clone()),
custom_headers,
..Default::default()
};
self.store_custom_serialization_headers(row, col, &header_options)
}
#[cfg(feature = "serde")]
fn store_custom_serialization_headers(
&mut self,
row: RowNum,
col: ColNum,
header_options: &SerializeFieldOptions,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if header_options.struct_name.is_empty() {
return Err(XlsxError::ParameterError(
"Serialization headers couldn't be determined, or a struct with `#[serde(flatten)]` was used. \
See the limitations section in the `rust_xlsxwriter::serializer` documentation."
.to_string(),
));
}
if header_options.custom_headers.is_empty() {
return Err(XlsxError::ParameterError(format!(
"No members found/specified for struct '{}'",
header_options.struct_name
)));
}
let mut fields = HashMap::new();
let min_row = row;
let min_col = col;
let mut max_row = row;
let mut max_col = col;
let col_initial = col;
let write_headers = header_options.has_headers;
let mut col_offset = 0;
for custom_header in &header_options.custom_headers {
if custom_header.skip {
continue;
}
let col = col_initial + col_offset as u16;
let mut custom_header = custom_header.clone();
custom_header.col = col;
max_col = col;
col_offset += 1;
if let Some(width) = custom_header.width {
self.set_column_width(col, width)?;
} else if let Some(pixel_width) = custom_header.pixel_width {
self.set_column_width_pixels(col, pixel_width)?;
}
if let Some(format) = &custom_header.column_format {
self.set_column_format(col, format)?;
}
if write_headers {
if let Some(format) = &custom_header.header_format {
self.write_string_with_format(
max_row,
col,
&custom_header.header_name,
format,
)?;
} else if let Some(format) = &header_options.header_format {
self.write_string_with_format(
max_row,
col,
&custom_header.header_name,
format,
)?;
} else {
self.write_string(max_row, col, &custom_header.header_name)?;
}
}
fields.insert(custom_header.field_name.clone(), custom_header);
}
if write_headers {
max_row += 1;
}
if let Some(header_config) = self
.serializer_state
.structs
.get_mut(&header_options.struct_name)
{
if let Some(table_data) = header_config.get_table() {
self.write_serialized_table(&table_data)?;
}
}
let table = match &header_options.table {
Some(table) => {
let mut table = table.clone();
if !header_options.has_headers {
table.show_header_row = false;
}
table.is_serde_table = true;
Some(table)
}
None => None,
};
self.serializer_state.structs.insert(
header_options.struct_name.clone(),
SerializationHeaderConfig {
fields,
min_row,
min_col,
max_row,
max_col,
table,
},
);
Ok(self)
}
#[cfg(feature = "serde")]
fn serialize_data_structure<T>(&mut self, data_structure: &T) -> Result<(), XlsxError>
where
T: Serialize,
{
data_structure.serialize(self)?;
Ok(())
}
#[cfg(feature = "serde")]
pub(crate) fn serialize_to_worksheet_cell(
&mut self,
data: impl IntoExcelData,
) -> Result<(), XlsxError> {
let result = self.serializer_state.current_state();
match result {
Ok(result) => {
let (row, col, value_format) = result;
match &*value_format {
Some(format) => self.write_with_format(row, col, data, format).map(|_| ()),
None => self.write(row, col, data).map(|_| ()),
}
}
Err(()) => Ok(()),
}
}
#[cfg(feature = "serde")]
pub(crate) fn store_serialized_tables(&mut self) -> Result<&mut Worksheet, XlsxError> {
let tables = self.serializer_state.get_tables();
for table_data in tables {
self.write_serialized_table(&table_data)?;
}
Ok(self)
}
#[cfg(feature = "serde")]
pub(crate) fn write_serialized_table(
&mut self,
table_data: &TableData,
) -> Result<&mut Worksheet, XlsxError> {
let min_col = table_data.1;
let max_col = table_data.3;
let min_row = table_data.0;
let mut max_row = table_data.2;
let table = &table_data.4;
if table.show_total_row {
max_row += 1;
}
self.add_table(min_row, min_col, max_row, max_col, table)
}
pub fn set_right_to_left(&mut self, enable: bool) -> &mut Worksheet {
self.right_to_left = enable;
self
}
pub fn set_active(&mut self, enable: bool) -> &mut Worksheet {
self.active = enable;
if self.active {
self.selected = true;
self.visible = Visible::Default;
}
self
}
pub fn set_selected(&mut self, enable: bool) -> &mut Worksheet {
self.selected = enable;
if self.selected {
self.visible = Visible::Default;
}
self
}
pub fn set_hidden(&mut self, enable: bool) -> &mut Worksheet {
if enable {
self.visible = Visible::Hidden;
} else {
self.visible = Visible::Default;
}
if self.visible == Visible::Hidden {
self.selected = false;
self.active = false;
}
self
}
pub fn set_very_hidden(&mut self, enable: bool) -> &mut Worksheet {
if enable {
self.visible = Visible::VeryHidden;
} else {
self.visible = Visible::Default;
}
if self.visible == Visible::VeryHidden {
self.selected = false;
self.active = false;
}
self
}
pub fn set_first_tab(&mut self, enable: bool) -> &mut Worksheet {
self.first_sheet = enable;
if self.selected {
self.visible = Visible::Default;
}
self
}
pub fn set_tab_color(&mut self, color: impl Into<Color>) -> &mut Worksheet {
let color = color.into();
if color.is_valid() {
self.tab_color = color;
}
self
}
pub fn set_paper_size(&mut self, paper_size: u8) -> &mut Worksheet {
self.paper_size = paper_size;
self.page_setup_changed = true;
self
}
pub fn set_page_order(&mut self, enable: bool) -> &mut Worksheet {
self.default_page_order = enable;
if !enable {
self.page_setup_changed = true;
}
self
}
pub fn set_landscape(&mut self) -> &mut Worksheet {
self.is_portrait = false;
self.page_setup_changed = true;
self
}
pub fn set_portrait(&mut self) -> &mut Worksheet {
self.is_portrait = true;
self.page_setup_changed = true;
self
}
pub fn set_view_normal(&mut self) -> &mut Worksheet {
self.page_view = PageView::Normal;
self
}
pub fn set_view_page_layout(&mut self) -> &mut Worksheet {
self.page_view = PageView::PageLayout;
self.page_setup_changed = true;
self
}
pub fn set_view_page_break_preview(&mut self) -> &mut Worksheet {
self.page_view = PageView::PageBreaks;
self.page_setup_changed = true;
self
}
pub fn set_page_breaks(&mut self, breaks: &[RowNum]) -> Result<&mut Worksheet, XlsxError> {
if breaks.is_empty() {
return Ok(self);
}
let breaks = Self::process_pagebreaks(breaks)?;
if *breaks.last().unwrap() >= ROW_MAX {
return Err(XlsxError::RowColumnLimitError);
}
self.horizontal_breaks = breaks;
Ok(self)
}
pub fn set_vertical_page_breaks(
&mut self,
breaks: &[u32],
) -> Result<&mut Worksheet, XlsxError> {
if breaks.is_empty() {
return Ok(self);
}
let breaks = Self::process_pagebreaks(breaks)?;
if *breaks.last().unwrap() >= u32::from(COL_MAX) {
return Err(XlsxError::RowColumnLimitError);
}
self.vertical_breaks = breaks;
Ok(self)
}
pub fn set_zoom(&mut self, zoom: u16) -> &mut Worksheet {
if !(10..=400).contains(&zoom) {
eprintln!("Zoom factor {zoom} outside Excel range: 10 <= zoom <= 400.");
return self;
}
self.zoom = zoom;
self
}
pub fn set_zoom_to_fit(&mut self, enable: bool) -> &mut Worksheet {
self.zoom_to_fit = enable;
self
}
pub fn set_freeze_panes(
&mut self,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
self.panes.freeze_cell = (row, col);
Ok(self)
}
pub fn set_freeze_panes_top_cell(
&mut self,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
self.panes.top_cell = (row, col);
Ok(self)
}
pub fn set_header(&mut self, header: impl Into<String>) -> &mut Worksheet {
let header = header.into();
let header_expanded = header
.replace("&[Tab]", "&A")
.replace("&[Date]", "&D")
.replace("&[File]", "&F")
.replace("&[Page]", "&P")
.replace("&[Path]", "&Z")
.replace("&[Time]", "&T")
.replace("&[Pages]", "&N")
.replace("&[Picture]", "&G");
if header_expanded.chars().count() > 255 {
eprintln!("Header string exceeds Excel's limit of 255 characters.");
return self;
}
self.header = header;
self.page_setup_changed = true;
self.head_footer_changed = true;
self
}
pub fn set_footer(&mut self, footer: impl Into<String>) -> &mut Worksheet {
let footer = footer.into();
let footer_expanded = footer
.replace("&[Tab]", "&A")
.replace("&[Date]", "&D")
.replace("&[File]", "&F")
.replace("&[Page]", "&P")
.replace("&[Path]", "&Z")
.replace("&[Time]", "&T")
.replace("&[Pages]", "&N")
.replace("&[Picture]", "&G");
if footer_expanded.chars().count() > 255 {
eprintln!("Footer string exceeds Excel's limit of 255 characters.");
return self;
}
self.footer = footer;
self.page_setup_changed = true;
self.head_footer_changed = true;
self
}
pub fn set_header_image(
&mut self,
image: &Image,
position: HeaderImagePosition,
) -> Result<&mut Worksheet, XlsxError> {
if !Self::verify_header_footer_image(&self.header, &position) {
let error = format!(
"No &[Picture] or &[G] variable in header string: '{}' for position = '{:?}'",
self.header, position
);
return Err(XlsxError::ParameterError(error));
}
let mut image = image.clone();
image.header_position = position.clone();
image.is_header = true;
self.header_footer_images[position as usize] = Some(image);
Ok(self)
}
pub fn set_footer_image(
&mut self,
image: &Image,
position: HeaderImagePosition,
) -> Result<&mut Worksheet, XlsxError> {
if !Self::verify_header_footer_image(&self.footer, &position) {
let error = format!(
"No &[Picture] or &[G] variable in footer string: '{}' for position = '{:?}'",
self.footer, position
);
return Err(XlsxError::ParameterError(error));
}
let mut image = image.clone();
image.header_position = position.clone();
image.is_header = false;
self.header_footer_images[3 + position as usize] = Some(image);
Ok(self)
}
pub fn set_header_footer_scale_with_doc(&mut self, enable: bool) -> &mut Worksheet {
self.header_footer_scale_with_doc = enable;
if !enable {
self.page_setup_changed = true;
self.head_footer_changed = true;
}
self
}
pub fn set_header_footer_align_with_page(&mut self, enable: bool) -> &mut Worksheet {
self.header_footer_align_with_page = enable;
if !enable {
self.page_setup_changed = true;
self.head_footer_changed = true;
}
self
}
pub fn set_margins(
&mut self,
left: f64,
right: f64,
top: f64,
bottom: f64,
header: f64,
footer: f64,
) -> &mut Worksheet {
if left >= 0.0 {
self.margin_left = left;
self.page_setup_changed = true;
}
if right >= 0.0 {
self.margin_right = right;
self.page_setup_changed = true;
}
if top >= 0.0 {
self.margin_top = top;
self.page_setup_changed = true;
}
if bottom >= 0.0 {
self.margin_bottom = bottom;
self.page_setup_changed = true;
}
if header >= 0.0 {
self.margin_header = header;
self.page_setup_changed = true;
}
if footer >= 0.0 {
self.margin_footer = footer;
self.page_setup_changed = true;
}
self
}
pub fn set_print_first_page_number(&mut self, page_number: u16) -> &mut Worksheet {
self.first_page_number = page_number;
self.page_setup_changed = true;
self
}
pub fn set_print_scale(&mut self, scale: u16) -> &mut Worksheet {
if !(10..=400).contains(&scale) {
eprintln!("Scale factor {scale} outside Excel range: 10 <= zoom <= 400.");
return self;
}
self.fit_to_page = false;
self.print_scale = scale;
self.page_setup_changed = true;
self
}
pub fn set_print_fit_to_pages(&mut self, width: u16, height: u16) -> &mut Worksheet {
self.fit_width = width;
self.fit_height = height;
self.print_scale = 100;
self.fit_to_page = true;
self.page_setup_changed = true;
self
}
pub fn set_print_center_horizontally(&mut self, enable: bool) -> &mut Worksheet {
self.center_horizontally = enable;
if enable {
self.print_options_changed = true;
self.page_setup_changed = true;
}
self
}
pub fn set_print_center_vertically(&mut self, enable: bool) -> &mut Worksheet {
self.center_vertically = enable;
if enable {
self.print_options_changed = true;
self.page_setup_changed = true;
}
self
}
pub fn set_screen_gridlines(&mut self, enable: bool) -> &mut Worksheet {
self.screen_gridlines = enable;
self
}
pub fn set_print_gridlines(&mut self, enable: bool) -> &mut Worksheet {
self.print_gridlines = enable;
if enable {
self.print_options_changed = true;
self.page_setup_changed = true;
}
self
}
pub fn set_print_black_and_white(&mut self, enable: bool) -> &mut Worksheet {
self.print_black_and_white = enable;
if enable {
self.page_setup_changed = true;
}
self
}
pub fn set_print_draft(&mut self, enable: bool) -> &mut Worksheet {
self.print_draft = enable;
if enable {
self.page_setup_changed = true;
}
self
}
pub fn set_print_headings(&mut self, enable: bool) -> &mut Worksheet {
self.print_headings = enable;
if enable {
self.print_options_changed = true;
self.page_setup_changed = true;
}
self
}
pub fn set_print_area(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
if first_row == 0 && first_col == 0 && last_row == ROW_MAX - 1 && last_col == COL_MAX - 1 {
return Ok(self);
}
self.print_area_defined_name.in_use = true;
self.print_area_defined_name.name_type = DefinedNameType::PrintArea;
self.print_area_defined_name.first_row = first_row;
self.print_area_defined_name.first_col = first_col;
self.print_area_defined_name.last_row = last_row;
self.print_area_defined_name.last_col = last_col;
self.page_setup_changed = true;
Ok(self)
}
pub fn set_repeat_rows(
&mut self,
first_row: RowNum,
last_row: RowNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, 0) || !self.check_dimensions_only(last_row, 0) {
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row {
return Err(XlsxError::RowColumnOrderError);
}
self.repeat_row_cols_defined_name.in_use = true;
self.repeat_row_cols_defined_name.name_type = DefinedNameType::PrintTitles;
self.repeat_row_cols_defined_name.first_row = first_row;
self.repeat_row_cols_defined_name.last_row = last_row;
self.page_setup_changed = true;
Ok(self)
}
pub fn set_repeat_columns(
&mut self,
first_col: ColNum,
last_col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(0, first_col) || !self.check_dimensions_only(0, last_col) {
return Err(XlsxError::RowColumnLimitError);
}
if first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
self.repeat_row_cols_defined_name.in_use = true;
self.repeat_row_cols_defined_name.name_type = DefinedNameType::PrintTitles;
self.repeat_row_cols_defined_name.first_col = first_col;
self.repeat_row_cols_defined_name.last_col = last_col;
self.page_setup_changed = true;
Ok(self)
}
pub fn autofit(&mut self) -> &mut Worksheet {
self.autofit_worksheet()
}
pub fn set_autofit_max_row(&mut self, max_row: RowNum) -> &mut Worksheet {
self.max_autofit_row = std::cmp::min(max_row, ROW_MAX - 1);
self
}
pub fn set_autofit_max_width(&mut self, max_width: u32) -> &mut Worksheet {
self.max_autofit_width = std::cmp::min(max_width, MAX_AUTOFIT_WIDTH_PIXELS);
self
}
#[doc(hidden)]
#[deprecated(
since = "0.93.0",
note = "use `set_autofit_max_width()` and `autofit()` instead"
)]
pub fn autofit_to_max_width(&mut self, max_width: u32) -> &mut Worksheet {
self.set_autofit_max_width(max_width);
self.autofit_worksheet()
}
pub fn set_vba_name(&mut self, name: impl Into<String>) -> Result<&mut Worksheet, XlsxError> {
let name = name.into();
utility::validate_vba_name(&name)?;
self.vba_codename = Some(name);
Ok(self)
}
pub fn set_nan_value(&mut self, value: impl Into<String>) -> &mut Worksheet {
self.nan = value.into();
self
}
pub fn set_infinity_value(&mut self, value: impl Into<String>) -> &mut Worksheet {
self.infinity = value.into();
self
}
pub fn set_neg_infinity_value(&mut self, value: impl Into<String>) -> &mut Worksheet {
self.neg_infinity = value.into();
self
}
pub fn ignore_error(
&mut self,
row: RowNum,
col: ColNum,
error_type: IgnoreError,
) -> Result<&mut Worksheet, XlsxError> {
self.ignore_error_range(row, col, row, col, error_type)
}
pub fn ignore_error_range(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
error_type: IgnoreError,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let range = utility::cell_range(first_row, first_col, last_row, last_col);
if !self
.ignored_error_cells
.insert((first_row, first_col, last_row, last_col))
{
return Err(XlsxError::ParameterError(format!(
"Excel only allows on rule per cell. Cell/Range '{range}' was previously used."
)));
}
self.ignored_errors
.entry(error_type)
.and_modify(|sqref| *sqref = format!("{sqref} {range}"))
.or_insert(range);
Ok(self)
}
fn set_writing_ahead(&mut self, enable: bool) {
if self.use_constant_memory {
self.is_writing_ahead = enable;
}
}
fn get_min_row(&self) -> RowNum {
if self.dimensions.first_row == ROW_MAX {
0
} else {
self.dimensions.first_row
}
}
fn get_min_col(&self) -> ColNum {
if self.dimensions.first_col == COL_MAX {
0
} else {
self.dimensions.first_col
}
}
pub(crate) fn hide_autofilter_rows(&mut self) {
if self.filter_conditions.is_empty() || self.filter_automatic_off {
return;
}
let first_row = self.autofilter_defined_name.first_row + 1; let last_row = self.autofilter_defined_name.last_row;
for col_num in self.filter_conditions.clone().keys() {
let filter_condition = self.filter_conditions.get(col_num).unwrap().clone();
for row_num in first_row..=last_row {
if filter_condition.is_list_filter {
if !self.row_matches_list_filter(row_num, *col_num, &filter_condition) {
self.set_row_hidden(row_num).unwrap();
}
} else {
if !self.row_matches_custom_filters(row_num, *col_num, &filter_condition) {
self.set_row_hidden(row_num).unwrap();
}
}
}
}
}
fn row_matches_list_filter(
&self,
row_num: RowNum,
col_num: ColNum,
filter_condition: &FilterCondition,
) -> bool {
let mut has_cell_data = false;
if let Some(columns) = self.data_table.get(&row_num) {
if let Some(cell) = columns.get(&col_num) {
has_cell_data = true;
match cell {
CellType::String { string, .. }
| CellType::RichString {
raw_string: string, ..
} => {
let cell_string = string.clone().to_lowercase().trim().to_string();
for filter in &filter_condition.list {
if cell_string == filter.string.to_lowercase().trim() {
return true;
}
}
if filter_condition.should_match_blanks && cell_string.is_empty() {
return true;
}
}
CellType::Number { number, .. } => {
for filter in &filter_condition.list {
if filter.data_type == FilterDataType::Number
&& number == &filter.number
{
return true;
}
}
}
CellType::Blank { .. } if filter_condition.should_match_blanks => {
return true;
}
_ => {}
}
}
}
if !has_cell_data && filter_condition.should_match_blanks {
return true;
}
false
}
fn row_matches_custom_filters(
&self,
row_num: RowNum,
col_num: ColNum,
filter_condition: &FilterCondition,
) -> bool {
let condition1;
let condition2;
if let Some(data) = &filter_condition.custom1 {
condition1 = self.row_matches_custom_filter(row_num, col_num, data);
} else {
condition1 = false;
}
if let Some(data) = &filter_condition.custom2 {
condition2 = self.row_matches_custom_filter(row_num, col_num, data);
} else {
return condition1;
}
if filter_condition.apply_logical_or {
condition1 || condition2
} else {
condition1 && condition2
}
}
fn row_matches_custom_filter(
&self,
row_num: RowNum,
col_num: ColNum,
filter: &FilterData,
) -> bool {
if let Some(columns) = self.data_table.get(&row_num) {
if let Some(cell) = columns.get(&col_num) {
match cell {
CellType::String { string, .. }
| CellType::RichString {
raw_string: string, ..
} => {
let cell_string = string.clone().to_lowercase().trim().to_string();
let filter_string = filter.string.to_lowercase().trim().to_string();
match filter.criteria {
FilterCriteria::EqualTo => return cell_string == filter_string,
FilterCriteria::NotEqualTo => return cell_string != filter_string,
FilterCriteria::LessThan => return cell_string < filter_string,
FilterCriteria::GreaterThan => return cell_string > filter_string,
FilterCriteria::LessThanOrEqualTo => {
return cell_string <= filter_string
}
FilterCriteria::GreaterThanOrEqualTo => {
return cell_string >= filter_string
}
FilterCriteria::EndsWith => {
return cell_string.ends_with(&filter_string)
}
FilterCriteria::DoesNotEndWith => {
return !cell_string.ends_with(&filter_string)
}
FilterCriteria::BeginsWith => {
return cell_string.starts_with(&filter_string)
}
FilterCriteria::DoesNotBeginWith => {
return !cell_string.starts_with(&filter_string)
}
FilterCriteria::Contains => {
return cell_string.contains(&filter_string)
}
FilterCriteria::DoesNotContain => {
return !cell_string.contains(&filter_string)
}
}
}
CellType::Number { number, .. } => {
if filter.data_type == FilterDataType::Number {
match filter.criteria {
FilterCriteria::EqualTo => return *number == filter.number,
FilterCriteria::LessThan => return *number < filter.number,
FilterCriteria::NotEqualTo => return *number != filter.number,
FilterCriteria::GreaterThan => return *number > filter.number,
FilterCriteria::LessThanOrEqualTo => {
return *number <= filter.number
}
FilterCriteria::GreaterThanOrEqualTo => {
return *number >= filter.number
}
_ => {}
}
}
}
CellType::Blank { .. } => {
if filter.criteria == FilterCriteria::NotEqualTo && filter.string == " " {
return false;
}
}
_ => {
if filter.criteria == FilterCriteria::NotEqualTo && filter.string == " " {
return true;
}
}
}
}
}
false
}
pub(crate) fn process_pagebreaks(breaks: &[u32]) -> Result<Vec<u32>, XlsxError> {
let unique_breaks: HashSet<u32> = breaks.iter().copied().collect();
let mut breaks: Vec<u32> = unique_breaks.into_iter().collect();
breaks.sort_unstable();
if breaks[0] == 0 {
breaks.remove(0);
}
if breaks.len() > 1023 {
let error =
"Maximum number of horizontal or vertical pagebreaks allowed by Excel is 1023"
.to_string();
return Err(XlsxError::ParameterError(error));
}
Ok(breaks)
}
fn store_number(
&mut self,
row: RowNum,
col: ColNum,
number: impl Into<f64>,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_number_type(row, col, number.into(), format, false)
}
fn store_datetime(
&mut self,
row: RowNum,
col: ColNum,
number: f64,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
self.store_number_type(row, col, number, format, true)
}
fn store_number_type(
&mut self,
row: RowNum,
col: ColNum,
number: f64,
format: Option<&Format>,
is_datetime: bool,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if number.is_nan() {
return self.store_string(row, col, self.nan.clone(), format);
}
if number.is_infinite() {
if number == f64::INFINITY {
return self.store_string(row, col, self.infinity.clone(), format);
}
return self.store_string(row, col, self.neg_infinity.clone(), format);
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => self.row_col_format(row, col),
};
let cell = if is_datetime {
CellType::DateTime { number, xf_index }
} else {
CellType::Number { number, xf_index }
};
self.insert_cell(row, col, cell);
Ok(self)
}
fn store_string(
&mut self,
row: RowNum,
col: ColNum,
string: String,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
if string.is_empty() {
match format {
Some(format) => return self.write_blank(row, col, format),
None => return Ok(self),
};
}
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if string.chars().count() > MAX_STRING_LEN {
return Err(XlsxError::MaxStringLengthExceeded);
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => self.row_col_format(row, col),
};
let cell = if self.use_inline_strings {
CellType::InlineString {
string: Arc::from(string),
xf_index,
}
} else {
let mut string_id = None;
let string = Arc::from(string);
if self.has_workbook_global_sst {
let mut string_table = self.string_table.lock().unwrap();
string_id = Some(string_table.shared_string_index(Arc::clone(&string)));
}
CellType::String {
string,
xf_index,
string_id,
}
};
if !self.use_inline_strings {
self.has_local_string_table = true;
}
self.insert_cell(row, col, cell);
Ok(self)
}
fn store_rich_string(
&mut self,
row: RowNum,
col: ColNum,
string: &str,
raw_string: &str,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
if string.is_empty() {
match format {
Some(format) => return self.write_blank(row, col, format),
None => return Ok(self),
};
}
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if string.chars().count() > MAX_STRING_LEN {
return Err(XlsxError::MaxStringLengthExceeded);
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => self.row_col_format(row, col),
};
let mut string_id = None;
let string = Arc::from(string);
let raw_string = Arc::from(raw_string);
if self.has_workbook_global_sst {
let mut string_table = self.string_table.lock().unwrap();
string_id = Some(string_table.shared_string_index(Arc::clone(&string)));
}
let cell = CellType::RichString {
string,
xf_index,
raw_string,
string_id,
};
self.insert_cell(row, col, cell);
if !self.use_inline_strings {
self.has_local_string_table = true;
}
Ok(self)
}
fn store_formula(
&mut self,
row: RowNum,
col: ColNum,
formula: Formula,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
if formula.has_dynamic_function {
return self.store_array_formula(row, col, row, col, formula, format, true);
}
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => self.row_col_format(row, col),
};
let result = if formula.result.is_empty() {
self.default_result.clone()
} else {
formula.result.clone()
};
let cell = CellType::Formula {
formula: Box::from(formula.formula_string),
xf_index,
result,
};
self.insert_cell(row, col, cell);
Ok(self)
}
#[allow(clippy::too_many_arguments)]
fn store_array_formula(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
formula: Formula,
format: Option<&Format>,
is_dynamic: bool,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(first_row, first_col)
|| !self.check_dimensions(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => self.row_col_format(first_row, first_col),
};
let range = utility::cell_range(first_row, first_col, last_row, last_col);
let mut is_dynamic = is_dynamic;
if !is_dynamic && formula.has_dynamic_function {
is_dynamic = true;
}
if is_dynamic {
self.has_dynamic_arrays = true;
}
let result = if formula.result.is_empty() {
self.default_result.clone()
} else {
formula.result.clone()
};
let cell = CellType::ArrayFormula {
formula: Box::from(formula.formula_string),
xf_index,
result,
is_dynamic,
range: range.into_boxed_str(),
};
self.insert_cell(first_row, first_col, cell);
for col in first_col + 1..=last_col {
match format {
Some(format) => self.write_number_with_format(first_row, col, 0, format)?,
None => self.write_number(first_row, col, 0)?,
};
}
self.set_writing_ahead(true);
for row in first_row + 1..=last_row {
for col in first_col..=last_col {
match format {
Some(format) => self.write_number_with_format(row, col, 0, format)?,
None => self.write_number(row, col, 0)?,
};
}
}
self.set_writing_ahead(false);
Ok(self)
}
fn store_blank(
&mut self,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let mut xf_index = self.format_xf_index(format);
if xf_index == 0 {
xf_index = self.row_col_format(row, col);
}
let cell = CellType::Blank { xf_index };
self.insert_cell(row, col, cell);
Ok(self)
}
fn store_boolean(
&mut self,
row: RowNum,
col: ColNum,
boolean: bool,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => self.row_col_format(row, col),
};
let cell = CellType::Boolean { boolean, xf_index };
self.insert_cell(row, col, cell);
Ok(self)
}
fn store_url(
&mut self,
row: RowNum,
col: ColNum,
url: &Url,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
let mut hyperlink = url.clone();
hyperlink.initialize()?;
match format {
Some(format) => self.write_string_with_format(row, col, &hyperlink.text, format)?,
None => {
let hyperlink_format = self.default_format.clone().set_hyperlink();
self.write_string_with_format(row, col, &hyperlink.text, &hyperlink_format)?
}
};
self.hyperlinks.insert((row, col), hyperlink);
Ok(self)
}
fn store_embedded_image(
&mut self,
row: RowNum,
col: ColNum,
image: &Image,
format: Option<&Format>,
) -> Result<&mut Worksheet, XlsxError> {
if !self.check_dimensions(row, col) {
return Err(XlsxError::RowColumnLimitError);
}
if self.use_constant_memory && row < self.current_row {
return Err(XlsxError::ParameterError(format!(
"Cannot embed image to previously written row {row} in 'constant memory' mode."
)));
}
let image_id = match self.embedded_image_ids.get(&image.hash) {
Some(image_id) => *image_id,
None => {
let image_id = self.embedded_image_ids.len() as u32;
self.embedded_image_ids.insert(image.hash.clone(), image_id);
self.embedded_images.push(image.clone());
image_id
}
};
if !image.alt_text.is_empty() {
self.has_embedded_image_descriptions = true;
}
self.image_types[image.image_type.clone() as usize] = true;
if let Some(url) = &image.url {
self.hyperlinks.insert((row, col), url.clone());
}
let xf_index = match format {
Some(format) => self.format_xf_index(format),
None => match image.url {
Some(_) => {
let format = self.default_format.clone().set_hyperlink();
self.format_xf_index(&format)
}
None => self.row_col_format(row, col),
},
};
let cell = CellType::Error {
xf_index,
value: image_id,
};
self.insert_cell(row, col, cell);
Ok(self)
}
#[allow(clippy::similar_names)]
fn get_rich_string(segments: &[(&Format, &str)]) -> Result<(String, String), XlsxError> {
if segments.is_empty() {
let error = "Rich string must contain at least 1 (&Format, &str) tuple.";
return Err(XlsxError::ParameterError(error.to_string()));
}
let xf_formats: Vec<Format> = vec![];
let dxf_formats: Vec<Format> = vec![];
let mut styler = Styles::new(
&xf_formats,
&dxf_formats,
0,
0,
0,
vec![],
false,
false,
true,
);
let mut raw_string = String::new();
let mut first_segment = true;
for (format, string) in segments {
if string.is_empty() {
let error = "Strings in rich string (&Format, &str) tuples cannot be blank.";
return Err(XlsxError::ParameterError(error.to_string()));
}
raw_string.push_str(string);
let whitespace = ['\t', '\n', ' '];
let attributes = if string.starts_with(whitespace) || string.ends_with(whitespace) {
vec![("xml:space", "preserve")]
} else {
vec![]
};
if format.is_default() && first_segment {
xml_start_tag_only(&mut styler.writer, "r");
xml_data_element(&mut styler.writer, "t", string, &attributes);
xml_end_tag(&mut styler.writer, "r");
} else {
xml_start_tag_only(&mut styler.writer, "r");
styler.write_font(&format.font, false);
xml_data_element(&mut styler.writer, "t", string, &attributes);
xml_end_tag(&mut styler.writer, "r");
}
first_segment = false;
}
let got = xmlwriter::cursor_to_string(&styler.writer);
Ok((got, raw_string))
}
fn insert_cell(&mut self, row: RowNum, col: ColNum, cell: CellType) {
if self.use_constant_memory {
#[cfg(feature = "constant_memory")]
{
if row < self.current_row {
eprintln!(
"Ignoring write to previously written row {row} in 'constant memory' mode."
);
return;
}
if row > self.current_row {
if self.is_writing_ahead {
Self::insert_cell_to_table(row, col, cell, &mut self.write_ahead);
return;
}
self.flush_to_row(row);
}
Self::insert_cell_to_table(row, col, cell, &mut self.data_table);
}
} else {
Self::insert_cell_to_table(row, col, cell, &mut self.data_table);
}
}
fn insert_cell_to_table(
row: RowNum,
col: ColNum,
cell: CellType,
data_table: &mut BTreeMap<RowNum, BTreeMap<ColNum, CellType>>,
) {
match data_table.entry(row) {
Entry::Occupied(mut entry) => {
let columns = entry.get_mut();
columns.insert(col, cell);
}
Entry::Vacant(entry) => {
let columns = BTreeMap::from([(col, cell)]);
entry.insert(columns);
}
}
}
fn insert_cell_format(&mut self, row: RowNum, col: ColNum, format_id: u32) {
match self.data_table.entry(row) {
Entry::Occupied(mut entry) => {
let columns = entry.get_mut();
match columns.get_mut(&col) {
Some(cell) => match cell {
CellType::Blank { xf_index, .. }
| CellType::Error { xf_index, .. }
| CellType::String { xf_index, .. }
| CellType::Number { xf_index, .. }
| CellType::Boolean { xf_index, .. }
| CellType::Formula { xf_index, .. }
| CellType::DateTime { xf_index, .. }
| CellType::RichString { xf_index, .. }
| CellType::InlineString { xf_index, .. }
| CellType::ArrayFormula { xf_index, .. } => {
*xf_index = format_id;
}
},
None => {
let cell = CellType::Blank {
xf_index: format_id,
};
columns.insert(col, cell);
}
}
}
Entry::Vacant(entry) => {
if format_id > 0 {
let cell = CellType::Blank {
xf_index: format_id,
};
let columns = BTreeMap::from([(col, cell)]);
entry.insert(columns);
}
}
}
}
fn update_cell_format(&mut self, row: RowNum, col: ColNum, format_id: u32) {
let Some(columns) = self.data_table.get_mut(&row) else {
return;
};
let Some(cell) = columns.get_mut(&col) else {
return;
};
match cell {
CellType::Blank { xf_index, .. }
| CellType::Error { xf_index, .. }
| CellType::String { xf_index, .. }
| CellType::Number { xf_index, .. }
| CellType::Boolean { xf_index, .. }
| CellType::Formula { xf_index, .. }
| CellType::DateTime { xf_index, .. }
| CellType::RichString { xf_index, .. }
| CellType::InlineString { xf_index, .. }
| CellType::ArrayFormula { xf_index, .. } => {
*xf_index = format_id;
}
}
}
fn clear_cell_internal(&mut self, row: RowNum, col: ColNum) {
let Some(columns) = self.data_table.get_mut(&row) else {
return;
};
columns.remove(&col);
}
fn clear_cell_format_internal(&mut self, row: RowNum, col: ColNum) {
self.update_cell_format(row, col, 0);
}
fn set_range_border_cell(
&mut self,
row: RowNum,
col: ColNum,
cell_format: &Format,
border_format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let cell_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::All);
self.set_cell_format(row, col, &cell_format)
}
fn set_range_border_row(
&mut self,
row: RowNum,
first_col: ColNum,
last_col: ColNum,
cell_format: &Format,
border_format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let left_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::RowLeft);
let right_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::RowRight);
let center_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::RowCenter);
for col in first_col..=last_col {
if col == first_col {
self.set_cell_format(row, col, &left_format)?;
} else if col == last_col {
self.set_cell_format(row, col, &right_format)?;
} else {
self.set_cell_format(row, col, ¢er_format)?;
}
}
Ok(self)
}
fn set_range_border_col(
&mut self,
first_row: RowNum,
last_row: RowNum,
col: ColNum,
cell_format: &Format,
border_format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let top_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::ColTop);
let bottom_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::ColBottom);
let center_format =
Self::combined_border_format(cell_format, border_format, BorderPosition::ColCenter);
for row in first_row..=last_row {
if row == first_row {
self.set_cell_format(row, col, &top_format)?;
} else if row == last_row {
self.set_cell_format(row, col, &bottom_format)?;
} else {
self.set_cell_format(row, col, ¢er_format)?;
}
}
Ok(self)
}
fn set_range_border_range(
&mut self,
first_row: RowNum,
last_row: RowNum,
first_col: ColNum,
last_col: ColNum,
cell_format: &Format,
border_format: &Format,
) -> Result<&mut Worksheet, XlsxError> {
let top_left =
Self::combined_border_format(cell_format, border_format, BorderPosition::TopLeft);
let top_center =
Self::combined_border_format(cell_format, border_format, BorderPosition::TopCenter);
let top_right =
Self::combined_border_format(cell_format, border_format, BorderPosition::TopRight);
let center_left =
Self::combined_border_format(cell_format, border_format, BorderPosition::CenterLeft);
let center_center =
Self::combined_border_format(cell_format, border_format, BorderPosition::CenterCenter);
let center_right =
Self::combined_border_format(cell_format, border_format, BorderPosition::CenterRight);
let bottom_left =
Self::combined_border_format(cell_format, border_format, BorderPosition::BottomLeft);
let bottom_center =
Self::combined_border_format(cell_format, border_format, BorderPosition::BottomCenter);
let bottom_right =
Self::combined_border_format(cell_format, border_format, BorderPosition::BottomRight);
for row in first_row..=last_row {
for col in first_col..=last_col {
if row == first_row {
if col == first_col {
self.set_cell_format(row, col, &top_left)?;
} else if col == last_col {
self.set_cell_format(row, col, &top_right)?;
} else {
self.set_cell_format(row, col, &top_center)?;
}
} else if row == last_row {
if col == first_col {
self.set_cell_format(row, col, &bottom_left)?;
} else if col == last_col {
self.set_cell_format(row, col, &bottom_right)?;
} else {
self.set_cell_format(row, col, &bottom_center)?;
}
} else if col == first_col {
self.set_cell_format(row, col, ¢er_left)?;
} else if col == last_col {
self.set_cell_format(row, col, ¢er_right)?;
} else {
self.set_cell_format(row, col, ¢er_center)?;
}
}
}
Ok(self)
}
fn combined_border_format(
cell_format: &Format,
border_format: &Format,
position: BorderPosition,
) -> Format {
let mut cell_format = cell_format.clone();
match position {
BorderPosition::All
| BorderPosition::RowLeft
| BorderPosition::RowCenter
| BorderPosition::RowRight
| BorderPosition::ColTop
| BorderPosition::TopLeft
| BorderPosition::TopCenter
| BorderPosition::TopRight => {
cell_format.borders.top_style = border_format.borders.top_style;
cell_format.borders.top_color = border_format.borders.top_color;
}
_ => {}
}
match position {
BorderPosition::All
| BorderPosition::RowLeft
| BorderPosition::RowCenter
| BorderPosition::RowRight
| BorderPosition::ColBottom
| BorderPosition::BottomLeft
| BorderPosition::BottomCenter
| BorderPosition::BottomRight => {
cell_format.borders.bottom_style = border_format.borders.bottom_style;
cell_format.borders.bottom_color = border_format.borders.bottom_color;
}
_ => {}
}
match position {
BorderPosition::All
| BorderPosition::RowLeft
| BorderPosition::ColTop
| BorderPosition::ColCenter
| BorderPosition::ColBottom
| BorderPosition::TopLeft
| BorderPosition::CenterLeft
| BorderPosition::BottomLeft => {
cell_format.borders.left_style = border_format.borders.left_style;
cell_format.borders.left_color = border_format.borders.left_color;
}
_ => {}
}
match position {
BorderPosition::All
| BorderPosition::RowRight
| BorderPosition::ColTop
| BorderPosition::ColCenter
| BorderPosition::ColBottom
| BorderPosition::TopRight
| BorderPosition::CenterRight
| BorderPosition::BottomRight => {
cell_format.borders.right_style = border_format.borders.right_style;
cell_format.borders.right_color = border_format.borders.right_color;
}
_ => {}
}
cell_format
}
fn set_column_width_internal(
&mut self,
col: ColNum,
width: u32,
autofit: bool,
) -> Result<&mut Worksheet, XlsxError> {
if width == 0 {
return self.set_column_hidden(col);
}
if col >= COL_MAX {
return Err(XlsxError::RowColumnLimitError);
}
self.store_column_width(col, width, autofit);
Ok(self)
}
fn store_column_width(&mut self, col: ColNum, width: u32, autofit: bool) {
let mut width = width;
if width > self.max_col_width {
width = self.max_col_width;
}
match self.changed_cols.get_mut(&col) {
Some(col_options) => {
if autofit && !col_options.autofit {
if width > col_options.width {
col_options.width = width;
col_options.autofit = true;
}
} else {
col_options.width = width;
col_options.autofit = autofit;
}
}
None => {
let col_options = ColOptions {
width,
xf_index: 0,
hidden: false,
level: 0,
collapsed: false,
autofit,
format: None,
};
self.changed_cols.insert(col, col_options);
}
}
}
fn check_dimensions(&mut self, row: RowNum, col: ColNum) -> bool {
if row >= ROW_MAX {
return false;
}
if col >= COL_MAX {
return false;
}
self.dimensions.first_row = cmp::min(self.dimensions.first_row, row);
self.dimensions.first_col = cmp::min(self.dimensions.first_col, col);
if self.use_constant_memory {
if row >= self.current_row {
self.dimensions.last_row = cmp::max(self.dimensions.last_row, row);
self.dimensions.last_col = cmp::max(self.dimensions.last_col, col);
}
} else {
self.dimensions.last_row = cmp::max(self.dimensions.last_row, row);
self.dimensions.last_col = cmp::max(self.dimensions.last_col, col);
}
true
}
#[allow(clippy::unused_self)]
pub(crate) fn check_dimensions_only(&mut self, row: RowNum, col: ColNum) -> bool {
if row >= ROW_MAX {
return false;
}
if col >= COL_MAX {
return false;
}
true
}
fn format_xf_index(&mut self, format: &Format) -> u32 {
if self.has_workbook_global_xfs {
self.format_xf_index_global(format)
} else {
self.format_xf_index_local(format)
}
}
fn format_xf_index_global(&mut self, format: &Format) -> u32 {
let xf_indices = self.workbook_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.workbook_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_xf_index_local(&mut self, format: &Format) -> u32 {
match self.xf_indices.get_mut(format) {
Some(xf_index) => *xf_index,
None => {
let xf_index = self.xf_formats.len() as u32;
self.xf_formats.push(format.clone());
self.xf_indices.insert(format.clone(), xf_index);
if format.font.is_hyperlink {
self.has_hyperlink_style = true;
}
xf_index
}
}
}
fn get_global_xf_index(&self, xf_index: u32) -> u32 {
if self.has_workbook_global_xfs {
xf_index
} else {
self.global_xf_indices[xf_index as usize]
}
}
fn row_col_format(&mut self, row: RowNum, col: ColNum) -> u32 {
let Some(col_option) = self.changed_cols.get(&col) else {
return 0;
};
let Some(col_format) = &col_option.format else {
return 0;
};
let Some(row_option) = self.changed_rows.get(&row) else {
return 0;
};
let Some(row_format) = &row_option.format else {
return 0;
};
let combined_format = Format::merge(row_format, col_format);
self.format_xf_index(&combined_format)
}
#[doc(hidden)] pub 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;
let mut dxf_format = format.clone();
dxf_format.is_dxf_format = true;
self.dxf_formats.push(dxf_format.clone());
self.dxf_indices.insert(dxf_format, dxf_index);
if format.font.is_hyperlink {
self.has_hyperlink_style = true;
}
dxf_index
}
}
}
pub(crate) fn set_global_xf_indices(&mut self, workbook_xf_indices: &[u32]) {
self.global_xf_indices = workbook_xf_indices.to_vec();
}
pub(crate) fn set_global_dxf_indices(&mut self, workbook_dxf_indices: &[u32]) {
self.global_dxf_indices = workbook_dxf_indices.to_vec();
}
fn get_cell_xf_index(
&mut self,
xf_index: u32,
row_options: Option<&RowOptions>,
col_num: ColNum,
) -> u32 {
let mut xf_index = xf_index;
if xf_index == 0 {
if let Some(row_options) = row_options {
xf_index = row_options.xf_index;
}
}
if xf_index == 0 {
if let Some(col_options) = self.changed_cols.get(&col_num) {
xf_index = col_options.xf_index;
}
}
if xf_index != 0 {
xf_index = self.get_global_xf_index(xf_index);
}
xf_index
}
pub(crate) fn prepare_worksheet_images(
&mut self,
image_ids: &mut HashMap<String, u32>,
image_id: &mut u32,
drawing_id: u32,
) {
for (cell, image) in &self.images.clone() {
let row = cell.0;
let col = cell.1;
let mut drawing_hyperlink = None;
let image_id = match image_ids.get(&image.hash) {
Some(image_id) => *image_id,
None => {
*image_id += 1;
image_ids.insert(image.hash.clone(), *image_id);
*image_id
}
};
if let Some(hyperlink) = &image.url {
let mut hyperlink = hyperlink.clone();
let target = hyperlink.target();
let target_mode = hyperlink.target_mode();
let rel_id = match self.drawing_rel_ids.get(&hyperlink.link) {
Some(rel_id) => *rel_id,
None => {
let rel_id = 1 + self.drawing_rel_ids.len() as u32;
self.drawing_rel_ids.insert(hyperlink.link.clone(), rel_id);
self.drawing_relationships.push((
"hyperlink".to_string(),
target,
target_mode,
));
rel_id
}
};
hyperlink.rel_id = rel_id;
drawing_hyperlink = Some(hyperlink);
}
let rel_id = match self.drawing_rel_ids.get(&image.hash) {
Some(rel_id) => *rel_id,
None => {
let rel_id = 1 + self.drawing_rel_ids.len() as u32;
self.drawing_rel_ids.insert(image.hash.clone(), rel_id);
let image_name =
format!("../media/image{image_id}.{}", image.image_type.extension());
self.drawing_relationships.push((
"image".to_string(),
image_name,
String::new(),
));
rel_id
}
};
let mut drawing_info = self.position_object_emus(row, col, image);
drawing_info.rel_id = rel_id;
drawing_info.url.clone_from(&drawing_hyperlink);
self.drawing.drawings.push(drawing_info);
self.image_types[image.image_type.clone() as usize] = true;
}
let drawing_name = format!("../drawings/drawing{drawing_id}.xml");
self.drawing_object_relationships.push((
"drawing".to_string(),
drawing_name,
String::new(),
));
self.has_drawing_object_linkage = true;
}
pub(crate) fn prepare_background_image(&mut self, image_id: u32, image: &Image) {
let image_name = format!("../media/image{image_id}.{}", image.image_type.extension());
self.image_types[image.image_type.clone() as usize] = true;
self.background_relationships
.push(("image".to_string(), image_name, String::new()));
}
pub(crate) fn prepare_worksheet_shapes(&mut self, shape_id: u32, drawing_id: u32) {
for (shape_id, (cell, shape)) in (shape_id..).zip(self.shapes.clone().iter()) {
let row = cell.0;
let col = cell.1;
let mut drawing_hyperlink = None;
if let Some(hyperlink) = &shape.url {
let mut hyperlink = hyperlink.clone();
let target = hyperlink.target();
let target_mode = hyperlink.target_mode();
let rel_id = match self.drawing_rel_ids.get(&hyperlink.link) {
Some(rel_id) => *rel_id,
None => {
let rel_id = 1 + self.drawing_rel_ids.len() as u32;
self.drawing_rel_ids.insert(hyperlink.link.clone(), rel_id);
self.drawing_relationships.push((
"hyperlink".to_string(),
target,
target_mode,
));
rel_id
}
};
hyperlink.rel_id = rel_id;
drawing_hyperlink = Some(hyperlink);
}
let mut drawing_info = self.position_object_emus(row, col, shape);
drawing_info.rel_id = shape_id;
drawing_info.url.clone_from(&drawing_hyperlink);
self.drawing.drawings.push(drawing_info);
self.drawing.shapes.push(shape.clone());
}
if self.drawing_object_relationships.is_empty() {
let drawing_name = format!("../drawings/drawing{drawing_id}.xml");
self.drawing_object_relationships.push((
"drawing".to_string(),
drawing_name,
String::new(),
));
self.has_drawing_object_linkage = true;
}
}
pub(crate) fn prepare_header_footer_images(
&mut self,
image_ids: &mut HashMap<String, u32>,
image_id: &mut u32,
) {
let mut rel_ids: HashMap<String, u32> = HashMap::new();
for image in self.header_footer_images.clone().into_iter().flatten() {
let image_id = match image_ids.get(&image.hash) {
Some(image_id) => *image_id,
None => {
*image_id += 1;
image_ids.insert(image.hash.clone(), *image_id);
*image_id
}
};
let rel_id = match rel_ids.get(&image.hash) {
Some(rel_id) => *rel_id,
None => {
let rel_id = 1 + rel_ids.len() as u32;
rel_ids.insert(image.hash.clone(), rel_id);
let image_name =
format!("../media/image{image_id}.{}", image.image_type.extension());
self.vml_drawing_relationships.push((
"image".to_string(),
image_name,
String::new(),
));
rel_id
}
};
let mut vml_info = image.vml_info();
vml_info.rel_id = rel_id;
self.header_footer_vml_info.push(vml_info);
self.image_types[image.image_type as usize] = true;
}
}
pub(crate) fn add_vml_drawing_rel_link(&mut self, drawing_id: u32) {
let vml_drawing_name = format!("../drawings/vmlDrawing{drawing_id}.vml");
self.drawing_object_relationships.push((
"vmlDrawing".to_string(),
vml_drawing_name,
String::new(),
));
}
pub(crate) fn prepare_vml_objects(&mut self, vml_data_id: u32, vml_shape_id: u32) -> u32 {
let mut button_id = 1;
let mut note_count = 0;
for columns in self.notes.values_mut() {
for note in columns.values_mut() {
if self.show_all_notes && note.is_visible.is_none() {
note.is_visible = Some(true);
}
let Some(note_author) = ¬e.author else {
continue;
};
match self.note_authors.get(note_author) {
Some(id) => {
note.author_id = *id;
}
None => {
let id = self.note_authors.len();
self.note_authors.insert(note_author.clone(), id);
note.author_id = id;
}
}
}
}
for (cell_row, columns) in &self.notes.clone() {
for (cell_col, note) in columns {
let note_row = note.row();
let note_col = note.col();
let mut vml_info = note.vml_info();
vml_info.drawing_info = self.position_object_pixels(note_row, note_col, note);
vml_info.row = *cell_row;
vml_info.col = *cell_col;
self.comments_vml_info.push(vml_info);
note_count += 1;
}
}
for ((row, col, _, _), button) in self.buttons.clone() {
let mut button = button.clone();
if button.name.is_empty() {
button.name = format!("Button {button_id}");
}
if button.macro_name.is_empty() {
button.macro_name = format!("[0]!Button{button_id}_Click");
} else {
button.macro_name = format!("[0]!{}", button.macro_name);
}
let mut vml_info = button.vml_info();
vml_info.drawing_info = self.position_object_pixels(row, col, &button);
self.buttons_vml_info.push(vml_info);
button_id += 1;
}
let mut oid_map = vml_data_id.to_string();
for i in 0..note_count / 1024 {
let next_id = vml_data_id + i + 1;
oid_map = format!("{oid_map},{next_id}");
}
self.vml_data_id = oid_map;
self.vml_shape_id = vml_shape_id;
note_count
}
pub(crate) fn add_comment_rel_link(&mut self, comment_id: u32) {
let comment_name = format!("../comments{comment_id}.xml");
self.comment_relationships
.push(("comments".to_string(), comment_name, String::new()));
}
pub(crate) fn prepare_worksheet_charts(&mut self, chart_id: u32, drawing_id: u32) {
for (chart_id, chart) in (chart_id..).zip(self.charts.values_mut()) {
chart.id = chart_id;
chart.add_axis_ids(chart_id);
}
let mut rel_id = self.drawing_relationships.len() as u32;
self.drawing_rel_ids.insert("Chart".to_string(), rel_id);
for (cell, chart) in &mut self.charts.clone() {
let row = cell.0;
let col = cell.1;
let chart_id = chart.id;
let chart_name = format!("../charts/chart{chart_id}.xml");
self.drawing_relationships
.push(("chart".to_string(), chart_name, String::new()));
let mut drawing_info = self.position_object_emus(row, col, chart);
rel_id += 1;
drawing_info.rel_id = rel_id;
if self.is_chartsheet {
drawing_info.drawing_type = DrawingType::ChartSheet;
drawing_info.is_portrait = self.is_portrait;
}
self.drawing.drawings.push(drawing_info);
}
if self.is_chartsheet && self.protection_on {
if let Some(chart) = self.charts.get_mut(&(0, 0, 0, 0)) {
chart.protection_on = true;
}
}
if !self.has_drawing_object_linkage {
let drawing_name = format!("../drawings/drawing{drawing_id}.xml");
self.drawing_object_relationships.push((
"drawing".to_string(),
drawing_name,
String::new(),
));
}
}
pub(crate) fn prepare_worksheet_tables(&mut self, mut table_id: u32) -> u32 {
for table in &mut self.tables {
table.index = table_id;
if table.name.is_empty() {
table.name = format!("Table{table_id}");
}
for column in &mut table.columns {
if let Some(format) = column.format.as_mut() {
format.dxf_index = self.global_dxf_indices[format.dxf_index as usize];
}
}
self.table_relationships.push((
"table".to_string(),
format!("../tables/table{table_id}.xml"),
String::new(),
));
table_id += 1;
}
table_id
}
fn position_object_emus<T>(&mut self, row: RowNum, col: ColNum, object: &T) -> DrawingInfo
where
T: DrawingObject,
{
let mut drawing_info = self.position_object_pixels(row, col, object);
drawing_info.to.col_offset = round_to_emus(drawing_info.to.col_offset);
drawing_info.to.row_offset = round_to_emus(drawing_info.to.row_offset);
drawing_info.from.col_offset = round_to_emus(drawing_info.from.col_offset);
drawing_info.from.row_offset = round_to_emus(drawing_info.from.row_offset);
drawing_info.col_absolute *= 9525;
drawing_info.row_absolute *= 9525;
drawing_info.width = round_to_emus(drawing_info.width);
drawing_info.height = round_to_emus(drawing_info.height);
drawing_info
}
fn position_object_pixels<T>(&mut self, row: RowNum, col: ColNum, object: &T) -> DrawingInfo
where
T: DrawingObject,
{
let mut row_start: RowNum = row; let mut col_start: ColNum = col;
let mut x1: u32 = object.x_offset(); let mut y1: u32 = object.y_offset();
let mut row_end: RowNum; let mut col_end: ColNum;
let mut x2: f64; let mut y2: f64;
let width = object.width_scaled(); let height = object.height_scaled();
let mut x_abs: u32 = 0; let mut y_abs: u32 = 0;
for col in 0..col_start {
x_abs += self.column_pixel_width(col, object.object_movement());
}
x_abs += x1;
for row in 0..row_start {
y_abs += self.row_pixel_height(row, object.object_movement());
}
y_abs += y1;
loop {
let col_size = self.column_pixel_width(col_start, object.object_movement());
if x1 >= col_size {
x1 -= col_size;
col_start += 1;
} else {
break;
}
}
loop {
let row_size = self.row_pixel_height(row_start, object.object_movement());
if y1 >= row_size {
y1 -= row_size;
row_start += 1;
} else {
break;
}
}
col_end = col_start;
row_end = row_start;
x2 = width + f64::from(x1);
y2 = height + f64::from(y1);
loop {
let col_size = f64::from(self.column_pixel_width(col_end, object.object_movement()));
if x2 >= col_size {
x2 -= col_size;
col_end += 1;
} else {
break;
}
}
loop {
let row_size = f64::from(self.row_pixel_height(row_end, object.object_movement()));
if y2 >= row_size {
y2 -= row_size;
row_end += 1;
} else {
break;
}
}
let from = DrawingCoordinates {
col: u32::from(col_start),
row: row_start,
col_offset: f64::from(x1),
row_offset: f64::from(y1),
};
let to = DrawingCoordinates {
col: u32::from(col_end),
row: row_end,
col_offset: x2,
row_offset: y2,
};
DrawingInfo {
from,
to,
col_absolute: u64::from(x_abs),
row_absolute: u64::from(y_abs),
width,
height,
name: object.name(),
description: object.alt_text(),
decorative: object.decorative(),
object_movement: object.object_movement(),
drawing_type: object.drawing_type(),
rel_id: 0,
url: None,
is_portrait: false,
}
}
fn column_pixel_width(&mut self, col: ColNum, position: ObjectMovement) -> u32 {
match self.changed_cols.get(&col) {
Some(col_options) => {
let hidden = col_options.hidden;
if hidden && position != ObjectMovement::MoveAndSizeWithCellsAfter {
0
} else {
col_options.width
}
}
None => self.default_col_width,
}
}
fn row_pixel_height(&mut self, row: RowNum, position: ObjectMovement) -> u32 {
match self.changed_rows.get(&row) {
Some(row_options) => {
let hidden = row_options.hidden;
if hidden && position != ObjectMovement::MoveAndSizeWithCellsAfter {
0
} else {
row_options.height
}
}
None => self.default_row_height,
}
}
pub(crate) fn reset(&mut self) {
xmlwriter::reset(&mut self.writer);
xmlwriter::reset(&mut self.drawing.writer);
for chart in self.charts.values_mut() {
xmlwriter::reset(&mut chart.writer);
}
for table in &mut self.tables {
xmlwriter::reset(&mut table.writer);
}
self.rel_count = 0;
self.comment_relationships.clear();
self.drawing_object_relationships.clear();
self.drawing_rel_ids.clear();
self.drawing_relationships.clear();
self.drawing.drawings.clear();
self.header_footer_vml_info.clear();
self.hyperlink_relationships.clear();
self.table_relationships.clear();
self.vml_drawing_relationships.clear();
self.background_relationships.clear();
}
pub(crate) fn has_relationships(&self) -> bool {
!self.hyperlink_relationships.is_empty()
|| !self.drawing_object_relationships.is_empty()
|| !self.table_relationships.is_empty()
|| !self.background_relationships.is_empty()
}
pub(crate) fn has_header_footer_images(&self) -> bool {
self.header_footer_images[0].is_some()
|| self.header_footer_images[1].is_some()
|| self.header_footer_images[2].is_some()
|| self.header_footer_images[3].is_some()
|| self.header_footer_images[4].is_some()
|| self.header_footer_images[5].is_some()
}
fn verify_header_footer_image(string: &str, position: &HeaderImagePosition) -> bool {
match position {
HeaderImagePosition::Left => {
let segments: Vec<&str> = string.split("&L").collect();
if segments.len() == 2 {
let right_segment = segments[1];
let segments: Vec<&str> = right_segment.split("&C").collect();
let left_segment = segments[0];
let segments: Vec<&str> = left_segment.split("&R").collect();
let left_segment = segments[0];
left_segment.contains("&[Picture]") || left_segment.contains("&G")
} else {
false
}
}
HeaderImagePosition::Right => {
let segments: Vec<&str> = string.split("&R").collect();
if segments.len() == 2 {
let right_segment = segments[1];
let segments: Vec<&str> = right_segment.split("&C").collect();
let left_segment = segments[0];
let segments: Vec<&str> = left_segment.split("&L").collect();
let left_segment = segments[0];
left_segment.contains("&[Picture]") || left_segment.contains("&G")
} else {
false
}
}
HeaderImagePosition::Center => {
let segments: Vec<&str> = string.split("&C").collect();
if segments.len() == 2 {
let right_segment = segments[1];
let segments: Vec<&str> = right_segment.split("&L").collect();
let left_segment = segments[0];
let segments: Vec<&str> = left_segment.split("&R").collect();
let left_segment = segments[0];
left_segment.contains("&[Picture]") || left_segment.contains("&G")
} else {
false
}
}
}
}
pub(crate) fn get_cache_data(
&self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> ChartRangeCacheData {
let mut cache = ChartRangeCacheData::new();
let mut data = vec![];
for row_num in first_row..=last_row {
match self.data_table.get(&row_num) {
Some(columns) => {
for col_num in first_col..=last_col {
match columns.get(&col_num) {
Some(cell) => match cell {
CellType::String { string, .. } => {
data.push(string.to_string());
cache.cache_type = ChartRangeCacheDataType::String;
}
CellType::Number { number, .. } => {
if cache.cache_type != ChartRangeCacheDataType::String {
cache.cache_type = ChartRangeCacheDataType::Number;
}
data.push(number.to_string());
}
CellType::DateTime { number, .. } => {
if cache.cache_type != ChartRangeCacheDataType::String {
cache.cache_type = ChartRangeCacheDataType::Date;
}
data.push(number.to_string());
}
_ => data.push(String::new()),
},
None => data.push(String::new()),
}
}
}
None => data.push(String::new()),
}
}
let row_dim = (last_row - first_row + 1) as usize;
let col_dim = (last_col - first_col + 1) as usize;
cache.major_dim = cmp::max(row_dim, col_dim);
cache.minor_dim = cmp::min(row_dim, col_dim);
if cache.cache_type == ChartRangeCacheDataType::String && row_dim > 1 && col_dim > 1 {
cache.cache_type = ChartRangeCacheDataType::MultiLevelString;
}
cache.data = data;
cache
}
pub(crate) fn default_table_headers(
&self,
first_row: RowNum,
first_col: ColNum,
last_col: ColNum,
show_header_row: bool,
) -> Vec<String> {
let mut headers = vec![];
for col_num in first_col..=last_col {
headers.push(format!("Column{}", col_num - first_col + 1));
}
if !show_header_row {
return headers;
}
if let Some(columns) = self.data_table.get(&first_row) {
for col_num in first_col..=last_col {
if let Some(CellType::String { string, .. }) = columns.get(&col_num) {
headers[(col_num - first_col) as usize] = string.to_string();
} else if let Some(CellType::InlineString { string, .. }) = columns.get(&col_num) {
headers[(col_num - first_col) as usize] = string.to_string();
}
}
}
headers
}
fn autofit_worksheet(&mut self) -> &mut Worksheet {
let mut max_widths: HashMap<ColNum, u32> = HashMap::new();
#[cfg(feature = "enhanced_autofit")]
let number_formatters = self.get_number_formatters();
let (first_row, last_row) = if self.use_constant_memory {
(self.current_row, self.current_row)
} else {
(self.dimensions.first_row, self.dimensions.last_row)
};
for row_num in first_row..=last_row {
if row_num >= self.max_autofit_row {
break;
}
if let Some(columns) = self.data_table.get(&row_num) {
for col_num in self.dimensions.first_col..=self.dimensions.last_col {
if let Some(cell) = columns.get(&col_num) {
let mut pixel_width = match cell {
CellType::String { string, .. }
| CellType::InlineString { string, .. }
| CellType::RichString {
raw_string: string, ..
} => {
let mut max = 0;
for segment in string.lines() {
let length = utility::pixel_width(segment);
max = cmp::max(max, length);
}
max
}
#[cfg(not(feature = "enhanced_autofit"))]
CellType::Number { number, .. } => 7 * number.to_string().len() as u32,
#[cfg(feature = "enhanced_autofit")]
CellType::Number { number, xf_index } => self.formatted_number_width(
row_num,
col_num,
*number,
*xf_index,
7 * number.to_string().len() as u32,
&number_formatters,
),
#[cfg(not(feature = "enhanced_autofit"))]
CellType::DateTime { .. } => 68,
#[cfg(feature = "enhanced_autofit")]
CellType::DateTime { number, xf_index } => self.formatted_number_width(
row_num,
col_num,
*number,
*xf_index,
68,
&number_formatters,
),
CellType::Boolean { boolean, .. } => {
if *boolean {
31
} else {
36
}
}
CellType::Formula { result, .. }
| CellType::ArrayFormula { result, .. } => {
if result.as_ref() == "0" || result.is_empty() {
0
} else {
utility::pixel_width(result)
}
}
CellType::Blank { .. } | CellType::Error { .. } => 0,
};
let is_autofilter_row = if self.use_constant_memory {
self.cells_with_autofilter
.contains_key(&(self.current_row, col_num))
} else {
self.cells_with_autofilter.contains_key(&(row_num, col_num))
};
if pixel_width > 0 && is_autofilter_row {
pixel_width += 16;
}
if pixel_width > 0 {
pixel_width += 7;
}
pixel_width = std::cmp::min(pixel_width, self.max_autofit_width);
if pixel_width > 0 {
match max_widths.get_mut(&col_num) {
Some(max) => {
if pixel_width > *max {
*max = pixel_width;
}
}
None => {
max_widths.insert(col_num, pixel_width);
}
}
}
}
}
}
}
for (col, pixel_width) in &max_widths {
self.store_column_width(*col, *pixel_width, true);
}
self
}
#[cfg(feature = "enhanced_autofit")]
fn formatted_number_width(
&self,
row: RowNum,
col: ColNum,
number: f64,
xf_index: u32,
default: u32,
number_formatters: &HashMap<u32, NumberFormat>,
) -> u32 {
let mut xf_index = xf_index;
if xf_index == 0 {
if let Some(row_options) = self.changed_rows.get(&row) {
xf_index = row_options.xf_index;
}
}
if xf_index == 0 {
if let Some(col_options) = self.changed_cols.get(&col) {
xf_index = col_options.xf_index;
}
}
if xf_index == 0 {
return default;
}
if let Some(formatter) = number_formatters.get(&xf_index) {
let formatted_string = formatter.format(number, &FormatOptions::default());
utility::pixel_width(&formatted_string)
} else {
default
}
}
#[cfg(feature = "enhanced_autofit")]
fn get_number_formatters(&self) -> HashMap<u32, NumberFormat> {
use std::collections::hash_map::Entry;
let mut number_formatters: HashMap<u32, NumberFormat> = HashMap::new();
if self.has_workbook_global_xfs {
let xf_formats = self.workbook_xf_indices.read().expect("RwLock poisoned");
for (cell_format, index) in xf_formats.iter() {
let number_format = &cell_format.num_format;
if !number_format.is_empty() {
if let Entry::Vacant(e) = number_formatters.entry(*index) {
if let Ok(formatter) = NumberFormat::parse(number_format) {
e.insert(formatter);
}
}
}
}
} else {
for (index, cell_format) in self.xf_formats.iter().enumerate() {
let number_format = &cell_format.num_format;
if !number_format.is_empty() {
if let Entry::Vacant(e) = number_formatters.entry(index as u32) {
if let Ok(formatter) = NumberFormat::parse(number_format) {
e.insert(formatter);
}
}
}
}
}
number_formatters
}
fn set_grouped_rows(
&mut self,
first_row: RowNum,
last_row: RowNum,
collapsed: bool,
) -> Result<&mut Worksheet, XlsxError> {
let min_col = self.get_min_col();
if !self.check_dimensions(first_row, min_col) || !self.check_dimensions(last_row, min_col) {
return Err(XlsxError::RowColumnLimitError);
}
if first_row > last_row {
return Err(XlsxError::RowColumnOrderError);
}
for row in first_row..=last_row {
self.set_grouped_row(row, collapsed)?;
}
Ok(self)
}
fn set_grouped_row(
&mut self,
row: RowNum,
collapsed: bool,
) -> Result<&mut Worksheet, XlsxError> {
match self.changed_rows.get_mut(&row) {
Some(row_options) => {
let next_level = row_options.level + 1;
if next_level > 7 {
return Err(XlsxError::MaxGroupLevelExceeded);
}
self.max_outline_row_level = std::cmp::max(next_level, self.max_outline_row_level);
row_options.level = next_level;
if collapsed {
row_options.hidden = true;
}
}
None => {
let row_options = RowOptions {
height: self.default_row_height,
xf_index: 0,
hidden: collapsed,
level: 1,
collapsed: false,
format: None,
};
self.changed_rows.insert(row, row_options);
}
}
Ok(self)
}
fn set_collapsed_row(&mut self, row: RowNum) -> Result<&mut Worksheet, XlsxError> {
let min_col = self.get_min_col();
if !self.check_dimensions(row, min_col) {
return Err(XlsxError::RowColumnLimitError);
}
match self.changed_rows.get_mut(&row) {
Some(row_options) => row_options.collapsed = true,
None => {
let row_options = RowOptions {
height: self.default_row_height,
xf_index: 0,
hidden: false,
level: 0,
collapsed: true,
format: None,
};
self.changed_rows.insert(row, row_options);
}
}
Ok(self)
}
fn set_grouped_columns(
&mut self,
first_col: ColNum,
last_col: ColNum,
collapsed: bool,
) -> Result<&mut Worksheet, XlsxError> {
let min_row = self.get_min_row();
if !self.check_dimensions(min_row, first_col) || !self.check_dimensions(min_row, last_col) {
return Err(XlsxError::RowColumnLimitError);
}
if first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}
for col in first_col..=last_col {
self.set_grouped_column(col, collapsed)?;
}
Ok(self)
}
fn set_grouped_column(
&mut self,
column: ColNum,
collapsed: bool,
) -> Result<&mut Worksheet, XlsxError> {
match self.changed_cols.get_mut(&column) {
Some(column_options) => {
let next_level = column_options.level + 1;
if next_level > 7 {
return Err(XlsxError::MaxGroupLevelExceeded);
}
self.max_outline_col_level = std::cmp::max(next_level, self.max_outline_col_level);
column_options.level = next_level;
if collapsed {
column_options.hidden = true;
}
}
None => {
let col_options = ColOptions {
width: self.default_col_width,
xf_index: 0,
hidden: collapsed,
level: 1,
collapsed: false,
autofit: false,
format: None,
};
self.changed_cols.insert(column, col_options);
}
}
Ok(self)
}
fn set_collapsed_column(&mut self, col: ColNum) -> Result<&mut Worksheet, XlsxError> {
let min_row = self.get_min_row();
if !self.check_dimensions(min_row, col) {
return Err(XlsxError::RowColumnLimitError);
}
match self.changed_cols.get_mut(&col) {
Some(column_options) => column_options.collapsed = true,
None => {
let column_options = ColOptions {
width: self.default_col_width,
xf_index: 0,
hidden: false,
level: 0,
collapsed: true,
autofit: false,
format: None,
};
self.changed_cols.insert(col, column_options);
}
}
Ok(self)
}
pub(crate) fn assemble_xml_file(&mut self) {
if self.is_chartsheet {
return self.assemble_chartsheet();
}
self.assemble_xml_file_start();
self.write_data_table();
self.assemble_xml_file_end();
}
pub(crate) fn assemble_xml_file_start(&mut self) {
xml_declaration(&mut self.writer);
self.write_worksheet();
self.write_sheet_pr();
self.write_dimension();
self.write_sheet_views();
self.write_sheet_format_pr();
self.write_cols();
if !self.has_sheet_data
&& self.data_table.is_empty()
&& self.notes.is_empty()
&& self.changed_rows.is_empty()
{
xml_empty_tag_only(&mut self.writer, "sheetData");
} else {
xml_start_tag_only(&mut self.writer, "sheetData");
self.has_sheet_data = true;
}
}
pub(crate) fn assemble_xml_file_end(&mut self) {
if self.has_sheet_data {
xml_end_tag(&mut self.writer, "sheetData");
}
if self.protection_on {
self.write_sheet_protection();
}
if !self.unprotected_ranges.is_empty() {
self.write_protected_ranges();
}
if !self.autofilter_area.is_empty() {
self.write_auto_filter();
}
if !self.merged_ranges.is_empty() {
self.write_merge_cells();
}
if self.has_conditional_formats {
self.write_conditional_formats();
}
if !self.data_validations.is_empty() {
self.write_data_validations();
}
if !self.hyperlinks.is_empty() {
self.write_hyperlinks();
}
if self.print_options_changed {
self.write_print_options();
}
self.write_page_margins();
if self.page_setup_changed {
self.write_page_setup();
}
if self.head_footer_changed {
self.write_header_footer();
}
if !self.horizontal_breaks.is_empty() {
self.write_row_breaks();
}
if !self.vertical_breaks.is_empty() {
self.write_col_breaks();
}
if !self.ignored_errors.is_empty() {
self.write_ignored_errors();
}
if !self.drawing.drawings.is_empty() {
self.write_drawing();
}
if self.has_vml {
self.write_legacy_drawing();
}
if self.has_header_footer_images() {
self.write_legacy_drawing_hf();
}
if self.background_image.is_some() {
self.write_picture();
}
if !self.tables.is_empty() {
self.write_table_parts();
}
if self.use_x14_extensions {
self.write_extensions();
}
xml_end_tag(&mut self.writer, "worksheet");
}
pub(crate) fn assemble_chartsheet(&mut self) {
xml_declaration(&mut self.writer);
self.write_chartsheet();
self.write_sheet_pr();
self.write_sheet_views();
if self.protection_on {
self.write_sheet_protection();
}
if !self.unprotected_ranges.is_empty() {
self.write_protected_ranges();
}
if self.print_options_changed {
self.write_print_options();
}
self.write_page_margins();
if self.page_setup_changed {
self.write_page_setup();
}
if self.head_footer_changed {
self.write_header_footer();
}
if !self.drawing.drawings.is_empty() {
self.write_drawing();
}
if self.has_header_footer_images() {
self.write_legacy_drawing_hf();
}
xml_end_tag(&mut self.writer, "chartsheet");
}
fn write_worksheet(&mut self) {
let mut attributes = vec![
(
"xmlns",
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
),
(
"xmlns:r",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
),
];
if self.use_x14_extensions {
attributes.push((
"xmlns:mc",
"http://schemas.openxmlformats.org/markup-compatibility/2006",
));
attributes.push((
"xmlns:x14ac",
"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac",
));
attributes.push(("mc:Ignorable", "x14ac"));
}
xml_start_tag(&mut self.writer, "worksheet", &attributes);
}
fn write_chartsheet(&mut self) {
let attributes = [
(
"xmlns",
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
),
(
"xmlns:r",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
),
];
xml_start_tag(&mut self.writer, "chartsheet", &attributes);
}
fn write_sheet_pr(&mut self) {
if self.filter_conditions.is_empty()
&& !self.fit_to_page
&& (self.tab_color == Color::Default || self.tab_color == Color::Automatic)
&& self.vba_codename.is_none()
&& !self.is_chartsheet
&& !self.outline_symbols_above
&& !self.outline_symbols_left
{
return;
}
let mut attributes = vec![];
if let Some(codename) = &self.vba_codename {
attributes.push(("codeName", codename.clone()));
}
if !self.filter_conditions.is_empty() {
attributes.push(("filterMode", "1".to_string()));
}
if self.fit_to_page
|| self.outline_symbols_above
|| self.outline_symbols_left
|| (self.tab_color != Color::Default && self.tab_color != Color::Automatic)
{
xml_start_tag(&mut self.writer, "sheetPr", &attributes);
self.write_tab_color();
self.write_outline_pr();
self.write_page_set_up_pr();
xml_end_tag(&mut self.writer, "sheetPr");
} else {
xml_empty_tag(&mut self.writer, "sheetPr", &attributes);
}
}
fn write_page_set_up_pr(&mut self) {
if !self.fit_to_page {
return;
}
let attributes = [("fitToPage", "1")];
xml_empty_tag(&mut self.writer, "pageSetUpPr", &attributes);
}
fn write_tab_color(&mut self) {
if self.tab_color == Color::Default {
return;
}
let attributes = self.tab_color.attributes();
xml_empty_tag(&mut self.writer, "tabColor", &attributes);
}
fn write_outline_pr(&mut self) {
if !self.outline_symbols_above && !self.outline_symbols_left {
return;
}
let mut attributes = vec![];
if self.outline_symbols_above {
attributes.push(("summaryBelow", "0".to_string()));
}
if self.outline_symbols_left {
attributes.push(("summaryRight", "0".to_string()));
}
xml_empty_tag(&mut self.writer, "outlinePr", &attributes);
}
fn write_dimension(&mut self) {
let mut attributes = vec![];
let mut range = "A1".to_string();
if self.dimensions.first_row == 0
&& self.dimensions.first_col == 0
&& self.dimensions.last_row == 0
&& self.dimensions.last_col == COL_MAX - 1
{
range = "A1".to_string();
}
else if self.dimensions.first_row != ROW_MAX
|| self.dimensions.first_col != COL_MAX
|| self.dimensions.last_row != 0
|| self.dimensions.last_col != 0
{
range = utility::cell_range(
self.dimensions.first_row,
self.dimensions.first_col,
self.dimensions.last_row,
self.dimensions.last_col,
);
}
attributes.push(("ref", range));
xml_empty_tag(&mut self.writer, "dimension", &attributes);
}
fn write_sheet_views(&mut self) {
xml_start_tag_only(&mut self.writer, "sheetViews");
self.write_sheet_view();
xml_end_tag(&mut self.writer, "sheetViews");
}
fn write_sheet_view(&mut self) {
let mut attributes = vec![];
if !self.screen_gridlines {
attributes.push(("showGridLines", "0".to_string()));
}
if self.right_to_left {
attributes.push(("rightToLeft", "1".to_string()));
}
if self.selected {
attributes.push(("tabSelected", "1".to_string()));
}
match self.page_view {
PageView::PageLayout => {
attributes.push(("view", "pageLayout".to_string()));
}
PageView::PageBreaks => {
attributes.push(("view", "pageBreakPreview".to_string()));
}
PageView::Normal => {}
}
if !self.top_left_cell.is_empty() {
attributes.push(("topLeftCell", self.top_left_cell.clone()));
}
if self.zoom != 100 {
attributes.push(("zoomScale", self.zoom.to_string()));
match self.page_view {
PageView::PageLayout => {
attributes.push(("zoomScalePageLayoutView", self.zoom.to_string()));
}
PageView::PageBreaks => {
attributes.push(("zoomScaleSheetLayoutView", self.zoom.to_string()));
}
PageView::Normal => {
if !self.is_chartsheet {
attributes.push(("zoomScaleNormal", self.zoom.to_string()));
}
}
}
}
attributes.push(("workbookViewId", "0".to_string()));
if self.is_chartsheet && self.zoom_to_fit {
attributes.push(("zoomToFit", "1".to_string()));
}
if self.panes.is_empty() && self.selected_range.0.is_empty() {
xml_empty_tag(&mut self.writer, "sheetView", &attributes);
} else {
xml_start_tag(&mut self.writer, "sheetView", &attributes);
if self.panes.is_empty() {
self.write_selections();
} else {
self.write_panes();
}
xml_end_tag(&mut self.writer, "sheetView");
}
}
fn write_selections(&mut self) {
if self.selected_range.0.is_empty() {
return;
}
let active_cell = self.selected_range.0.clone();
let range = self.selected_range.1.clone();
self.write_selection("", &active_cell, &range);
}
fn write_panes(&mut self) {
if self.panes.is_empty() {
return;
}
let row = self.panes.freeze_cell.0;
let col = self.panes.freeze_cell.1;
let range = self.selected_range.1.clone();
let active_cell = self.selected_range.0.clone();
if row > 0 && col > 0 {
self.write_pane("bottomRight");
self.write_selection(
"topRight",
&utility::row_col_to_cell(0, col),
&utility::row_col_to_cell(0, col),
);
self.write_selection(
"bottomLeft",
&utility::row_col_to_cell(row, 0),
&utility::row_col_to_cell(row, 0),
);
self.write_selection("bottomRight", &active_cell, &range);
} else if col > 0 {
self.write_pane("topRight");
self.write_selection("topRight", &active_cell, &range);
} else {
self.write_pane("bottomLeft");
self.write_selection("bottomLeft", &active_cell, &range);
}
}
fn write_pane(&mut self, active_pane: &str) {
let row = self.panes.freeze_cell.0;
let col = self.panes.freeze_cell.1;
let mut attributes = vec![];
if col > 0 {
attributes.push(("xSplit", col.to_string()));
}
if row > 0 {
attributes.push(("ySplit", row.to_string()));
}
attributes.push(("topLeftCell", self.panes.top_left()));
attributes.push(("activePane", active_pane.to_string()));
attributes.push(("state", "frozen".to_string()));
xml_empty_tag(&mut self.writer, "pane", &attributes);
}
fn write_selection(&mut self, position: &str, active_cell: &str, range: &str) {
let mut attributes = vec![];
if !position.is_empty() {
attributes.push(("pane", position.to_string()));
}
if !active_cell.is_empty() {
attributes.push(("activeCell", active_cell.to_string()));
}
if !range.is_empty() {
attributes.push(("sqref", range.to_string()));
}
xml_empty_tag(&mut self.writer, "selection", &attributes);
}
fn write_sheet_format_pr(&mut self) {
let height_in_chars = f64::from(self.default_row_height) * 0.75;
let mut attributes = vec![("defaultRowHeight", height_in_chars.to_string())];
if self.default_row_height != self.original_row_height {
attributes.push(("customHeight", "1".to_string()));
}
if self.hide_unused_rows {
attributes.push(("zeroHeight", "1".to_string()));
}
if self.max_outline_row_level > 0 {
attributes.push(("outlineLevelRow", self.max_outline_row_level.to_string()));
}
if self.max_outline_col_level > 0 {
attributes.push(("outlineLevelCol", self.max_outline_col_level.to_string()));
}
if self.use_x14_extensions {
attributes.push(("x14ac:dyDescent", "0.25".to_string()));
}
xml_empty_tag(&mut self.writer, "sheetFormatPr", &attributes);
}
fn write_merge_cells(&mut self) {
let attributes = [("count", self.merged_ranges.len().to_string())];
xml_start_tag(&mut self.writer, "mergeCells", &attributes);
for merge_range in &self.merged_ranges.clone() {
self.write_merge_cell(merge_range);
}
xml_end_tag(&mut self.writer, "mergeCells");
}
fn write_merge_cell(&mut self, merge_range: &CellRange) {
let attributes = [("ref", merge_range.to_range_string())];
xml_empty_tag(&mut self.writer, "mergeCell", &attributes);
}
fn write_hyperlinks(&mut self) {
xml_start_tag_only(&mut self.writer, "hyperlinks");
let mut rel_id = 1u32;
for (cell, hyperlink) in &mut self.hyperlinks.clone() {
rel_id = hyperlink.increment_rel_id(rel_id);
self.write_hyperlink(cell.0, cell.1, hyperlink);
}
self.rel_count = rel_id - 1;
xml_end_tag(&mut self.writer, "hyperlinks");
}
fn write_conditional_formats(&mut self) {
let mut guid_index = 1;
let mut priority = 1;
for cell_range in &self.conditional_format_order {
let conditionals_for_range = self.conditional_formats.get(cell_range).unwrap();
let has_x14_only = conditionals_for_range
.iter()
.all(|rule| rule.has_x14_only());
if !has_x14_only {
let attributes = [("sqref", cell_range.as_str())];
xml_start_tag(&mut self.writer, "conditionalFormatting", &attributes);
}
for conditional_format in conditionals_for_range {
let mut guid = String::new();
if conditional_format.has_x14_extensions() {
guid = format!(
"{{DA7ABA51-AAAA-BBBB-{:04X}-{:012X}}}",
self.sheet_index + 1,
guid_index
);
guid_index += 1;
}
let mut dxf_index: Option<u32> = None;
if let Some(local_index) = conditional_format.format_index() {
dxf_index = Some(self.global_dxf_indices[local_index as usize]);
}
if !conditional_format.has_x14_only() {
let rule = conditional_format.rule(dxf_index, priority, cell_range, &guid);
xml_raw_string(&mut self.writer, &rule);
}
priority += 1;
}
if !has_x14_only {
xml_end_tag(&mut self.writer, "conditionalFormatting");
}
}
}
fn write_conditional_formattings(&mut self) {
xml_start_tag_only(&mut self.writer, "x14:conditionalFormattings");
let mut guid_index = 1;
let mut priority = 1;
for (cell_range, conditionals_for_range) in &self.conditional_formats {
if !conditionals_for_range
.iter()
.any(|rule| rule.has_x14_extensions())
{
continue;
}
let attributes = [(
"xmlns:xm",
"http://schemas.microsoft.com/office/excel/2006/main",
)];
xml_start_tag(&mut self.writer, "x14:conditionalFormatting", &attributes);
for conditional_format in conditionals_for_range {
if conditional_format.has_x14_extensions() {
let guid = format!(
"{{DA7ABA51-AAAA-BBBB-{:04X}-{:012X}}}",
self.sheet_index + 1,
guid_index
);
guid_index += 1;
let rule = conditional_format.x14_rule(priority, &guid);
xml_raw_string(&mut self.writer, &rule);
}
priority += 1;
}
xml_data_element_only(&mut self.writer, "xm:sqref", cell_range);
xml_end_tag(&mut self.writer, "x14:conditionalFormatting");
}
xml_end_tag(&mut self.writer, "x14:conditionalFormattings");
xml_end_tag(&mut self.writer, "ext");
}
fn write_data_validations(&mut self) {
let attributes = [("count", self.data_validations.len().to_string())];
xml_start_tag(&mut self.writer, "dataValidations", &attributes);
for (range, data_validation) in &self.data_validations.clone() {
self.write_data_validation(range, data_validation);
}
xml_end_tag(&mut self.writer, "dataValidations");
}
fn write_data_validation(&mut self, range: &str, data_validation: &DataValidation) {
if data_validation.validation_type == DataValidationType::Any {
self.write_data_validation_any(range, data_validation);
return;
}
let mut attributes = vec![("type", data_validation.validation_type.to_string())];
match data_validation.error_style {
DataValidationErrorStyle::Warning | DataValidationErrorStyle::Information => {
attributes.push(("errorStyle", data_validation.error_style.to_string()));
}
DataValidationErrorStyle::Stop => {}
}
match &data_validation.rule {
&DataValidationRuleInternal::Between(_, _)
| DataValidationRuleInternal::CustomFormula(_)
| DataValidationRuleInternal::ListSource(_) => {
}
_ => {
attributes.push(("operator", data_validation.rule.to_string()));
}
}
if data_validation.ignore_blank {
attributes.push(("allowBlank", "1".to_string()));
}
if !data_validation.show_dropdown {
attributes.push(("showDropDown", "1".to_string()));
}
if data_validation.show_input_message {
attributes.push(("showInputMessage", "1".to_string()));
}
if data_validation.show_error_message {
attributes.push(("showErrorMessage", "1".to_string()));
}
if !data_validation.error_title.is_empty() {
attributes.push(("errorTitle", data_validation.error_title.clone()));
}
if !data_validation.error_message.is_empty() {
attributes.push(("error", data_validation.error_message.clone()));
}
if !data_validation.input_title.is_empty() {
attributes.push(("promptTitle", data_validation.input_title.clone()));
}
if !data_validation.input_message.is_empty() {
attributes.push(("prompt", data_validation.input_message.clone()));
}
attributes.push(("sqref", range.to_string()));
xml_start_tag(&mut self.writer, "dataValidation", &attributes);
match &data_validation.rule {
DataValidationRuleInternal::EqualTo(value)
| DataValidationRuleInternal::NotEqualTo(value)
| DataValidationRuleInternal::LessThan(value)
| DataValidationRuleInternal::LessThanOrEqualTo(value)
| DataValidationRuleInternal::GreaterThan(value)
| DataValidationRuleInternal::GreaterThanOrEqualTo(value)
| DataValidationRuleInternal::ListSource(value)
| DataValidationRuleInternal::CustomFormula(value) => {
xml_data_element_only(&mut self.writer, "formula1", value);
}
DataValidationRuleInternal::Between(min, max)
| DataValidationRuleInternal::NotBetween(min, max) => {
xml_data_element_only(&mut self.writer, "formula1", min);
xml_data_element_only(&mut self.writer, "formula2", max);
}
}
xml_end_tag(&mut self.writer, "dataValidation");
}
fn write_data_validation_any(&mut self, range: &str, data_validation: &DataValidation) {
let mut attributes = vec![];
if data_validation.ignore_blank {
attributes.push(("allowBlank", "1".to_string()));
}
if !data_validation.show_dropdown {
attributes.push(("showDropDown", "1".to_string()));
}
if data_validation.show_input_message {
attributes.push(("showInputMessage", "1".to_string()));
}
if data_validation.show_error_message {
attributes.push(("showErrorMessage", "1".to_string()));
}
if !data_validation.error_title.is_empty() {
attributes.push(("errorTitle", data_validation.error_title.clone()));
}
if !data_validation.error_message.is_empty() {
attributes.push(("error", data_validation.error_message.clone()));
}
if !data_validation.input_title.is_empty() {
attributes.push(("promptTitle", data_validation.input_title.clone()));
}
if !data_validation.input_message.is_empty() {
attributes.push(("prompt", data_validation.input_message.clone()));
}
attributes.push(("sqref", range.to_string()));
xml_empty_tag(&mut self.writer, "dataValidation", &attributes);
}
fn write_hyperlink(&mut self, row: RowNum, col: ColNum, hyperlink: &mut Url) {
let mut attributes = vec![("ref", utility::row_col_to_cell(row, col))];
if let Some(columns) = self.data_table.get(&row) {
if let Some(cell) = columns.get(&col) {
match cell {
CellType::String { .. } => {}
_ => {
hyperlink.is_object_link = true;
}
}
}
}
match hyperlink.link_type {
HyperlinkType::Url | HyperlinkType::File => {
let rel_id = hyperlink.rel_id;
attributes.push(("r:id", format!("rId{rel_id}")));
if !hyperlink.anchor.is_empty() {
attributes.push(("location", hyperlink.anchor.clone()));
}
if hyperlink.is_object_link {
attributes.push(("display", hyperlink.link.clone()));
}
if !hyperlink.tool_tip.is_empty() {
attributes.push(("tooltip", hyperlink.tool_tip.clone()));
}
self.hyperlink_relationships.push((
"hyperlink".to_string(),
hyperlink.target(),
hyperlink.target_mode(),
));
}
HyperlinkType::Internal => {
attributes.push(("location", hyperlink.anchor.clone()));
if !hyperlink.tool_tip.is_empty() {
attributes.push(("tooltip", hyperlink.tool_tip.clone()));
}
attributes.push(("display", hyperlink.text.clone()));
}
HyperlinkType::Unknown => {}
}
xml_empty_tag(&mut self.writer, "hyperlink", &attributes);
}
fn write_print_options(&mut self) {
let mut attributes = vec![];
if self.center_horizontally {
attributes.push(("horizontalCentered", "1".to_string()));
}
if self.center_vertically {
attributes.push(("verticalCentered", "1".to_string()));
}
if self.print_headings {
attributes.push(("headings", "1".to_string()));
}
if self.print_gridlines {
attributes.push(("gridLines", "1".to_string()));
}
xml_empty_tag(&mut self.writer, "printOptions", &attributes);
}
fn write_page_margins(&mut self) {
let attributes = [
("left", self.margin_left.to_string()),
("right", self.margin_right.to_string()),
("top", self.margin_top.to_string()),
("bottom", self.margin_bottom.to_string()),
("header", self.margin_header.to_string()),
("footer", self.margin_footer.to_string()),
];
xml_empty_tag(&mut self.writer, "pageMargins", &attributes);
}
fn write_page_setup(&mut self) {
let mut attributes = vec![];
if self.paper_size > 0 {
attributes.push(("paperSize", self.paper_size.to_string()));
}
if self.print_scale != 100 {
attributes.push(("scale", self.print_scale.to_string()));
}
if self.fit_to_page {
if self.fit_width != 1 {
attributes.push(("fitToWidth", self.fit_width.to_string()));
}
if self.fit_height != 1 {
attributes.push(("fitToHeight", self.fit_height.to_string()));
}
}
if !self.default_page_order {
attributes.push(("pageOrder", "overThenDown".to_string()));
}
if self.is_portrait {
attributes.push(("orientation", "portrait".to_string()));
} else {
attributes.push(("orientation", "landscape".to_string()));
}
if self.first_page_number > 0 {
attributes.push(("useFirstPageNumber", self.first_page_number.to_string()));
}
if self.print_black_and_white {
attributes.push(("blackAndWhite", "1".to_string()));
}
if self.print_draft {
attributes.push(("draft", "1".to_string()));
}
attributes.push(("horizontalDpi", "200".to_string()));
attributes.push(("verticalDpi", "200".to_string()));
xml_empty_tag(&mut self.writer, "pageSetup", &attributes);
}
fn write_auto_filter(&mut self) {
let attributes = [("ref", self.autofilter_area.clone())];
if self.filter_conditions.is_empty() {
xml_empty_tag(&mut self.writer, "autoFilter", &attributes);
} else {
xml_start_tag(&mut self.writer, "autoFilter", &attributes);
let col_offset = self.autofilter_defined_name.first_col;
for col in self.filter_conditions.clone().keys() {
let filter_condition = self.filter_conditions.get(col).unwrap().clone();
self.write_filter_column(*col - col_offset, &filter_condition);
}
xml_end_tag(&mut self.writer, "autoFilter");
}
}
fn write_filter_column(&mut self, col: ColNum, filter_condition: &FilterCondition) {
let attributes = [("colId", col.to_string())];
xml_start_tag(&mut self.writer, "filterColumn", &attributes);
if filter_condition.is_list_filter {
self.write_list_filters(filter_condition);
} else {
self.write_custom_filters(filter_condition);
}
xml_end_tag(&mut self.writer, "filterColumn");
}
fn write_list_filters(&mut self, filter_condition: &FilterCondition) {
let mut attributes = vec![];
if filter_condition.should_match_blanks {
attributes.push(("blank", "1".to_string()));
}
if filter_condition.list.is_empty() {
xml_empty_tag(&mut self.writer, "filters", &attributes);
} else {
xml_start_tag(&mut self.writer, "filters", &attributes);
for data in &filter_condition.list {
self.write_filter(data.string.clone());
}
xml_end_tag(&mut self.writer, "filters");
}
}
fn write_filter(&mut self, value: String) {
let attributes = [("val", value)];
xml_empty_tag(&mut self.writer, "filter", &attributes);
}
fn write_custom_filters(&mut self, filter_condition: &FilterCondition) {
let mut attributes = vec![];
if !filter_condition.apply_logical_or {
attributes.push(("and", "1".to_string()));
}
xml_start_tag(&mut self.writer, "customFilters", &attributes);
if let Some(data) = filter_condition.custom1.as_ref() {
self.write_custom_filter(data);
}
if let Some(data) = filter_condition.custom2.as_ref() {
self.write_custom_filter(data);
}
xml_end_tag(&mut self.writer, "customFilters");
}
fn write_custom_filter(&mut self, data: &FilterData) {
let mut attributes = vec![];
if !data.criteria.operator().is_empty() {
attributes.push(("operator", data.criteria.operator()));
}
attributes.push(("val", data.value()));
xml_empty_tag(&mut self.writer, "customFilter", &attributes);
}
pub(crate) fn update_string_table_ids(&mut self, string_table: Arc<Mutex<SharedStringsTable>>) {
if !self.has_local_string_table {
return;
}
let mut string_table = string_table.lock().unwrap();
for columns in self.data_table.values_mut() {
for cell in columns.values_mut() {
match cell {
CellType::String {
string, string_id, ..
}
| CellType::RichString {
string, string_id, ..
} if string_id.is_none() => {
let string_index = string_table.shared_string_index(Arc::clone(string));
*string_id = Some(string_index);
}
_ => {}
}
}
}
}
#[allow(clippy::too_many_lines)]
fn write_data_table(&mut self) {
let spans = self.calculate_spans();
let mut col_names = HashMap::new();
let mut temp_table: BTreeMap<RowNum, BTreeMap<ColNum, CellType>> = BTreeMap::new();
let mut temp_changed_rows: HashMap<RowNum, RowOptions> = HashMap::new();
mem::swap(&mut temp_table, &mut self.data_table);
mem::swap(&mut temp_changed_rows, &mut self.changed_rows);
for row_num in self.dimensions.first_row..=self.dimensions.last_row {
let span_index = row_num / 16;
let span = spans.get(&span_index).map(AsRef::as_ref);
let row_options = temp_changed_rows.get(&row_num);
let row_has_notes = self.notes.contains_key(&row_num);
let Some(columns) = temp_table.get(&row_num) else {
if row_options.is_some() || row_has_notes {
self.write_table_row(row_num, span, row_options, false);
}
continue;
};
self.write_table_row(row_num, span, row_options, true);
for (&col_num, cell) in columns {
let col_name = if col_num < 26 {
&COLUMN_LETTERS[col_num as usize..(col_num + 1) as usize]
} else {
col_names
.entry(col_num)
.or_insert_with(|| utility::column_number_to_name(col_num))
};
match cell {
CellType::Number { number, xf_index }
| CellType::DateTime { number, xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_number_cell(
&mut self.writer,
row_num + 1,
col_name,
*number,
xf_index,
);
}
CellType::String {
string_id,
xf_index,
..
}
| CellType::RichString {
string_id,
xf_index,
..
} => {
if let Some(string_id) = string_id {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_string_cell(
&mut self.writer,
row_num + 1,
col_name,
*string_id,
xf_index,
);
}
}
CellType::InlineString {
string, xf_index, ..
} => {
Self::write_inline_string_cell(
&mut self.writer,
row_num + 1,
col_name,
string,
*xf_index,
);
}
CellType::Formula {
formula,
xf_index,
result,
} => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_formula_cell(
&mut self.writer,
row_num + 1,
col_name,
formula,
xf_index,
result,
);
}
CellType::ArrayFormula {
formula,
xf_index,
result,
is_dynamic,
range,
} => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_array_formula_cell(
&mut self.writer,
row_num + 1,
col_name,
formula,
xf_index,
result,
*is_dynamic,
range,
);
}
CellType::Blank { xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_blank_cell(&mut self.writer, row_num + 1, col_name, xf_index);
}
CellType::Boolean { boolean, xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_boolean_cell(
&mut self.writer,
row_num + 1,
col_name,
*boolean,
xf_index,
);
}
CellType::Error { value, xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
let image_id = self.global_embedded_image_indices[*value as usize];
Self::write_error_cell(
&mut self.writer,
row_num + 1,
col_name,
image_id,
xf_index,
);
}
}
}
xml_end_tag(&mut self.writer, "row");
}
mem::swap(&mut temp_table, &mut self.data_table);
mem::swap(&mut temp_changed_rows, &mut self.changed_rows);
}
#[cfg(feature = "constant_memory")]
pub(crate) fn flush_last_row(&mut self) {
let mut remaining_rows: Vec<_> = self.write_ahead.keys().copied().collect();
let max_changed_row = self.changed_rows.keys().max().unwrap_or(&0);
if *max_changed_row > self.current_row {
for row_num in self.current_row..=*max_changed_row {
if self.changed_rows.contains_key(&row_num) {
remaining_rows.push(row_num);
}
}
}
remaining_rows.push(self.current_row);
remaining_rows.sort_unstable();
remaining_rows.dedup();
if self.data_table.contains_key(&self.current_row) {
remaining_rows.push(1 + *remaining_rows.last().unwrap());
}
for remaining_row in remaining_rows {
self.flush_data_row(remaining_row);
}
}
#[cfg(feature = "constant_memory")]
fn flush_to_row(&mut self, next_row: RowNum) {
let mut intermediate_rows: Vec<_> = self
.write_ahead
.keys()
.filter(|x| **x < next_row)
.copied()
.collect();
for row_num in self.current_row + 1..next_row {
if self.changed_rows.contains_key(&row_num) {
intermediate_rows.push(row_num);
}
}
intermediate_rows.sort_unstable();
intermediate_rows.dedup();
for intermediate_row in intermediate_rows {
self.flush_data_row(intermediate_row);
}
self.flush_data_row(next_row);
}
#[allow(clippy::too_many_lines)]
#[cfg(feature = "constant_memory")]
fn flush_data_row(&mut self, next_row: RowNum) {
let current_row = self.current_row;
let mut temp_table: BTreeMap<RowNum, BTreeMap<ColNum, CellType>> = BTreeMap::new();
let mut temp_changed_rows: HashMap<RowNum, RowOptions> = HashMap::new();
mem::swap(&mut temp_table, &mut self.data_table);
mem::swap(&mut temp_changed_rows, &mut self.changed_rows);
let row_options = temp_changed_rows.get(¤t_row);
let Some(columns) = temp_table.get(¤t_row) else {
if row_options.is_some() {
self.write_constant_table_row(current_row, row_options, false);
}
mem::swap(&mut temp_changed_rows, &mut self.changed_rows);
if let Some(columns) = self.write_ahead.remove(&next_row) {
self.data_table.insert(next_row, columns);
}
self.current_row = next_row;
return;
};
self.write_constant_table_row(current_row, row_options, true);
for (&col_num, cell) in columns {
let col_name = if col_num < 26 {
&COLUMN_LETTERS[col_num as usize..(col_num + 1) as usize]
} else {
&utility::column_number_to_name(col_num)
};
match cell {
CellType::Number { number, xf_index } | CellType::DateTime { number, xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_number_cell(
&mut self.file_writer,
current_row + 1,
col_name,
*number,
xf_index,
);
}
CellType::String {
string_id,
xf_index,
..
} => {
if let Some(string_id) = string_id {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_string_cell(
&mut self.file_writer,
current_row + 1,
col_name,
*string_id,
xf_index,
);
}
}
CellType::RichString {
string, xf_index, ..
} => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_inline_rich_string_cell(
&mut self.file_writer,
current_row + 1,
col_name,
string,
xf_index,
);
}
CellType::InlineString {
string, xf_index, ..
} => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_inline_string_cell(
&mut self.file_writer,
current_row + 1,
col_name,
string,
xf_index,
);
}
CellType::Formula {
formula,
xf_index,
result,
} => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_formula_cell(
&mut self.file_writer,
current_row + 1,
col_name,
formula,
xf_index,
result,
);
}
CellType::ArrayFormula {
formula,
xf_index,
result,
is_dynamic,
range,
} => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_array_formula_cell(
&mut self.file_writer,
current_row + 1,
col_name,
formula,
xf_index,
result,
*is_dynamic,
range,
);
}
CellType::Blank { xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_blank_cell(
&mut self.file_writer,
current_row + 1,
col_name,
xf_index,
);
}
CellType::Boolean { boolean, xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
Self::write_boolean_cell(
&mut self.file_writer,
current_row + 1,
col_name,
*boolean,
xf_index,
);
}
CellType::Error { value, xf_index } => {
let xf_index = self.get_cell_xf_index(*xf_index, row_options, col_num);
let image_id = self.global_embedded_image_indices[*value as usize];
Self::write_error_cell(
&mut self.file_writer,
current_row + 1,
col_name,
image_id,
xf_index,
);
}
}
}
xml_end_tag(&mut self.file_writer, "row");
mem::swap(&mut temp_changed_rows, &mut self.changed_rows);
if let Some(columns) = self.write_ahead.remove(&next_row) {
self.data_table.insert(next_row, columns);
}
self.current_row = next_row;
}
fn calculate_spans(&mut self) -> HashMap<u32, String> {
let mut spans: HashMap<RowNum, String> = HashMap::new();
let mut span_min = COL_MAX;
let mut span_max = 0;
for row_num in self.dimensions.first_row..=self.dimensions.last_row {
if let Some(columns) = self.data_table.get(&row_num) {
for &col_num in columns.keys() {
if span_min == COL_MAX {
span_min = col_num;
span_max = col_num;
} else {
span_min = cmp::min(span_min, col_num);
span_max = cmp::max(span_max, col_num);
}
}
}
if let Some(columns) = self.notes.get(&row_num) {
for &col_num in columns.keys() {
if span_min == COL_MAX {
span_min = col_num;
span_max = col_num;
} else {
span_min = cmp::min(span_min, col_num);
span_max = cmp::max(span_max, col_num);
}
}
}
if (row_num + 1) % 16 == 0 || row_num == self.dimensions.last_row {
let span_index = row_num / 16;
if span_min != COL_MAX {
span_min += 1;
span_max += 1;
let span_range = format!("{span_min}:{span_max}");
spans.insert(span_index, span_range);
span_min = COL_MAX;
}
}
}
spans
}
fn write_table_row(
&mut self,
row_num: RowNum,
span: Option<&str>,
row_options: Option<&RowOptions>,
has_data: bool,
) {
let row_num = (row_num + 1).to_string();
let mut attributes = vec![("r", row_num)];
if let Some(span_range) = span {
attributes.push(("spans", span_range.to_string()));
}
if self.use_x14_extensions {
attributes.push(("x14ac:dyDescent", "0.25".to_string()));
}
if let Some(row_options) = row_options {
let xf_index = row_options.xf_index;
if xf_index != 0 {
let xf_index = self.get_global_xf_index(xf_index);
attributes.push(("s", xf_index.to_string()));
attributes.push(("customFormat", "1".to_string()));
}
if row_options.height != self.original_row_height
|| self.default_row_height != self.original_row_height
{
let height_in_chars = f64::from(row_options.height) * 0.75;
attributes.push(("ht", height_in_chars.to_string()));
}
if row_options.hidden {
attributes.push(("hidden", "1".to_string()));
}
if row_options.height != self.original_row_height
|| self.default_row_height != self.original_row_height
{
attributes.push(("customHeight", "1".to_string()));
}
if row_options.level > 0 {
attributes.push(("outlineLevel", row_options.level.to_string()));
}
if row_options.collapsed {
attributes.push(("collapsed", "1".to_string()));
}
} else if self.default_row_height != self.original_row_height {
let height_in_chars = f64::from(self.default_row_height) * 0.75;
attributes.push(("ht", height_in_chars.to_string()));
attributes.push(("customHeight", "1".to_string()));
}
if has_data {
xml_start_tag(&mut self.writer, "row", &attributes);
} else {
xml_empty_tag(&mut self.writer, "row", &attributes);
}
}
#[cfg(feature = "constant_memory")]
fn write_constant_table_row(
&mut self,
row_num: RowNum,
row_options: Option<&RowOptions>,
has_data: bool,
) {
let row_num = (row_num + 1).to_string();
let mut attributes = vec![("r", row_num)];
if self.use_x14_extensions {
attributes.push(("x14ac:dyDescent", "0.25".to_string()));
}
if let Some(row_options) = row_options {
let xf_index = row_options.xf_index;
if xf_index != 0 {
attributes.push(("s", xf_index.to_string()));
attributes.push(("customFormat", "1".to_string()));
}
if row_options.height != self.original_row_height
|| self.default_row_height != self.original_row_height
{
let height_in_chars = f64::from(row_options.height) * 0.75;
attributes.push(("ht", height_in_chars.to_string()));
}
if row_options.hidden {
attributes.push(("hidden", "1".to_string()));
}
if row_options.height != self.original_row_height
|| self.default_row_height != self.original_row_height
{
attributes.push(("customHeight", "1".to_string()));
}
} else if self.default_row_height != self.original_row_height {
let height_in_chars = f64::from(self.default_row_height) * 0.75;
attributes.push(("ht", height_in_chars.to_string()));
attributes.push(("customHeight", "1".to_string()));
}
if has_data {
xml_start_tag(&mut self.file_writer, "row", &attributes);
} else {
xml_empty_tag(&mut self.file_writer, "row", &attributes);
}
}
fn write_number_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
number: f64,
xf_index: u32,
) {
#[cfg(feature = "zmij")]
let mut buffer = zmij::Buffer::new();
#[cfg(all(feature = "ryu", not(feature = "zmij")))]
let mut buffer = ryu::Buffer::new();
#[cfg(any(feature = "ryu", feature = "zmij"))]
let number = buffer.format_finite(number);
if xf_index > 0 {
write!(
writer,
r#"<c r="{col_name}{row}" s="{xf_index}"><v>{number}</v></c>"#
)
.expect(XML_WRITE_ERROR);
} else {
write!(writer, r#"<c r="{col_name}{row}"><v>{number}</v></c>"#).expect(XML_WRITE_ERROR);
}
}
fn write_string_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
string_index: u32,
xf_index: u32,
) {
if xf_index > 0 {
write!(
writer,
r#"<c r="{col_name}{row}" s="{xf_index}" t="s"><v>{string_index}</v></c>"#
)
.expect(XML_WRITE_ERROR);
} else {
write!(
writer,
r#"<c r="{col_name}{row}" t="s"><v>{string_index}</v></c>"#
)
.expect(XML_WRITE_ERROR);
}
}
fn write_inline_string_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
string: &str,
xf_index: u32,
) {
let whitespace = ['\t', '\n', ' '];
let preserve = if string.starts_with(whitespace) || string.ends_with(whitespace) {
" xml:space=\"preserve\""
} else {
""
};
let string = crate::xmlwriter::escape_xml_escapes(string);
let string = crate::xmlwriter::escape_xml_data(&string);
if xf_index > 0 {
write!(
writer,
r#"<c r="{col_name}{row}" s="{xf_index}" t="inlineStr"><is><t{preserve}>{string}</t></is></c>"#
)
.expect(XML_WRITE_ERROR);
} else {
write!(
writer,
r#"<c r="{col_name}{row}" t="inlineStr"><is><t{preserve}>{string}</t></is></c>"#
)
.expect(XML_WRITE_ERROR);
}
}
#[cfg(feature = "constant_memory")]
fn write_inline_rich_string_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
string: &str,
xf_index: u32,
) {
if xf_index > 0 {
write!(
writer,
r#"<c r="{col_name}{row}" s="{xf_index}" t="inlineStr"><is>{string}</is></c>"#
)
.expect(XML_WRITE_ERROR);
} else {
write!(
writer,
r#"<c r="{col_name}{row}" t="inlineStr"><is>{string}</is></c>"#
)
.expect(XML_WRITE_ERROR);
}
}
fn write_formula_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
formula: &str,
xf_index: u32,
result: &str,
) {
let style = if xf_index > 0 {
format!(r#" s="{xf_index}""#)
} else {
String::new()
};
let result_type = if result.parse::<f64>().is_err() {
match result {
"#DIV/0!" | "#N/A" | "#NAME?" | "#NULL!" | "#NUM!" | "#REF!" | "#VALUE!"
| "#GETTING_DATA" => r#" t="e""#,
"TRUE" | "FALSE" => r#" t="b""#,
_ => r#" t="str""#,
}
} else {
""
};
let formula = crate::xmlwriter::escape_xml_data(formula);
let mut result = crate::xmlwriter::escape_xml_data(result);
if result == "TRUE" {
result = Cow::Owned("1".to_string());
}
if result == "FALSE" {
result = Cow::Owned("0".to_string());
}
write!(
writer,
r#"<c r="{col_name}{row}"{style}{result_type}><f>{formula}</f><v>{result}</v></c>"#
)
.expect(XML_WRITE_ERROR);
}
#[allow(clippy::too_many_arguments)]
fn write_array_formula_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
formula: &str,
xf_index: u32,
result: &str,
is_dynamic: bool,
range: &str,
) {
let style = if xf_index > 0 {
format!(r#" s="{xf_index}""#)
} else {
String::new()
};
let cm = if is_dynamic { r#" cm="1""# } else { "" };
let result_type = if result.parse::<f64>().is_err() {
match result {
"#DIV/0!" | "#N/A" | "#NAME?" | "#NULL!" | "#NUM!" | "#REF!" | "#VALUE!"
| "#GETTING_DATA" => r#" t="e""#,
"TRUE" | "FALSE" => r#" t="b""#,
_ => r#" t="str""#,
}
} else {
""
};
let formula = crate::xmlwriter::escape_xml_data(formula);
let mut result = crate::xmlwriter::escape_xml_data(result);
if result == "TRUE" {
result = Cow::Owned("1".to_string());
}
if result == "FALSE" {
result = Cow::Owned("0".to_string());
}
write!(
writer,
r#"<c r="{col_name}{row}"{style}{cm}{result_type}><f t="array" ref="{range}">{formula}</f><v>{result}</v></c>"#
)
.expect(XML_WRITE_ERROR);
}
fn write_blank_cell<W: Write>(writer: &mut W, row: RowNum, col_name: &str, xf_index: u32) {
if xf_index > 0 {
write!(writer, r#"<c r="{col_name}{row}" s="{xf_index}"/>"#).expect(XML_WRITE_ERROR);
}
}
fn write_boolean_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
boolean: bool,
xf_index: u32,
) {
let boolean = i32::from(boolean);
if xf_index > 0 {
write!(
writer,
r#"<c r="{col_name}{row}" s="{xf_index}" t="b"><v>{boolean}</v></c>"#
)
.expect(XML_WRITE_ERROR);
} else {
write!(
writer,
r#"<c r="{col_name}{row}" t="b"><v>{boolean}</v></c>"#
)
.expect(XML_WRITE_ERROR);
}
}
fn write_error_cell<W: Write>(
writer: &mut W,
row: RowNum,
col_name: &str,
value: u32,
xf_index: u32,
) {
if xf_index > 0 {
write!(
writer,
r#"<c r="{col_name}{row}" s="{xf_index}" t="e" vm="{value}"><v>#VALUE!</v></c>"#
)
.expect(XML_WRITE_ERROR);
} else {
write!(
writer,
r#"<c r="{col_name}{row}" t="e" vm="{value}"><v>#VALUE!</v></c>"#
)
.expect(XML_WRITE_ERROR);
}
}
fn write_cols(&mut self) {
if self.changed_cols.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "cols");
let mut col_options: Vec<_> = self
.changed_cols
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect();
col_options.sort_unstable_by_key(|x| x.0);
let mut col_options = col_options.into_iter();
let first_col_options = col_options.next().unwrap();
let mut first_col = first_col_options.0;
let mut prev_col_options = first_col_options.1;
let mut last_col = first_col;
for (col_num, col_options) in col_options {
if col_num == last_col + 1 && col_options == prev_col_options {
last_col = col_num;
} else {
self.write_col_element(first_col, last_col, &prev_col_options);
first_col = col_num;
last_col = first_col;
prev_col_options = col_options;
}
}
self.write_col_element(first_col, last_col, &prev_col_options);
xml_end_tag(&mut self.writer, "cols");
}
fn write_col_element(&mut self, first_col: ColNum, last_col: ColNum, col_options: &ColOptions) {
let first_col = first_col + 1;
let last_col = last_col + 1;
let mut width = col_options.width;
let xf_index = col_options.xf_index;
let has_custom_width = width != self.default_col_width;
let hidden = col_options.hidden;
if width == self.default_col_width && hidden {
width = 0;
}
let char_width = f64::from(width * 256 / self.max_digit_width) / 256.0;
let mut attributes = vec![
("min", first_col.to_string()),
("max", last_col.to_string()),
("width", char_width.to_string()),
];
if xf_index > 0 {
let xf_index = self.get_global_xf_index(xf_index);
attributes.push(("style", xf_index.to_string()));
}
if col_options.autofit {
attributes.push(("bestFit", "1".to_string()));
}
if hidden {
attributes.push(("hidden", "1".to_string()));
}
if has_custom_width || hidden {
attributes.push(("customWidth", "1".to_string()));
}
if col_options.level > 0 {
attributes.push(("outlineLevel", col_options.level.to_string()));
}
if col_options.collapsed {
attributes.push(("collapsed", "1".to_string()));
}
xml_empty_tag(&mut self.writer, "col", &attributes);
}
fn write_header_footer(&mut self) {
let mut attributes = vec![];
if !self.header_footer_scale_with_doc {
attributes.push(("scaleWithDoc", "0".to_string()));
}
if !self.header_footer_align_with_page {
attributes.push(("alignWithMargins", "0".to_string()));
}
if self.header.is_empty() && self.footer.is_empty() {
xml_empty_tag(&mut self.writer, "headerFooter", &attributes);
} else {
xml_start_tag(&mut self.writer, "headerFooter", &attributes);
if !self.header.is_empty() {
self.write_odd_header();
}
if !self.footer.is_empty() {
self.write_odd_footer();
}
xml_end_tag(&mut self.writer, "headerFooter");
}
}
fn write_odd_header(&mut self) {
let header = self
.header
.replace("&[Tab]", "&A")
.replace("&[Date]", "&D")
.replace("&[File]", "&F")
.replace("&[Page]", "&P")
.replace("&[Path]", "&Z")
.replace("&[Time]", "&T")
.replace("&[Pages]", "&N")
.replace("&[Picture]", "&G");
xml_data_element_only(&mut self.writer, "oddHeader", &header);
}
fn write_odd_footer(&mut self) {
let footer = self
.footer
.replace("&[Tab]", "&A")
.replace("&[Date]", "&D")
.replace("&[File]", "&F")
.replace("&[Page]", "&P")
.replace("&[Path]", "&Z")
.replace("&[Time]", "&T")
.replace("&[Pages]", "&N")
.replace("&[Picture]", "&G");
xml_data_element_only(&mut self.writer, "oddFooter", &footer);
}
fn write_drawing(&mut self) {
self.rel_count += 1;
let attributes = [("r:id", format!("rId{}", self.rel_count))];
xml_empty_tag(&mut self.writer, "drawing", &attributes);
}
fn write_legacy_drawing(&mut self) {
self.rel_count += 1;
let attributes = [("r:id", format!("rId{}", self.rel_count))];
xml_empty_tag(&mut self.writer, "legacyDrawing", &attributes);
}
fn write_legacy_drawing_hf(&mut self) {
self.rel_count += 1;
let attributes = [("r:id", format!("rId{}", self.rel_count))];
xml_empty_tag(&mut self.writer, "legacyDrawingHF", &attributes);
}
fn write_picture(&mut self) {
self.rel_count += 1;
let attributes = [("r:id", format!("rId{}", self.rel_count))];
xml_empty_tag(&mut self.writer, "picture", &attributes);
}
fn write_table_parts(&mut self) {
let num_tables = self.tables.len();
let attributes = [("count", num_tables.to_string())];
xml_start_tag(&mut self.writer, "tableParts", &attributes);
for _ in 1..=num_tables {
self.rel_count += 1;
self.write_table_part(self.rel_count);
}
xml_end_tag(&mut self.writer, "tableParts");
}
fn write_table_part(&mut self, index: u32) {
let attributes = [("r:id", format!("rId{index}"))];
xml_empty_tag(&mut self.writer, "tablePart", &attributes);
}
fn write_sheet_protection(&mut self) {
let mut attributes = vec![];
if self.protection_hash != 0x0000 {
attributes.push(("password", format!("{:04X}", self.protection_hash)));
}
if self.is_chartsheet {
if self.protection_options.contents {
attributes.push(("content", "1".to_string()));
}
if self.protection_options.edit_objects {
attributes.push(("objects", "1".to_string()));
}
} else {
attributes.push(("sheet", "1".to_string()));
if !self.protection_options.edit_objects {
attributes.push(("objects", "1".to_string()));
}
if !self.protection_options.edit_scenarios {
attributes.push(("scenarios", "1".to_string()));
}
if self.protection_options.format_cells {
attributes.push(("formatCells", "0".to_string()));
}
if self.protection_options.format_columns {
attributes.push(("formatColumns", "0".to_string()));
}
if self.protection_options.format_rows {
attributes.push(("formatRows", "0".to_string()));
}
if self.protection_options.insert_columns {
attributes.push(("insertColumns", "0".to_string()));
}
if self.protection_options.insert_rows {
attributes.push(("insertRows", "0".to_string()));
}
if self.protection_options.insert_links {
attributes.push(("insertHyperlinks", "0".to_string()));
}
if self.protection_options.delete_columns {
attributes.push(("deleteColumns", "0".to_string()));
}
if self.protection_options.delete_rows {
attributes.push(("deleteRows", "0".to_string()));
}
if !self.protection_options.select_locked_cells {
attributes.push(("selectLockedCells", "1".to_string()));
}
if self.protection_options.sort {
attributes.push(("sort", "0".to_string()));
}
if self.protection_options.use_autofilter {
attributes.push(("autoFilter", "0".to_string()));
}
if self.protection_options.use_pivot_tables {
attributes.push(("pivotTables", "0".to_string()));
}
if !self.protection_options.select_unlocked_cells {
attributes.push(("selectUnlockedCells", "1".to_string()));
}
}
xml_empty_tag(&mut self.writer, "sheetProtection", &attributes);
}
fn write_protected_ranges(&mut self) {
xml_start_tag_only(&mut self.writer, "protectedRanges");
for (range, name, hash) in self.unprotected_ranges.clone() {
self.write_protected_range(range, name, hash);
}
xml_end_tag(&mut self.writer, "protectedRanges");
}
fn write_protected_range(&mut self, range: String, name: String, hash: u16) {
let mut attributes = vec![];
if hash > 0 {
attributes.push(("password", format!("{hash:04X}")));
}
attributes.push(("sqref", range));
attributes.push(("name", name));
xml_empty_tag(&mut self.writer, "protectedRange", &attributes);
}
fn write_row_breaks(&mut self) {
let attributes = [
("count", self.horizontal_breaks.len().to_string()),
("manualBreakCount", self.horizontal_breaks.len().to_string()),
];
xml_start_tag(&mut self.writer, "rowBreaks", &attributes);
for row_num in self.horizontal_breaks.clone() {
self.write_row_brk(row_num);
}
xml_end_tag(&mut self.writer, "rowBreaks");
}
fn write_row_brk(&mut self, row_num: u32) {
let attributes = [
("id", row_num.to_string()),
("max", "16383".to_string()),
("man", "1".to_string()),
];
xml_empty_tag(&mut self.writer, "brk", &attributes);
}
fn write_col_breaks(&mut self) {
let attributes = [
("count", self.vertical_breaks.len().to_string()),
("manualBreakCount", self.vertical_breaks.len().to_string()),
];
xml_start_tag(&mut self.writer, "colBreaks", &attributes);
for col_num in self.vertical_breaks.clone() {
self.write_col_brk(col_num);
}
xml_end_tag(&mut self.writer, "colBreaks");
}
fn write_col_brk(&mut self, col_num: u32) {
let attributes = [
("id", col_num.to_string()),
("max", "1048575".to_string()),
("man", "1".to_string()),
];
xml_empty_tag(&mut self.writer, "brk", &attributes);
}
fn write_extensions(&mut self) {
xml_start_tag_only(&mut self.writer, "extLst");
if self.has_x14_conditional_formats {
let attributes = [
(
"xmlns:x14",
"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
),
("uri", "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"),
];
xml_start_tag(&mut self.writer, "ext", &attributes);
self.write_conditional_formattings();
}
if self.has_sparklines {
let attributes = [
(
"xmlns:x14",
"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
),
("uri", "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"),
];
xml_start_tag(&mut self.writer, "ext", &attributes);
self.write_sparkline_groups();
}
xml_end_tag(&mut self.writer, "extLst");
}
fn write_sparkline_groups(&mut self) {
let attributes = [(
"xmlns:xm",
"http://schemas.microsoft.com/office/excel/2006/main",
)];
xml_start_tag(&mut self.writer, "x14:sparklineGroups", &attributes);
let sparklines = self.sparklines.clone();
for sparkline in sparklines.into_iter().rev() {
self.write_sparkline_group(&sparkline);
}
xml_end_tag(&mut self.writer, "x14:sparklineGroups");
xml_end_tag(&mut self.writer, "ext");
}
fn write_sparkline_group(&mut self, sparkline: &Sparkline) {
let mut attributes = vec![];
if let Some(max) = sparkline.custom_max {
attributes.push(("manualMax", max.to_string()));
}
if let Some(min) = sparkline.custom_min {
attributes.push(("manualMin", min.to_string()));
}
match sparkline.sparkline_type {
SparklineType::Column => {
attributes.push(("type", "column".to_string()));
}
SparklineType::WinLose => {
attributes.push(("type", "stacked".to_string()));
}
SparklineType::Line => {}
}
if let Some(weight) = sparkline.line_weight {
attributes.push(("lineWeight", weight.to_string()));
}
if sparkline.date_range.has_data() {
attributes.push(("dateAxis", "1".to_string()));
}
match sparkline.show_empty_cells_as {
ChartEmptyCells::Gaps | ChartEmptyCells::Connected => {
attributes.push((
"displayEmptyCellsAs",
sparkline.show_empty_cells_as.to_string(),
));
}
ChartEmptyCells::Zero => {}
}
if sparkline.show_markers {
attributes.push(("markers", "1".to_string()));
}
if sparkline.show_high_point {
attributes.push(("high", "1".to_string()));
}
if sparkline.show_low_point {
attributes.push(("low", "1".to_string()));
}
if sparkline.show_first_point {
attributes.push(("first", "1".to_string()));
}
if sparkline.show_last_point {
attributes.push(("last", "1".to_string()));
}
if sparkline.show_negative_points {
attributes.push(("negative", "1".to_string()));
}
if sparkline.show_axis {
attributes.push(("displayXAxis", "1".to_string()));
}
if sparkline.show_hidden_data {
attributes.push(("displayHidden", "1".to_string()));
}
if sparkline.custom_min.is_some() {
attributes.push(("minAxisType", "custom".to_string()));
} else if sparkline.group_min {
attributes.push(("minAxisType", "group".to_string()));
}
if sparkline.custom_max.is_some() {
attributes.push(("maxAxisType", "custom".to_string()));
} else if sparkline.group_max {
attributes.push(("maxAxisType", "group".to_string()));
}
if sparkline.show_right_to_left {
attributes.push(("rightToLeft", "1".to_string()));
}
xml_start_tag(&mut self.writer, "x14:sparklineGroup", &attributes);
self.write_sparkline_color("x14:colorSeries", sparkline.series_color);
self.write_sparkline_color("x14:colorNegative", sparkline.negative_points_color);
self.write_sparkline_color("x14:colorAxis", sparkline.axis_color);
self.write_sparkline_color("x14:colorMarkers", sparkline.markers_color);
self.write_sparkline_color("x14:colorFirst", sparkline.first_point_color);
self.write_sparkline_color("x14:colorLast", sparkline.last_point_color);
self.write_sparkline_color("x14:colorHigh", sparkline.high_point_color);
self.write_sparkline_color("x14:colorLow", sparkline.low_point_color);
if sparkline.date_range.has_data() {
xml_data_element_only(&mut self.writer, "xm:f", &sparkline.date_range.formula());
}
self.write_sparklines(sparkline);
xml_end_tag(&mut self.writer, "x14:sparklineGroup");
}
fn write_sparkline_color(&mut self, name: &str, color: Color) {
xml_empty_tag(&mut self.writer, name, &color.attributes());
}
fn write_sparklines(&mut self, sparkline: &Sparkline) {
xml_start_tag_only(&mut self.writer, "x14:sparklines");
for range in &sparkline.ranges {
xml_start_tag_only(&mut self.writer, "x14:sparkline");
xml_data_element_only(&mut self.writer, "xm:f", &range.1);
xml_data_element_only(&mut self.writer, "xm:sqref", &range.0);
xml_end_tag(&mut self.writer, "x14:sparkline");
}
xml_end_tag(&mut self.writer, "x14:sparklines");
}
fn write_ignored_errors(&mut self) {
xml_start_tag_only(&mut self.writer, "ignoredErrors");
for error_type in IgnoreError::iterator() {
let error_name = error_type.to_string();
if let Some(error_range) = self.ignored_errors.get(&error_type) {
let attributes = [
("sqref", error_range.clone()),
(&error_name, "1".to_string()),
];
xml_empty_tag(&mut self.writer, "ignoredError", &attributes);
}
}
xml_end_tag(&mut self.writer, "ignoredErrors");
}
}
pub trait IntoExcelData {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError>;
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError>;
}
macro_rules! write_string_trait_impl {
($($t:ty)*) => ($(
impl IntoExcelData for $t {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_string(row, col, self.into(), None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_string(row, col, self.into(), Some(format))
}
}
)*)
}
write_string_trait_impl!(&str &String String Cow<'_, str>);
macro_rules! write_number_trait_impl {
($($t:ty)*) => ($(
impl IntoExcelData for $t {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_number(row, col, self, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_number(row, col, self, Some(format))
}
}
)*)
}
write_number_trait_impl!(u8 i8 u16 i16 u32 i32 f32 f64);
macro_rules! write_number_trait_impl {
($($t:ty)*) => ($(
impl IntoExcelData for $t {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
#[allow(clippy::cast_precision_loss)]
worksheet.store_number(row, col, self as f64, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
#[allow(clippy::cast_precision_loss)]
worksheet.store_number(row, col, self as f64, Some(format))
}
}
)*)
}
write_number_trait_impl!(u64 i64);
impl IntoExcelData for bool {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_boolean(row, col, self, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_boolean(row, col, self, Some(format))
}
}
impl IntoExcelData for &ExcelDateTime {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = self.to_excel();
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = self.to_excel();
worksheet.store_datetime(row, col, number, Some(format))
}
}
impl IntoExcelData for ExcelDateTime {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = self.to_excel_serial_date();
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = self.to_excel_serial_date();
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelData for &chrono::NaiveDateTime {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = ExcelDateTime::chrono_datetime_to_excel(self);
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = ExcelDateTime::chrono_datetime_to_excel(self);
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelData for &chrono::NaiveDate {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = ExcelDateTime::chrono_date_to_excel(self);
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = ExcelDateTime::chrono_date_to_excel(self);
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelData for &chrono::NaiveTime {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = ExcelDateTime::chrono_time_to_excel(self);
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = ExcelDateTime::chrono_time_to_excel(self);
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "jiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl IntoExcelData for &jiff::civil::DateTime {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = ExcelDateTime::jiff_datetime_to_excel(self);
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = ExcelDateTime::jiff_datetime_to_excel(self);
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "jiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl IntoExcelData for &jiff::civil::Date {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = ExcelDateTime::jiff_date_to_excel(self);
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = ExcelDateTime::jiff_date_to_excel(self);
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "jiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl IntoExcelData for &jiff::civil::Time {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let number = ExcelDateTime::jiff_time_to_excel(self);
worksheet.store_datetime(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let number = ExcelDateTime::jiff_time_to_excel(self);
worksheet.store_datetime(row, col, number, Some(format))
}
}
#[cfg(feature = "rust_decimal")]
impl IntoExcelData for Decimal {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
let Some(number) = self.to_f64() else {
return Err(XlsxError::ParameterError(format!(
"Cannot represent Decimal {:?} as an Excel f64 value.",
self
)));
};
worksheet.store_number(row, col, number, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
let Some(number) = self.to_f64() else {
return Err(XlsxError::ParameterError(format!(
"Cannot represent Decimal {:?} as an Excel f64 value.",
self
)));
};
worksheet.store_number(row, col, number, Some(format))
}
}
impl IntoExcelData for Formula {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_formula(row, col, self, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_formula(row, col, self, Some(format))
}
}
impl IntoExcelData for &Formula {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_formula(row, col, (*self).clone(), None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_formula(row, col, (*self).clone(), Some(format))
}
}
impl IntoExcelData for Url {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_url(row, col, &self, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_url(row, col, &self, Some(format))
}
}
impl IntoExcelData for &Url {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
worksheet.store_url(row, col, self, None)
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
worksheet.store_url(row, col, self, Some(format))
}
}
impl<T: IntoExcelData> IntoExcelData for Option<T> {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
match self {
Some(data) => worksheet.write(row, col, data),
None => Ok(worksheet),
}
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
match self {
Some(data) => worksheet.write_with_format(row, col, data, format),
None => worksheet.write_blank(row, col, format),
}
}
}
impl<T: IntoExcelData, E: IntoExcelData> IntoExcelData for Result<T, E> {
fn write(
self,
worksheet: &mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError> {
match self {
Ok(data) => worksheet.write(row, col, data),
Err(data) => worksheet.write(row, col, data),
}
}
fn write_with_format<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&'a mut Worksheet, XlsxError> {
match self {
Ok(data) => worksheet.write_with_format(row, col, data, format),
Err(data) => worksheet.write_with_format(row, col, data, format),
}
}
}
fn round_to_emus(dimension: f64) -> f64 {
(dimension * 9525.0).round()
}
#[derive(Clone)]
pub(crate) struct CellRange {
pub(crate) first_row: RowNum,
pub(crate) first_col: ColNum,
pub(crate) last_row: RowNum,
pub(crate) last_col: ColNum,
}
impl CellRange {
pub(crate) fn new(
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> CellRange {
CellRange {
first_row,
first_col,
last_row,
last_col,
}
}
pub(crate) fn to_range_string(&self) -> String {
utility::cell_range(self.first_row, self.first_col, self.last_row, self.last_col)
}
pub(crate) fn to_error_string(&self) -> String {
format!(
"({}, {}, {}, {}) / {}",
self.first_row,
self.first_col,
self.last_row,
self.last_col,
utility::cell_range(self.first_row, self.first_col, self.last_row, self.last_col)
)
}
}
impl Default for CellRange {
fn default() -> Self {
CellRange {
first_row: ROW_MAX,
first_col: COL_MAX,
last_row: 0,
last_col: 0,
}
}
}
#[derive(Clone)]
struct RowOptions {
height: u32,
xf_index: u32,
level: u8,
hidden: bool,
collapsed: bool,
format: Option<Format>,
}
#[derive(Clone, PartialEq)]
struct ColOptions {
width: u32,
xf_index: u32,
level: u8,
hidden: bool,
collapsed: bool,
autofit: bool,
format: Option<Format>,
}
#[derive(Clone)]
enum CellType {
ArrayFormula {
formula: Box<str>,
xf_index: u32,
result: Box<str>,
is_dynamic: bool,
range: Box<str>,
},
Blank {
xf_index: u32,
},
Boolean {
boolean: bool,
xf_index: u32,
},
Error {
xf_index: u32,
value: u32,
},
Formula {
formula: Box<str>,
xf_index: u32,
result: Box<str>,
},
Number {
number: f64,
xf_index: u32,
},
DateTime {
number: f64,
xf_index: u32,
},
String {
string: Arc<str>,
xf_index: u32,
string_id: Option<u32>,
},
RichString {
string: Arc<str>,
xf_index: u32,
raw_string: Arc<str>,
string_id: Option<u32>,
},
InlineString {
string: Arc<str>,
xf_index: u32,
},
}
#[derive(Clone, Copy)]
enum PageView {
Normal,
PageLayout,
PageBreaks,
}
#[derive(PartialEq)]
enum FilterType {
Table,
Worksheet,
}
#[derive(Clone)]
struct Panes {
freeze_cell: (RowNum, ColNum),
top_cell: (RowNum, ColNum),
}
impl Panes {
fn is_empty(&self) -> bool {
self.freeze_cell.0 == 0 && self.freeze_cell.1 == 0
}
fn top_left(&self) -> String {
if self.top_cell.0 == 0 && self.top_cell.1 == 0 {
utility::row_col_to_cell(self.freeze_cell.0, self.freeze_cell.1)
} else {
utility::row_col_to_cell(self.top_cell.0, self.top_cell.1)
}
}
}
#[derive(Clone)]
pub(crate) struct DefinedName {
pub(crate) in_use: bool,
pub(crate) name: String,
pub(crate) sort_name: String,
pub(crate) range: String,
pub(crate) quoted_sheet_name: String,
pub(crate) index: u16,
pub(crate) name_type: DefinedNameType,
pub(crate) first_row: RowNum,
pub(crate) first_col: ColNum,
pub(crate) last_row: RowNum,
pub(crate) last_col: ColNum,
}
impl DefinedName {
pub(crate) fn new() -> DefinedName {
DefinedName {
in_use: false,
name: String::new(),
sort_name: String::new(),
range: String::new(),
quoted_sheet_name: String::new(),
index: 0,
name_type: DefinedNameType::Global,
first_row: ROW_MAX,
first_col: COL_MAX,
last_row: 0,
last_col: 0,
}
}
pub(crate) fn initialize(&mut self, sheet_name: &str) {
self.quoted_sheet_name = sheet_name.to_string();
self.set_range();
self.set_sort_name();
}
pub(crate) fn app_name(&self) -> String {
match self.name_type {
DefinedNameType::Local => format!("{}!{}", self.quoted_sheet_name, self.name),
DefinedNameType::PrintArea => format!("{}!Print_Area", self.quoted_sheet_name),
DefinedNameType::Autofilter => String::new(),
DefinedNameType::PrintTitles => format!("{}!Print_Titles", self.quoted_sheet_name),
DefinedNameType::Global => {
if self.range.contains('!') {
self.name.clone()
} else {
String::new()
}
}
}
}
pub(crate) fn name(&self) -> String {
match self.name_type {
DefinedNameType::PrintArea => "_xlnm.Print_Area".to_string(),
DefinedNameType::Autofilter => "_xlnm._FilterDatabase".to_string(),
DefinedNameType::PrintTitles => "_xlnm.Print_Titles".to_string(),
_ => self.name.clone(),
}
}
pub(crate) fn set_sort_name(&mut self) {
let mut sort_name = match self.name_type {
DefinedNameType::PrintArea => "Print_Area{}".to_string(),
DefinedNameType::Autofilter => "_FilterDatabase{}".to_string(),
DefinedNameType::PrintTitles => "Print_Titles".to_string(),
_ => self.name.clone(),
};
sort_name = sort_name.replace('\'', "");
self.sort_name = sort_name.to_lowercase();
}
pub(crate) fn set_range(&mut self) {
match self.name_type {
DefinedNameType::Autofilter | DefinedNameType::PrintArea => {
let range = if self.first_col == 0 && self.last_col == COL_MAX - 1 {
format!("${}:${}", self.first_row + 1, self.last_row + 1)
} else if self.first_row == 0 && self.last_row == ROW_MAX - 1 {
format!(
"${}:${}",
utility::column_number_to_name(self.first_col),
utility::column_number_to_name(self.last_col)
)
} else {
utility::cell_range_absolute(
self.first_row,
self.first_col,
self.last_row,
self.last_col,
)
};
self.range = format!("{}!{}", self.quoted_sheet_name, range);
}
DefinedNameType::PrintTitles => {
let mut range = String::new();
if self.first_col != COL_MAX || self.last_col != 0 {
range = format!(
"{}!${}:${}",
self.quoted_sheet_name,
utility::column_number_to_name(self.first_col),
utility::column_number_to_name(self.last_col)
);
}
if self.first_row != ROW_MAX || self.last_row != 0 {
let row_range = format!(
"{}!${}:${}",
self.quoted_sheet_name,
self.first_row + 1,
self.last_row + 1
);
if range.is_empty() {
range = row_range;
} else {
range = format!("{range},{row_range}");
}
}
self.range = range;
}
_ => {}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum IgnoreError {
NumberStoredAsText,
FormulaError,
FormulaDiffers,
FormulaRefersToEmptyCells,
FormulaOmitsCells,
DataValidationError,
TwoDigitTextYear,
UnlockedCellsWithFormula,
InconsistentColumnFormula,
}
impl IgnoreError {
pub fn iterator() -> impl Iterator<Item = IgnoreError> {
[
Self::NumberStoredAsText,
Self::FormulaError,
Self::FormulaDiffers,
Self::FormulaOmitsCells,
Self::UnlockedCellsWithFormula,
Self::InconsistentColumnFormula,
Self::FormulaRefersToEmptyCells,
Self::DataValidationError,
Self::TwoDigitTextYear,
]
.iter()
.copied()
}
}
impl fmt::Display for IgnoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FormulaError => write!(f, "evalError"),
Self::FormulaDiffers => write!(f, "formula"),
Self::TwoDigitTextYear => write!(f, "TwoDigitTextYear"),
Self::FormulaOmitsCells => write!(f, "formulaRange"),
Self::DataValidationError => write!(f, "listDataValidation"),
Self::NumberStoredAsText => write!(f, "numberStoredAsText"),
Self::UnlockedCellsWithFormula => write!(f, "unlockedFormula"),
Self::InconsistentColumnFormula => write!(f, "calculatedColumn"),
Self::FormulaRefersToEmptyCells => write!(f, "emptyCellReference"),
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum DefinedNameType {
Autofilter,
Global,
Local,
PrintArea,
PrintTitles,
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum Visible {
Default,
Hidden,
VeryHidden,
}
pub(crate) enum BorderPosition {
All,
RowLeft,
RowCenter,
RowRight,
ColTop,
ColCenter,
ColBottom,
TopLeft,
TopCenter,
TopRight,
CenterLeft,
CenterCenter,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight,
}