use crate::error::Result;
use crate::fast_writer::UltraLowMemoryWorkbook;
use crate::types::{CellStyle, CellValue};
use std::path::Path;
pub struct ExcelWriter {
inner: UltraLowMemoryWorkbook,
current_sheet_name: String,
current_row: u32,
}
impl ExcelWriter {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut inner = UltraLowMemoryWorkbook::new(path)?;
inner.add_worksheet("Sheet1")?;
Ok(ExcelWriter {
inner,
current_sheet_name: "Sheet1".to_string(),
current_row: 0,
})
}
pub fn with_compression<P: AsRef<Path>>(path: P, compression_level: u32) -> Result<Self> {
let mut inner = UltraLowMemoryWorkbook::with_compression(path, compression_level)?;
inner.add_worksheet("Sheet1")?;
Ok(ExcelWriter {
inner,
current_sheet_name: "Sheet1".to_string(),
current_row: 0,
})
}
pub fn set_compression_level(&mut self, level: u32) {
self.inner.set_compression_level(level);
}
pub fn compression_level(&self) -> u32 {
self.inner.compression_level()
}
pub fn write_row<I, S>(&mut self, data: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.inner.write_row(data)?;
self.current_row += 1;
Ok(())
}
pub fn write_rows_batch<I, R, S>(&mut self, rows: I) -> Result<()>
where
I: IntoIterator<Item = R>,
R: IntoIterator<Item = S>,
S: AsRef<str>,
{
for row_data in rows {
self.write_row(row_data)?;
}
Ok(())
}
pub fn write_rows_typed_batch(&mut self, rows: &[Vec<CellValue>]) -> Result<()> {
for row_cells in rows {
self.write_row_typed(row_cells)?;
}
Ok(())
}
pub fn write_row_typed(&mut self, cells: &[CellValue]) -> Result<()> {
use crate::types::StyledCell;
let styled_cells: Vec<StyledCell> = cells
.iter()
.map(|cell| StyledCell::new(cell.clone(), CellStyle::Default))
.collect();
self.inner.write_row_styled(&styled_cells)?;
self.current_row += 1;
Ok(())
}
pub fn write_row_styled(&mut self, cells: &[(CellValue, CellStyle)]) -> Result<()> {
use crate::types::StyledCell;
let styled_cells: Vec<StyledCell> = cells
.iter()
.map(|(value, style)| StyledCell::new(value.clone(), *style))
.collect();
self.inner.write_row_styled(&styled_cells)?;
self.current_row += 1;
Ok(())
}
pub fn write_row_with_style(&mut self, values: &[CellValue], style: CellStyle) -> Result<()> {
let cells: Vec<_> = values.iter().map(|v| (v.clone(), style)).collect();
self.write_row_styled(&cells)
}
pub fn write_header_bold<I, S>(&mut self, headers: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
use crate::types::CellStyle;
let cells: Vec<_> = headers
.into_iter()
.map(|h| {
(
CellValue::String(h.as_ref().to_string()),
CellStyle::HeaderBold,
)
})
.collect();
self.write_row_styled(&cells)
}
pub fn write_header<I, S>(&mut self, headers: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.write_row(headers)
}
pub fn add_sheet(&mut self, name: &str) -> Result<()> {
self.inner.add_worksheet(name)?;
self.current_sheet_name = name.to_string();
self.current_row = 0;
Ok(())
}
pub fn set_column_width(&mut self, col: u32, width: f64) -> Result<()> {
self.inner.set_column_width(col, width)
}
pub fn set_next_row_height(&mut self, height: f64) -> Result<()> {
self.inner.set_next_row_height(height)
}
pub fn protect_sheet(&mut self, options: crate::types::ProtectionOptions) -> Result<()> {
self.inner.protect_sheet(options)
}
pub fn set_flush_interval(&mut self, interval: u32) {
self.inner.set_flush_interval(interval);
}
pub fn set_max_buffer_size(&mut self, size: usize) {
self.inner.set_max_buffer_size(size);
}
pub fn save(self) -> Result<()> {
self.inner.close()
}
pub fn current_row(&self) -> u32 {
self.current_row
}
}
pub struct ExcelWriterBuilder {
path: String,
default_sheet_name: Option<String>,
flush_interval: Option<u32>,
max_buffer_size: Option<usize>,
}
impl ExcelWriterBuilder {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
ExcelWriterBuilder {
path: path.as_ref().to_string_lossy().to_string(),
default_sheet_name: None,
flush_interval: None,
max_buffer_size: None,
}
}
pub fn with_sheet_name(mut self, name: &str) -> Self {
self.default_sheet_name = Some(name.to_string());
self
}
pub fn with_flush_interval(mut self, interval: u32) -> Self {
self.flush_interval = Some(interval);
self
}
pub fn with_max_buffer_size(mut self, size: usize) -> Self {
self.max_buffer_size = Some(size);
self
}
pub fn build(self) -> Result<ExcelWriter> {
let mut inner = UltraLowMemoryWorkbook::new(&self.path)?;
let sheet_name = self
.default_sheet_name
.unwrap_or_else(|| "Sheet1".to_string());
inner.add_worksheet(&sheet_name)?;
let mut writer = ExcelWriter {
inner,
current_row: 0,
current_sheet_name: sheet_name,
};
if let Some(interval) = self.flush_interval {
writer.set_flush_interval(interval);
}
if let Some(size) = self.max_buffer_size {
writer.set_max_buffer_size(size);
}
Ok(writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_writer_creation() {
let temp = NamedTempFile::new().unwrap();
let writer = ExcelWriter::new(temp.path());
assert!(writer.is_ok());
let writer = writer.unwrap();
assert!(writer.save().is_ok());
}
#[test]
fn test_write_row() {
let temp = NamedTempFile::new().unwrap();
let mut writer = ExcelWriter::new(temp.path()).unwrap();
assert!(writer.write_row(["A", "B", "C"]).is_ok());
assert!(writer.write_row(["1", "2", "3"]).is_ok());
assert_eq!(writer.current_row(), 2);
assert!(writer.save().is_ok());
}
#[test]
fn test_write_row_typed() {
let temp = NamedTempFile::new().unwrap();
let mut writer = ExcelWriter::new(temp.path()).unwrap();
use crate::types::CellValue;
let row = vec![
CellValue::String("Text".to_string()),
CellValue::Int(42),
CellValue::Float(1234.56),
CellValue::Bool(true),
];
assert!(writer.write_row_typed(&row).is_ok());
assert_eq!(writer.current_row(), 1);
assert!(writer.save().is_ok());
}
#[test]
fn test_builder() {
let temp = NamedTempFile::new().unwrap();
let writer = ExcelWriterBuilder::new(temp.path())
.with_sheet_name("CustomSheet")
.with_flush_interval(500)
.with_max_buffer_size(512 * 1024)
.build();
assert!(writer.is_ok());
let writer = writer.unwrap();
assert_eq!(writer.current_sheet_name, "CustomSheet");
assert!(writer.save().is_ok());
}
#[test]
fn test_add_sheet() {
let temp = NamedTempFile::new().unwrap();
let mut writer = ExcelWriter::new(temp.path()).unwrap();
writer.write_row(["Sheet1 Data"]).unwrap();
assert_eq!(writer.current_row(), 1);
writer.add_sheet("Sheet2").unwrap();
assert_eq!(writer.current_row(), 0);
assert_eq!(writer.current_sheet_name, "Sheet2");
writer.write_row(["Sheet2 Data"]).unwrap();
assert_eq!(writer.current_row(), 1);
assert!(writer.save().is_ok());
}
#[test]
fn test_write_header() {
let temp = NamedTempFile::new().unwrap();
let mut writer = ExcelWriter::new(temp.path()).unwrap();
writer.write_header(["ID", "Name", "Email"]).unwrap();
writer
.write_row(["1", "Alice", "alice@example.com"])
.unwrap();
assert_eq!(writer.current_row(), 2);
assert!(writer.save().is_ok());
}
#[test]
fn test_batch_write() {
let temp = NamedTempFile::new().unwrap();
let mut writer = ExcelWriter::new(temp.path()).unwrap();
let data = vec![
vec!["A1", "B1", "C1"],
vec!["A2", "B2", "C2"],
vec!["A3", "B3", "C3"],
];
writer.write_rows_batch(&data).unwrap();
assert_eq!(writer.current_row(), 3);
assert!(writer.save().is_ok());
}
#[test]
fn test_formula_support() {
let temp = NamedTempFile::new().unwrap();
let mut writer = ExcelWriter::new(temp.path()).unwrap();
use crate::types::CellValue;
writer.write_header(["Value 1", "Value 2", "Sum"]).unwrap();
writer
.write_row_typed(&[
CellValue::Int(10),
CellValue::Int(20),
CellValue::Formula("=A2+B2".to_string()),
])
.unwrap();
writer
.write_row_typed(&[
CellValue::Int(15),
CellValue::Int(25),
CellValue::Formula("=A3+B3".to_string()),
])
.unwrap();
writer
.write_row_typed(&[
CellValue::String("Total".to_string()),
CellValue::Empty,
CellValue::Formula("=SUM(C2:C3)".to_string()),
])
.unwrap();
assert_eq!(writer.current_row(), 4);
assert!(writer.save().is_ok());
}
}