use crate::IoError;
use crate::load_limits::enforce_sheet_load_limits;
use crate::traits::{
AccessGranularity, BackendCaps, CellData, MergedRange, NamedRange, SaveDestination, SheetData,
SpreadsheetReader, SpreadsheetWriter, TableDefinition,
};
use crate::traits::{DefinedName, DefinedNameDefinition, DefinedNameScope};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufReader, Read, Write};
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
struct JsonWorkbook {
#[serde(default = "default_version")]
version: u32,
#[serde(default)]
compression: Option<CompressionType>,
#[serde(default)]
sources: Vec<JsonSource>,
#[serde(default)]
defined_names: Vec<JsonDefinedName>,
#[serde(default)]
sheets: BTreeMap<String, JsonSheet>,
}
type FormulaBatch = (String, Vec<(u32, u32, formualizer_parse::ASTNode)>);
#[derive(Clone, Copy, Debug)]
pub struct JsonReadOptions {
pub strict_dates: bool,
}
impl Default for JsonReadOptions {
fn default() -> Self {
Self { strict_dates: true }
}
}
fn default_version() -> u32 {
1
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
enum CompressionType {
#[default]
None,
Lz4,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
struct JsonSheet {
#[serde(default)]
cells: Vec<JsonCell>,
#[serde(default)]
dimensions: Option<(u32, u32)>,
#[serde(default)]
hidden: bool,
#[serde(default)]
date_system_1904: bool,
#[serde(default)]
merged_cells: Vec<MergedRange>,
#[serde(default)]
tables: Vec<TableDefinition>,
#[serde(default)]
named_ranges: Vec<NamedRange>,
#[serde(default)]
row_hidden_manual: Vec<u32>,
#[serde(default)]
row_hidden_filter: Vec<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct JsonDefinedName {
name: String,
#[serde(default)]
scope: DefinedNameScope,
#[serde(default, skip_serializing_if = "Option::is_none")]
scope_sheet: Option<String>,
definition: JsonDefinedNameDefinition,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "lowercase")]
enum JsonDefinedNameDefinition {
Range {
address: formualizer_common::RangeAddress,
},
Literal {
value: JsonValue,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct JsonCell {
row: u32,
col: u32,
#[serde(default)]
value: Option<JsonValue>,
#[serde(default)]
formula: Option<String>,
#[serde(default)]
style: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "lowercase")]
enum JsonSource {
Scalar { name: String, version: Option<u64> },
Table { name: String, version: Option<u64> },
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", content = "value")]
enum JsonValue {
Int(i64),
Number(f64),
Text(String),
Boolean(bool),
Empty,
Date(String),
DateTime(String),
Time(String),
Duration(i64),
Array(Vec<Vec<JsonValue>>),
Error(String),
Pending,
}
pub struct JsonAdapter {
data: JsonWorkbook,
path: Option<PathBuf>,
read_options: JsonReadOptions,
caps: BackendCaps,
}
impl Default for JsonAdapter {
fn default() -> Self {
Self::new()
}
}
impl JsonAdapter {
pub fn new() -> Self {
Self::new_with_options(JsonReadOptions::default())
}
pub fn new_with_options(read_options: JsonReadOptions) -> Self {
Self {
data: JsonWorkbook::default(),
path: None,
read_options,
caps: BackendCaps {
read: true,
write: true,
streaming: false,
tables: true,
named_ranges: true,
formulas: true,
styles: true,
lazy_loading: false,
random_access: true,
bytes_input: true,
date_system_1904: false,
merged_cells: true,
rich_text: false,
hyperlinks: false,
data_validations: false,
shared_formulas: false,
},
}
}
pub fn read_options(&self) -> &JsonReadOptions {
&self.read_options
}
pub fn set_read_options(&mut self, opts: JsonReadOptions) {
self.read_options = opts;
}
fn migrate_legacy_named_ranges(&mut self) {
if !self.data.defined_names.is_empty() {
return;
}
use rustc_hash::FxHashSet;
let mut seen: FxHashSet<(DefinedNameScope, Option<String>, String)> = FxHashSet::default();
for (sheet_name, sheet) in &self.data.sheets {
for nr in &sheet.named_ranges {
let scope = match nr.scope {
crate::traits::NamedRangeScope::Workbook => DefinedNameScope::Workbook,
crate::traits::NamedRangeScope::Sheet => DefinedNameScope::Sheet,
};
let scope_sheet = match nr.scope {
crate::traits::NamedRangeScope::Workbook => None,
crate::traits::NamedRangeScope::Sheet => Some(sheet_name.clone()),
};
let key = (scope.clone(), scope_sheet.clone(), nr.name.clone());
if !seen.insert(key) {
continue;
}
self.data.defined_names.push(JsonDefinedName {
name: nr.name.clone(),
scope,
scope_sheet,
definition: JsonDefinedNameDefinition::Range {
address: nr.address.clone(),
},
});
}
}
}
fn to_sheet_data(
js: &JsonSheet,
sheet_name: &str,
opts: &JsonReadOptions,
) -> Result<SheetData, IoError> {
let mut cells: BTreeMap<(u32, u32), CellData> = BTreeMap::new();
for (idx, c) in js.cells.iter().enumerate() {
let lit = match c.value.as_ref() {
Some(v) => Some(json_to_literal(
v,
opts,
&format!("sheets.{sheet_name}.cells[{idx}].value"),
)?),
None => None,
};
cells.insert(
(c.row, c.col),
CellData {
value: lit,
formula: c.formula.clone(),
style: c.style,
},
);
}
Ok(SheetData {
cells,
dimensions: js.dimensions,
tables: js.tables.clone(),
named_ranges: js.named_ranges.clone(),
date_system_1904: js.date_system_1904,
merged_cells: js.merged_cells.clone(),
hidden: js.hidden,
row_hidden_manual: js.row_hidden_manual.clone(),
row_hidden_filter: js.row_hidden_filter.clone(),
})
}
pub fn to_json_string(&self) -> Result<String, IoError> {
Ok(serde_json::to_string_pretty(&self.data)?)
}
fn ensure_sheet_mut(&mut self, name: &str) -> &mut JsonSheet {
self.data.sheets.entry(name.to_string()).or_default()
}
pub fn set_dimensions(&mut self, sheet: &str, dims: Option<(u32, u32)>) {
self.ensure_sheet_mut(sheet).dimensions = dims;
}
pub fn set_date_system_1904(&mut self, sheet: &str, value: bool) {
self.ensure_sheet_mut(sheet).date_system_1904 = value;
}
pub fn set_merged_cells(&mut self, sheet: &str, merged: Vec<MergedRange>) {
self.ensure_sheet_mut(sheet).merged_cells = merged;
}
pub fn set_tables(&mut self, sheet: &str, tables: Vec<TableDefinition>) {
self.ensure_sheet_mut(sheet).tables = tables;
}
pub fn set_named_ranges(&mut self, sheet: &str, named: Vec<NamedRange>) {
self.ensure_sheet_mut(sheet).named_ranges = named;
}
pub fn set_defined_names(&mut self, names: Vec<DefinedName>) {
self.data.defined_names = names
.into_iter()
.map(|dn| match dn.definition {
DefinedNameDefinition::Range { address } => JsonDefinedName {
name: dn.name,
scope: dn.scope,
scope_sheet: dn.scope_sheet,
definition: JsonDefinedNameDefinition::Range { address },
},
DefinedNameDefinition::Literal { value } => JsonDefinedName {
name: dn.name,
scope: dn.scope,
scope_sheet: dn.scope_sheet,
definition: JsonDefinedNameDefinition::Literal {
value: literal_to_json(&value),
},
},
})
.collect();
}
}
impl SpreadsheetReader for JsonAdapter {
type Error = IoError;
fn access_granularity(&self) -> AccessGranularity {
AccessGranularity::Workbook
}
fn capabilities(&self) -> BackendCaps {
self.caps.clone()
}
fn sheet_names(&self) -> Result<Vec<String>, Self::Error> {
Ok(self.data.sheets.keys().cloned().collect())
}
fn open_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
{
let file = File::open(path.as_ref())?;
let reader = BufReader::new(file);
let data: JsonWorkbook = serde_json::from_reader(reader)?;
let mut adapter = JsonAdapter {
data,
path: Some(path.as_ref().to_path_buf()),
..JsonAdapter::new()
};
adapter.migrate_legacy_named_ranges();
Ok(adapter)
}
fn open_reader(reader: Box<dyn Read + Send + Sync>) -> Result<Self, Self::Error>
where
Self: Sized,
{
let data: JsonWorkbook = serde_json::from_reader(reader)?;
let mut adapter = JsonAdapter {
data,
..JsonAdapter::new()
};
adapter.migrate_legacy_named_ranges();
Ok(adapter)
}
fn open_bytes(bytes: Vec<u8>) -> Result<Self, Self::Error>
where
Self: Sized,
{
let data: JsonWorkbook = serde_json::from_slice(&bytes)?;
let mut adapter = JsonAdapter {
data,
..JsonAdapter::new()
};
adapter.migrate_legacy_named_ranges();
Ok(adapter)
}
fn defined_names(&mut self) -> Result<Vec<DefinedName>, Self::Error> {
self.migrate_legacy_named_ranges();
self.data
.defined_names
.iter()
.enumerate()
.map(|(idx, dn)| {
let def = match &dn.definition {
JsonDefinedNameDefinition::Range { address } => DefinedNameDefinition::Range {
address: address.clone(),
},
JsonDefinedNameDefinition::Literal { value } => {
DefinedNameDefinition::Literal {
value: json_to_literal(
value,
&self.read_options,
&format!("defined_names[{idx}].definition.value"),
)?,
}
}
};
Ok(DefinedName {
name: dn.name.clone(),
scope: dn.scope.clone(),
scope_sheet: dn.scope_sheet.clone(),
definition: def,
})
})
.collect::<Result<Vec<_>, IoError>>()
}
fn read_range(
&mut self,
sheet: &str,
start: (u32, u32),
end: (u32, u32),
) -> Result<BTreeMap<(u32, u32), CellData>, Self::Error> {
if let Some(js) = self.data.sheets.get(sheet) {
let mut out = BTreeMap::new();
for c in &js.cells {
if c.row >= start.0 && c.row <= end.0 && c.col >= start.1 && c.col <= end.1 {
let lit = match c.value.as_ref() {
Some(v) => Some(json_to_literal(
v,
&self.read_options,
&format!("sheets.{sheet}.cells[row={},col={}].value", c.row, c.col),
)?),
None => None,
};
out.insert(
(c.row, c.col),
CellData {
value: lit,
formula: c.formula.clone(),
style: c.style,
},
);
}
}
Ok(out)
} else {
Ok(BTreeMap::new())
}
}
fn read_sheet(&mut self, sheet: &str) -> Result<SheetData, Self::Error> {
if let Some(js) = self.data.sheets.get(sheet) {
Self::to_sheet_data(js, sheet, &self.read_options)
} else {
Ok(SheetData {
cells: BTreeMap::new(),
dimensions: None,
tables: vec![],
named_ranges: vec![],
date_system_1904: false,
merged_cells: vec![],
hidden: false,
row_hidden_manual: vec![],
row_hidden_filter: vec![],
})
}
}
fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
self.data.sheets.get(sheet).and_then(|s| s.dimensions)
}
fn is_loaded(&self, _sheet: &str, _row: Option<u32>, _col: Option<u32>) -> bool {
true
}
}
impl SpreadsheetWriter for JsonAdapter {
type Error = IoError;
fn write_cell(
&mut self,
sheet: &str,
row: u32,
col: u32,
data: CellData,
) -> Result<(), Self::Error> {
let sheet_entry = self.data.sheets.entry(sheet.to_string()).or_default();
if let Some(cell) = sheet_entry
.cells
.iter_mut()
.find(|c| c.row == row && c.col == col)
{
cell.value = data.value.as_ref().map(literal_to_json);
cell.formula = data.formula;
cell.style = data.style;
} else {
sheet_entry.cells.push(JsonCell {
row,
col,
value: data.value.as_ref().map(literal_to_json),
formula: data.formula,
style: data.style,
});
}
Ok(())
}
fn write_range(
&mut self,
sheet: &str,
cells: BTreeMap<(u32, u32), CellData>,
) -> Result<(), Self::Error> {
for ((r, c), d) in cells {
self.write_cell(sheet, r, c, d)?;
}
Ok(())
}
fn clear_range(
&mut self,
sheet: &str,
start: (u32, u32),
end: (u32, u32),
) -> Result<(), Self::Error> {
if let Some(js) = self.data.sheets.get_mut(sheet) {
js.cells.retain(|c| {
!(c.row >= start.0 && c.row <= end.0 && c.col >= start.1 && c.col <= end.1)
});
}
Ok(())
}
fn create_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
self.data.sheets.entry(name.to_string()).or_default();
Ok(())
}
fn delete_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
self.data.sheets.remove(name);
Ok(())
}
fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), Self::Error> {
if let Some(sheet) = self.data.sheets.remove(old) {
self.data.sheets.insert(new.to_string(), sheet);
}
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn save(&mut self) -> Result<(), Self::Error> {
if let Some(path) = &self.path {
let mut file = File::create(path)?;
let s = serde_json::to_string_pretty(&self.data)?;
file.write_all(s.as_bytes())?;
Ok(())
} else {
Ok(())
}
}
fn save_to<'a>(&mut self, dest: SaveDestination<'a>) -> Result<Option<Vec<u8>>, Self::Error> {
match dest {
SaveDestination::InPlace => self.save().map(|_| None),
SaveDestination::Path(path) => {
let mut file = File::create(path)?;
let s = serde_json::to_string_pretty(&self.data)?;
file.write_all(s.as_bytes())?;
self.path = Some(path.to_path_buf());
Ok(None)
}
SaveDestination::Writer(writer) => {
let s = serde_json::to_string_pretty(&self.data)?;
writer.write_all(s.as_bytes())?;
Ok(None)
}
SaveDestination::Bytes => {
let s = serde_json::to_vec_pretty(&self.data)?;
Ok(Some(s))
}
}
}
}
fn literal_to_json(v: &formualizer_common::LiteralValue) -> JsonValue {
use formualizer_common::LiteralValue as L;
match v {
L::Int(i) => JsonValue::Int(*i),
L::Number(n) => JsonValue::Number(*n),
L::Text(s) => JsonValue::Text(s.clone()),
L::Boolean(b) => JsonValue::Boolean(*b),
L::Empty => JsonValue::Empty,
L::Array(arr) => JsonValue::Array(
arr.iter()
.map(|row| row.iter().map(literal_to_json).collect())
.collect(),
),
L::Date(d) => JsonValue::Date(d.to_string()),
L::DateTime(dt) => JsonValue::DateTime(dt.to_string()),
L::Time(t) => JsonValue::Time(t.to_string()),
L::Duration(dur) => JsonValue::Duration(dur.num_seconds()),
L::Error(e) => JsonValue::Error(e.kind.to_string()),
L::Pending => JsonValue::Pending,
}
}
fn json_to_literal(
v: &JsonValue,
opts: &JsonReadOptions,
path: &str,
) -> Result<formualizer_common::LiteralValue, IoError> {
use formualizer_common::LiteralValue as L;
Ok(match v {
JsonValue::Int(i) => L::Int(*i),
JsonValue::Number(n) => L::Number(*n),
JsonValue::Text(s) => L::Text(s.clone()),
JsonValue::Boolean(b) => L::Boolean(*b),
JsonValue::Empty => L::Empty,
JsonValue::Array(arr) => L::Array(
arr.iter()
.map(|row| {
row.iter()
.map(|v| json_to_literal(v, opts, path))
.collect::<Result<Vec<_>, IoError>>()
})
.collect::<Result<Vec<_>, IoError>>()?,
),
JsonValue::Date(s) => match chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
Ok(d) => L::Date(d),
Err(_) if opts.strict_dates => {
return Err(IoError::Backend {
backend: "json".to_string(),
message: format!("Invalid date at {path}: '{s}'"),
});
}
Err(_) => L::Text(s.clone()),
},
JsonValue::DateTime(s) => {
let parsed = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
.or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S"));
match parsed {
Ok(dt) => L::DateTime(dt),
Err(_) if opts.strict_dates => {
return Err(IoError::Backend {
backend: "json".to_string(),
message: format!("Invalid datetime at {path}: '{s}'"),
});
}
Err(_) => L::Text(s.clone()),
}
}
JsonValue::Time(s) => match chrono::NaiveTime::parse_from_str(s, "%H:%M:%S") {
Ok(t) => L::Time(t),
Err(_) if opts.strict_dates => {
return Err(IoError::Backend {
backend: "json".to_string(),
message: format!("Invalid time at {path}: '{s}'"),
});
}
Err(_) => L::Text(s.clone()),
},
JsonValue::Duration(secs) => L::Duration(chrono::Duration::seconds(*secs)),
JsonValue::Error(code) => L::Error(
formualizer_common::error::ExcelError::from_error_string(code),
),
JsonValue::Pending => L::Pending,
})
}
impl<R> formualizer_eval::engine::ingest::EngineLoadStream<R> for JsonAdapter
where
R: formualizer_eval::traits::EvaluationContext,
{
type Error = IoError;
fn stream_into_engine(
&mut self,
engine: &mut formualizer_eval::engine::Engine<R>,
) -> Result<(), Self::Error> {
let any_1904 = self.data.sheets.values().any(|s| s.date_system_1904);
engine.config.date_system = if any_1904 {
formualizer_eval::engine::DateSystem::Excel1904
} else {
formualizer_eval::engine::DateSystem::Excel1900
};
for name in self.data.sheets.keys() {
engine
.add_sheet(name)
.map_err(|e| IoError::from_backend("json", e))?;
}
for src in &self.data.sources {
match src {
JsonSource::Scalar { name, version } => engine
.define_source_scalar(name, *version)
.map_err(|e| IoError::from_backend("json", e))?,
JsonSource::Table { name, version } => {
engine
.define_source_table(name, *version)
.map_err(|e| IoError::from_backend("json", e))?
}
}
}
let chunk_rows: usize = 32 * 1024;
let mut eager_formula_batches: Vec<FormulaBatch> = Vec::new();
for (name, sheet) in self.data.sheets.iter() {
let dims = sheet.dimensions.unwrap_or_else(|| {
let mut max_r = 0u32;
let mut max_c = 0u32;
for c in &sheet.cells {
if c.row > max_r {
max_r = c.row;
}
if c.col > max_c {
max_c = c.col;
}
}
(max_r, max_c)
});
let rows = dims.0 as usize;
let cols = dims.1 as usize;
enforce_sheet_load_limits(
"json",
name,
dims.0,
dims.1,
sheet.cells.len(),
engine.workbook_load_limits(),
)?;
let mut aib = formualizer_eval::arrow_store::IngestBuilder::new(
name,
cols,
chunk_rows,
engine.config.date_system,
);
let mut cell_map: BTreeMap<(u32, u32), &JsonCell> = BTreeMap::new();
for c in &sheet.cells {
cell_map.insert((c.row, c.col), c);
}
for r in 1..=rows {
let mut row_vals: Vec<formualizer_common::LiteralValue> =
vec![formualizer_common::LiteralValue::Empty; cols];
for c in 1..=cols {
if let Some(cell) = cell_map.get(&(r as u32, c as u32))
&& let Some(v) = &cell.value
{
row_vals[c - 1] = json_to_literal(
v,
&self.read_options,
&format!(
"sheets.{name}.cells[row={},col={}].value",
r as u32, c as u32
),
)?;
}
}
aib.append_row(&row_vals)
.map_err(|e| IoError::from_backend("json", e))?;
}
let asheet = aib.finish();
let store = engine.sheet_store_mut();
if let Some(pos) = store.sheets.iter().position(|s| s.name.as_ref() == name) {
store.sheets[pos] = asheet;
} else {
store.sheets.push(asheet);
}
if let Some(sheet_id) = engine.sheet_id(name) {
for table in &sheet.tables {
let (sr, sc, er, ec) = table.range;
let sr0 = sr.saturating_sub(1);
let sc0 = sc.saturating_sub(1);
let er0 = er.saturating_sub(1);
let ec0 = ec.saturating_sub(1);
let start_ref = formualizer_eval::reference::CellRef::new(
sheet_id,
formualizer_eval::reference::Coord::new(sr0, sc0, true, true),
);
let end_ref = formualizer_eval::reference::CellRef::new(
sheet_id,
formualizer_eval::reference::Coord::new(er0, ec0, true, true),
);
let range_ref = formualizer_eval::reference::RangeRef::new(start_ref, end_ref);
engine.define_table(
&table.name,
range_ref,
table.header_row,
table.headers.clone(),
table.totals_row,
)?;
}
}
if engine.config.defer_graph_building {
for c in &sheet.cells {
if let Some(f) = &c.formula {
if f.is_empty() {
continue;
}
engine.stage_formula_text(name, c.row, c.col, f.clone());
}
}
} else {
let mut formulas: Vec<(u32, u32, formualizer_parse::ASTNode)> = Vec::new();
for c in &sheet.cells {
if let Some(f) = &c.formula {
if f.is_empty() {
continue;
}
let with_eq = if f.starts_with('=') {
f.clone()
} else {
format!("={f}")
};
match formualizer_parse::parser::parse(&with_eq) {
Ok(parsed) => formulas.push((c.row, c.col, parsed)),
Err(e) => {
if let Some(recovered) = engine
.handle_formula_parse_error(
name,
c.row,
c.col,
&with_eq,
e.to_string(),
)
.map_err(IoError::Engine)?
{
formulas.push((c.row, c.col, recovered));
}
}
}
}
}
if !formulas.is_empty() {
eager_formula_batches.push((name.clone(), formulas));
}
}
for row in &sheet.row_hidden_manual {
engine
.set_row_hidden(
name,
*row,
true,
formualizer_eval::engine::RowVisibilitySource::Manual,
)
.map_err(|e| IoError::from_backend("json", e))?;
}
for row in &sheet.row_hidden_filter {
engine
.set_row_hidden(
name,
*row,
true,
formualizer_eval::engine::RowVisibilitySource::Filter,
)
.map_err(|e| IoError::from_backend("json", e))?;
}
}
if !engine.config.defer_graph_building && !eager_formula_batches.is_empty() {
let mut builder = engine.begin_bulk_ingest();
for (sheet_name, formulas) in eager_formula_batches {
let sid = builder.add_sheet(&sheet_name);
builder.add_formulas(sid, formulas.into_iter());
}
builder.finish().map_err(IoError::Engine)?;
}
{
use formualizer_eval::engine::named_range::{NameScope, NamedDefinition};
use formualizer_eval::reference::{CellRef, Coord};
use rustc_hash::FxHashSet;
let defined = self
.defined_names()
.map_err(|e| IoError::from_backend("json", e))?;
let mut seen: FxHashSet<(DefinedNameScope, Option<String>, String)> =
FxHashSet::default();
for dn in defined {
let key = (dn.scope.clone(), dn.scope_sheet.clone(), dn.name.clone());
if !seen.insert(key) {
continue;
}
let scope = match dn.scope {
DefinedNameScope::Workbook => NameScope::Workbook,
DefinedNameScope::Sheet => {
let sheet_name =
dn.scope_sheet.as_deref().ok_or_else(|| IoError::Backend {
backend: "json".to_string(),
message: format!(
"sheet-scoped defined name `{}` missing scope_sheet",
dn.name
),
})?;
let sid = engine
.sheet_id(sheet_name)
.ok_or_else(|| IoError::Backend {
backend: "json".to_string(),
message: format!("scope sheet not found: {sheet_name}"),
})?;
NameScope::Sheet(sid)
}
};
let definition = match dn.definition {
DefinedNameDefinition::Range { address } => {
let sheet_id = engine
.sheet_id(&address.sheet)
.or_else(|| engine.add_sheet(&address.sheet).ok())
.ok_or_else(|| IoError::Backend {
backend: "json".to_string(),
message: format!("sheet not found: {}", address.sheet),
})?;
let sr0 = address.start_row.saturating_sub(1);
let sc0 = address.start_col.saturating_sub(1);
let er0 = address.end_row.saturating_sub(1);
let ec0 = address.end_col.saturating_sub(1);
let start_ref = CellRef::new(sheet_id, Coord::new(sr0, sc0, true, true));
if sr0 == er0 && sc0 == ec0 {
NamedDefinition::Cell(start_ref)
} else {
let end_ref = CellRef::new(sheet_id, Coord::new(er0, ec0, true, true));
let range_ref =
formualizer_eval::reference::RangeRef::new(start_ref, end_ref);
NamedDefinition::Range(range_ref)
}
}
DefinedNameDefinition::Literal { value } => NamedDefinition::Literal(value),
};
engine.define_name(&dn.name, definition, scope)?;
}
}
for name in self.data.sheets.keys() {
engine.finalize_sheet_index(name);
}
engine.set_first_load_assume_new(false);
engine.reset_ensure_touched();
Ok(())
}
}