use std::collections::HashMap;
use anyhow::{anyhow, Result};
use rust_xlsxwriter::{Workbook, Worksheet, XlsxError, Format, IntoExcelData};
use crate::{CellValue, ColNum, RowNum};
impl IntoExcelData for CellValue<'_> {
fn write<'a>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
) -> Result<&'a mut Worksheet, XlsxError> {
match self {
CellValue::Blank => {},
CellValue::Error(_) => {},
CellValue::Bool(v) => {
worksheet.write_boolean(row, col, v)?;
},
CellValue::Number(v) => {
worksheet.write_number(row, col, v)?;
},
CellValue::Date(v) => {
worksheet.write_number_with_format(row, col, v, &FMT_DEFAULT_DATE)?;
},
CellValue::Time(v) => {
worksheet.write_number_with_format(row, col, v, &FMT_DEFAULT_TIME)?;
},
CellValue::Datetime(v) => {
worksheet.write_number_with_format(row, col, v, &FMT_DEFAULT_DATETIME)?;
},
CellValue::Shared(v) => {
worksheet.write_string(row, col, v)?;
},
CellValue::String(v) => {
worksheet.write_string(row, col, v)?;
}
};
Ok(worksheet)
}
fn write_with_format<'a, 'b>(
self,
worksheet: &'a mut Worksheet,
row: RowNum,
col: ColNum,
format: &'b Format,
) -> Result<&'a mut Worksheet, XlsxError> {
match self {
CellValue::Blank => {},
CellValue::Error(_) => {},
CellValue::Bool(v) => {
worksheet.write_boolean_with_format(row, col, v, format)?;
},
CellValue::Number(v) => {
worksheet.write_number_with_format(row, col, v, format)?;
},
CellValue::Date(v) => {
worksheet.write_number_with_format(row, col, v, format)?;
},
CellValue::Time(v) => {
worksheet.write_number_with_format(row, col, v, format)?;
},
CellValue::Datetime(v) => {
worksheet.write_number_with_format(row, col, v, format)?;
},
CellValue::Shared(v) => {
worksheet.write_string_with_format(row, col, v, format)?;
},
CellValue::String(v) => {
worksheet.write_string_with_format(row, col, v, format)?;
}
};
Ok(worksheet)
}
}
pub struct XlsxWriter {
book: Workbook,
rows: HashMap<String, RowNum>,
open: bool,
columns: HashMap<String, HashMap<String, ColNum>>,
prepends: HashMap<String, bool>
}
impl XlsxWriter {
pub fn new() -> Self {
Self {
open: true,
book: Workbook::new(),
rows: HashMap::new(),
columns: HashMap::new(),
prepends: HashMap::new()
}
}
#[inline]
pub fn has_sheet(&mut self, shname: &String) -> bool {
match self.book.worksheet_from_name(&shname) {
Ok(_) => true,
Err(_) => false
}
}
pub fn with_columns(&mut self, shname: String, columns: Vec<String>, add_to_top: bool) {
let mut maps = HashMap::with_capacity(columns.len());
for (i, colname) in columns.into_iter().enumerate() {
maps.insert(colname, i as ColNum);
}
self.columns.insert(shname.clone(), maps);
self.prepends.insert(shname, add_to_top);
}
#[inline]
pub fn get_sheet_mut<'a, 'b>(&'a mut self, shname: &'b str) -> Result<(&'a mut Worksheet, RowNum)> {
if self.open {
if !self.rows.contains_key(shname) {
let sheet = self.book.add_worksheet_with_constant_memory();
sheet.set_name(shname)?;
if self.prepends.get(shname) == Some(&true) {
if let Some(columns) = self.columns.get(shname) {
for (colval, colnum) in columns {
sheet.write(0, *colnum, colval)?;
}
self.rows.insert(shname.to_owned(), 1);
} else {
self.rows.insert(shname.to_owned(), 0);
}
} else {
self.rows.insert(shname.to_owned(), 0);
}
}
Ok((self.book.worksheet_from_name(shname)?, self.rows.get(shname).unwrap_or(&0).to_owned()))
} else {
Err(anyhow!("cannot write saved workbook"))
}
}
pub fn append_row<T: IntoExcelData, H: IntoExcelData+Clone>(&mut self, shname: &str, nrow: Option<&RowNum>, data: Vec<T>, pre_cells: &Vec<H>) -> Result<()> {
let (sheet, mut irow) = self.get_sheet_mut(shname)?;
let mut icol = 0;
let npre = pre_cells.len() as ColNum;
if npre > 0 {
sheet.write_row(irow, icol, pre_cells.clone())?;
icol += npre;
}
if let Some(nrow) = nrow {
sheet.write_number(irow, icol, *nrow)?;
icol += 1;
}
sheet.write_row(irow, icol, data)?;
irow += 1;
self.rows.insert(shname.to_owned(), irow);
Ok(())
}
pub fn append_rows<T: IntoExcelData, H: IntoExcelData+Clone>(&mut self, shname: &str, nrows: Vec<u32>, data: Vec<Vec<T>>, pre_cells: &Vec<H>) -> Result<()> {
let (sheet, mut irow) = self.get_sheet_mut(shname)?;
if nrows.len() > 0 && nrows.len() != data.len() {
return Err(anyhow!("the length of nrows is not equal to the length of data".to_string()));
}
let mut icol;
let npre = pre_cells.len() as ColNum;
for (i, rdata) in data.into_iter().enumerate() {
icol = 0;
if npre > 0 {
sheet.write_row(irow, icol, pre_cells.clone())?;
icol += npre;
}
if let Some(nrow) = nrows.get(i) {
sheet.write_number(irow, icol, *nrow)?;
icol += 1;
}
sheet.write_row(irow, icol, rdata)?;
irow += 1;
};
self.rows.insert(shname.to_owned(), irow);
Ok(())
}
pub fn append_row_by_name<T: IntoExcelData>(&mut self, shname: &str, data: HashMap<String, T>) -> Result<()> {
if let Some(columns) = self.columns.get(shname) {
let columns = columns.clone();
let (sheet, mut irow) = self.get_sheet_mut(shname)?;
for (colname, colval) in data.into_iter() {
if let Some(colnum) = columns.get(&colname) {
sheet.write(irow, *colnum, colval)?;
} else {
return Err(anyhow!("column name {} not found", colname));
}
}
irow += 1;
self.rows.insert(shname.to_owned(), irow);
Ok(())
} else {
Err(anyhow!("columns not set"))
}
}
pub fn append_rows_by_name<T: IntoExcelData>(&mut self, shname: &str, data: Vec<HashMap<String, T>>) -> Result<()> {
if let Some(columns) = self.columns.get(shname) {
let columns = columns.clone();
let (sheet, mut irow) = self.get_sheet_mut(shname)?;
for rdata in data.into_iter() {
for (colname, colval) in rdata.into_iter() {
if let Some(colnum) = columns.get(&colname) {
sheet.write(irow, *colnum, colval)?;
} else {
return Err(anyhow!("column name {} not found", colname));
}
}
irow += 1;
};
self.rows.insert(shname.to_owned(), irow);
Ok(())
} else {
Err(anyhow!("columns not set"))
}
}
pub fn save_as<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
if self.open {
self.book.save(path)?;
self.open = false;
Ok(())
} else {
Err(anyhow!("cannot save saved workbook"))
}
}
}
lazy_static::lazy_static! {
static ref FMT_DEFAULT_DATE: Format = Format::new().set_num_format_index(14);
static ref FMT_DEFAULT_TIME: Format = Format::new().set_num_format_index(21);
static ref FMT_DEFAULT_DATETIME: Format = Format::new().set_num_format_index(22);
static ref EMPTY_COLUMNS: HashMap<String, ColNum> = HashMap::new();
}