#![warn(missing_docs)]
mod tests;
use std::io::Cursor;
use std::{collections::HashSet, fmt};
use crate::xmlwriter::{
xml_data_element_only, xml_declaration, xml_empty_tag, xml_end_tag, xml_start_tag,
xml_start_tag_only,
};
use crate::{utility::ToXmlBoolean, CellRange, Format, Formula, RowNum, XlsxError};
#[derive(Clone)]
pub struct Table {
pub(crate) writer: Cursor<Vec<u8>>,
pub(crate) columns: Vec<TableColumn>,
pub(crate) index: u32,
pub(crate) name: String,
pub(crate) style: TableStyle,
pub(crate) cell_range: CellRange,
pub(crate) show_header_row: bool,
pub(crate) show_total_row: bool,
pub(crate) show_first_column: bool,
pub(crate) show_last_column: bool,
pub(crate) show_banded_rows: bool,
pub(crate) show_banded_columns: bool,
pub(crate) show_autofilter: bool,
pub(crate) is_serde_table: bool,
pub(crate) alt_text: String,
pub(crate) alt_text_title: String,
}
impl Table {
#[allow(clippy::new_without_default)]
pub fn new() -> Table {
let writer = Cursor::new(Vec::with_capacity(2048));
Table {
writer,
columns: vec![],
index: 0,
name: String::new(),
style: TableStyle::Medium9,
cell_range: CellRange::default(),
show_first_column: false,
show_last_column: false,
show_banded_rows: true,
show_banded_columns: false,
show_autofilter: true,
show_header_row: true,
show_total_row: false,
is_serde_table: false,
alt_text: String::new(),
alt_text_title: String::new(),
}
}
pub fn set_header_row(mut self, enable: bool) -> Table {
self.show_header_row = enable;
if !self.show_header_row {
self.show_autofilter = false;
}
self
}
pub fn set_total_row(mut self, enable: bool) -> Table {
self.show_total_row = enable;
self
}
pub fn set_banded_rows(mut self, enable: bool) -> Table {
self.show_banded_rows = enable;
self
}
pub fn set_banded_columns(mut self, enable: bool) -> Table {
self.show_banded_columns = enable;
self
}
pub fn set_first_column(mut self, enable: bool) -> Table {
self.show_first_column = enable;
self
}
pub fn set_last_column(mut self, enable: bool) -> Table {
self.show_last_column = enable;
self
}
pub fn set_autofilter(mut self, enable: bool) -> Table {
self.show_autofilter = enable;
self
}
pub fn set_columns(mut self, columns: &[TableColumn]) -> Table {
self.columns = columns.to_vec();
self
}
pub fn set_name(mut self, name: impl Into<String>) -> Table {
self.name = name.into();
self
}
pub fn set_style(mut self, style: TableStyle) -> Table {
self.style = style;
self
}
pub fn set_alt_text(mut self, alt_text: impl Into<String>) -> Table {
self.alt_text = alt_text.into();
self
}
pub fn set_alt_text_title(mut self, title: impl Into<String>) -> Table {
self.alt_text_title = title.into();
self
}
#[doc(hidden)]
pub fn has_header_row(&self) -> bool {
self.show_header_row
}
#[doc(hidden)]
pub fn has_total_row(&self) -> bool {
self.show_total_row
}
pub(crate) fn initialize_columns(
&mut self,
default_headers: &[String],
) -> Result<(), XlsxError> {
let mut seen_column_names = HashSet::new();
let num_columns = self.cell_range.last_col - self.cell_range.first_col + 1;
self.columns
.resize_with(num_columns as usize, TableColumn::default);
for (index, column) in self.columns.iter_mut().enumerate() {
if column.name.is_empty() {
column.name.clone_from(&default_headers[index]);
}
if seen_column_names.contains(&column.name.to_lowercase()) {
return Err(XlsxError::TableError(format!(
"Column name '{}' already exists in Table at {}",
column.name,
self.cell_range.to_error_string()
)));
}
seen_column_names.insert(column.name.to_lowercase().clone());
}
Ok(())
}
pub(crate) fn first_data_row(&self) -> RowNum {
if self.show_header_row {
self.cell_range.first_row + 1
} else {
self.cell_range.first_row
}
}
pub(crate) fn last_data_row(&self) -> RowNum {
if self.show_total_row {
self.cell_range.last_row - 1
} else {
self.cell_range.last_row
}
}
pub(crate) fn assemble_xml_file(&mut self) {
xml_declaration(&mut self.writer);
self.write_table();
if self.show_autofilter && self.show_header_row {
self.write_auto_filter();
}
self.write_columns();
self.write_table_style_info();
if !self.alt_text.is_empty() || !self.alt_text_title.is_empty() {
self.write_extension_list();
}
xml_end_tag(&mut self.writer, "table");
}
fn write_table(&mut self) {
let schema = "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string();
let range = self.cell_range.to_range_string();
let name = if self.name.is_empty() {
format!("Table{}", self.index)
} else {
self.name.clone()
};
let mut attributes = vec![
("xmlns", schema),
("id", self.index.to_string()),
("name", name.clone()),
("displayName", name),
("ref", range),
];
if !self.show_header_row {
attributes.push(("headerRowCount", "0".to_string()));
}
if self.show_total_row {
attributes.push(("totalsRowCount", "1".to_string()));
} else {
attributes.push(("totalsRowShown", "0".to_string()));
}
xml_start_tag(&mut self.writer, "table", &attributes);
}
fn write_auto_filter(&mut self) {
let mut autofilter_range = self.cell_range.clone();
if self.show_total_row {
autofilter_range.last_row -= 1;
}
let attributes = vec![("ref", autofilter_range.to_range_string())];
xml_empty_tag(&mut self.writer, "autoFilter", &attributes);
}
fn write_columns(&mut self) {
let attributes = vec![("count", self.columns.len().to_string())];
xml_start_tag(&mut self.writer, "tableColumns", &attributes);
for (index, column) in self.columns.clone().iter().enumerate() {
self.write_column(index + 1, column);
}
xml_end_tag(&mut self.writer, "tableColumns");
}
fn write_column(&mut self, index: usize, column: &TableColumn) {
let mut attributes = vec![("id", index.to_string()), ("name", column.name.clone())];
if !column.total_label.is_empty() {
attributes.push(("totalsRowLabel", column.total_label.clone()));
} else if column.total_function != TableFunction::None {
attributes.push(("totalsRowFunction", column.total_function.to_string()));
}
if let Some(format) = &column.format {
attributes.push(("dataDxfId", format.dxf_index.to_string()));
}
if column.formula.is_some() || matches!(&column.total_function, TableFunction::Custom(_)) {
xml_start_tag(&mut self.writer, "tableColumn", &attributes);
if let Some(formula) = &column.formula {
self.write_calculated_column_formula(&formula.formula_string);
}
if let TableFunction::Custom(formula) = &column.total_function {
self.write_totals_row_formula(&formula.formula_string);
}
xml_end_tag(&mut self.writer, "tableColumn");
} else {
xml_empty_tag(&mut self.writer, "tableColumn", &attributes);
}
}
fn write_calculated_column_formula(&mut self, formula: &str) {
xml_data_element_only(&mut self.writer, "calculatedColumnFormula", formula);
}
fn write_totals_row_formula(&mut self, formula: &str) {
xml_data_element_only(&mut self.writer, "totalsRowFormula", formula);
}
fn write_table_style_info(&mut self) {
let mut attributes = vec![];
if self.style != TableStyle::None {
attributes.push(("name", self.style.to_string()));
}
attributes.push(("showFirstColumn", self.show_first_column.to_xml_bool()));
attributes.push(("showLastColumn", self.show_last_column.to_xml_bool()));
attributes.push(("showRowStripes", self.show_banded_rows.to_xml_bool()));
attributes.push(("showColumnStripes", self.show_banded_columns.to_xml_bool()));
xml_empty_tag(&mut self.writer, "tableStyleInfo", &attributes);
}
fn write_extension_list(&mut self) {
let attributes = [
("uri", "{504A1905-F514-4f6f-8877-14C23A59335A}"),
(
"xmlns:x14",
"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
),
];
xml_start_tag_only(&mut self.writer, "extLst");
xml_start_tag(&mut self.writer, "ext", &attributes);
self.write_x14_table();
xml_end_tag(&mut self.writer, "ext");
xml_end_tag(&mut self.writer, "extLst");
}
fn write_x14_table(&mut self) {
let mut attributes = vec![];
if !self.alt_text_title.is_empty() {
attributes.push(("altText", self.alt_text_title.clone()));
}
if !self.alt_text.is_empty() {
attributes.push(("altTextSummary", self.alt_text.clone()));
}
xml_empty_tag(&mut self.writer, "x14:table", &attributes);
}
}
#[derive(Clone)]
pub struct TableColumn {
pub(crate) name: String,
pub(crate) total_function: TableFunction,
pub(crate) total_label: String,
pub(crate) formula: Option<Formula>,
pub(crate) format: Option<Format>,
pub(crate) header_format: Option<Format>,
}
impl TableColumn {
pub fn new() -> TableColumn {
TableColumn {
name: String::new(),
total_function: TableFunction::None,
total_label: String::new(),
formula: None,
format: None,
header_format: None,
}
}
pub fn set_header(mut self, caption: impl Into<String>) -> TableColumn {
self.name = caption.into();
self
}
pub fn set_total_function(mut self, function: TableFunction) -> TableColumn {
self.total_function = function;
self
}
pub fn set_total_label(mut self, label: impl Into<String>) -> TableColumn {
self.total_label = label.into();
self
}
pub fn set_formula(mut self, formula: impl Into<Formula>) -> TableColumn {
let mut formula = formula.into();
formula = formula.clone().escape_table_functions();
self.formula = Some(formula);
self
}
pub fn set_format(mut self, format: impl Into<Format>) -> TableColumn {
self.format = Some(format.into());
self
}
pub fn set_header_format(mut self, format: impl Into<Format>) -> TableColumn {
self.header_format = Some(format.into());
self
}
pub(crate) fn total_function(&self) -> Formula {
let column_name = self
.name
.replace('\'', "''")
.replace('#', "'#")
.replace(']', "']")
.replace('[', "'[");
match &self.total_function {
TableFunction::None => Formula::new(""),
TableFunction::Max => Formula::new(format!("SUBTOTAL(104,[{column_name}])")),
TableFunction::Min => Formula::new(format!("SUBTOTAL(105,[{column_name}])")),
TableFunction::Sum => Formula::new(format!("SUBTOTAL(109,[{column_name}])")),
TableFunction::Var => Formula::new(format!("SUBTOTAL(110,[{column_name}])")),
TableFunction::Count => Formula::new(format!("SUBTOTAL(103,[{column_name}])")),
TableFunction::StdDev => Formula::new(format!("SUBTOTAL(107,[{column_name}])")),
TableFunction::Average => Formula::new(format!("SUBTOTAL(101,[{column_name}])")),
TableFunction::CountNumbers => Formula::new(format!("SUBTOTAL(102,[{column_name}])")),
TableFunction::Custom(formula) => formula.clone(),
}
}
}
impl Default for TableColumn {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, PartialEq)]
pub enum TableFunction {
None,
Average,
Count,
CountNumbers,
Max,
Min,
Sum,
StdDev,
Var,
Custom(Formula),
}
impl fmt::Display for TableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Max => write!(f, "max"),
Self::Min => write!(f, "min"),
Self::Sum => write!(f, "sum"),
Self::Var => write!(f, "var"),
Self::None => write!(f, "None"),
Self::Count => write!(f, "count"),
Self::StdDev => write!(f, "stdDev"),
Self::Average => write!(f, "average"),
Self::CountNumbers => write!(f, "countNums"),
Self::Custom(_) => write!(f, "custom"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TableStyle {
None,
Light1,
Light2,
Light3,
Light4,
Light5,
Light6,
Light7,
Light8,
Light9,
Light10,
Light11,
Light12,
Light13,
Light14,
Light15,
Light16,
Light17,
Light18,
Light19,
Light20,
Light21,
Medium1,
Medium2,
Medium3,
Medium4,
Medium5,
Medium6,
Medium7,
Medium8,
Medium9,
Medium10,
Medium11,
Medium12,
Medium13,
Medium14,
Medium15,
Medium16,
Medium17,
Medium18,
Medium19,
Medium20,
Medium21,
Medium22,
Medium23,
Medium24,
Medium25,
Medium26,
Medium27,
Medium28,
Dark1,
Dark2,
Dark3,
Dark4,
Dark5,
Dark6,
Dark7,
Dark8,
Dark9,
Dark10,
Dark11,
}
impl fmt::Display for TableStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "TableStyleNone"),
Self::Light1 => write!(f, "TableStyleLight1"),
Self::Light2 => write!(f, "TableStyleLight2"),
Self::Light3 => write!(f, "TableStyleLight3"),
Self::Light4 => write!(f, "TableStyleLight4"),
Self::Light5 => write!(f, "TableStyleLight5"),
Self::Light6 => write!(f, "TableStyleLight6"),
Self::Light7 => write!(f, "TableStyleLight7"),
Self::Light8 => write!(f, "TableStyleLight8"),
Self::Light9 => write!(f, "TableStyleLight9"),
Self::Light10 => write!(f, "TableStyleLight10"),
Self::Light11 => write!(f, "TableStyleLight11"),
Self::Light12 => write!(f, "TableStyleLight12"),
Self::Light13 => write!(f, "TableStyleLight13"),
Self::Light14 => write!(f, "TableStyleLight14"),
Self::Light15 => write!(f, "TableStyleLight15"),
Self::Light16 => write!(f, "TableStyleLight16"),
Self::Light17 => write!(f, "TableStyleLight17"),
Self::Light18 => write!(f, "TableStyleLight18"),
Self::Light19 => write!(f, "TableStyleLight19"),
Self::Light20 => write!(f, "TableStyleLight20"),
Self::Light21 => write!(f, "TableStyleLight21"),
Self::Medium1 => write!(f, "TableStyleMedium1"),
Self::Medium2 => write!(f, "TableStyleMedium2"),
Self::Medium3 => write!(f, "TableStyleMedium3"),
Self::Medium4 => write!(f, "TableStyleMedium4"),
Self::Medium5 => write!(f, "TableStyleMedium5"),
Self::Medium6 => write!(f, "TableStyleMedium6"),
Self::Medium7 => write!(f, "TableStyleMedium7"),
Self::Medium8 => write!(f, "TableStyleMedium8"),
Self::Medium9 => write!(f, "TableStyleMedium9"),
Self::Medium10 => write!(f, "TableStyleMedium10"),
Self::Medium11 => write!(f, "TableStyleMedium11"),
Self::Medium12 => write!(f, "TableStyleMedium12"),
Self::Medium13 => write!(f, "TableStyleMedium13"),
Self::Medium14 => write!(f, "TableStyleMedium14"),
Self::Medium15 => write!(f, "TableStyleMedium15"),
Self::Medium16 => write!(f, "TableStyleMedium16"),
Self::Medium17 => write!(f, "TableStyleMedium17"),
Self::Medium18 => write!(f, "TableStyleMedium18"),
Self::Medium19 => write!(f, "TableStyleMedium19"),
Self::Medium20 => write!(f, "TableStyleMedium20"),
Self::Medium21 => write!(f, "TableStyleMedium21"),
Self::Medium22 => write!(f, "TableStyleMedium22"),
Self::Medium23 => write!(f, "TableStyleMedium23"),
Self::Medium24 => write!(f, "TableStyleMedium24"),
Self::Medium25 => write!(f, "TableStyleMedium25"),
Self::Medium26 => write!(f, "TableStyleMedium26"),
Self::Medium27 => write!(f, "TableStyleMedium27"),
Self::Medium28 => write!(f, "TableStyleMedium28"),
Self::Dark1 => write!(f, "TableStyleDark1"),
Self::Dark2 => write!(f, "TableStyleDark2"),
Self::Dark3 => write!(f, "TableStyleDark3"),
Self::Dark4 => write!(f, "TableStyleDark4"),
Self::Dark5 => write!(f, "TableStyleDark5"),
Self::Dark6 => write!(f, "TableStyleDark6"),
Self::Dark7 => write!(f, "TableStyleDark7"),
Self::Dark8 => write!(f, "TableStyleDark8"),
Self::Dark9 => write!(f, "TableStyleDark9"),
Self::Dark10 => write!(f, "TableStyleDark10"),
Self::Dark11 => write!(f, "TableStyleDark11"),
}
}
}
impl From<&Table> for Table {
fn from(value: &Table) -> Table {
(*value).clone()
}
}