Skip to main content

ravenrs_sheet/model/
cell.rs

1//! Cells.
2
3use std::sync::Arc;
4
5use crate::base::{RangeRef, Value};
6use crate::formula::Expr;
7use serde::{Deserialize, Serialize};
8
9use crate::model::python::PyCode;
10use crate::model::style::StyleId;
11
12/// A parsed formula. `text` is the canonical formula text without the leading `=`.
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
14pub struct Formula {
15    pub text: String,
16    #[serde(skip)]
17    pub expr: Option<Arc<Expr>>,
18    /// Legacy array formula (Ctrl+Shift+Enter) covering this range; the formula lives in the
19    /// top-left cell.
20    pub array: Option<RangeRef>,
21    /// Read from a file that marks this formula as a dynamic-array formula (cell metadata `cm`).
22    /// The writer keeps the mark, so a formula Excel evaluates as an array is never saved as a
23    /// legacy formula (which Excel evaluates with implicit intersection). Formulas entered or
24    /// rewritten in GridCraft start without it, and the writer decides from their shape.
25    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
26    pub dynamic: bool,
27}
28
29impl Formula {
30    /// Parses `text` (with or without `=`). Unparseable formulas keep their text and evaluate to
31    /// `#NAME?`.
32    pub fn new(text: &str) -> Formula {
33        let body = text.strip_prefix('=').unwrap_or(text);
34        match crate::formula::parse(body) {
35            Ok(e) => Formula { text: crate::formula::print(&e), expr: Some(Arc::new(e)), array: None, dynamic: false },
36            Err(_) => Formula { text: body.to_string(), expr: None, array: None, dynamic: false },
37        }
38    }
39    pub fn from_expr(e: Expr) -> Formula {
40        Formula { text: crate::formula::print(&e), expr: Some(Arc::new(e)), array: None, dynamic: false }
41    }
42    /// The parsed expression, re-parsing after deserialization.
43    pub fn expr(&self) -> Option<Expr> {
44        self.expr.as_deref().cloned().or_else(|| crate::formula::parse(&self.text).ok())
45    }
46    /// The parsed expression without copying it (re-parses after deserialization).
47    pub fn expr_arc(&self) -> Option<Arc<Expr>> {
48        self.expr.clone().or_else(|| crate::formula::parse(&self.text).ok().map(Arc::new))
49    }
50    /// Runs `f` on the parsed expression, borrowing the cached tree or, after deserialization,
51    /// a freshly parsed one that is dropped afterwards. Unlike [`expr`](Self::expr) it never copies
52    /// the tree, and unlike [`expr_arc`](Self::expr_arc) it needs no reference count or `Arc`
53    /// allocation. `None` when the text does not parse.
54    pub fn with_expr<R>(&self, f: impl FnOnce(&Expr) -> R) -> Option<R> {
55        match &self.expr {
56            Some(e) => Some(f(e)),
57            None => crate::formula::parse(&self.text).ok().map(|e| f(&e)),
58        }
59    }
60}
61
62/// A static picture stored as cell content, independent of floating drawing anchors.
63#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
64pub struct CellPicture {
65    /// Encoded PNG/JPEG bytes, base64 in JSON. Sheet entries share the payload through Arc.
66    #[serde(with = "crate::model::b64")]
67    pub data: Vec<u8>,
68    pub mime: String,
69    pub alt: String,
70}
71
72#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
73pub struct Cell {
74    /// Constant value, or the cached result of the formula or Python code.
75    pub value: Value,
76    pub formula: Option<Arc<Formula>>,
77    pub style: StyleId,
78    /// Python code, for a Python cell (which has no formula).
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub py: Option<Arc<PyCode>>,
81}
82
83impl Cell {
84    pub fn value(v: Value) -> Cell {
85        Cell { value: v, formula: None, style: StyleId::DEFAULT, py: None }
86    }
87    pub fn formula(f: Formula) -> Cell {
88        Cell { value: Value::Empty, formula: Some(Arc::new(f)), style: StyleId::DEFAULT, py: None }
89    }
90    /// A value cell with this style.
91    pub fn styled(v: Value, style: StyleId) -> Cell {
92        Cell { value: v, formula: None, style, py: None }
93    }
94    /// No value, no formula, no Python code and the default style: storing it is pointless.
95    pub fn is_blank(&self) -> bool {
96        self.value.is_empty() && self.formula.is_none() && self.style == StyleId::DEFAULT && self.py.is_none()
97    }
98    /// Whether the cell holds content (a value, a formula or Python code), formats aside.
99    pub fn has_content(&self) -> bool {
100        !self.value.is_empty() || self.formula.is_some() || self.py.is_some()
101    }
102    /// Removes the content (value, formula and Python code), keeping the style.
103    pub fn clear_content(&mut self) {
104        self.value = Value::Empty;
105        self.formula = None;
106        self.py = None;
107    }
108    /// What the formula bar shows: a formula, a Python cell's code, or the value.
109    pub fn input_text(&self) -> String {
110        if let Some(py) = &self.py {
111            return py.code.clone();
112        }
113        if let Some(f) = &self.formula {
114            return format!("={}", f.text);
115        }
116        match &self.value {
117            Value::Text(t) => {
118                // Text that would parse as something else is shown with its apostrophe by Excel
119                // only when entered that way; we show it plainly.
120                t.to_string()
121            }
122            v => v.display(),
123        }
124    }
125    /// What the formula bar shows when not editing: [`input_text`](Self::input_text), with a
126    /// legacy array formula in braces (`{=A1:A2*2}`).
127    pub fn bar_text(&self) -> String {
128        match &self.formula {
129            Some(f) if f.array.is_some() => format!("{{={}}}", f.text),
130            _ => self.input_text(),
131        }
132    }
133}