Skip to main content

cellrune/
cell.rs

1use std::fmt;
2use std::sync::{Arc, OnceLock};
3
4use crate::{CellAddress, FormulaCell, ValidationError};
5
6/// Semantic category inferred from an XLSX number format without changing the stored value.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8pub enum NumberFormatKind {
9    /// The General format or an omitted style.
10    #[default]
11    General,
12    /// A non-date numeric display format.
13    Number,
14    /// A calendar date display format.
15    Date,
16    /// A wall-clock time display format.
17    Time,
18    /// A combined calendar date and wall-clock time display format.
19    DateTime,
20    /// An elapsed-time display format such as `[h]:mm:ss`.
21    Duration,
22}
23
24/// Number-format metadata attached to a cell while preserving its raw numeric value.
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
26pub struct NumberFormat {
27    id: u32,
28    code: Option<Box<str>>,
29    kind: NumberFormatKind,
30}
31
32impl NumberFormat {
33    /// Constructs a validated built-in Excel number format.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`ValidationError::BuiltInNumberFormatId`] when `id` is in the custom-format
38    /// range.
39    pub fn built_in(id: u32, kind: NumberFormatKind) -> Result<Self, ValidationError> {
40        if id >= 164 {
41            return Err(ValidationError::BuiltInNumberFormatId { value: id });
42        }
43        Ok(Self {
44            id,
45            code: None,
46            kind,
47        })
48    }
49
50    /// Constructs a validated custom Excel number format.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`ValidationError::CustomNumberFormatId`] when `id` is reserved for built-in
55    /// formats, or [`ValidationError::NumberFormatCodeEmpty`] when `code` is empty.
56    pub fn custom(
57        id: u32,
58        code: impl Into<String>,
59        kind: NumberFormatKind,
60    ) -> Result<Self, ValidationError> {
61        if id < 164 {
62            return Err(ValidationError::CustomNumberFormatId { value: id });
63        }
64        let code = code.into();
65        if code.is_empty() {
66            return Err(ValidationError::NumberFormatCodeEmpty);
67        }
68        Ok(Self {
69            id,
70            code: Some(code.into_boxed_str()),
71            kind,
72        })
73    }
74
75    pub(crate) fn new(id: u32, code: Option<Box<str>>, kind: NumberFormatKind) -> Self {
76        Self { id, code, kind }
77    }
78
79    /// Returns the workbook-local OOXML number-format identifier.
80    pub const fn id(&self) -> u32 {
81        self.id
82    }
83
84    /// Returns the custom or known built-in format code, when retained.
85    pub fn code(&self) -> Option<&str> {
86        self.code.as_deref()
87    }
88
89    /// Returns the style-derived semantic category.
90    pub const fn kind(&self) -> NumberFormatKind {
91        self.kind
92    }
93}
94
95/// A finite IEEE-754 number accepted at the workbook boundary.
96#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
97pub struct FiniteNumber(f64);
98
99impl FiniteNumber {
100    /// Rejects NaN and positive or negative infinity.
101    ///
102    /// # Errors
103    ///
104    /// Returns [`ValidationError::NonFiniteNumber`] when `value` is not finite.
105    pub fn new(value: f64) -> Result<Self, ValidationError> {
106        if !value.is_finite() {
107            return Err(ValidationError::NonFiniteNumber);
108        }
109        Ok(Self(value))
110    }
111
112    /// Returns the underlying finite number.
113    pub const fn get(self) -> f64 {
114        self.0
115    }
116}
117
118impl TryFrom<f64> for FiniteNumber {
119    type Error = ValidationError;
120
121    fn try_from(value: f64) -> Result<Self, Self::Error> {
122        Self::new(value)
123    }
124}
125
126/// An error value stored by a spreadsheet cell.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128#[non_exhaustive]
129pub enum ExcelError {
130    /// `#NULL!`
131    Null,
132    /// `#DIV/0!`
133    DivisionByZero,
134    /// `#VALUE!`
135    Value,
136    /// `#REF!`
137    Reference,
138    /// `#NAME?`
139    Name,
140    /// `#NUM!`
141    Number,
142    /// `#N/A`
143    NotAvailable,
144    /// `#GETTING_DATA`
145    GettingData,
146    /// `#SPILL!`
147    Spill,
148    /// `#CALC!`
149    Calculation,
150}
151
152impl ExcelError {
153    /// Returns the canonical display form.
154    pub const fn as_str(self) -> &'static str {
155        match self {
156            Self::Null => "#NULL!",
157            Self::DivisionByZero => "#DIV/0!",
158            Self::Value => "#VALUE!",
159            Self::Reference => "#REF!",
160            Self::Name => "#NAME?",
161            Self::Number => "#NUM!",
162            Self::NotAvailable => "#N/A",
163            Self::GettingData => "#GETTING_DATA",
164            Self::Spill => "#SPILL!",
165            Self::Calculation => "#CALC!",
166        }
167    }
168}
169
170impl fmt::Display for ExcelError {
171    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172        formatter.write_str(self.as_str())
173    }
174}
175
176/// A literal or saved cell value.
177#[derive(Debug, Clone, PartialEq, Default)]
178#[non_exhaustive]
179pub enum CellValue {
180    /// No value is present.
181    #[default]
182    Blank,
183    /// A finite numeric value.
184    Number(FiniteNumber),
185    /// A Unicode string.
186    Text(String),
187    /// A logical value.
188    Logical(bool),
189    /// An Excel error value.
190    Error(ExcelError),
191}
192
193impl CellValue {
194    /// Validates and constructs a numeric cell value.
195    ///
196    /// # Errors
197    ///
198    /// Returns [`ValidationError::NonFiniteNumber`] when `value` is not finite.
199    pub fn number(value: f64) -> Result<Self, ValidationError> {
200        Ok(Self::Number(FiniteNumber::new(value)?))
201    }
202}
203
204/// The mutually exclusive content stored at a sparse cell address.
205#[derive(Debug, Clone, PartialEq)]
206pub enum CellContent {
207    /// A non-formula literal.
208    Literal(CellValue),
209    /// A formula and its independent saved result.
210    Formula(FormulaCell),
211}
212
213/// A sparse cell with a validated address and content.
214#[derive(Debug, Clone, PartialEq)]
215pub struct Cell {
216    address: CellAddress,
217    content: Arc<CellContent>,
218    number_format: Arc<NumberFormat>,
219}
220
221fn shared_number_format(number_format: NumberFormat) -> Arc<NumberFormat> {
222    static DEFAULT: OnceLock<Arc<NumberFormat>> = OnceLock::new();
223
224    if number_format == NumberFormat::default() {
225        Arc::clone(DEFAULT.get_or_init(|| Arc::new(NumberFormat::default())))
226    } else {
227        Arc::new(number_format)
228    }
229}
230
231impl Cell {
232    /// Constructs a cell from already validated parts.
233    pub fn new(address: CellAddress, content: CellContent) -> Self {
234        Self {
235            address,
236            content: Arc::new(content),
237            number_format: shared_number_format(NumberFormat::default()),
238        }
239    }
240
241    pub(crate) fn with_number_format(
242        address: CellAddress,
243        content: CellContent,
244        number_format: NumberFormat,
245    ) -> Self {
246        Self {
247            address,
248            content: Arc::new(content),
249            number_format: shared_number_format(number_format),
250        }
251    }
252
253    /// Returns the address.
254    pub const fn address(&self) -> CellAddress {
255        self.address
256    }
257
258    /// Returns the cell content.
259    pub fn content(&self) -> &CellContent {
260        self.content.as_ref()
261    }
262
263    /// Returns number-format metadata without converting the raw cell value.
264    pub fn number_format(&self) -> &NumberFormat {
265        self.number_format.as_ref()
266    }
267
268    pub(crate) fn with_content_and_number_format(
269        address: CellAddress,
270        content: CellContent,
271        number_format: NumberFormat,
272    ) -> Self {
273        Self {
274            address,
275            content: Arc::new(content),
276            number_format: shared_number_format(number_format),
277        }
278    }
279
280    pub(crate) fn with_replaced_content(&self, content: CellContent) -> Self {
281        Self {
282            address: self.address,
283            content: Arc::new(content),
284            number_format: Arc::clone(&self.number_format),
285        }
286    }
287
288    pub(crate) fn with_replaced_number_format(&self, number_format: NumberFormat) -> Self {
289        Self {
290            address: self.address,
291            content: Arc::clone(&self.content),
292            number_format: shared_number_format(number_format),
293        }
294    }
295
296    #[cfg(test)]
297    pub(crate) fn shares_content_with(&self, other: &Self) -> bool {
298        Arc::ptr_eq(&self.content, &other.content)
299    }
300
301    #[cfg(test)]
302    pub(crate) fn shares_number_format_with(&self, other: &Self) -> bool {
303        Arc::ptr_eq(&self.number_format, &other.number_format)
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn clone_and_metadata_edit_share_large_immutable_payloads() {
313        let original = Cell::with_number_format(
314            CellAddress::from_a1("A1").expect("cell address"),
315            CellContent::Literal(CellValue::Text("x".repeat(8_192))),
316            NumberFormat::custom(164, "0.000", NumberFormatKind::Number).expect("format"),
317        );
318        let cloned = original.clone();
319        assert!(original.shares_content_with(&cloned));
320        assert!(original.shares_number_format_with(&cloned));
321
322        let reformatted = original.with_replaced_number_format(NumberFormat::default());
323        assert!(original.shares_content_with(&reformatted));
324        assert!(!original.shares_number_format_with(&reformatted));
325    }
326}