use icu_casemap::CaseMapperBorrowed;
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::table::Table;
use crate::table_ref::{self, ParsedRangeBounds};
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 && limits::exceeds_cell_cap(self.total_cells() + 1) {
return Err(WorkbookError::Mutation(format!(
"cannot set cell: workbook already holds {} populated cells, the limit \
(scope ADR Decision 5)",
limits::MAX_CELLS_PER_WORKBOOK
)));
}
let prev = self.sheets_mut()[idx].set(addr, cell);
self.expand_table_on_append(idx, addr);
Ok(prev)
}
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
)));
}
if let Some(existing) = self.table_index(name) {
return Err(WorkbookError::Mutation(format!(
"cannot define named range {name:?}: it collides with the existing table {:?} \
under simple case folding (structured-references spec §4)",
self.tables()[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)
}
pub fn define_table(&mut self, name: &str, r: &str) -> Result<&Table, WorkbookError> {
let bounds = self.validate_table_definition(name, r)?;
if self.tables().len() >= limits::MAX_TABLES {
return Err(WorkbookError::Mutation(format!(
"cannot define table: workbook already has {} tables, the limit \
(scope ADR Decision 5)",
limits::MAX_TABLES
)));
}
if let Some(existing) = self.table_index(name) {
return Err(WorkbookError::Mutation(format!(
"cannot define table {name:?}: it collides with the existing table {:?} \
under simple case folding (structured-references spec §4)",
self.tables()[existing].name
)));
}
if let Some(existing) = self.name_index(name) {
return Err(WorkbookError::Mutation(format!(
"cannot define table {name:?}: it collides with the existing named range {:?} \
under simple case folding (structured-references spec §4)",
self.names()[existing].name
)));
}
if let Some(other) = self.overlapping_table(&bounds, None) {
return Err(WorkbookError::Mutation(format!(
"cannot define table {name:?}: its range overlaps the existing table {other:?} \
(structured-references spec §4)"
)));
}
self.tables_mut().push(Table {
name: name.to_owned(),
r#ref: r.to_owned(),
});
Ok(self.tables().last().expect("just pushed a table"))
}
pub fn redefine_table(&mut self, name: &str, r: &str) -> Result<&Table, WorkbookError> {
let bounds = self.validate_table_definition(name, r)?;
let idx = self.table_index(name).ok_or_else(|| {
WorkbookError::Mutation(format!("cannot redefine table: no table {name:?} exists"))
})?;
if let Some(other) = self.overlapping_table(&bounds, Some(idx)) {
return Err(WorkbookError::Mutation(format!(
"cannot redefine table {name:?}: its range overlaps the existing table {other:?} \
(structured-references spec §4)"
)));
}
self.tables_mut()[idx].r#ref = r.to_owned();
Ok(&self.tables()[idx])
}
pub fn remove_table(&mut self, name: &str) -> Option<Table> {
self.table_index(name).map(|i| self.tables_mut().remove(i))
}
pub fn table(&self, name: &str) -> Option<&Table> {
self.table_index(name).map(|i| &self.tables()[i])
}
fn table_index(&self, name: &str) -> Option<usize> {
let folder = CaseMapperBorrowed::new();
let target = simple_fold(&folder, name);
self.tables()
.iter()
.position(|t| simple_fold(&folder, &t.name) == target)
}
fn validate_table_definition(
&self,
name: &str,
r: &str,
) -> Result<ParsedRangeBounds, WorkbookError> {
if !named_ref::is_valid_name(name) {
return Err(WorkbookError::Mutation(format!(
"table 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 \
(structured-references spec §4)"
)));
}
let parsed = named_ref::parse_canonical_ref(r).map_err(WorkbookError::Mutation)?;
if self.sheet(&parsed.sheet).is_none() {
return Err(WorkbookError::Mutation(format!(
"table {name:?} refers to sheet {:?}, which does not exist \
(structured-references spec §4)",
parsed.sheet
)));
}
let mut bounds = table_ref::parsed_range_bounds(r, &parsed).ok_or_else(|| {
WorkbookError::Mutation(format!(
"table {name:?} has a malformed ref: a table ref must be a range \
(structured-references spec §4)"
))
})?;
let folder = CaseMapperBorrowed::new();
bounds.sheet = simple_fold(&folder, &bounds.sheet);
Ok(bounds)
}
fn overlapping_table(
&self,
bounds: &ParsedRangeBounds,
exclude_idx: Option<usize>,
) -> Option<String> {
let folder = CaseMapperBorrowed::new();
for (i, t) in self.tables().iter().enumerate() {
if Some(i) == exclude_idx {
continue;
}
let Ok(parsed) = named_ref::parse_canonical_ref(&t.r#ref) else {
continue;
};
let Some(mut other_bounds) = table_ref::parsed_range_bounds(&t.r#ref, &parsed) else {
continue;
};
other_bounds.sheet = simple_fold(&folder, &other_bounds.sheet);
if table_ref::ranges_overlap(bounds, &other_bounds) {
return Some(t.name.clone());
}
}
None
}
fn expand_table_on_append(&mut self, sheet_idx: usize, addr: Address) {
let folder = CaseMapperBorrowed::new();
let sheet_name = simple_fold(&folder, self.sheets()[sheet_idx].name());
let Some(idx) = self.tables().iter().position(|t| {
let Ok(parsed) = named_ref::parse_canonical_ref(&t.r#ref) else {
return false;
};
let Some(bounds) = table_ref::parsed_range_bounds(&t.r#ref, &parsed) else {
return false;
};
simple_fold(&folder, &bounds.sheet) == sheet_name
&& bounds.row_end + 1 == addr.row
&& bounds.col_start <= addr.column
&& addr.column <= bounds.col_end
}) else {
return;
};
let t = &self.tables()[idx];
let parsed = named_ref::parse_canonical_ref(&t.r#ref)
.expect("stored table ref is already canonical");
let bounds = table_ref::parsed_range_bounds(&t.r#ref, &parsed)
.expect("stored table ref is already a validated range");
let new_bounds = ParsedRangeBounds {
sheet: simple_fold(&folder, &bounds.sheet),
row_start: bounds.row_start,
row_end: addr.row,
col_start: bounds.col_start,
col_end: bounds.col_end,
};
if self.overlapping_table(&new_bounds, Some(idx)).is_some() {
return; }
let sheet_token = named_ref::quote_sheet_if_needed(&parsed.sheet);
let start = Address::new(bounds.row_start, bounds.col_start)
.expect("bounds were derived from an already-validated ref");
let end = Address::new(addr.row, bounds.col_end)
.expect("bounds were derived from an already-validated ref");
self.tables_mut()[idx].r#ref = format!("{sheet_token}!{}:{}", start.to_a1(), end.to_a1());
}
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> {
truecalc_core::parse_formula(formula)
.map(|_| ())
.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(())
}