#[cfg(test)] mod tests; use crate::cell::AsRangeString; use crate::{ error, AsExcelDate, AsPaletteColor, Cell, Chart, Col, ColRange, DateMode, Format, Formats, Index, IndexRange, Result, Row, RowRange, SharedStrings, WorkbookSheetProperties, XmlWritable, XmlWriter, }; use chrono::{DateTime, NaiveTime, Utc}; use indexmap::{indexmap, IndexMap, IndexSet}; use rgb::RGB8; use std::cell::RefCell; use std::cmp::max; use std::collections::BTreeMap; use std::convert::TryFrom; use std::path::Path; use std::rc::Rc; const STR_MAX: usize = 0x7fff; const DEFAULT_COL_WIDTH: f64 = 8.43f64; const DEFAULT_ROW_HEIGHT: f64 = 15.0f64; const F1: &str = "formula1"; const F2: &str = "formula2"; struct Inner { sheet_index: usize, shared_strings: SharedStrings, formats: Formats, workbook_sheet_properties: WorkbookSheetProperties, cells: BTreeMap>, row_dimension: Option, col_dimension: Option, col_options: BTreeMap< Col, (ColRange, Option, f64, Option), >, outline_col_level: OutlineLevel, row_options: BTreeMap< Row, (RowRange, Option, f64, Option), >, page_margins: PageMargins, data_validations: Vec, tab_color: Option, fit_to_pages: Option, outline: Outline, merged_ranges: Vec, } struct PageFit { width: u16, height: u16, } impl Inner { fn insert_cell(&mut self, index: Index, cell: Cell) -> Result<()> { let Index { row, col } = index; self.extend_row_dimension_to(row); self.extend_col_dimension_to(col); self.cells .entry(row) .or_insert_with(Default::default) .insert(col, cell); Ok(()) } fn build_sheet_pr(&self) -> SheetPr { SheetPr { tab_color: self.tab_color, fit_page: self.fit_to_pages.is_some(), filter_on: false, outline: self.outline.clone(), } } fn build_dimension(&self) -> Dimension { Dimension { range: IndexRange::new_from_ranges( self.row_dimension.unwrap_or_default(), self.col_dimension.unwrap_or_default(), ), } } fn build_sheet_format_pr(&self) -> SheetFormatPr { // TODO: fill all the required parameters SheetFormatPr { default_row_height: 15f64, } } fn build_sheet_views(&self) -> SheetViews { SheetViews { view: SheetView { tab_selected: self .workbook_sheet_properties .selected(self.sheet_index), }, } } fn build_sheet_data(&self) -> SheetData { SheetData { cells: &self.cells } } fn build_columns(&self) -> Columns { Columns { col_options: &self.col_options, } } fn build_merge_cells(&self) -> MergeCells { MergeCells { merged_ranges: &self.merged_ranges, } } fn extend_row_dimension_to(&mut self, r: R) -> &mut Self where RowRange: From, { match self.row_dimension { Some(ref mut dimension) => { dimension.extend_to(r); } None => self.row_dimension = Some(RowRange::from(r)), } self } fn extend_col_dimension_to(&mut self, c: C) -> &mut Self where ColRange: From, { match self.col_dimension { Some(ref mut dimension) => { dimension.extend_to(c); } None => self.col_dimension = Some(ColRange::from(c)), } self } fn set_column_options( &mut self, range: ColRange, width: f64, format: Option<&Format>, options: Option, ) -> Result<()> { let RowColOptions { hidden, level, collapsed: _, } = options.clone().unwrap_or_default(); if format.is_some() || (width != DEFAULT_COL_WIDTH && hidden) { self.extend_col_dimension_to(range); } self.outline_col_level = max(self.outline_col_level, level); let format_index = self.formats.create_or_get_index(format); self.col_options .insert(range.first(), (range, options, width, format_index)); Ok(()) } fn set_row_options( &mut self, range: RowRange, height: f64, format: Option<&Format>, options: Option, ) -> Result<()> { let RowColOptions { hidden, level, collapsed: _, } = options.clone().unwrap_or_default(); if format.is_some() || (height != DEFAULT_ROW_HEIGHT && hidden) { self.extend_row_dimension_to(range); } self.outline_col_level = max(self.outline_col_level, level); let format_index = self.formats.create_or_get_index(format); self.row_options .insert(range.first(), (range, options, height, format_index)); Ok(()) } } #[derive(Clone)] pub struct Worksheet { inner: Rc>, } impl Worksheet { pub(crate) fn new( sheet_index: usize, shared_strings: SharedStrings, formats: Formats, workbook_sheet_properties: WorkbookSheetProperties, ) -> Self { Worksheet { inner: Rc::new(RefCell::new(Inner { sheet_index, shared_strings, formats, workbook_sheet_properties, cells: Default::default(), row_dimension: None, col_dimension: None, col_options: Default::default(), outline_col_level: Default::default(), row_options: Default::default(), page_margins: Default::default(), data_validations: Default::default(), tab_color: Default::default(), fit_to_pages: Default::default(), outline: Default::default(), merged_ranges: Default::default(), })), } } pub fn write_number( &mut self, index: Index, number: f64, format: Option<&Format>, ) -> Result<()> { let mut inner = self.inner.borrow_mut(); let format = inner.formats.create_or_get_index(format); inner.insert_cell(index, Cell::new_number(number, format)) } pub fn write_string>( &mut self, index: Index, string: S, format: Option<&Format>, ) -> Result<()> { if string.as_ref() == "" { self.write_blank(index, format) } else { Self::check_str_max_length(string.as_ref())?; let mut inner = self.inner.borrow_mut(); let string_index = inner.shared_strings.create_or_get_index(string, false); let format = inner.formats.create_or_get_index(format); inner .insert_cell(index, Cell::new_string(string_index, format)) } } pub fn write_formula>( &mut self, index: Index, formula: S, format: Option<&Format>, ) -> Result<()> { self.write_formula_num(index, formula, format, 0f64) } pub fn write_array_formula>( &mut self, start: Index, end: Index, formula: S, format: Option<&Format>, ) -> Result<()> { unimplemented!(); } pub fn write_array_formula_num>( &mut self, start: Index, end: Index, formula: S, format: Option<&Format>, number: f64, ) -> Result<()> { unimplemented!(); } pub fn write_datetime( &mut self, index: Index, datetime: DateTime, format: Option<&Format>, ) -> Result<()> { let excel_date = datetime.as_excel_date(DateMode::BasedOn1900); self.write_number(index, excel_date, format) } pub fn write_url>( &mut self, index: Index, url: S, format: Option<&Format>, ) -> Result<()> { unimplemented!(); } pub fn write_boolean( &mut self, index: Index, value: bool, format: Option<&Format>, ) -> Result<()> { unimplemented!(); } pub fn write_blank( &mut self, index: Index, format: Option<&Format>, ) -> Result<()> { let mut inner = self.inner.borrow_mut(); if format.is_some() { let format = inner.formats.create_or_get_index(format); inner.insert_cell(index, Cell::new_blank(format)) } else { Ok(()) } } pub fn write_formula_num>( &mut self, index: Index, formula: S, format: Option<&Format>, result: f64, ) -> Result<()> { let mut inner = self.inner.borrow_mut(); let format = inner.formats.create_or_get_index(format); inner.insert_cell( index, Cell::new_formula(formula.into(), result, format), ) } pub fn write_rich_string( &mut self, index: Index, rich_string: &[RichStringTuple], format: Option<&Format>, ) -> Result<()> { unimplemented!(); } pub fn set_row( &mut self, range: RowRange, height: f64, format: Option<&Format>, ) -> Result<()> { self.inner .borrow_mut() .set_row_options(range, height, format, None) } pub fn set_row_options( &mut self, range: RowRange, height: f64, format: Option<&Format>, options: RowColOptions, ) -> Result<()> { self.inner.borrow_mut().set_row_options( range, height, format, Some(options), ) } pub fn set_column( &mut self, range: ColRange, width: f64, format: Option<&Format>, ) -> Result<()> { self.inner .borrow_mut() .set_column_options(range, width, format, None) } pub fn set_column_options( &mut self, range: ColRange, width: f64, format: Option<&Format>, options: RowColOptions, ) -> Result<()> { self.inner.borrow_mut().set_column_options( range, width, format, Some(options), ) } pub fn insert_image>( &mut self, index: Index, filename: P, ) -> Result<()> { unimplemented!(); } pub fn insert_image_options>( &mut self, index: Index, filename: P, options: ImageOptions, ) -> Result<()> { unimplemented!(); } pub fn insert_image_buffer( &mut self, index: Index, image_buffer: &[u8], ) -> Result<()> { unimplemented!(); } pub fn insert_image_buffer_options( &mut self, index: Index, image_buffer: &[u8], options: ImageOptions, ) -> Result<()> { unimplemented!(); } pub fn insert_chart( &mut self, index: Index, chart: Chart, ) -> Result<()> { unimplemented!(); } pub fn insert_chart_options( &mut self, index: Index, chart: Chart, options: ImageOptions, ) -> Result<()> { unimplemented!(); } pub fn merge_range( &mut self, range: IndexRange, format: Option<&Format>, ) -> Result<()> { let data_index = range.top_left(); for row in range.row_range.iter() { for col in range.col_range.iter() { let index = Index { row, col }; if index != data_index { self.write_blank(index, format)?; } } } // If the range is only one cell, we don't need to merge it. if range.top_left() != range.bottom_right() { self.inner.borrow_mut().merged_ranges.push(range); } Ok(()) } pub fn merge_range_str( &mut self, range: IndexRange, string: &str, format: Option<&Format>, ) -> Result<()> { self.write_string(range.top_left(), string, format)?; self.merge_range(range, format) } pub fn autofilter(&mut self, range: IndexRange) -> Result<()> { unimplemented!(); } pub fn data_validation( &mut self, validation: Validation, ) -> Result<()> { self.inner.borrow_mut().data_validations.push(validation); Ok(()) } pub fn activate(&mut self) { let inner = self.inner.borrow_mut(); inner .workbook_sheet_properties .set_active(inner.sheet_index); } pub fn select(&mut self) { let mut inner = self.inner.borrow_mut(); let index = inner.sheet_index; inner.workbook_sheet_properties.set_selected(index, true); } pub fn hide(&mut self) { let mut inner = self.inner.borrow_mut(); let index = inner.sheet_index; inner.workbook_sheet_properties.set_hidden(index, true); } pub fn set_first_sheet(&mut self) { // TODO: this probably fits better into the Workbook. unimplemented!(); } pub fn freeze_panes(&mut self, index: Index) { unimplemented!(); } pub fn split_panes(&mut self, vertical: f64, horizontal: f64) { unimplemented!(); } pub fn set_selection(&mut self, start: Index, end: Index) { unimplemented!(); } pub fn set_landscape(&mut self) { unimplemented!(); } pub fn set_portrait(&mut self) { unimplemented!(); } pub fn set_page_view(&mut self) { unimplemented!(); } pub fn set_paper(&mut self, paper_type: Paper) { unimplemented!(); } pub fn set_margins( &mut self, left: f64, right: f64, top: f64, bottom: f64, ) { unimplemented!(); } pub fn set_header>(&mut self, string: S) -> Result<()> { unimplemented!(); } pub fn set_footer>(&mut self, string: S) -> Result<()> { unimplemented!(); } pub fn set_header_options>( &mut self, string: S, options: HeaderFooterOptions, ) -> Result<()> { unimplemented!(); } pub fn set_footer_options>( &mut self, string: S, options: HeaderFooterOptions, ) -> Result<()> { unimplemented!(); } pub fn set_h_pagebreaks(&mut self, breaks: &[Row]) -> Result<()> { unimplemented!(); } pub fn set_v_pagebreaks(&mut self, breaks: &[Col]) -> Result<()> { unimplemented!(); } pub fn print_across(&mut self) { unimplemented!(); } pub fn set_zoom(&mut self, scale: u16) { unimplemented!(); } pub fn set_gridlines(&mut self, option: Gridlines) { unimplemented!(); } pub fn center_horizontally(&mut self) { unimplemented!(); } pub fn center_vertically(&mut self) { unimplemented!(); } pub fn print_row_col_headers(&mut self) { unimplemented!(); } pub fn repeat_rows(&mut self, range: RowRange) -> Result<()> { unimplemented!(); } pub fn repeat_columns(&mut self, range: ColRange) -> Result<()> { unimplemented!(); } pub fn print_area(&mut self, range: IndexRange) -> Result<()> { unimplemented!(); } /// Store the vertical and horizontal number of pages that will define /// the maximum area printed. pub fn fit_to_pages(&mut self, width: u16, height: u16) { self.inner.borrow_mut().fit_to_pages = Some(PageFit { width, height }) } pub fn set_start_page(&mut self, start_page: u16) { unimplemented!(); } pub fn set_print_scale(&mut self, scale: u16) { unimplemented!(); } pub fn right_to_left(&mut self) { unimplemented!(); } pub fn hide_zero(&mut self) { unimplemented!(); } pub fn set_tab_color(&mut self, color: RGB8) { self.inner.borrow_mut().tab_color = Some(color); } pub fn protect( &mut self, password: Option<&str>, options: Protection, ) { unimplemented!(); } pub fn outline_settings( &mut self, visible: bool, symbols_below: bool, symbols_right: bool, auto_style: bool, ) { unimplemented!(); } pub fn set_default_row( &mut self, height: f64, hide_unused_rows: bool, ) { unimplemented!(); } pub fn set_vba_name>(&mut self, name: N) -> Result<()> { unimplemented!(); } fn check_str_max_length>(string: S) -> Result<()> { if string.as_ref().chars().count() > STR_MAX { error::MaxStringLengthExceeded { maximum: STR_MAX }.fail() } else { Ok(()) } } } pub struct RichStringTuple { pub format: Option, pub string: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] pub enum OutlineLevel { Level0, Level1, Level2, Level3, Level4, Level5, Level6, Level7, } impl Default for OutlineLevel { fn default() -> OutlineLevel { OutlineLevel::Level0 } } impl Into for OutlineLevel { fn into(self) -> u8 { self as u8 } } impl TryFrom for OutlineLevel { type Error = error::Error; fn try_from(level: u8) -> Result { match level { 0 => Ok(OutlineLevel::Level0), 1 => Ok(OutlineLevel::Level1), 2 => Ok(OutlineLevel::Level2), 3 => Ok(OutlineLevel::Level3), 4 => Ok(OutlineLevel::Level4), 5 => Ok(OutlineLevel::Level5), 6 => Ok(OutlineLevel::Level6), 7 => Ok(OutlineLevel::Level7), _ => error::OutlineLevelOutOfRange { min: 0, max: 7, level, } .fail(), } } } #[derive(Clone, Debug, Default)] pub struct RowColOptions { pub level: OutlineLevel, pub hidden: bool, pub collapsed: bool, } pub struct ImageOptions { pub x_offset: i32, pub y_offset: i32, pub x_scale: f64, pub y_scale: f64, } pub struct Validation { validation_type: ValidationType, range: IndexRange, /// Should be true by default. ignore_blank: bool, /// Should be true by default. show_input: bool, /// Should be true by default. show_error: bool, pub input_title: Option, pub input_message: Option, pub error_title: Option, pub error_message: Option, pub error_type: ValidationErrorType, } impl Validation { pub fn new( validation_type: ValidationType, range: impl Into, ) -> Self { Validation { validation_type, range: range.into(), ignore_blank: true, show_input: true, show_error: true, input_title: None, input_message: None, error_title: None, error_message: None, error_type: ValidationErrorType::Stop, } } } impl XmlWritable for Validation { fn write_xml(&self, w: &mut W) -> Result<()> { let mut attrs = IndexMap::new(); let t = &self.validation_type; if let Some(t) = t.get_type() { attrs.insert("type", t); } if let Some(o) = t.get_operator() { attrs.insert("operator", o); } match self.error_type { ValidationErrorType::Stop => {} ValidationErrorType::Warning => { attrs.insert("errorStyle", "warning"); } ValidationErrorType::Information => { attrs.insert("errorStyle", "information"); } } if self.ignore_blank { attrs.insert("allowBlank", "1"); } if let Some(d) = t.get_show_dropdown() { attrs.insert("showDropDown", d); } if self.show_input { attrs.insert("showInputMessage", "1"); } if self.show_error { attrs.insert("showErrorMessage", "1"); } if let Some(ref t) = self.error_title { attrs.insert("errorTitle", t); } if let Some(ref t) = self.error_message { attrs.insert("error", t); } if let Some(ref t) = self.input_title { attrs.insert("promptTitle", t); } if let Some(ref t) = self.input_message { attrs.insert("prompt", t); } let sqref = self.range.as_range_string(); attrs.insert("sqref", &sqref); let tag = "dataValidation"; let formulas = t.get_formulas(); if formulas.is_empty() { w.empty_tag_with_attrs(tag, attrs)?; } else { w.start_tag_with_attrs(tag, attrs)?; for (k, v) in formulas.iter() { w.tag_with_text(k, v)?; } w.end_tag(tag)?; } Ok(()) } } impl XmlWritable for Vec { fn write_xml(&self, w: &mut W) -> Result<()> { if self.is_empty() { Ok(()) } else { let tag = "dataValidations"; let attrs = indexmap! { "count" => format!("{}", self.len()), }; w.start_tag_with_attrs(tag, attrs)?; for validation in self.iter() { validation.write_xml(w)?; } w.end_tag(tag)?; Ok(()) } } } pub enum ValidationType { /// Restrict cell input to whole/integer numbers only. Integer(Criterion), /// Restrict cell input to whole/integer numbers only, using a cell /// reference. IntegerFormula(Criterion), /// Restrict cell input to decimal numbers only. Decimal(Criterion), /// Restrict cell input to decimal numbers only, using a cell /// reference. DecimalFormula(Criterion), /// Restrict cell input to a list of strings in a dropdown. List { /// The list of allowed values. values: IndexSet, /// Show the drop-down. // TODO: this is inverse logic in libxlsxwriter - need to check // which we need. show_dropdown: bool, }, /// Restrict cell input to a list of strings in a dropdown, using a /// cell range. ListFormula { /// A formula that yields the list of allowed values. formula: String, /// Show the drop-down. // TODO: this is inverse logic in libxlsxwriter - need to check // which we need. show_dropdown: bool, }, /// Restrict cell input to date values only. Date(Criterion>), /// Restrict cell input to date values only, using a cell reference. DateFormula(Criterion), /// Restrict cell input to date values only, as a serial number. /// Undocumented. DateNumber(Criterion), /// Restrict cell input to time values only. Time(Criterion), /// Restrict cell input to time values only, using a cell reference. TimeFormula(Criterion), /// Restrict cell input to time values only, as a serial number. /// Undocumented. TimeNumber(Criterion), /// Restrict cell input to strings of defined length. Length(Criterion), /// Restrict cell input to strings of defined length, using a cell /// reference. LengthFormula(Criterion), /// Restrict cell to input controlled by a custom formula that /// returns a boolean value. CustomFormula(String), /// Allow any type of input. Mainly only useful for pop-up messages. Any, } impl ValidationType { fn get_formulas(&self) -> IndexMap<&'static str, String> { use ValidationType as D; match self { D::Integer(c) => c.get_formulas(), D::Decimal(c) => c.get_formulas(), D::List { values, .. } => { let s = values .iter() .cloned() .collect::>() .join(",") .to_string(); indexmap! { F1 => format!("\"{}\"", s), } } D::Date(c) => c.get_formulas(), D::DateNumber(c) => c.get_formulas(), D::Time(c) => c.get_formulas(), D::TimeNumber(c) => c.get_formulas(), D::Length(c) => c.get_formulas(), D::IntegerFormula(c) => c.get_formulas(), D::DecimalFormula(c) => c.get_formulas(), D::ListFormula { formula, .. } => indexmap! { F1 => formula.formula_string(), }, D::DateFormula(c) => c.get_formulas(), D::TimeFormula(c) => c.get_formulas(), D::LengthFormula(c) => c.get_formulas(), D::CustomFormula(f) => indexmap! { F1 => f.to_string() }, D::Any => indexmap! {}, } } fn get_type(&self) -> Option<&'static str> { use ValidationType as D; match self { D::Integer(_) | D::IntegerFormula(_) => Some("whole"), D::Decimal(_) | D::DecimalFormula(_) => Some("decimal"), D::List { .. } | D::ListFormula { .. } => Some("list"), D::Date(_) | D::DateNumber(_) | D::DateFormula(_) => { Some("date") } D::Time(_) | D::TimeNumber(_) | D::TimeFormula(_) => { Some("time") } D::Length(_) | D::LengthFormula(_) => Some("textLength"), D::CustomFormula(_) => Some("custom"), D::Any => None, } } fn get_operator(&self) -> Option<&'static str> { use ValidationType as D; match self { D::Integer(c) => c.get_operator(), D::IntegerFormula(c) => c.get_operator(), D::Decimal(c) => c.get_operator(), D::DecimalFormula(c) => c.get_operator(), D::Date(c) => c.get_operator(), D::DateNumber(c) => c.get_operator(), D::DateFormula(c) => c.get_operator(), D::Time(c) => c.get_operator(), D::TimeNumber(c) => c.get_operator(), D::TimeFormula(c) => c.get_operator(), D::Length(c) => c.get_operator(), D::LengthFormula(c) => c.get_operator(), D::List { .. } | D::ListFormula { .. } | D::CustomFormula(_) | D::Any => None, } } fn get_show_dropdown(&self) -> Option<&'static str> { use ValidationType as D; match self { D::List { show_dropdown, .. } | D::ListFormula { show_dropdown, .. } if !show_dropdown => { Some("1") } D::List { .. } | D::ListFormula { .. } | D::Integer(_) | D::IntegerFormula(_) | D::Decimal(_) | D::DecimalFormula(_) | D::Date(_) | D::DateNumber(_) | D::DateFormula(_) | D::Time(_) | D::TimeNumber(_) | D::TimeFormula(_) | D::Length(_) | D::LengthFormula(_) | D::CustomFormula(_) | D::Any => None, } } } pub enum ValidationErrorType { Stop, Warning, Information, } pub trait FormulaString { fn formula_string(&self) -> String; } impl FormulaString for f64 { fn formula_string(&self) -> String { format!("{}", *self) } } impl FormulaString for i64 { fn formula_string(&self) -> String { format!("{}", *self) } } impl FormulaString for usize { fn formula_string(&self) -> String { format!("{}", *self) } } impl FormulaString for String { fn formula_string(&self) -> String { self.trim_start_matches('=').to_string() } } impl FormulaString for DateTime { fn formula_string(&self) -> String { format!("{}", self.as_excel_date(DateMode::BasedOn1900)) } } impl FormulaString for NaiveTime { fn formula_string(&self) -> String { use chrono::Timelike; let seconds_since_midnight = self.num_seconds_from_midnight() as f64; const SECONDS_PER_DAY: f64 = 24f64 * 60f64 * 60f64; format!("{}", seconds_since_midnight / SECONDS_PER_DAY) } } pub enum Criterion { Between(T, T), NotBetween(T, T), EqualTo(T), NotEqualTo(T), GreaterThan(T), LessThan(T), GreaterThanOrEqualTo(T), LessThanOrEqualTo(T), } impl Criterion { pub fn get_operator(&self) -> Option<&'static str> { use Criterion as C; match self { C::Between(_, _) => { // Between is the default for 2 formulas and isn't added. None } C::NotBetween(_, _) => Some("notBetween"), C::EqualTo(_) => Some("equal"), C::NotEqualTo(_) => Some("notEqual"), C::GreaterThan(_) => Some("greaterThan"), C::LessThan(_) => Some("lessThan"), C::GreaterThanOrEqualTo(_) => Some("greaterThanOrEqual"), C::LessThanOrEqualTo(_) => Some("lessThanOrEqual"), } } pub fn get_formulas(&self) -> IndexMap<&'static str, String> { use Criterion as C; match self { C::Between(a, b) => indexmap! { F1 => a.formula_string(), F2 => b.formula_string(), }, C::NotBetween(a, b) => indexmap! { F1 => a.formula_string(), F2 => b.formula_string(), }, C::EqualTo(a) => indexmap! { F1 => a.formula_string() }, C::NotEqualTo(a) => indexmap! { F1 => a.formula_string() }, C::GreaterThan(a) => indexmap! { F1 => a.formula_string() }, C::LessThan(a) => indexmap! { F1 => a.formula_string() }, C::GreaterThanOrEqualTo(a) => indexmap! { F1 => a.formula_string(), }, C::LessThanOrEqualTo(a) => indexmap! { F1 => a.formula_string(), }, } } } #[derive(Clone, Copy, Debug)] pub enum Paper { PrinterDefault, Letter, LetterSmall, Tabloid, Ledger, Legal, Statement, Executive, A3, A4, A4Small, A5, B4, B5, Folio, Quarto, Paper10x14Inch, Paper11x17Inch, Note, Envelope9, Envelope10, Envelope11, Envelope12, Envelope14, CSizeSheet, DSizeSheet, ESizeSheet, EnvelopeDL, EnvelopeC3, EnvelopeC4, EnvelopeC5, EnvelopeC6, EnvelopeC65, EnvelopeB4, EnvelopeB5, EnvelopeB6, Envelope110x230mm, Monarch, Envelope3_5_8x6_1_2Inch, Fanfold, GermanStdFanfold, GermanLegalFanfold, } #[derive(Clone, Copy, Debug)] pub struct HeaderFooterOptions { pub margin: f64, } #[derive(Clone, Copy, Debug)] pub enum Gridlines { HideAllGridlines, ShowScreenGridlens, ShowPrintGridlines, ShowAllGridlines, } #[derive(Clone, Copy, Debug)] pub struct Protection { pub no_select_locked_cells: bool, pub no_select_unlocked_cells: bool, pub format_cells: bool, pub format_columns: bool, pub format_rows: bool, pub insert_columns: bool, pub insert_rows: bool, pub insert_hyperlinks: bool, pub delete_columns: bool, pub delete_rows: bool, pub sort: bool, pub autofilter: bool, pub pivot_tables: bool, pub scenarios: bool, pub objects: bool, pub no_content: bool, pub no_objects: bool, } impl Protection { pub fn all_protected() -> Self { Protection { no_select_locked_cells: true, no_select_unlocked_cells: true, format_cells: true, format_columns: true, format_rows: true, insert_columns: true, insert_rows: true, insert_hyperlinks: true, delete_columns: true, delete_rows: true, sort: true, autofilter: true, pivot_tables: true, scenarios: true, objects: true, no_content: true, no_objects: true, } } pub fn all_unprotected() -> Self { Protection { no_select_locked_cells: false, no_select_unlocked_cells: false, format_cells: false, format_columns: false, format_rows: false, insert_columns: false, insert_rows: false, insert_hyperlinks: false, delete_columns: false, delete_rows: false, sort: false, autofilter: false, pivot_tables: false, scenarios: false, objects: false, no_content: false, no_objects: false, } } } impl XmlWritable for Worksheet { fn write_xml(&self, w: &mut W) -> Result<()> { self.inner.borrow().write_xml(w) } } impl XmlWritable for Inner { fn write_xml(&self, w: &mut W) -> Result<()> { let tag = "worksheet"; let attrs = indexmap! { "xmlns" => "http://schemas.openxmlformats.org/spreadsheetml/2006/main", "xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships", }; w.start_tag_with_attrs(tag, attrs)?; self.build_sheet_pr().write_xml(w)?; self.build_dimension().write_xml(w)?; self.build_sheet_views().write_xml(w)?; self.build_sheet_format_pr().write_xml(w)?; self.build_columns().write_xml(w)?; self.build_sheet_data().write_xml(w)?; // TODO: write sheetProtection // TODO: write autoFilter self.build_merge_cells().write_xml(w)?; self.data_validations.write_xml(w)?; // TODO: write hyperlinks // TODO: write printOptions self.page_margins.write_xml(w)?; // TODO: write pageSetup // TODO: write headerFooter // TODO: write rowBreaks // TODO: write colBreaks // TODO: write drawings w.end_tag(tag)?; Ok(()) } } struct PageMargins { left: f64, right: f64, top: f64, bottom: f64, header: f64, footer: f64, } impl Default for PageMargins { fn default() -> Self { PageMargins { left: 0.7, right: 0.7, top: 0.75, bottom: 0.75, header: 0.3, footer: 0.3, } } } impl XmlWritable for PageMargins { fn write_xml(&self, w: &mut W) -> Result<()> { let attrs = indexmap! { "left" => format!("{}", self.left), "right" => format!("{}", self.right), "top" => format!("{}", self.top), "bottom" => format!("{}", self.bottom), "header" => format!("{}", self.header), "footer" => format!("{}", self.footer), }; w.empty_tag_with_attrs("pageMargins", attrs) } } struct Dimension { range: IndexRange, } impl XmlWritable for Dimension { fn write_xml(&self, w: &mut W) -> Result<()> { let attrs = indexmap! { "ref" => self.range.as_range_string(), }; w.empty_tag_with_attrs("dimension", attrs) } } struct SheetPr { tab_color: Option, fit_page: bool, filter_on: bool, outline: Outline, } #[derive(Debug, Eq, PartialEq, Clone)] struct Outline { style: bool, below: bool, right: bool, on: bool, } impl Default for Outline { fn default() -> Self { Outline { style: false, below: true, right: true, on: true, } } } impl XmlWritable for SheetPr { fn write_xml(&self, w: &mut W) -> Result<()> { let outline_changed = self.outline != Outline::default(); if !self.fit_page && !self.filter_on && self.tab_color.is_none() && outline_changed { Ok(()) } else { let tag = "sheetPr"; let mut attrs = indexmap! {}; // TODO: check vba_codename if self.filter_on { attrs.insert("filterMode", "1"); } if self.fit_page || self.tab_color.is_some() || outline_changed { w.start_tag_with_attrs(tag, attrs)?; self.write_tab_color(w)?; self.outline.write_xml(w)?; self.write_page_set_up_pr(w)?; w.end_tag(tag)?; } else { w.empty_tag_with_attrs(tag, attrs)?; } Ok(()) } } } impl SheetPr { fn write_tab_color(&self, w: &mut W) -> Result<()> { if let Some(ref color) = self.tab_color { let attrs = indexmap! { "rgb" => color.as_palette_color(), }; w.empty_tag_with_attrs("tabColor", attrs)?; } Ok(()) } fn write_page_set_up_pr(&self, w: &mut W) -> Result<()> { if self.fit_page { let attrs = indexmap! { "fitToPage" => "1" }; w.empty_tag_with_attrs("pageSetUpPr", attrs)?; } Ok(()) } } impl XmlWritable for Outline { fn write_xml(&self, w: &mut W) -> Result<()> { if self == &Outline::default() { Ok(()) } else { let mut attrs = indexmap! {}; if self.style { attrs.insert("applyStyles", "1"); } if !self.below { attrs.insert("summaryBelow", "0"); } if !self.right { attrs.insert("summaryRight", "0"); } if !self.on { attrs.insert("showOutlineSymbols", "0"); } w.empty_tag_with_attrs("outlinePr", attrs) } } } struct SheetViews { view: SheetView, } impl XmlWritable for SheetViews { fn write_xml(&self, w: &mut W) -> Result<()> { w.tag_with_xml_writable("sheetViews", &self.view) } } struct SheetView { tab_selected: bool, } impl XmlWritable for SheetView { fn write_xml(&self, w: &mut W) -> Result<()> { let mut attrs = indexmap! {}; if self.tab_selected { attrs.insert("tabSelected", "1"); } attrs.insert("workbookViewId", "0"); // TODO: push all the other attributes as in // libxlsxwriter/worksheet.c _worksheet_write_sheet_view(). w.empty_tag_with_attrs("sheetView", attrs) } } struct SheetFormatPr { default_row_height: f64, } impl XmlWritable for SheetFormatPr { fn write_xml(&self, w: &mut W) -> Result<()> { let attrs = indexmap! { "defaultRowHeight" => format!("{}", self.default_row_height), }; // TODO: push all the other attributes as in // libxlsxwriter/worksheet.c _worksheet_write_sheet_format_pr(). w.empty_tag_with_attrs("sheetFormatPr", attrs) } } struct Columns<'a> { col_options: &'a BTreeMap< Col, (ColRange, Option, f64, Option), >, } impl<'a> XmlWritable for Columns<'a> { fn write_xml(&self, w: &mut W) -> Result<()> { if !self.col_options.is_empty() { let tag = "cols"; w.start_tag(tag)?; for (range, options, width, format_index) in self.col_options.values().cloned() { // TODO: fetch the format index if required. Column { range, options, width, format_index, } .write_xml(w)?; } w.end_tag(tag)?; } Ok(()) } } struct MergeCells<'a> { merged_ranges: &'a Vec, } impl<'a> XmlWritable for MergeCells<'a> { fn write_xml(&self, w: &mut W) -> Result<()> { if !self.merged_ranges.is_empty() { let tag = "mergeCells"; let attrs = indexmap! { "count" => format!("{}", self.merged_ranges.len()) }; w.start_tag_with_attrs(tag, attrs)?; for range in self.merged_ranges.iter() { let attrs = indexmap! {"ref" => range.as_range_string() }; w.empty_tag_with_attrs("mergeCell", attrs)?; } w.end_tag(tag)?; } Ok(()) } } struct Column { range: ColRange, options: Option, width: f64, format_index: Option, } impl XmlWritable for Column { fn write_xml(&self, w: &mut W) -> Result<()> { // TODO: get the format index let first: u16 = self.range.first().into(); let last: u16 = self.range.last().into(); let mut attrs = indexmap! { "min" => format!("{}", first + 1), "max" => format!("{}", last + 1), }; if self.width != DEFAULT_COL_WIDTH { attrs.insert("customWidth", "1".to_string()); attrs.insert("width", format!("{}", self.width)); } // TODO: write the style // TODO: write the customWidth if let Some(ref options) = self.options { if options.level > OutlineLevel::Level0 { let level: u8 = options.level.into(); attrs.insert("outlineLevel", format!("{}", level)); } if options.collapsed { attrs.insert("collapsed", "1".to_string()); } } w.empty_tag_with_attrs("col", attrs) } } struct SheetData<'a> { cells: &'a BTreeMap>, } impl XmlWritable for SheetData<'_> { fn write_xml(&self, w: &mut W) -> Result<()> { let tag = "sheetData"; if self.cells.is_empty() { w.empty_tag(tag)?; } else { w.start_tag(tag)?; // Spans are the same for blocks of 16 rows. fn calculate_spans( rows: &BTreeMap>, span_index: u32, ) -> Result<(u32, ColRange)> { let start = Row::try_from(span_index * 16)?; let end = Row::try_from(std::cmp::min( crate::cell::ROW_MAX - 1, (span_index + 1) * 16 - 1, ))?; let mut range: Option = None; for (_, cells) in rows.iter().filter(|(index, _)| { **index >= start && **index <= end }) { let first: Option = cells.keys().next().cloned(); let last: Option = cells.keys().rev().next().cloned(); match (first, last, range.take()) { (Some(first), Some(last), Some(mut r)) => { r.extend_to(first); r.extend_to(last); range = Some(r); } (Some(first), Some(last), None) => { let mut r = ColRange::from(first); r.extend_to(last); range = Some(r); } (Some(_), None, _) | (None, Some(_), _) => { unreachable!() } (None, None, _) => {} } } Ok((span_index, range.unwrap_or_default())) } let mut spans = calculate_spans(self.cells, 0)?; for (row, cells) in self.cells { let tag = "row"; let row_index: u32 = (*row).into(); let span_block_index = row_index / 16; if spans.0 != span_block_index { spans = calculate_spans(self.cells, span_block_index)?; } let first: u16 = spans.1.first().into(); let last: u16 = spans.1.last().into(); let attrs = indexmap! { "r" => row.as_range_string(), "spans" => format!("{}:{}", first + 1, last + 1), }; w.start_tag_with_attrs(tag, attrs)?; for (col, cell) in cells { CellWriter { inner: cell, index: Index::new(*row, *col), } .write_xml(w)?; } w.end_tag(tag)?; } w.end_tag(tag)?; } Ok(()) } } struct CellWriter<'a> { inner: &'a Cell, index: Index, } impl XmlWritable for CellWriter<'_> { fn write_xml(&self, w: &mut W) -> Result<()> { use crate::cell::Data as D; match self.inner.data { D::Number(ref number) => self.write_number_cell(w, *number), D::String(ref index) => self.write_string_cell(w, *index), D::InlineString(_) => unimplemented!(), D::InlineRichString(_) => unimplemented!(), D::Formula(ref formula, ref value) => { self.write_formula_cell(w, formula, *value) } D::ArrayFormula(_) => unimplemented!(), D::Blank => self.write_blank_cell(w), D::Boolean(_) => unimplemented!(), D::HyperlinkUrl(_) => unimplemented!(), D::HyperlinkInternal(_) => unimplemented!(), D::HyperlinkExternal(_) => unimplemented!(), } } } impl CellWriter<'_> { fn write_number_cell( &self, w: &mut W, value: f64, ) -> Result<()> { let tag = "c"; let mut attrs = indexmap! { "r" => self.index.as_range_string(), "t" => "n".to_string(), }; if let Some(ref style) = self.inner.format { attrs.insert("s", format!("{}", style)); } w.start_tag_with_attrs(tag, attrs)?; w.tag_with_text("v", &format!("{}", value))?; w.end_tag(tag)?; Ok(()) } fn write_string_cell( &self, w: &mut W, string_index: usize, ) -> Result<()> { let tag = "c"; let mut attrs = indexmap! {}; attrs.insert("r", self.index.as_range_string()); if let Some(ref style) = self.inner.format { attrs.insert("s", format!("{}", style)); } attrs.insert("t", "s".to_string()); w.start_tag_with_attrs(tag, attrs)?; w.tag_with_text("v", &format!("{}", string_index))?; w.end_tag(tag)?; Ok(()) } fn write_formula_cell( &self, w: &mut W, formula: &str, value: f64, ) -> Result<()> { let tag = "c"; let mut attrs = indexmap! {}; attrs.insert("r", self.index.as_range_string()); if let Some(ref style) = self.inner.format { attrs.insert("s", format!("{}", style)); } w.start_tag_with_attrs(tag, attrs)?; w.tag_with_text("f", formula)?; w.tag_with_text("v", &format!("{}", value))?; w.end_tag(tag)?; Ok(()) } fn write_blank_cell(&self, w: &mut W) -> Result<()> { let tag = "c"; let mut attrs = indexmap! {}; attrs.insert("r", self.index.as_range_string()); if let Some(ref style) = self.inner.format { attrs.insert("s", format!("{}", style)); } w.empty_tag_with_attrs(tag, attrs)?; Ok(()) } }