use crate::common::types::{ Color, FontSize };
use crate::error::{ OfficeError, Result, XlsxError };
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum CellType {
Empty,
Number,
Text,
Boolean,
DateTime,
Formula,
Error,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CellValue {
Empty,
Number(f64),
Text(String),
Boolean(bool),
DateTime(f64),
Formula(String),
Error(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CellReference {
pub column: u32,
pub row: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CellFormat {
pub number_format: Option<String>,
pub font_size: Option<FontSize>,
pub font_color: Option<Color>,
pub background_color: Option<Color>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub reference: CellReference,
pub value: CellValue,
pub format: Option<CellFormat>,
pub style_id: Option<u32>,
}
impl CellReference {
pub fn new(column: u32, row: u32) -> Self {
Self { column, row }
}
pub fn from_a1(reference: &str) -> Result<Self> {
let reference = reference.trim().to_uppercase();
let mut col_str = String::new();
let mut row_str = String::new();
let mut in_number = false;
for ch in reference.chars() {
if ch.is_ascii_digit() {
in_number = true;
row_str.push(ch);
} else if ch.is_ascii_alphabetic() && !in_number {
col_str.push(ch);
} else {
return Err(
OfficeError::Xlsx(XlsxError::InvalidCellReference {
reference: reference.to_string(),
})
);
}
}
if col_str.is_empty() || row_str.is_empty() {
return Err(
OfficeError::Xlsx(XlsxError::InvalidCellReference {
reference: reference.to_string(),
})
);
}
let mut column = 0u32;
for ch in col_str.chars() {
column = column * 26 + ((ch as u32) - ('A' as u32) + 1);
}
column -= 1;
let row = row_str
.parse::<u32>()
.map_err(|_| {
OfficeError::Xlsx(XlsxError::InvalidCellReference {
reference: reference.to_string(),
})
})?
.saturating_sub(1);
Ok(Self::new(column, row))
}
pub fn to_a1(&self) -> String {
let mut col_str = String::new();
let mut col = self.column + 1;
while col > 0 {
col -= 1;
col_str.insert(0, (b'A' + ((col % 26) as u8)) as char);
col /= 26;
}
format!("{}{}", col_str, self.row + 1)
}
}
impl CellValue {
pub fn cell_type(&self) -> CellType {
match self {
CellValue::Empty => CellType::Empty,
CellValue::Number(_) => CellType::Number,
CellValue::Text(_) => CellType::Text,
CellValue::Boolean(_) => CellType::Boolean,
CellValue::DateTime(_) => CellType::DateTime,
CellValue::Formula(_) => CellType::Formula,
CellValue::Error(_) => CellType::Error,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
CellValue::Number(n) => Some(*n),
CellValue::DateTime(d) => Some(*d),
CellValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
CellValue::Text(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_text(&self) -> String {
match self {
CellValue::Empty => String::new(),
CellValue::Number(n) => n.to_string(),
CellValue::Text(s) => s.clone(),
CellValue::Boolean(b) => b.to_string(),
CellValue::DateTime(d) => d.to_string(),
CellValue::Formula(f) => f.clone(),
CellValue::Error(e) => e.clone(),
}
}
pub fn is_empty(&self) -> bool {
matches!(self, CellValue::Empty)
}
}
impl CellFormat {
pub fn new() -> Self {
Self {
number_format: None,
font_size: None,
font_color: None,
background_color: None,
bold: false,
italic: false,
underline: false,
}
}
pub fn with_number_format(mut self, format: String) -> Self {
self.number_format = Some(format);
self
}
pub fn with_font_size(mut self, size: FontSize) -> Self {
self.font_size = Some(size);
self
}
pub fn with_font_color(mut self, color: Color) -> Self {
self.font_color = Some(color);
self
}
pub fn with_background_color(mut self, color: Color) -> Self {
self.background_color = Some(color);
self
}
pub fn with_bold(mut self, bold: bool) -> Self {
self.bold = bold;
self
}
pub fn with_italic(mut self, italic: bool) -> Self {
self.italic = italic;
self
}
pub fn with_underline(mut self, underline: bool) -> Self {
self.underline = underline;
self
}
}
impl Cell {
pub fn new(reference: CellReference, value: CellValue) -> Self {
Self {
reference,
value,
format: None,
style_id: None,
}
}
pub fn empty(reference: CellReference) -> Self {
Self::new(reference, CellValue::Empty)
}
pub fn set_value(&mut self, value: CellValue) {
self.value = value;
}
pub fn set_format(&mut self, format: CellFormat) {
self.format = Some(format);
}
pub fn set_style_id(&mut self, style_id: u32) {
self.style_id = Some(style_id);
}
pub fn cell_type(&self) -> CellType {
self.value.cell_type()
}
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
}
impl Default for CellFormat {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for CellReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_a1())
}
}
impl fmt::Display for CellValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_text())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cell_reference_a1() {
let ref1 = CellReference::from_a1("A1").unwrap();
assert_eq!(ref1.column, 0);
assert_eq!(ref1.row, 0);
assert_eq!(ref1.to_a1(), "A1");
let ref2 = CellReference::from_a1("Z26").unwrap();
assert_eq!(ref2.column, 25);
assert_eq!(ref2.row, 25);
assert_eq!(ref2.to_a1(), "Z26");
let ref3 = CellReference::from_a1("AA1").unwrap();
assert_eq!(ref3.column, 26);
assert_eq!(ref3.row, 0);
assert_eq!(ref3.to_a1(), "AA1");
}
#[test]
fn test_cell_value_conversion() {
let val = CellValue::Number(42.5);
assert_eq!(val.as_number(), Some(42.5));
assert_eq!(val.as_text(), "42.5");
let val = CellValue::Text("Hello".to_string());
assert_eq!(val.as_text(), "Hello");
assert_eq!(val.as_number(), None);
}
}