#[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, BTreeMap<Col, Cell>>,
row_dimension: Option<RowRange>,
col_dimension: Option<ColRange>,
col_options: BTreeMap<
Col,
(ColRange, Option<RowColOptions>, f64, Option<usize>),
>,
outline_col_level: OutlineLevel,
row_options: BTreeMap<
Row,
(RowRange, Option<RowColOptions>, f64, Option<usize>),
>,
page_margins: PageMargins,
data_validations: Vec<Validation>,
tab_color: Option<RGB8>,
fit_to_pages: Option<PageFit>,
outline: Outline,
merged_ranges: Vec<IndexRange>,
}
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 {
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<R>(&mut self, r: R) -> &mut Self
where
RowRange: From<R>,
{
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<C>(&mut self, c: C) -> &mut Self
where
ColRange: From<C>,
{
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<RowColOptions>,
) -> 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<RowColOptions>,
) -> 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<RefCell<Inner>>,
}
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<S: AsRef<str>>(
&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<S: Into<String>>(
&mut self,
index: Index,
formula: S,
format: Option<&Format>,
) -> Result<()> {
self.write_formula_num(index, formula, format, 0f64)
}
pub fn write_array_formula<S: AsRef<str>>(
&mut self,
start: Index,
end: Index,
formula: S,
format: Option<&Format>,
) -> Result<()> {
unimplemented!();
}
pub fn write_array_formula_num<S: AsRef<str>>(
&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<Utc>,
format: Option<&Format>,
) -> Result<()> {
let excel_date = datetime.as_excel_date(DateMode::BasedOn1900);
self.write_number(index, excel_date, format)
}
pub fn write_url<S: AsRef<str>>(
&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<S: Into<String>>(
&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<P: AsRef<Path>>(
&mut self,
index: Index,
filename: P,
) -> Result<()> {
unimplemented!();
}
pub fn insert_image_options<P: AsRef<Path>>(
&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 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) {
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<S: AsRef<str>>(&mut self, string: S) -> Result<()> {
unimplemented!();
}
pub fn set_footer<S: AsRef<str>>(&mut self, string: S) -> Result<()> {
unimplemented!();
}
pub fn set_header_options<S: AsRef<str>>(
&mut self,
string: S,
options: HeaderFooterOptions,
) -> Result<()> {
unimplemented!();
}
pub fn set_footer_options<S: AsRef<str>>(
&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!();
}
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<N: AsRef<str>>(&mut self, name: N) -> Result<()> {
unimplemented!();
}
fn check_str_max_length<S: AsRef<str>>(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<Format>,
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<u8> for OutlineLevel {
fn into(self) -> u8 {
self as u8
}
}
impl TryFrom<u8> for OutlineLevel {
type Error = error::Error;
fn try_from(level: u8) -> Result<Self> {
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,
ignore_blank: bool,
show_input: bool,
show_error: bool,
pub input_title: Option<String>,
pub input_message: Option<String>,
pub error_title: Option<String>,
pub error_message: Option<String>,
pub error_type: ValidationErrorType,
}
impl Validation {
pub fn new(
validation_type: ValidationType,
range: impl Into<IndexRange>,
) -> 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<W: XmlWriter>(&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<Validation> {
fn write_xml<W: XmlWriter>(&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 {
Integer(Criterion<i64>),
IntegerFormula(Criterion<String>),
Decimal(Criterion<f64>),
DecimalFormula(Criterion<String>),
List {
values: IndexSet<String>,
show_dropdown: bool,
},
ListFormula {
formula: String,
show_dropdown: bool,
},
Date(Criterion<DateTime<Utc>>),
DateFormula(Criterion<String>),
DateNumber(Criterion<i64>),
Time(Criterion<NaiveTime>),
TimeFormula(Criterion<String>),
TimeNumber(Criterion<f64>),
Length(Criterion<usize>),
LengthFormula(Criterion<String>),
CustomFormula(String),
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::<Vec<_>>()
.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<Utc> {
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<T: FormulaString> {
Between(T, T),
NotBetween(T, T),
EqualTo(T),
NotEqualTo(T),
GreaterThan(T),
LessThan(T),
GreaterThanOrEqualTo(T),
LessThanOrEqualTo(T),
}
impl<T: FormulaString> Criterion<T> {
pub fn get_operator(&self) -> Option<&'static str> {
use Criterion as C;
match self {
C::Between(_, _) => {
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<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
self.inner.borrow().write_xml(w)
}
}
impl XmlWritable for Inner {
fn write_xml<W: XmlWriter>(&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)?;
self.build_merge_cells().write_xml(w)?;
self.data_validations.write_xml(w)?;
self.page_margins.write_xml(w)?;
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<W: XmlWriter>(&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<W: XmlWriter>(&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<RGB8>,
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<W: XmlWriter>(&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! {};
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<W: XmlWriter>(&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<W: XmlWriter>(&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<W: XmlWriter>(&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<W: XmlWriter>(&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<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
let mut attrs = indexmap! {};
if self.tab_selected {
attrs.insert("tabSelected", "1");
}
attrs.insert("workbookViewId", "0");
w.empty_tag_with_attrs("sheetView", attrs)
}
}
struct SheetFormatPr {
default_row_height: f64,
}
impl XmlWritable for SheetFormatPr {
fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
let attrs = indexmap! {
"defaultRowHeight" => format!("{}", self.default_row_height),
};
w.empty_tag_with_attrs("sheetFormatPr", attrs)
}
}
struct Columns<'a> {
col_options: &'a BTreeMap<
Col,
(ColRange, Option<RowColOptions>, f64, Option<usize>),
>,
}
impl<'a> XmlWritable for Columns<'a> {
fn write_xml<W: XmlWriter>(&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()
{
Column {
range,
options,
width,
format_index,
}
.write_xml(w)?;
}
w.end_tag(tag)?;
}
Ok(())
}
}
struct MergeCells<'a> {
merged_ranges: &'a Vec<IndexRange>,
}
impl<'a> XmlWritable for MergeCells<'a> {
fn write_xml<W: XmlWriter>(&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<RowColOptions>,
width: f64,
format_index: Option<usize>,
}
impl XmlWritable for Column {
fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
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));
}
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<Row, BTreeMap<Col, Cell>>,
}
impl XmlWritable for SheetData<'_> {
fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
let tag = "sheetData";
if self.cells.is_empty() {
w.empty_tag(tag)?;
} else {
w.start_tag(tag)?;
fn calculate_spans<T>(
rows: &BTreeMap<Row, BTreeMap<Col, T>>,
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<ColRange> = None;
for (_, cells) in rows.iter().filter(|(index, _)| {
**index >= start && **index <= end
}) {
let first: Option<Col> = cells.keys().next().cloned();
let last: Option<Col> =
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<W: XmlWriter>(&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<W: XmlWriter>(
&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<W: XmlWriter>(
&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<W: XmlWriter>(
&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<W: XmlWriter>(&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(())
}
}