formualizer_eval/engine/graph/
sheets.rs1use super::ast_utils::update_internal_sheet_references;
2use super::*;
3use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
4
5const TOMBSTONE_SHEET_PREFIX: &str = "__FZ_MISSING_SHEET__";
6
7impl DependencyGraph {
8 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
13 if let Some(id) = self.sheet_reg.get_id(name) {
14 return Ok(id);
15 }
16
17 let sheet_id = self.sheet_reg.id_for(name);
18 self.sheet_indexes.entry(sheet_id).or_default();
19
20 self.heal_orphaned_formulas(name);
22 self.resolve_pending_symbol("sheet", name);
23 Ok(sheet_id)
24 }
25
26 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
28 let result = self.remove_sheet_impl(sheet_id);
29 if result.is_ok() {
30 self.drop_retired_ids_of_sheet(sheet_id);
31 }
32 self.authority_end_structural();
33 result
34 }
35
36 fn formulas_referencing_sheet(&self, sheet_id: SheetId) -> Vec<VertexId> {
39 use crate::engine::refs::{self, SemanticReference};
40 struct Probe<'a> {
41 graph: &'a DependencyGraph,
42 sheet_id: SheetId,
43 hit: bool,
44 }
45 fn visit(
46 p: &mut Probe<'_>,
47 r: SemanticReference<'_>,
48 key: Option<SheetId>,
49 ) -> Result<(), ExcelError> {
50 let name = match &r {
51 SemanticReference::Cell(c) => c.sheet.name(),
52 SemanticReference::FiniteRange(rg) | SemanticReference::OpenRange(rg) => {
53 rg.sheet.name()
54 }
55 _ => return Ok(()),
56 };
57 let id = match (key, name) {
58 (Some(id), _) => Some(id),
59 (None, Some(n)) => p.graph.sheet_id(n),
60 (None, None) => None,
61 };
62 if id == Some(p.sheet_id) {
63 p.hit = true;
64 }
65 Ok(())
66 }
67 let mut out = Vec::new();
68 for (v, f) in self.vertex_formulas.iter() {
69 let ast = f.root();
72 let mut probe = Probe {
73 graph: self,
74 sheet_id,
75 hit: false,
76 };
77 let _ = refs::visit_arena_references_keyed(
78 ast,
79 &mut probe,
80 |p| p.graph.data_store(),
81 |p| p.graph.sheet_reg(),
82 visit,
83 );
84 if probe.hit {
85 out.push(v);
86 }
87 }
88 out.sort_unstable();
89 out
90 }
91
92 fn remove_sheet_impl(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
93 self.authority_note_structural(true);
94 let old_name = self.sheet_reg.name(sheet_id).to_string();
95 if old_name.is_empty() {
96 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
97 }
98
99 let sheet_count = self.sheet_reg.all_sheets().len();
100 if sheet_count <= 1 {
101 return Err(
102 ExcelError::new(ExcelErrorKind::Value).with_message("Cannot remove the last sheet")
103 );
104 }
105
106 self.begin_batch();
107
108 let vertices_to_delete: Vec<VertexId> = self
112 .grid_vertices_in_sheet(sheet_id)
113 .map(|(id, _)| id)
114 .collect();
115
116 let formulas_to_update = self.formulas_referencing_sheet(sheet_id);
120
121 for &formula_id in &formulas_to_update {
122 self.tombstone_registry
123 .add_orphan(old_name.clone(), formula_id);
124 self.rewrite_formula_sheet_to_tombstone(formula_id, &old_name);
125 }
126
127 for formula_id in formulas_to_update {
128 self.mark_as_ref_error(formula_id);
129 }
130
131 let ref_err = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
137 let mut name_vertices_to_update: Vec<VertexId> = Vec::new();
138 let mut dirty_vertices: Vec<VertexId> = Vec::new();
139
140 for nr in self.named_ranges.values_mut() {
141 match &nr.definition {
142 NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
143 nr.definition = NamedDefinition::Literal(ref_err.clone());
144 name_vertices_to_update.push(nr.vertex);
145 dirty_vertices.push(nr.vertex);
146 }
147 NamedDefinition::Range(r)
148 if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
149 {
150 nr.definition = NamedDefinition::Literal(ref_err.clone());
151 name_vertices_to_update.push(nr.vertex);
152 dirty_vertices.push(nr.vertex);
153 }
154 _ => {}
155 }
156 }
157 for nr in self.sheet_named_ranges.values_mut() {
158 match &nr.definition {
159 NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
160 nr.definition = NamedDefinition::Literal(ref_err.clone());
161 name_vertices_to_update.push(nr.vertex);
162 dirty_vertices.push(nr.vertex);
163 }
164 NamedDefinition::Range(r)
165 if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
166 {
167 nr.definition = NamedDefinition::Literal(ref_err.clone());
168 name_vertices_to_update.push(nr.vertex);
169 dirty_vertices.push(nr.vertex);
170 }
171 _ => {}
172 }
173 }
174
175 for vid in name_vertices_to_update {
177 self.update_vertex_value_ref(vid, &ref_err);
178 }
179 for &vid in &dirty_vertices {
180 self.mark_vertex_dirty(vid);
181 }
182 self.mark_dirty_many(&dirty_vertices);
184
185 for vertex_id in vertices_to_delete {
186 if let Some(cell_ref) = self.get_cell_ref_for_vertex(vertex_id) {
187 self.cell_to_vertex.remove(&cell_ref);
188 }
189
190 self.remove_all_edges(vertex_id);
191
192 if let Some(coord) = self.store.grid_addr(vertex_id)
193 && let Some(index) = self.sheet_indexes.get_mut(&sheet_id)
194 {
195 index.remove_vertex(coord, vertex_id);
196 }
197
198 self.clear_pending_name_references(vertex_id);
199 self.forget_declared_dynamic_anchor(vertex_id);
200 self.vertex_formulas.remove(&vertex_id);
201 self.vertex_values.remove(&vertex_id);
202
203 self.mark_deleted(vertex_id, true);
204 }
205
206 let sheet_names_to_remove: Vec<(SheetId, String)> = self
207 .sheet_named_ranges
208 .keys()
209 .filter(|(sid, _)| *sid == sheet_id)
210 .cloned()
211 .collect();
212
213 for key in sheet_names_to_remove {
214 if let Some(named_range) = self.sheet_named_ranges.remove(&key) {
215 if !self.config.case_sensitive_names {
216 let normalized = key.1.to_lowercase();
217 self.sheet_named_ranges_lookup
218 .remove(&(sheet_id, normalized));
219 } else {
220 self.sheet_named_ranges_lookup.remove(&key);
221 }
222 self.mark_named_vertex_deleted(&named_range);
223 }
224 }
225
226 self.sheet_indexes.remove(&sheet_id);
227
228 if self.default_sheet_id == sheet_id
229 && let Some(&new_default) = self.sheet_indexes.keys().next()
230 {
231 self.default_sheet_id = new_default;
232 }
233
234 self.sheet_reg.remove(sheet_id)?;
235 self.end_batch();
236
237 Ok(())
238 }
239
240 fn tombstone_marker(sheet_name: &str) -> String {
241 format!("{TOMBSTONE_SHEET_PREFIX}{sheet_name}")
242 }
243
244 pub(crate) fn is_tombstone_sheet(sheet_name: &str) -> bool {
250 sheet_name.starts_with(TOMBSTONE_SHEET_PREFIX)
251 }
252
253 fn rewrite_formula_sheet_to_tombstone(&mut self, vertex_id: VertexId, sheet_name: &str) {
254 let Some(ast) = self.get_formula(vertex_id) else {
255 return;
256 };
257
258 let marker = Self::tombstone_marker(sheet_name);
259 let mut updated_ast = ast.clone();
260 updated_ast.update_sheet_references(Some(sheet_name), &marker);
261
262 if updated_ast != ast {
263 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
264 self.materialize_vertex(vertex_id);
265 self.vertex_formulas.insert(vertex_id, updated_ast_id);
266 }
267 }
268
269 fn heal_orphaned_formulas(&mut self, sheet_name: &str) {
270 let orphans = self.tombstone_registry.take_orphans(sheet_name);
271 let marker = Self::tombstone_marker(sheet_name);
272
273 for vertex_id in orphans {
274 let Some(ast) = self.get_formula(vertex_id) else {
275 continue;
276 };
277
278 if !self.is_ref_error(vertex_id) {
281 continue;
282 }
283
284 let mut updated_ast = ast.clone();
286 updated_ast.update_sheet_references(Some(&marker), sheet_name);
287
288 if updated_ast == ast {
289 continue;
291 }
292
293 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
294 self.materialize_vertex(vertex_id);
295 self.vertex_formulas.insert(vertex_id, updated_ast_id);
296 self.rebuild_formula_dependencies(vertex_id, &updated_ast);
297 }
298 }
299 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
301 let result = self.rename_sheet_impl(sheet_id, new_name);
302 self.authority_end_structural();
303 result
304 }
305
306 fn rename_sheet_impl(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
307 self.authority_note_structural(true);
308 if new_name.is_empty() || new_name.len() > 255 {
309 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
310 }
311
312 let old_name = self.sheet_reg.name(sheet_id).to_string();
313
314 if old_name.is_empty() {
315 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
316 }
317
318 if let Some(existing_id) = self.sheet_reg.get_id(new_name) {
319 if existing_id != sheet_id {
320 return Err(ExcelError::new(ExcelErrorKind::Value)
321 .with_message(format!("Sheet '{new_name}' already exists")));
322 }
323 return Ok(());
324 }
325
326 self.sheet_reg.rename(sheet_id, new_name)?;
327 let old_key = old_name.to_ascii_lowercase();
332 self.renamed_sheet_aliases
333 .retain(|k, _| *k != new_name.to_ascii_lowercase());
334 self.renamed_sheet_aliases.insert(old_key, sheet_id);
335
336 self.begin_batch();
337
338 self.heal_orphaned_formulas(new_name);
340
341 let formulas_to_update: Vec<VertexId> = self.vertex_formulas.keys().collect();
343 for formula_id in formulas_to_update {
344 if let Some(ast) = self.get_formula(formula_id) {
345 let mut updated_ast = ast.clone();
346 updated_ast.update_sheet_references(Some(&old_name), new_name);
347
348 if ast != updated_ast {
349 self.rebuild_formula_dependencies(formula_id, &updated_ast);
350 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
351 self.vertex_formulas.insert(formula_id, updated_ast_id);
352 }
353 }
354 }
355
356 self.end_batch();
357 Ok(())
358 }
359
360 pub fn duplicate_sheet(
362 &mut self,
363 source_sheet_id: SheetId,
364 new_name: &str,
365 ) -> Result<SheetId, ExcelError> {
366 let result = self.duplicate_sheet_impl(source_sheet_id, new_name);
367 self.authority_end_structural();
368 result
369 }
370
371 fn duplicate_sheet_impl(
372 &mut self,
373 source_sheet_id: SheetId,
374 new_name: &str,
375 ) -> Result<SheetId, ExcelError> {
376 self.authority_note_structural(true);
377 if new_name.is_empty() || new_name.len() > 255 {
378 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
379 }
380
381 let source_name = self.sheet_reg.name(source_sheet_id).to_string();
382 if source_name.is_empty() {
383 return Err(
384 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
385 );
386 }
387
388 if self.sheet_reg.get_id(new_name).is_some() {
389 return Err(ExcelError::new(ExcelErrorKind::Value)
390 .with_message(format!("Sheet '{new_name}' already exists")));
391 }
392
393 let new_sheet_id = self.add_sheet(new_name)?;
394
395 self.begin_batch();
396
397 let source_vertices: Vec<(VertexId, GridAddr)> =
398 self.grid_vertices_in_sheet(source_sheet_id).collect();
399
400 let mut vertex_mapping = FxHashMap::default();
401
402 for (old_id, coord) in &source_vertices {
403 let row = coord.row();
404 let col = coord.col();
405 let kind = self.store.kind(*old_id);
406
407 let new_id = self
408 .store
409 .allocate(VertexAddr::grid(*coord), new_sheet_id, 0x01);
410 #[cfg(any(test, feature = "legacy_oracle"))]
411 {
412 self.edges.add_vertex(VertexAddr::grid(*coord), new_id.0);
413 self.oracle_cell_vertex_created((new_sheet_id, coord.row(), coord.col()), new_id);
414 }
415 self.sheet_index_mut(new_sheet_id)
416 .add_vertex(*coord, new_id);
417
418 self.store.set_kind(new_id, kind);
419
420 if let Some(&value_ref) = self.vertex_values.get(old_id) {
421 self.vertex_values.insert(new_id, value_ref);
422 }
423
424 vertex_mapping.insert(*old_id, new_id);
425
426 let cell_ref = CellRef::new(new_sheet_id, Coord::new(row, col, true, true));
427 self.cell_to_vertex.insert(cell_ref, new_id);
428 }
429
430 let sheet_names: Vec<(String, NamedRange)> = self
431 .sheet_named_ranges
432 .iter()
433 .filter(|((sid, _), _)| *sid == source_sheet_id)
434 .map(|((_, name), range)| (name.clone(), range.clone()))
435 .collect();
436
437 for (name, mut named_range) in sheet_names {
438 named_range.scope = NameScope::Sheet(new_sheet_id);
439
440 match &mut named_range.definition {
441 NamedDefinition::Cell(cell_ref) if cell_ref.sheet_id == source_sheet_id => {
442 cell_ref.sheet_id = new_sheet_id;
443 }
444 NamedDefinition::Range(range_ref)
445 if range_ref.start.sheet_id == source_sheet_id =>
446 {
447 range_ref.start.sheet_id = new_sheet_id;
448 range_ref.end.sheet_id = new_sheet_id;
449 }
450 _ => {}
451 }
452
453 #[cfg(any(test, feature = "legacy_oracle"))]
454 named_range.dependents.clear();
455 let name_vertex = self.allocate_name_vertex(named_range.scope);
456 if matches!(named_range.definition, NamedDefinition::Range(_)) {
457 self.store.set_kind(name_vertex, VertexKind::NamedArray);
458 } else {
459 self.store.set_kind(name_vertex, VertexKind::NamedScalar);
460 }
461 named_range.vertex = name_vertex;
462
463 let referenced_names = self.rebuild_name_dependencies(
464 name_vertex,
465 &named_range.definition,
466 named_range.scope,
467 )?;
468 if !referenced_names.is_empty() {
469 self.attach_vertex_to_names(name_vertex, &referenced_names);
470 }
471
472 self.sheet_named_ranges
473 .insert((new_sheet_id, name.clone()), named_range);
474 self.sheet_named_ranges_lookup
475 .insert((new_sheet_id, self.name_lookup_key(&name)), name.clone());
476 self.name_vertex_lookup
477 .insert(name_vertex, (NameScope::Sheet(new_sheet_id), name));
478 }
479
480 for (old_id, _) in &source_vertices {
481 if let Some(&new_id) = vertex_mapping.get(old_id)
482 && let Some(ast) = self.get_formula(*old_id)
483 {
484 let updated_ast = update_internal_sheet_references(
485 &ast,
486 &source_name,
487 new_name,
488 source_sheet_id,
489 new_sheet_id,
490 );
491
492 let new_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
493 self.vertex_formulas.insert(new_id, new_ast_id);
494
495 if let Ok((deps, range_deps, vertexless, name_vertices)) =
496 self.extract_dependencies(&updated_ast, new_sheet_id)
497 {
498 let mapped_deps: Vec<VertexId> = deps
499 .iter()
500 .map(|&dep_id| vertex_mapping.get(&dep_id).copied().unwrap_or(dep_id))
501 .collect();
502
503 self.add_dependent_edges(new_id, &mapped_deps);
504 self.note_vertexless_deps(
505 new_id,
506 vertexless
507 .iter()
508 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
509 );
510 self.add_range_dependent_edges(new_id, &range_deps, new_sheet_id);
511
512 if !name_vertices.is_empty() {
513 self.attach_vertex_to_names(new_id, &name_vertices);
514 }
515 }
516 }
517 }
518
519 self.end_batch();
520
521 Ok(new_sheet_id)
522 }
523}