formualizer_eval/engine/graph/
sheets.rs1use super::ast_utils::update_internal_sheet_references;
2use super::*;
3use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
4
5impl DependencyGraph {
6 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
11 if let Some(id) = self.sheet_reg.get_id(name) {
12 return Ok(id);
13 }
14
15 let sheet_id = self.sheet_reg.id_for(name);
16 self.sheet_indexes.entry(sheet_id).or_default();
17
18 self.heal_orphaned_formulas(name);
20 Ok(sheet_id)
21 }
22
23 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
25 let old_name = self.sheet_reg.name(sheet_id).to_string();
26 if old_name.is_empty() {
27 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
28 }
29
30 let sheet_count = self.sheet_reg.all_sheets().len();
31 if sheet_count <= 1 {
32 return Err(
33 ExcelError::new(ExcelErrorKind::Value).with_message("Cannot remove the last sheet")
34 );
35 }
36
37 self.begin_batch();
38
39 let vertices_to_delete: Vec<VertexId> = self.vertices_in_sheet(sheet_id).collect();
40
41 let mut formulas_to_update: rustc_hash::FxHashSet<VertexId> =
44 rustc_hash::FxHashSet::default();
45
46 for &formula_id in self.vertex_formulas.keys() {
47 let deps = self.edges.out_edges(formula_id);
48 if deps
49 .iter()
50 .any(|&dep_id| self.store.sheet_id(dep_id) == sheet_id)
51 {
52 formulas_to_update.insert(formula_id);
53 }
54 }
55
56 for (&formula_id, ranges) in &self.formula_to_range_deps {
57 if ranges.iter().any(|r| match r.sheet {
58 SharedSheetLocator::Id(id) => id == sheet_id,
59 SharedSheetLocator::Name(ref n) => n.as_ref() == old_name,
60 SharedSheetLocator::Current => false,
61 }) {
62 formulas_to_update.insert(formula_id);
63 }
64 }
65
66 let formulas_to_update: Vec<VertexId> = formulas_to_update.into_iter().collect();
67
68 for &formula_id in &formulas_to_update {
69 self.tombstone_registry
70 .add_orphan(old_name.clone(), formula_id);
71 self.rewrite_formula_sheet_to_tombstone(formula_id, &old_name);
72 }
73
74 for formula_id in formulas_to_update {
75 self.mark_as_ref_error(formula_id);
76 }
77
78 let ref_err = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
84 let mut name_vertices_to_update: Vec<VertexId> = Vec::new();
85 let mut dirty_vertices: Vec<VertexId> = Vec::new();
86
87 for nr in self.named_ranges.values_mut() {
88 match &nr.definition {
89 NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
90 nr.definition = NamedDefinition::Literal(ref_err.clone());
91 name_vertices_to_update.push(nr.vertex);
92 dirty_vertices.push(nr.vertex);
93 dirty_vertices.extend(nr.dependents.iter().copied());
94 }
95 NamedDefinition::Range(r)
96 if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
97 {
98 nr.definition = NamedDefinition::Literal(ref_err.clone());
99 name_vertices_to_update.push(nr.vertex);
100 dirty_vertices.push(nr.vertex);
101 dirty_vertices.extend(nr.dependents.iter().copied());
102 }
103 _ => {}
104 }
105 }
106 for nr in self.sheet_named_ranges.values_mut() {
107 match &nr.definition {
108 NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
109 nr.definition = NamedDefinition::Literal(ref_err.clone());
110 name_vertices_to_update.push(nr.vertex);
111 dirty_vertices.push(nr.vertex);
112 dirty_vertices.extend(nr.dependents.iter().copied());
113 }
114 NamedDefinition::Range(r)
115 if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
116 {
117 nr.definition = NamedDefinition::Literal(ref_err.clone());
118 name_vertices_to_update.push(nr.vertex);
119 dirty_vertices.push(nr.vertex);
120 dirty_vertices.extend(nr.dependents.iter().copied());
121 }
122 _ => {}
123 }
124 }
125
126 for vid in name_vertices_to_update {
128 self.update_vertex_value(vid, ref_err.clone());
129 }
130 for vid in dirty_vertices {
131 self.mark_vertex_dirty(vid);
132 }
133
134 for vertex_id in vertices_to_delete {
135 if let Some(cell_ref) = self.get_cell_ref_for_vertex(vertex_id) {
136 self.cell_to_vertex.remove(&cell_ref);
137 }
138
139 self.remove_all_edges(vertex_id);
140
141 let coord = self.store.coord(vertex_id);
142 if let Some(index) = self.sheet_indexes.get_mut(&sheet_id) {
143 index.remove_vertex(coord, vertex_id);
144 }
145
146 self.clear_pending_name_references(vertex_id);
147 self.vertex_formulas.remove(&vertex_id);
148 self.vertex_values.remove(&vertex_id);
149
150 self.mark_deleted(vertex_id, true);
151 }
152
153 let sheet_names_to_remove: Vec<(SheetId, String)> = self
154 .sheet_named_ranges
155 .keys()
156 .filter(|(sid, _)| *sid == sheet_id)
157 .cloned()
158 .collect();
159
160 for key in sheet_names_to_remove {
161 if let Some(named_range) = self.sheet_named_ranges.remove(&key) {
162 if !self.config.case_sensitive_names {
163 let normalized = key.1.to_lowercase();
164 self.sheet_named_ranges_lookup
165 .remove(&(sheet_id, normalized));
166 } else {
167 self.sheet_named_ranges_lookup.remove(&key);
168 }
169 self.mark_named_vertex_deleted(&named_range);
170 }
171 }
172
173 self.sheet_indexes.remove(&sheet_id);
174
175 if self.default_sheet_id == sheet_id
176 && let Some(&new_default) = self.sheet_indexes.keys().next()
177 {
178 self.default_sheet_id = new_default;
179 }
180
181 self.sheet_reg.remove(sheet_id)?;
182 self.end_batch();
183
184 Ok(())
185 }
186
187 fn tombstone_marker(sheet_name: &str) -> String {
188 format!("__FZ_MISSING_SHEET__{sheet_name}")
189 }
190
191 fn rewrite_formula_sheet_to_tombstone(&mut self, vertex_id: VertexId, sheet_name: &str) {
192 let Some(ast_id) = self.vertex_formulas.get(&vertex_id).copied() else {
193 return;
194 };
195 let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg) else {
196 return;
197 };
198
199 let marker = Self::tombstone_marker(sheet_name);
200 let mut updated_ast = ast.clone();
201 updated_ast.update_sheet_references(Some(sheet_name), &marker);
202
203 if updated_ast != ast {
204 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
205 self.vertex_formulas.insert(vertex_id, updated_ast_id);
206 }
207 }
208
209 fn heal_orphaned_formulas(&mut self, sheet_name: &str) {
210 let orphans = self.tombstone_registry.take_orphans(sheet_name);
211 let marker = Self::tombstone_marker(sheet_name);
212
213 for vertex_id in orphans {
214 let Some(ast_id) = self.vertex_formulas.get(&vertex_id).copied() else {
215 continue;
216 };
217 let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg) else {
218 continue;
219 };
220
221 if !self.is_ref_error(vertex_id) {
224 continue;
225 }
226
227 let mut updated_ast = ast.clone();
229 updated_ast.update_sheet_references(Some(&marker), sheet_name);
230
231 if updated_ast == ast {
232 continue;
234 }
235
236 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
237 self.vertex_formulas.insert(vertex_id, updated_ast_id);
238 self.rebuild_formula_dependencies(vertex_id, &updated_ast);
239 }
240 }
241 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
243 if new_name.is_empty() || new_name.len() > 255 {
244 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
245 }
246
247 let old_name = self.sheet_reg.name(sheet_id).to_string();
248
249 if old_name.is_empty() {
250 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
251 }
252
253 if let Some(existing_id) = self.sheet_reg.get_id(new_name) {
254 if existing_id != sheet_id {
255 return Err(ExcelError::new(ExcelErrorKind::Value)
256 .with_message(format!("Sheet '{new_name}' already exists")));
257 }
258 return Ok(());
259 }
260
261 self.sheet_reg.rename(sheet_id, new_name)?;
262
263 self.begin_batch();
264
265 self.heal_orphaned_formulas(new_name);
267
268 let formulas_to_update: Vec<VertexId> = self.vertex_formulas.keys().copied().collect();
270 for formula_id in formulas_to_update {
271 if let Some(ast_id) = self.vertex_formulas.get(&formula_id)
272 && let Some(ast) = self.data_store.retrieve_ast(*ast_id, &self.sheet_reg)
273 {
274 let mut updated_ast = ast.clone();
275 updated_ast.update_sheet_references(Some(&old_name), new_name);
276
277 if ast != updated_ast {
278 self.rebuild_formula_dependencies(formula_id, &updated_ast);
279 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
280 self.vertex_formulas.insert(formula_id, updated_ast_id);
281 }
282 }
283 }
284
285 self.end_batch();
286 Ok(())
287 }
288
289 pub fn duplicate_sheet(
291 &mut self,
292 source_sheet_id: SheetId,
293 new_name: &str,
294 ) -> Result<SheetId, ExcelError> {
295 if new_name.is_empty() || new_name.len() > 255 {
296 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
297 }
298
299 let source_name = self.sheet_reg.name(source_sheet_id).to_string();
300 if source_name.is_empty() {
301 return Err(
302 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
303 );
304 }
305
306 if self.sheet_reg.get_id(new_name).is_some() {
307 return Err(ExcelError::new(ExcelErrorKind::Value)
308 .with_message(format!("Sheet '{new_name}' already exists")));
309 }
310
311 let new_sheet_id = self.add_sheet(new_name)?;
312
313 self.begin_batch();
314
315 let source_vertices: Vec<(VertexId, AbsCoord)> = self
316 .vertices_in_sheet(source_sheet_id)
317 .map(|id| (id, self.store.coord(id)))
318 .collect();
319
320 let mut vertex_mapping = FxHashMap::default();
321
322 for (old_id, coord) in &source_vertices {
323 let row = coord.row();
324 let col = coord.col();
325 let kind = self.store.kind(*old_id);
326
327 let new_id = self.store.allocate(*coord, new_sheet_id, 0x01);
328 self.edges.add_vertex(*coord, new_id.0);
329 self.sheet_index_mut(new_sheet_id)
330 .add_vertex(*coord, new_id);
331
332 self.store.set_kind(new_id, kind);
333
334 if let Some(&value_ref) = self.vertex_values.get(old_id) {
335 self.vertex_values.insert(new_id, value_ref);
336 }
337
338 vertex_mapping.insert(*old_id, new_id);
339
340 let cell_ref = CellRef::new(new_sheet_id, Coord::new(row, col, true, true));
341 self.cell_to_vertex.insert(cell_ref, new_id);
342 }
343
344 let sheet_names: Vec<(String, NamedRange)> = self
345 .sheet_named_ranges
346 .iter()
347 .filter(|((sid, _), _)| *sid == source_sheet_id)
348 .map(|((_, name), range)| (name.clone(), range.clone()))
349 .collect();
350
351 for (name, mut named_range) in sheet_names {
352 named_range.scope = NameScope::Sheet(new_sheet_id);
353
354 match &mut named_range.definition {
355 NamedDefinition::Cell(cell_ref) if cell_ref.sheet_id == source_sheet_id => {
356 cell_ref.sheet_id = new_sheet_id;
357 }
358 NamedDefinition::Range(range_ref) => {
359 if range_ref.start.sheet_id == source_sheet_id {
360 range_ref.start.sheet_id = new_sheet_id;
361 range_ref.end.sheet_id = new_sheet_id;
362 }
363 }
364 _ => {}
365 }
366
367 named_range.dependents.clear();
368 let name_vertex = self.allocate_name_vertex(named_range.scope);
369 if matches!(named_range.definition, NamedDefinition::Range(_)) {
370 self.store.set_kind(name_vertex, VertexKind::NamedArray);
371 } else {
372 self.store.set_kind(name_vertex, VertexKind::NamedScalar);
373 }
374 named_range.vertex = name_vertex;
375
376 let referenced_names = self.rebuild_name_dependencies(
377 name_vertex,
378 &named_range.definition,
379 named_range.scope,
380 );
381 if !referenced_names.is_empty() {
382 self.attach_vertex_to_names(name_vertex, &referenced_names);
383 }
384
385 self.sheet_named_ranges
386 .insert((new_sheet_id, name.clone()), named_range);
387 self.sheet_named_ranges_lookup
388 .insert((new_sheet_id, self.name_lookup_key(&name)), name.clone());
389 self.name_vertex_lookup
390 .insert(name_vertex, (NameScope::Sheet(new_sheet_id), name));
391 }
392
393 for (old_id, _) in &source_vertices {
394 if let Some(&new_id) = vertex_mapping.get(old_id)
395 && let Some(&ast_id) = self.vertex_formulas.get(old_id)
396 && let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg)
397 {
398 let updated_ast = update_internal_sheet_references(
399 &ast,
400 &source_name,
401 new_name,
402 source_sheet_id,
403 new_sheet_id,
404 );
405
406 let new_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
407 self.vertex_formulas.insert(new_id, new_ast_id);
408
409 if let Ok((deps, range_deps, _, name_vertices)) =
410 self.extract_dependencies(&updated_ast, new_sheet_id)
411 {
412 let mapped_deps: Vec<VertexId> = deps
413 .iter()
414 .map(|&dep_id| vertex_mapping.get(&dep_id).copied().unwrap_or(dep_id))
415 .collect();
416
417 self.add_dependent_edges(new_id, &mapped_deps);
418 self.add_range_dependent_edges(new_id, &range_deps, new_sheet_id);
419
420 if !name_vertices.is_empty() {
421 self.attach_vertex_to_names(new_id, &name_vertices);
422 }
423 }
424 }
425 }
426
427 self.end_batch();
428
429 Ok(new_sheet_id)
430 }
431}