use std::fmt;
use std::sync::{Arc, OnceLock};
use crate::{CellAddress, FormulaCell, ValidationError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum NumberFormatKind {
#[default]
General,
Number,
Date,
Time,
DateTime,
Duration,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct NumberFormat {
id: u32,
code: Option<Box<str>>,
kind: NumberFormatKind,
}
impl NumberFormat {
pub fn built_in(id: u32, kind: NumberFormatKind) -> Result<Self, ValidationError> {
if id >= 164 {
return Err(ValidationError::BuiltInNumberFormatId { value: id });
}
Ok(Self {
id,
code: None,
kind,
})
}
pub fn custom(
id: u32,
code: impl Into<String>,
kind: NumberFormatKind,
) -> Result<Self, ValidationError> {
if id < 164 {
return Err(ValidationError::CustomNumberFormatId { value: id });
}
let code = code.into();
if code.is_empty() {
return Err(ValidationError::NumberFormatCodeEmpty);
}
Ok(Self {
id,
code: Some(code.into_boxed_str()),
kind,
})
}
pub(crate) fn new(id: u32, code: Option<Box<str>>, kind: NumberFormatKind) -> Self {
Self { id, code, kind }
}
pub const fn id(&self) -> u32 {
self.id
}
pub fn code(&self) -> Option<&str> {
self.code.as_deref()
}
pub const fn kind(&self) -> NumberFormatKind {
self.kind
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct FiniteNumber(f64);
impl FiniteNumber {
pub fn new(value: f64) -> Result<Self, ValidationError> {
if !value.is_finite() {
return Err(ValidationError::NonFiniteNumber);
}
Ok(Self(value))
}
pub const fn get(self) -> f64 {
self.0
}
}
impl TryFrom<f64> for FiniteNumber {
type Error = ValidationError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ExcelError {
Null,
DivisionByZero,
Value,
Reference,
Name,
Number,
NotAvailable,
GettingData,
Spill,
Calculation,
}
impl ExcelError {
pub const fn as_str(self) -> &'static str {
match self {
Self::Null => "#NULL!",
Self::DivisionByZero => "#DIV/0!",
Self::Value => "#VALUE!",
Self::Reference => "#REF!",
Self::Name => "#NAME?",
Self::Number => "#NUM!",
Self::NotAvailable => "#N/A",
Self::GettingData => "#GETTING_DATA",
Self::Spill => "#SPILL!",
Self::Calculation => "#CALC!",
}
}
}
impl fmt::Display for ExcelError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub enum CellValue {
#[default]
Blank,
Number(FiniteNumber),
Text(String),
Logical(bool),
Error(ExcelError),
}
impl CellValue {
pub fn number(value: f64) -> Result<Self, ValidationError> {
Ok(Self::Number(FiniteNumber::new(value)?))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CellContent {
Literal(CellValue),
Formula(FormulaCell),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
address: CellAddress,
content: Arc<CellContent>,
number_format: Arc<NumberFormat>,
}
fn shared_number_format(number_format: NumberFormat) -> Arc<NumberFormat> {
static DEFAULT: OnceLock<Arc<NumberFormat>> = OnceLock::new();
if number_format == NumberFormat::default() {
Arc::clone(DEFAULT.get_or_init(|| Arc::new(NumberFormat::default())))
} else {
Arc::new(number_format)
}
}
impl Cell {
pub fn new(address: CellAddress, content: CellContent) -> Self {
Self {
address,
content: Arc::new(content),
number_format: shared_number_format(NumberFormat::default()),
}
}
pub(crate) fn with_number_format(
address: CellAddress,
content: CellContent,
number_format: NumberFormat,
) -> Self {
Self {
address,
content: Arc::new(content),
number_format: shared_number_format(number_format),
}
}
pub const fn address(&self) -> CellAddress {
self.address
}
pub fn content(&self) -> &CellContent {
self.content.as_ref()
}
pub fn number_format(&self) -> &NumberFormat {
self.number_format.as_ref()
}
pub(crate) fn with_content_and_number_format(
address: CellAddress,
content: CellContent,
number_format: NumberFormat,
) -> Self {
Self {
address,
content: Arc::new(content),
number_format: shared_number_format(number_format),
}
}
pub(crate) fn with_replaced_content(&self, content: CellContent) -> Self {
Self {
address: self.address,
content: Arc::new(content),
number_format: Arc::clone(&self.number_format),
}
}
pub(crate) fn with_replaced_number_format(&self, number_format: NumberFormat) -> Self {
Self {
address: self.address,
content: Arc::clone(&self.content),
number_format: shared_number_format(number_format),
}
}
#[cfg(test)]
pub(crate) fn shares_content_with(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.content, &other.content)
}
#[cfg(test)]
pub(crate) fn shares_number_format_with(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.number_format, &other.number_format)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone_and_metadata_edit_share_large_immutable_payloads() {
let original = Cell::with_number_format(
CellAddress::from_a1("A1").expect("cell address"),
CellContent::Literal(CellValue::Text("x".repeat(8_192))),
NumberFormat::custom(164, "0.000", NumberFormatKind::Number).expect("format"),
);
let cloned = original.clone();
assert!(original.shares_content_with(&cloned));
assert!(original.shares_number_format_with(&cloned));
let reformatted = original.with_replaced_number_format(NumberFormat::default());
assert!(original.shares_content_with(&reformatted));
assert!(!original.shares_number_format_with(&reformatted));
}
}