use icu_casemap::CaseMapperBorrowed;
use truecalc_core::Engine;
use crate::address::Address;
use crate::casefold::simple_fold;
use crate::cell::Cell;
use crate::error::WorkbookError;
use crate::limits;
use crate::named_range::NamedRange;
use crate::named_ref;
use crate::spill::spill_rect;
use crate::value::Value;
use crate::workbook::Workbook;
#[derive(Debug, Clone, PartialEq)]
pub enum CellInput {
Literal(Value),
Formula(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Resolved {
pub value: Value,
pub anchor: Option<Address>,
}
impl Workbook {
pub fn set(
&mut self,
sheet: &str,
addr: Address,
input: CellInput,
) -> Result<Option<Cell>, WorkbookError> {
let cell = match input {
CellInput::Literal(value) => {
if matches!(value, Value::Empty) {
return Err(WorkbookError::EmptyLiteral);
}
check_value_limits(&value)?;
Cell::literal(value)?
}
CellInput::Formula(formula) => {
check_formula_limit(&formula)?;
self.validate_formula(&formula)?;
Cell::with_formula(formula, Value::Empty)
}
};
let idx = self.sheet_index(sheet).ok_or_else(|| {
WorkbookError::Mutation(format!("cannot set cell: no sheet named {sheet:?}"))
})?;
let introduces_new_cell = !self.sheets()[idx].contains(addr);
if introduces_new_cell && self.total_cells() >= limits::MAX_CELLS_PER_WORKBOOK {
return Err(WorkbookError::Mutation(format!(
"cannot set cell: workbook already holds {} populated cells, the limit \
(scope ADR Decision 5)",
limits::MAX_CELLS_PER_WORKBOOK
)));
}
Ok(self.sheets_mut()[idx].set(addr, cell))
}
pub fn get(&self, sheet: &str, addr: Address) -> Option<&Cell> {
self.sheet(sheet).and_then(|ws| ws.get(addr))
}
pub fn resolved(&self, sheet: &str, addr: Address) -> Option<Resolved> {
let ws = self.sheet(sheet)?;
if let Some(cell) = ws.get(addr) {
return Some(Resolved {
value: cell.value().clone(),
anchor: None,
});
}
for (anchor_addr, cell) in ws.iter() {
let Value::Array(rows) = cell.value() else {
continue;
};
let nrows = rows.len();
let ncols = rows.first().map_or(0, Vec::len);
let Some(rect) = spill_rect(anchor_addr, nrows, ncols) else {
continue;
};
if anchor_addr == addr {
continue; }
if let Some((i, j)) = rect.offset_of(addr) {
let value = rows[i][j].clone();
return Some(Resolved {
value,
anchor: Some(anchor_addr),
});
}
}
None
}
pub fn spill_anchor(&self, sheet: &str, addr: Address) -> Option<Address> {
self.resolved(sheet, addr).and_then(|r| r.anchor)
}
pub fn clear(&mut self, sheet: &str, addr: Address) -> Option<Cell> {
self.sheet_mut(sheet).and_then(|ws| ws.clear(addr))
}
pub fn total_cells(&self) -> usize {
self.sheets().iter().map(|s| s.len()).sum()
}
pub fn define_name(&mut self, name: &str, r: &str) -> Result<&NamedRange, WorkbookError> {
self.validate_name_definition(name, r)?;
if self.names().len() >= limits::MAX_NAMED_RANGES {
return Err(WorkbookError::Mutation(format!(
"cannot define named range: workbook already has {} named ranges, the limit \
(scope ADR Decision 5)",
limits::MAX_NAMED_RANGES
)));
}
if let Some(existing) = self.name_index(name) {
return Err(WorkbookError::Mutation(format!(
"cannot define named range {name:?}: it collides with the existing name {:?} \
under simple case folding (schema spec §7)",
self.names()[existing].name
)));
}
self.names_mut().push(NamedRange {
name: name.to_owned(),
r#ref: r.to_owned(),
});
Ok(self.names().last().expect("just pushed a named range"))
}
pub fn redefine_name(&mut self, name: &str, r: &str) -> Result<&NamedRange, WorkbookError> {
self.validate_name_definition(name, r)?;
let idx = self.name_index(name).ok_or_else(|| {
WorkbookError::Mutation(format!(
"cannot redefine named range: no name {name:?} exists"
))
})?;
self.names_mut()[idx].r#ref = r.to_owned();
Ok(&self.names()[idx])
}
pub fn remove_name(&mut self, name: &str) -> Option<NamedRange> {
self.name_index(name).map(|i| self.names_mut().remove(i))
}
pub fn name(&self, name: &str) -> Option<&NamedRange> {
self.name_index(name).map(|i| &self.names()[i])
}
fn name_index(&self, name: &str) -> Option<usize> {
let folder = CaseMapperBorrowed::new();
let target = simple_fold(&folder, name);
self.names()
.iter()
.position(|n| simple_fold(&folder, &n.name) == target)
}
fn validate_name_definition(&self, name: &str, r: &str) -> Result<(), WorkbookError> {
if !named_ref::is_valid_name(name) {
return Err(WorkbookError::Mutation(format!(
"named-range name {name:?} is invalid: it must match ^[A-Za-z_][A-Za-z0-9_]*$ and \
must not be an A1 address, an R1C1-style reference, or a boolean (schema spec §7)"
)));
}
let parsed = named_ref::parse_canonical_ref(r).map_err(WorkbookError::Mutation)?;
if self.sheet(&parsed.sheet).is_none() {
return Err(WorkbookError::Mutation(format!(
"named range {name:?} refers to sheet {:?}, which does not exist (schema spec §7)",
parsed.sheet
)));
}
Ok(())
}
fn validate_formula(&self, formula: &str) -> Result<(), WorkbookError> {
let engine = match self.engine() {
truecalc_core::EngineFlavor::Sheets => Engine::sheets(),
truecalc_core::EngineFlavor::Excel => Engine::excel(),
};
engine
.validate(formula)
.map_err(|e| WorkbookError::Mutation(format!("formula {formula:?} is invalid: {e}")))
}
}
fn check_value_limits(value: &Value) -> Result<(), WorkbookError> {
match value {
Value::Text(s) => {
let len = s.chars().count();
if len > limits::MAX_TEXT_LEN {
return Err(WorkbookError::Mutation(format!(
"text value has {len} scalar values, exceeding the limit of {} \
(scope ADR Decision 5)",
limits::MAX_TEXT_LEN
)));
}
}
Value::Array(rows) => {
let elems: usize = rows.iter().map(|r| r.len()).sum();
if elems > limits::MAX_ARRAY_ELEMENTS {
return Err(WorkbookError::Mutation(format!(
"array value has {elems} elements, exceeding the limit of {} \
(scope ADR Decision 5)",
limits::MAX_ARRAY_ELEMENTS
)));
}
}
_ => {}
}
Ok(())
}
fn check_formula_limit(formula: &str) -> Result<(), WorkbookError> {
if formula.len() > limits::MAX_FORMULA_LEN {
return Err(WorkbookError::Mutation(format!(
"formula is {} bytes, exceeding the limit of {} bytes (scope ADR Decision 5)",
formula.len(),
limits::MAX_FORMULA_LEN
)));
}
Ok(())
}