1use crate::IoError;
2use crate::traits::{
3 AccessGranularity, BackendCaps, CellData, MergedRange, NamedRange, SaveDestination, SheetData,
4 SpreadsheetReader, SpreadsheetWriter, TableDefinition,
5};
6use crate::traits::{DefinedName, DefinedNameDefinition, DefinedNameScope};
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9use std::fs::File;
10use std::io::{BufReader, Read, Write};
11use std::path::{Path, PathBuf};
12
13#[derive(Serialize, Deserialize, Debug, Default, Clone)]
14struct JsonWorkbook {
15 #[serde(default = "default_version")]
16 version: u32,
17 #[serde(default)]
18 compression: Option<CompressionType>,
19 #[serde(default)]
20 sources: Vec<JsonSource>,
21
22 #[serde(default)]
23 defined_names: Vec<JsonDefinedName>,
24
25 #[serde(default)]
26 sheets: BTreeMap<String, JsonSheet>,
27}
28
29type FormulaBatch = (String, Vec<(u32, u32, formualizer_parse::ASTNode)>);
30
31#[derive(Clone, Copy, Debug)]
32pub struct JsonReadOptions {
33 pub strict_dates: bool,
37}
38
39impl Default for JsonReadOptions {
40 fn default() -> Self {
41 Self { strict_dates: true }
42 }
43}
44
45fn default_version() -> u32 {
46 1
47}
48
49#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
50#[serde(rename_all = "lowercase")]
51#[derive(Default)]
52enum CompressionType {
53 #[default]
54 None,
55 Lz4,
56}
57
58#[derive(Serialize, Deserialize, Debug, Default, Clone)]
59struct JsonSheet {
60 #[serde(default)]
61 cells: Vec<JsonCell>,
62 #[serde(default)]
63 dimensions: Option<(u32, u32)>,
64 #[serde(default)]
65 hidden: bool,
66 #[serde(default)]
67 date_system_1904: bool,
68 #[serde(default)]
69 merged_cells: Vec<MergedRange>,
70 #[serde(default)]
71 tables: Vec<TableDefinition>,
72 #[serde(default)]
73 named_ranges: Vec<NamedRange>,
74}
75
76#[derive(Serialize, Deserialize, Debug, Clone)]
77struct JsonDefinedName {
78 name: String,
79
80 #[serde(default)]
81 scope: DefinedNameScope,
82
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 scope_sheet: Option<String>,
85
86 definition: JsonDefinedNameDefinition,
87}
88
89#[derive(Serialize, Deserialize, Debug, Clone)]
90#[serde(tag = "type", rename_all = "lowercase")]
91enum JsonDefinedNameDefinition {
92 Range {
93 address: formualizer_common::RangeAddress,
94 },
95 Literal {
96 value: JsonValue,
97 },
98}
99
100#[derive(Serialize, Deserialize, Debug, Clone)]
101struct JsonCell {
102 row: u32,
103 col: u32,
104 #[serde(default)]
105 value: Option<JsonValue>,
106 #[serde(default)]
107 formula: Option<String>,
108 #[serde(default)]
109 style: Option<u32>,
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone)]
113#[serde(tag = "type", rename_all = "lowercase")]
114enum JsonSource {
115 Scalar { name: String, version: Option<u64> },
116 Table { name: String, version: Option<u64> },
117}
118
119#[derive(Serialize, Deserialize, Debug, Clone)]
120#[serde(tag = "type", content = "value")]
121enum JsonValue {
122 Int(i64),
123 Number(f64),
124 Text(String),
125 Boolean(bool),
126 Empty,
127 Date(String),
128 DateTime(String),
129 Time(String),
130 Duration(i64),
131 Array(Vec<Vec<JsonValue>>),
132 Error(String),
133 Pending,
134}
135
136pub struct JsonAdapter {
137 data: JsonWorkbook,
138 path: Option<PathBuf>,
139 read_options: JsonReadOptions,
140 caps: BackendCaps,
141}
142
143impl Default for JsonAdapter {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl JsonAdapter {
150 pub fn new() -> Self {
151 Self::new_with_options(JsonReadOptions::default())
152 }
153
154 pub fn new_with_options(read_options: JsonReadOptions) -> Self {
155 Self {
156 data: JsonWorkbook::default(),
157 path: None,
158 read_options,
159 caps: BackendCaps {
160 read: true,
161 write: true,
162 streaming: false,
163 tables: true,
164 named_ranges: true,
165 formulas: true,
166 styles: true,
167 lazy_loading: false,
168 random_access: true,
169 bytes_input: true,
170 date_system_1904: false,
171 merged_cells: true,
172 rich_text: false,
173 hyperlinks: false,
174 data_validations: false,
175 shared_formulas: false,
176 },
177 }
178 }
179
180 pub fn read_options(&self) -> &JsonReadOptions {
181 &self.read_options
182 }
183
184 pub fn set_read_options(&mut self, opts: JsonReadOptions) {
185 self.read_options = opts;
186 }
187
188 fn migrate_legacy_named_ranges(&mut self) {
189 if !self.data.defined_names.is_empty() {
190 return;
191 }
192
193 use rustc_hash::FxHashSet;
194
195 let mut seen: FxHashSet<(DefinedNameScope, Option<String>, String)> = FxHashSet::default();
196 for (sheet_name, sheet) in &self.data.sheets {
197 for nr in &sheet.named_ranges {
198 let scope = match nr.scope {
199 crate::traits::NamedRangeScope::Workbook => DefinedNameScope::Workbook,
200 crate::traits::NamedRangeScope::Sheet => DefinedNameScope::Sheet,
201 };
202 let scope_sheet = match nr.scope {
203 crate::traits::NamedRangeScope::Workbook => None,
204 crate::traits::NamedRangeScope::Sheet => Some(sheet_name.clone()),
205 };
206 let key = (scope.clone(), scope_sheet.clone(), nr.name.clone());
207 if !seen.insert(key) {
208 continue;
209 }
210
211 self.data.defined_names.push(JsonDefinedName {
212 name: nr.name.clone(),
213 scope,
214 scope_sheet,
215 definition: JsonDefinedNameDefinition::Range {
216 address: nr.address.clone(),
217 },
218 });
219 }
220 }
221 }
222
223 fn to_sheet_data(
224 js: &JsonSheet,
225 sheet_name: &str,
226 opts: &JsonReadOptions,
227 ) -> Result<SheetData, IoError> {
228 let mut cells: BTreeMap<(u32, u32), CellData> = BTreeMap::new();
229 for (idx, c) in js.cells.iter().enumerate() {
230 let lit = match c.value.as_ref() {
231 Some(v) => Some(json_to_literal(
232 v,
233 opts,
234 &format!("sheets.{sheet_name}.cells[{idx}].value"),
235 )?),
236 None => None,
237 };
238 cells.insert(
239 (c.row, c.col),
240 CellData {
241 value: lit,
242 formula: c.formula.clone(),
243 style: c.style,
244 },
245 );
246 }
247 Ok(SheetData {
248 cells,
249 dimensions: js.dimensions,
250 tables: js.tables.clone(),
251 named_ranges: js.named_ranges.clone(),
252 date_system_1904: js.date_system_1904,
253 merged_cells: js.merged_cells.clone(),
254 hidden: js.hidden,
255 })
256 }
257
258 pub fn to_json_string(&self) -> Result<String, IoError> {
259 Ok(serde_json::to_string_pretty(&self.data)?)
260 }
261
262 fn ensure_sheet_mut(&mut self, name: &str) -> &mut JsonSheet {
264 self.data.sheets.entry(name.to_string()).or_default()
265 }
266
267 pub fn set_dimensions(&mut self, sheet: &str, dims: Option<(u32, u32)>) {
268 self.ensure_sheet_mut(sheet).dimensions = dims;
269 }
270
271 pub fn set_date_system_1904(&mut self, sheet: &str, value: bool) {
272 self.ensure_sheet_mut(sheet).date_system_1904 = value;
273 }
274
275 pub fn set_merged_cells(&mut self, sheet: &str, merged: Vec<MergedRange>) {
276 self.ensure_sheet_mut(sheet).merged_cells = merged;
277 }
278
279 pub fn set_tables(&mut self, sheet: &str, tables: Vec<TableDefinition>) {
280 self.ensure_sheet_mut(sheet).tables = tables;
281 }
282
283 pub fn set_named_ranges(&mut self, sheet: &str, named: Vec<NamedRange>) {
284 self.ensure_sheet_mut(sheet).named_ranges = named;
285 }
286
287 pub fn set_defined_names(&mut self, names: Vec<DefinedName>) {
288 self.data.defined_names = names
289 .into_iter()
290 .map(|dn| match dn.definition {
291 DefinedNameDefinition::Range { address } => JsonDefinedName {
292 name: dn.name,
293 scope: dn.scope,
294 scope_sheet: dn.scope_sheet,
295 definition: JsonDefinedNameDefinition::Range { address },
296 },
297 DefinedNameDefinition::Literal { value } => JsonDefinedName {
298 name: dn.name,
299 scope: dn.scope,
300 scope_sheet: dn.scope_sheet,
301 definition: JsonDefinedNameDefinition::Literal {
302 value: literal_to_json(&value),
303 },
304 },
305 })
306 .collect();
307 }
308}
309
310impl SpreadsheetReader for JsonAdapter {
311 type Error = IoError;
312
313 fn access_granularity(&self) -> AccessGranularity {
314 AccessGranularity::Workbook
315 }
316
317 fn capabilities(&self) -> BackendCaps {
318 self.caps.clone()
319 }
320
321 fn sheet_names(&self) -> Result<Vec<String>, Self::Error> {
322 Ok(self.data.sheets.keys().cloned().collect())
323 }
324
325 fn open_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
326 where
327 Self: Sized,
328 {
329 let file = File::open(path.as_ref())?;
330 let reader = BufReader::new(file);
331 let data: JsonWorkbook = serde_json::from_reader(reader)?;
332 let mut adapter = JsonAdapter {
333 data,
334 path: Some(path.as_ref().to_path_buf()),
335 ..JsonAdapter::new()
336 };
337 adapter.migrate_legacy_named_ranges();
338 Ok(adapter)
339 }
340
341 fn open_reader(reader: Box<dyn Read + Send + Sync>) -> Result<Self, Self::Error>
342 where
343 Self: Sized,
344 {
345 let data: JsonWorkbook = serde_json::from_reader(reader)?;
346 let mut adapter = JsonAdapter {
347 data,
348 ..JsonAdapter::new()
349 };
350 adapter.migrate_legacy_named_ranges();
351 Ok(adapter)
352 }
353
354 fn open_bytes(bytes: Vec<u8>) -> Result<Self, Self::Error>
355 where
356 Self: Sized,
357 {
358 let data: JsonWorkbook = serde_json::from_slice(&bytes)?;
359 let mut adapter = JsonAdapter {
360 data,
361 ..JsonAdapter::new()
362 };
363 adapter.migrate_legacy_named_ranges();
364 Ok(adapter)
365 }
366
367 fn defined_names(&mut self) -> Result<Vec<DefinedName>, Self::Error> {
368 self.migrate_legacy_named_ranges();
369 self.data
370 .defined_names
371 .iter()
372 .enumerate()
373 .map(|(idx, dn)| {
374 let def = match &dn.definition {
375 JsonDefinedNameDefinition::Range { address } => DefinedNameDefinition::Range {
376 address: address.clone(),
377 },
378 JsonDefinedNameDefinition::Literal { value } => {
379 DefinedNameDefinition::Literal {
380 value: json_to_literal(
381 value,
382 &self.read_options,
383 &format!("defined_names[{idx}].definition.value"),
384 )?,
385 }
386 }
387 };
388 Ok(DefinedName {
389 name: dn.name.clone(),
390 scope: dn.scope.clone(),
391 scope_sheet: dn.scope_sheet.clone(),
392 definition: def,
393 })
394 })
395 .collect::<Result<Vec<_>, IoError>>()
396 }
397
398 fn read_range(
399 &mut self,
400 sheet: &str,
401 start: (u32, u32),
402 end: (u32, u32),
403 ) -> Result<BTreeMap<(u32, u32), CellData>, Self::Error> {
404 if let Some(js) = self.data.sheets.get(sheet) {
405 let mut out = BTreeMap::new();
406 for c in &js.cells {
407 if c.row >= start.0 && c.row <= end.0 && c.col >= start.1 && c.col <= end.1 {
408 let lit = match c.value.as_ref() {
409 Some(v) => Some(json_to_literal(
410 v,
411 &self.read_options,
412 &format!("sheets.{sheet}.cells[row={},col={}].value", c.row, c.col),
413 )?),
414 None => None,
415 };
416 out.insert(
417 (c.row, c.col),
418 CellData {
419 value: lit,
420 formula: c.formula.clone(),
421 style: c.style,
422 },
423 );
424 }
425 }
426 Ok(out)
427 } else {
428 Ok(BTreeMap::new())
429 }
430 }
431
432 fn read_sheet(&mut self, sheet: &str) -> Result<SheetData, Self::Error> {
433 if let Some(js) = self.data.sheets.get(sheet) {
434 Self::to_sheet_data(js, sheet, &self.read_options)
435 } else {
436 Ok(SheetData {
437 cells: BTreeMap::new(),
438 dimensions: None,
439 tables: vec![],
440 named_ranges: vec![],
441 date_system_1904: false,
442 merged_cells: vec![],
443 hidden: false,
444 })
445 }
446 }
447
448 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
449 self.data.sheets.get(sheet).and_then(|s| s.dimensions)
450 }
451
452 fn is_loaded(&self, _sheet: &str, _row: Option<u32>, _col: Option<u32>) -> bool {
453 true
454 }
455}
456
457impl SpreadsheetWriter for JsonAdapter {
458 type Error = IoError;
459
460 fn write_cell(
461 &mut self,
462 sheet: &str,
463 row: u32,
464 col: u32,
465 data: CellData,
466 ) -> Result<(), Self::Error> {
467 let sheet_entry = self.data.sheets.entry(sheet.to_string()).or_default();
468 if let Some(cell) = sheet_entry
469 .cells
470 .iter_mut()
471 .find(|c| c.row == row && c.col == col)
472 {
473 cell.value = data.value.as_ref().map(literal_to_json);
474 cell.formula = data.formula;
475 cell.style = data.style;
476 } else {
477 sheet_entry.cells.push(JsonCell {
478 row,
479 col,
480 value: data.value.as_ref().map(literal_to_json),
481 formula: data.formula,
482 style: data.style,
483 });
484 }
485 Ok(())
486 }
487
488 fn write_range(
489 &mut self,
490 sheet: &str,
491 cells: BTreeMap<(u32, u32), CellData>,
492 ) -> Result<(), Self::Error> {
493 for ((r, c), d) in cells {
494 self.write_cell(sheet, r, c, d)?;
495 }
496 Ok(())
497 }
498
499 fn clear_range(
500 &mut self,
501 sheet: &str,
502 start: (u32, u32),
503 end: (u32, u32),
504 ) -> Result<(), Self::Error> {
505 if let Some(js) = self.data.sheets.get_mut(sheet) {
506 js.cells.retain(|c| {
507 !(c.row >= start.0 && c.row <= end.0 && c.col >= start.1 && c.col <= end.1)
508 });
509 }
510 Ok(())
511 }
512
513 fn create_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
514 self.data.sheets.entry(name.to_string()).or_default();
515 Ok(())
516 }
517
518 fn delete_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
519 self.data.sheets.remove(name);
520 Ok(())
521 }
522
523 fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), Self::Error> {
524 if let Some(sheet) = self.data.sheets.remove(old) {
525 self.data.sheets.insert(new.to_string(), sheet);
526 }
527 Ok(())
528 }
529
530 fn flush(&mut self) -> Result<(), Self::Error> {
531 Ok(())
532 }
533
534 fn save(&mut self) -> Result<(), Self::Error> {
535 if let Some(path) = &self.path {
536 let mut file = File::create(path)?;
537 let s = serde_json::to_string_pretty(&self.data)?;
538 file.write_all(s.as_bytes())?;
539 Ok(())
540 } else {
541 Ok(())
542 }
543 }
544
545 fn save_to<'a>(&mut self, dest: SaveDestination<'a>) -> Result<Option<Vec<u8>>, Self::Error> {
546 match dest {
547 SaveDestination::InPlace => self.save().map(|_| None),
548 SaveDestination::Path(path) => {
549 let mut file = File::create(path)?;
550 let s = serde_json::to_string_pretty(&self.data)?;
551 file.write_all(s.as_bytes())?;
552 self.path = Some(path.to_path_buf());
553 Ok(None)
554 }
555 SaveDestination::Writer(writer) => {
556 let s = serde_json::to_string_pretty(&self.data)?;
557 writer.write_all(s.as_bytes())?;
558 Ok(None)
559 }
560 SaveDestination::Bytes => {
561 let s = serde_json::to_vec_pretty(&self.data)?;
562 Ok(Some(s))
563 }
564 }
565 }
566}
567
568fn literal_to_json(v: &formualizer_common::LiteralValue) -> JsonValue {
569 use formualizer_common::LiteralValue as L;
570 match v {
571 L::Int(i) => JsonValue::Int(*i),
572 L::Number(n) => JsonValue::Number(*n),
573 L::Text(s) => JsonValue::Text(s.clone()),
574 L::Boolean(b) => JsonValue::Boolean(*b),
575 L::Empty => JsonValue::Empty,
576 L::Array(arr) => JsonValue::Array(
577 arr.iter()
578 .map(|row| row.iter().map(literal_to_json).collect())
579 .collect(),
580 ),
581 L::Date(d) => JsonValue::Date(d.to_string()),
582 L::DateTime(dt) => JsonValue::DateTime(dt.to_string()),
583 L::Time(t) => JsonValue::Time(t.to_string()),
584 L::Duration(dur) => JsonValue::Duration(dur.num_seconds()),
585 L::Error(e) => JsonValue::Error(e.kind.to_string()),
586 L::Pending => JsonValue::Pending,
587 }
588}
589
590fn json_to_literal(
591 v: &JsonValue,
592 opts: &JsonReadOptions,
593 path: &str,
594) -> Result<formualizer_common::LiteralValue, IoError> {
595 use formualizer_common::LiteralValue as L;
596 Ok(match v {
597 JsonValue::Int(i) => L::Int(*i),
598 JsonValue::Number(n) => L::Number(*n),
599 JsonValue::Text(s) => L::Text(s.clone()),
600 JsonValue::Boolean(b) => L::Boolean(*b),
601 JsonValue::Empty => L::Empty,
602 JsonValue::Array(arr) => L::Array(
603 arr.iter()
604 .map(|row| {
605 row.iter()
606 .map(|v| json_to_literal(v, opts, path))
607 .collect::<Result<Vec<_>, IoError>>()
608 })
609 .collect::<Result<Vec<_>, IoError>>()?,
610 ),
611 JsonValue::Date(s) => match chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
612 Ok(d) => L::Date(d),
613 Err(_) if opts.strict_dates => {
614 return Err(IoError::Backend {
615 backend: "json".to_string(),
616 message: format!("Invalid date at {path}: '{s}'"),
617 });
618 }
619 Err(_) => L::Text(s.clone()),
620 },
621 JsonValue::DateTime(s) => {
622 let parsed = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
623 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S"));
624 match parsed {
625 Ok(dt) => L::DateTime(dt),
626 Err(_) if opts.strict_dates => {
627 return Err(IoError::Backend {
628 backend: "json".to_string(),
629 message: format!("Invalid datetime at {path}: '{s}'"),
630 });
631 }
632 Err(_) => L::Text(s.clone()),
633 }
634 }
635 JsonValue::Time(s) => match chrono::NaiveTime::parse_from_str(s, "%H:%M:%S") {
636 Ok(t) => L::Time(t),
637 Err(_) if opts.strict_dates => {
638 return Err(IoError::Backend {
639 backend: "json".to_string(),
640 message: format!("Invalid time at {path}: '{s}'"),
641 });
642 }
643 Err(_) => L::Text(s.clone()),
644 },
645 JsonValue::Duration(secs) => L::Duration(chrono::Duration::seconds(*secs)),
646 JsonValue::Error(code) => L::Error(
647 formualizer_common::error::ExcelError::from_error_string(code),
648 ),
649 JsonValue::Pending => L::Pending,
650 })
651}
652
653impl<R> formualizer_eval::engine::ingest::EngineLoadStream<R> for JsonAdapter
656where
657 R: formualizer_eval::traits::EvaluationContext,
658{
659 type Error = IoError;
660
661 fn stream_into_engine(
662 &mut self,
663 engine: &mut formualizer_eval::engine::Engine<R>,
664 ) -> Result<(), Self::Error> {
665 let any_1904 = self.data.sheets.values().any(|s| s.date_system_1904);
667 engine.config.date_system = if any_1904 {
668 formualizer_eval::engine::DateSystem::Excel1904
669 } else {
670 formualizer_eval::engine::DateSystem::Excel1900
671 };
672
673 for name in self.data.sheets.keys() {
675 engine
676 .add_sheet(name)
677 .map_err(|e| IoError::from_backend("json", e))?;
678 }
679
680 for src in &self.data.sources {
682 match src {
683 JsonSource::Scalar { name, version } => engine
684 .define_source_scalar(name, *version)
685 .map_err(|e| IoError::from_backend("json", e))?,
686 JsonSource::Table { name, version } => {
687 engine
688 .define_source_table(name, *version)
689 .map_err(|e| IoError::from_backend("json", e))?
690 }
691 }
692 }
693
694 let chunk_rows: usize = 32 * 1024;
696 let mut eager_formula_batches: Vec<FormulaBatch> = Vec::new();
697 for (name, sheet) in self.data.sheets.iter() {
698 let dims = sheet.dimensions.unwrap_or_else(|| {
699 let mut max_r = 0u32;
700 let mut max_c = 0u32;
701 for c in &sheet.cells {
702 if c.row > max_r {
703 max_r = c.row;
704 }
705 if c.col > max_c {
706 max_c = c.col;
707 }
708 }
709 (max_r, max_c)
710 });
711 let rows = dims.0 as usize;
712 let cols = dims.1 as usize;
713
714 let mut aib = formualizer_eval::arrow_store::IngestBuilder::new(
715 name,
716 cols,
717 chunk_rows,
718 engine.config.date_system,
719 );
720 let mut cell_map: BTreeMap<(u32, u32), &JsonCell> = BTreeMap::new();
722 for c in &sheet.cells {
723 cell_map.insert((c.row, c.col), c);
724 }
725 for r in 1..=rows {
726 let mut row_vals: Vec<formualizer_common::LiteralValue> =
727 vec![formualizer_common::LiteralValue::Empty; cols];
728 for c in 1..=cols {
729 if let Some(cell) = cell_map.get(&(r as u32, c as u32))
730 && let Some(v) = &cell.value
731 {
732 row_vals[c - 1] = json_to_literal(
733 v,
734 &self.read_options,
735 &format!(
736 "sheets.{name}.cells[row={},col={}].value",
737 r as u32, c as u32
738 ),
739 )?;
740 }
741 }
742 aib.append_row(&row_vals)
743 .map_err(|e| IoError::from_backend("json", e))?;
744 }
745 let asheet = aib.finish();
746 let store = engine.sheet_store_mut();
747 if let Some(pos) = store.sheets.iter().position(|s| s.name.as_ref() == name) {
748 store.sheets[pos] = asheet;
749 } else {
750 store.sheets.push(asheet);
751 }
752
753 if let Some(sheet_id) = engine.sheet_id(name) {
755 for table in &sheet.tables {
756 let (sr, sc, er, ec) = table.range;
757 let sr0 = sr.saturating_sub(1);
758 let sc0 = sc.saturating_sub(1);
759 let er0 = er.saturating_sub(1);
760 let ec0 = ec.saturating_sub(1);
761 let start_ref = formualizer_eval::reference::CellRef::new(
762 sheet_id,
763 formualizer_eval::reference::Coord::new(sr0, sc0, true, true),
764 );
765 let end_ref = formualizer_eval::reference::CellRef::new(
766 sheet_id,
767 formualizer_eval::reference::Coord::new(er0, ec0, true, true),
768 );
769 let range_ref = formualizer_eval::reference::RangeRef::new(start_ref, end_ref);
770 engine.define_table(
771 &table.name,
772 range_ref,
773 table.header_row,
774 table.headers.clone(),
775 table.totals_row,
776 )?;
777 }
778 }
779
780 if engine.config.defer_graph_building {
782 for c in &sheet.cells {
783 if let Some(f) = &c.formula {
784 if f.is_empty() {
785 continue;
786 }
787 engine.stage_formula_text(name, c.row, c.col, f.clone());
788 }
789 }
790 } else {
791 let mut formulas: Vec<(u32, u32, formualizer_parse::ASTNode)> = Vec::new();
792 for c in &sheet.cells {
793 if let Some(f) = &c.formula {
794 if f.is_empty() {
795 continue;
796 }
797 let with_eq = if f.starts_with('=') {
798 f.clone()
799 } else {
800 format!("={f}")
801 };
802 match formualizer_parse::parser::parse(&with_eq) {
803 Ok(parsed) => formulas.push((c.row, c.col, parsed)),
804 Err(e) => {
805 if let Some(recovered) = engine
806 .handle_formula_parse_error(
807 name,
808 c.row,
809 c.col,
810 &with_eq,
811 e.to_string(),
812 )
813 .map_err(IoError::Engine)?
814 {
815 formulas.push((c.row, c.col, recovered));
816 }
817 }
818 }
819 }
820 }
821 if !formulas.is_empty() {
822 eager_formula_batches.push((name.clone(), formulas));
823 }
824 }
825 }
826
827 if !engine.config.defer_graph_building && !eager_formula_batches.is_empty() {
828 let mut builder = engine.begin_bulk_ingest();
829 for (sheet_name, formulas) in eager_formula_batches {
830 let sid = builder.add_sheet(&sheet_name);
831 builder.add_formulas(sid, formulas.into_iter());
832 }
833 builder.finish().map_err(IoError::Engine)?;
834 }
835
836 {
838 use formualizer_eval::engine::named_range::{NameScope, NamedDefinition};
839 use formualizer_eval::reference::{CellRef, Coord};
840 use rustc_hash::FxHashSet;
841
842 let defined = self
843 .defined_names()
844 .map_err(|e| IoError::from_backend("json", e))?;
845
846 let mut seen: FxHashSet<(DefinedNameScope, Option<String>, String)> =
847 FxHashSet::default();
848 for dn in defined {
849 let key = (dn.scope.clone(), dn.scope_sheet.clone(), dn.name.clone());
850 if !seen.insert(key) {
851 continue;
852 }
853
854 let scope = match dn.scope {
855 DefinedNameScope::Workbook => NameScope::Workbook,
856 DefinedNameScope::Sheet => {
857 let sheet_name =
858 dn.scope_sheet.as_deref().ok_or_else(|| IoError::Backend {
859 backend: "json".to_string(),
860 message: format!(
861 "sheet-scoped defined name `{}` missing scope_sheet",
862 dn.name
863 ),
864 })?;
865 let sid = engine
866 .sheet_id(sheet_name)
867 .ok_or_else(|| IoError::Backend {
868 backend: "json".to_string(),
869 message: format!("scope sheet not found: {sheet_name}"),
870 })?;
871 NameScope::Sheet(sid)
872 }
873 };
874
875 let definition = match dn.definition {
876 DefinedNameDefinition::Range { address } => {
877 let sheet_id = engine
878 .sheet_id(&address.sheet)
879 .or_else(|| engine.add_sheet(&address.sheet).ok())
880 .ok_or_else(|| IoError::Backend {
881 backend: "json".to_string(),
882 message: format!("sheet not found: {}", address.sheet),
883 })?;
884
885 let sr0 = address.start_row.saturating_sub(1);
886 let sc0 = address.start_col.saturating_sub(1);
887 let er0 = address.end_row.saturating_sub(1);
888 let ec0 = address.end_col.saturating_sub(1);
889
890 let start_ref = CellRef::new(sheet_id, Coord::new(sr0, sc0, true, true));
891 if sr0 == er0 && sc0 == ec0 {
892 NamedDefinition::Cell(start_ref)
893 } else {
894 let end_ref = CellRef::new(sheet_id, Coord::new(er0, ec0, true, true));
895 let range_ref =
896 formualizer_eval::reference::RangeRef::new(start_ref, end_ref);
897 NamedDefinition::Range(range_ref)
898 }
899 }
900 DefinedNameDefinition::Literal { value } => NamedDefinition::Literal(value),
901 };
902
903 engine.define_name(&dn.name, definition, scope)?;
904 }
905 }
906
907 for name in self.data.sheets.keys() {
909 engine.finalize_sheet_index(name);
910 }
911 engine.set_first_load_assume_new(false);
912 engine.reset_ensure_touched();
913 Ok(())
914 }
915}