use crate::error::{ OfficeError, Result, XlsxError };
use crate::xlsx::cell::{ Cell, CellReference, CellValue };
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct WorksheetDimension {
pub min_row: u32,
pub max_row: u32,
pub min_column: u32,
pub max_column: u32,
}
#[derive(Debug, Clone)]
pub struct WorksheetProperties {
pub name: String,
pub hidden: bool,
pub selected: bool,
pub active: bool,
pub tab_color: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Worksheet {
pub properties: WorksheetProperties,
cells: HashMap<(u32, u32), Cell>,
dimension: Option<WorksheetDimension>,
shared_strings: Vec<String>,
}
impl WorksheetDimension {
pub fn new(min_row: u32, max_row: u32, min_column: u32, max_column: u32) -> Self {
Self {
min_row,
max_row,
min_column,
max_column,
}
}
pub fn row_count(&self) -> u32 {
if self.max_row >= self.min_row { self.max_row - self.min_row + 1 } else { 0 }
}
pub fn column_count(&self) -> u32 {
if self.max_column >= self.min_column { self.max_column - self.min_column + 1 } else { 0 }
}
pub fn contains(&self, row: u32, column: u32) -> bool {
row >= self.min_row &&
row <= self.max_row &&
column >= self.min_column &&
column <= self.max_column
}
}
impl WorksheetProperties {
pub fn new(name: String) -> Self {
Self {
name,
hidden: false,
selected: false,
active: false,
tab_color: None,
}
}
pub fn with_hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
pub fn with_selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
pub fn with_active(mut self, active: bool) -> Self {
self.active = active;
self
}
pub fn with_tab_color(mut self, color: String) -> Self {
self.tab_color = Some(color);
self
}
}
impl Worksheet {
pub fn new(name: String) -> Self {
Self {
properties: WorksheetProperties::new(name),
cells: HashMap::new(),
dimension: None,
shared_strings: Vec::new(),
}
}
pub fn name(&self) -> &str {
&self.properties.name
}
pub fn set_name(&mut self, name: String) {
self.properties.name = name;
}
pub fn get_cell(&self, row: u32, column: u32) -> Option<&Cell> {
self.cells.get(&(row, column))
}
pub fn get_cell_mut(&mut self, row: u32, column: u32) -> Option<&mut Cell> {
self.cells.get_mut(&(row, column))
}
pub fn get_cell_by_reference(&self, reference: &CellReference) -> Option<&Cell> {
self.get_cell(reference.row, reference.column)
}
pub fn set_cell(&mut self, cell: Cell) {
let row = cell.reference.row;
let column = cell.reference.column;
self.update_dimension(row, column);
self.cells.insert((row, column), cell);
}
pub fn set_cell_value(&mut self, row: u32, column: u32, value: CellValue) {
let reference = CellReference::new(column, row);
let cell = Cell::new(reference, value);
self.set_cell(cell);
}
pub fn set_cell_value_by_reference(&mut self, reference: &str, value: CellValue) -> Result<()> {
let cell_ref = CellReference::from_a1(reference)?;
self.set_cell_value(cell_ref.row, cell_ref.column, value);
Ok(())
}
pub fn remove_cell(&mut self, row: u32, column: u32) -> Option<Cell> {
let removed = self.cells.remove(&(row, column));
if removed.is_some() {
self.recalculate_dimension();
}
removed
}
pub fn cells(&self) -> impl Iterator<Item = &Cell> {
self.cells.values()
}
pub fn get_row(&self, row: u32) -> Vec<&Cell> {
self.cells
.values()
.filter(|cell| cell.reference.row == row)
.collect()
}
pub fn get_column(&self, column: u32) -> Vec<&Cell> {
self.cells
.values()
.filter(|cell| cell.reference.column == column)
.collect()
}
pub fn dimension(&self) -> Option<&WorksheetDimension> {
self.dimension.as_ref()
}
pub fn used_row_count(&self) -> u32 {
self.dimension.as_ref().map_or(0, |d| d.row_count())
}
pub fn used_column_count(&self) -> u32 {
self.dimension.as_ref().map_or(0, |d| d.column_count())
}
pub fn is_empty(&self) -> bool {
self.cells.is_empty()
}
pub fn clear(&mut self) {
self.cells.clear();
self.dimension = None;
}
pub fn cell_count(&self) -> usize {
self.cells.len()
}
fn update_dimension(&mut self, row: u32, column: u32) {
match &mut self.dimension {
Some(dim) => {
dim.min_row = dim.min_row.min(row);
dim.max_row = dim.max_row.max(row);
dim.min_column = dim.min_column.min(column);
dim.max_column = dim.max_column.max(column);
}
None => {
self.dimension = Some(WorksheetDimension::new(row, row, column, column));
}
}
}
fn recalculate_dimension(&mut self) {
if self.cells.is_empty() {
self.dimension = None;
return;
}
let mut min_row = u32::MAX;
let mut max_row = 0;
let mut min_column = u32::MAX;
let mut max_column = 0;
for cell in self.cells.values() {
min_row = min_row.min(cell.reference.row);
max_row = max_row.max(cell.reference.row);
min_column = min_column.min(cell.reference.column);
max_column = max_column.max(cell.reference.column);
}
self.dimension = Some(WorksheetDimension::new(min_row, max_row, min_column, max_column));
}
pub fn add_shared_string(&mut self, text: String) -> usize {
if let Some(index) = self.shared_strings.iter().position(|s| s == &text) {
return index;
}
self.shared_strings.push(text);
self.shared_strings.len() - 1
}
pub fn get_shared_string(&self, index: usize) -> Result<&str> {
self.shared_strings
.get(index)
.map(|s| s.as_str())
.ok_or_else(|| OfficeError::Xlsx(XlsxError::SharedStringIndexOutOfRange { index }))
}
pub fn shared_strings(&self) -> &[String] {
&self.shared_strings
}
}
impl Default for Worksheet {
fn default() -> Self {
Self::new("Sheet1".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xlsx::cell::CellReference;
#[test]
fn test_worksheet_basic_operations() {
let mut worksheet = Worksheet::new("Test Sheet".to_string());
worksheet.set_cell_value(0, 0, CellValue::Text("Hello".to_string()));
worksheet.set_cell_value(1, 1, CellValue::Number(42.0));
let cell = worksheet.get_cell(0, 0).unwrap();
assert_eq!(cell.value, CellValue::Text("Hello".to_string()));
let dim = worksheet.dimension().unwrap();
assert_eq!(dim.min_row, 0);
assert_eq!(dim.max_row, 1);
assert_eq!(dim.min_column, 0);
assert_eq!(dim.max_column, 1);
}
#[test]
fn test_worksheet_a1_reference() {
let mut worksheet = Worksheet::new("Test".to_string());
worksheet.set_cell_value_by_reference("A1", CellValue::Text("Test".to_string())).unwrap();
worksheet.set_cell_value_by_reference("B2", CellValue::Number(100.0)).unwrap();
let cell_a1 = worksheet.get_cell(0, 0).unwrap();
assert_eq!(cell_a1.value, CellValue::Text("Test".to_string()));
let cell_b2 = worksheet.get_cell(1, 1).unwrap();
assert_eq!(cell_b2.value, CellValue::Number(100.0));
}
#[test]
fn test_shared_strings() {
let mut worksheet = Worksheet::new("Test".to_string());
let index1 = worksheet.add_shared_string("Hello".to_string());
let index2 = worksheet.add_shared_string("World".to_string());
let index3 = worksheet.add_shared_string("Hello".to_string());
assert_eq!(index1, 0);
assert_eq!(index2, 1);
assert_eq!(index3, 0);
assert_eq!(worksheet.get_shared_string(0).unwrap(), "Hello");
assert_eq!(worksheet.get_shared_string(1).unwrap(), "World");
}
}