Skip to main content

formualizer_eval/engine/graph/
sheets.rs

1use super::ast_utils::{update_internal_sheet_references, update_sheet_references_in_ast};
2use super::*;
3
4impl DependencyGraph {
5    /// Add a new sheet to the workbook.
6    ///
7    /// Creates a new sheet with the given name. If a sheet with this name
8    /// already exists, returns its ID without error (idempotent operation).
9    pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
10        if let Some(id) = self.sheet_reg.get_id(name) {
11            return Ok(id);
12        }
13
14        let sheet_id = self.sheet_reg.id_for(name);
15        self.sheet_indexes.entry(sheet_id).or_default();
16        Ok(sheet_id)
17    }
18
19    /// Remove a sheet from the workbook.
20    pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
21        if self.sheet_reg.name(sheet_id).is_empty() {
22            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
23        }
24
25        let sheet_count = self.sheet_reg.all_sheets().len();
26        if sheet_count <= 1 {
27            return Err(
28                ExcelError::new(ExcelErrorKind::Value).with_message("Cannot remove the last sheet")
29            );
30        }
31
32        self.begin_batch();
33
34        let vertices_to_delete: Vec<VertexId> = self.vertices_in_sheet(sheet_id).collect();
35
36        let mut formulas_to_update = Vec::new();
37        for &formula_id in self.vertex_formulas.keys() {
38            let deps = self.edges.out_edges(formula_id);
39            for dep_id in deps {
40                if self.store.sheet_id(dep_id) == sheet_id {
41                    formulas_to_update.push(formula_id);
42                    break;
43                }
44            }
45        }
46
47        for formula_id in formulas_to_update {
48            self.mark_as_ref_error(formula_id);
49        }
50
51        // Invalidate defined names that reference the removed sheet.
52        //
53        // In canonical (Arrow-truth) mode, cell/formula vertices do not cache values in the graph,
54        // so we cannot rely on graph-stored ref errors. We must explicitly dirty name vertices and
55        // their dependents so that subsequent evaluation updates Arrow overlays.
56        let ref_err = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
57        let mut name_vertices_to_update: Vec<VertexId> = Vec::new();
58        let mut dirty_vertices: Vec<VertexId> = Vec::new();
59
60        for nr in self.named_ranges.values_mut() {
61            match &nr.definition {
62                NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
63                    nr.definition = NamedDefinition::Literal(ref_err.clone());
64                    name_vertices_to_update.push(nr.vertex);
65                    dirty_vertices.push(nr.vertex);
66                    dirty_vertices.extend(nr.dependents.iter().copied());
67                }
68                NamedDefinition::Range(r)
69                    if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
70                {
71                    nr.definition = NamedDefinition::Literal(ref_err.clone());
72                    name_vertices_to_update.push(nr.vertex);
73                    dirty_vertices.push(nr.vertex);
74                    dirty_vertices.extend(nr.dependents.iter().copied());
75                }
76                _ => {}
77            }
78        }
79        for nr in self.sheet_named_ranges.values_mut() {
80            match &nr.definition {
81                NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
82                    nr.definition = NamedDefinition::Literal(ref_err.clone());
83                    name_vertices_to_update.push(nr.vertex);
84                    dirty_vertices.push(nr.vertex);
85                    dirty_vertices.extend(nr.dependents.iter().copied());
86                }
87                NamedDefinition::Range(r)
88                    if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
89                {
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                _ => {}
96            }
97        }
98
99        // Update cached values for name vertices after the map borrows end.
100        for vid in name_vertices_to_update {
101            self.update_vertex_value(vid, ref_err.clone());
102        }
103        for vid in dirty_vertices {
104            self.mark_vertex_dirty(vid);
105        }
106
107        for vertex_id in vertices_to_delete {
108            if let Some(cell_ref) = self.get_cell_ref_for_vertex(vertex_id) {
109                self.cell_to_vertex.remove(&cell_ref);
110            }
111
112            self.remove_all_edges(vertex_id);
113
114            let coord = self.store.coord(vertex_id);
115            if let Some(index) = self.sheet_indexes.get_mut(&sheet_id) {
116                index.remove_vertex(coord, vertex_id);
117            }
118
119            self.vertex_formulas.remove(&vertex_id);
120            self.vertex_values.remove(&vertex_id);
121
122            self.mark_deleted(vertex_id, true);
123        }
124
125        let sheet_names_to_remove: Vec<(SheetId, String)> = self
126            .sheet_named_ranges
127            .keys()
128            .filter(|(sid, _)| *sid == sheet_id)
129            .cloned()
130            .collect();
131
132        for key in sheet_names_to_remove {
133            if let Some(named_range) = self.sheet_named_ranges.remove(&key) {
134                if !self.config.case_sensitive_names {
135                    let normalized = key.1.to_ascii_lowercase();
136                    self.sheet_named_ranges_lookup
137                        .remove(&(sheet_id, normalized));
138                } else {
139                    self.sheet_named_ranges_lookup.remove(&key);
140                }
141                self.mark_named_vertex_deleted(&named_range);
142            }
143        }
144
145        self.sheet_indexes.remove(&sheet_id);
146
147        if self.default_sheet_id == sheet_id
148            && let Some(&new_default) = self.sheet_indexes.keys().next()
149        {
150            self.default_sheet_id = new_default;
151        }
152
153        self.sheet_reg.remove(sheet_id)?;
154        self.end_batch();
155
156        Ok(())
157    }
158
159    /// Rename an existing sheet.
160    pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
161        if new_name.is_empty() || new_name.len() > 255 {
162            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
163        }
164
165        let old_name = self.sheet_reg.name(sheet_id);
166        if old_name.is_empty() {
167            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
168        }
169
170        if let Some(existing_id) = self.sheet_reg.get_id(new_name) {
171            if existing_id != sheet_id {
172                return Err(ExcelError::new(ExcelErrorKind::Value)
173                    .with_message(format!("Sheet '{new_name}' already exists")));
174            }
175            return Ok(());
176        }
177
178        let old_name = old_name.to_string();
179        self.sheet_reg.rename(sheet_id, new_name)?;
180
181        let formulas_to_update: Vec<VertexId> = self.vertex_formulas.keys().copied().collect();
182        for formula_id in formulas_to_update {
183            let ast_id = match self.get_formula_id(formula_id) {
184                Some(ast_id) => ast_id,
185                None => continue,
186            };
187            let ast = match self.data_store.retrieve_ast(ast_id, &self.sheet_reg) {
188                Some(ast) => ast,
189                None => continue,
190            };
191
192            let updated_ast = update_sheet_references_in_ast(&ast, &old_name, new_name);
193            if ast != updated_ast {
194                let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
195                self.vertex_formulas.insert(formula_id, updated_ast_id);
196                self.mark_vertex_dirty(formula_id);
197            }
198        }
199
200        Ok(())
201    }
202
203    /// Duplicate an existing sheet.
204    pub fn duplicate_sheet(
205        &mut self,
206        source_sheet_id: SheetId,
207        new_name: &str,
208    ) -> Result<SheetId, ExcelError> {
209        if new_name.is_empty() || new_name.len() > 255 {
210            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
211        }
212
213        let source_name = self.sheet_reg.name(source_sheet_id).to_string();
214        if source_name.is_empty() {
215            return Err(
216                ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
217            );
218        }
219
220        if self.sheet_reg.get_id(new_name).is_some() {
221            return Err(ExcelError::new(ExcelErrorKind::Value)
222                .with_message(format!("Sheet '{new_name}' already exists")));
223        }
224
225        let new_sheet_id = self.add_sheet(new_name)?;
226
227        self.begin_batch();
228
229        let source_vertices: Vec<(VertexId, AbsCoord)> = self
230            .vertices_in_sheet(source_sheet_id)
231            .map(|id| (id, self.store.coord(id)))
232            .collect();
233
234        let mut vertex_mapping = FxHashMap::default();
235
236        for (old_id, coord) in &source_vertices {
237            let row = coord.row();
238            let col = coord.col();
239            let kind = self.store.kind(*old_id);
240
241            let new_id = self.store.allocate(*coord, new_sheet_id, 0x01);
242            self.edges.add_vertex(*coord, new_id.0);
243            self.sheet_index_mut(new_sheet_id)
244                .add_vertex(*coord, new_id);
245
246            self.store.set_kind(new_id, kind);
247
248            if let Some(&value_ref) = self.vertex_values.get(old_id) {
249                self.vertex_values.insert(new_id, value_ref);
250            }
251
252            vertex_mapping.insert(*old_id, new_id);
253
254            let cell_ref = CellRef::new(new_sheet_id, Coord::new(row, col, true, true));
255            self.cell_to_vertex.insert(cell_ref, new_id);
256        }
257
258        for (old_id, _) in &source_vertices {
259            if let Some(&new_id) = vertex_mapping.get(old_id)
260                && let Some(&ast_id) = self.vertex_formulas.get(old_id)
261                && let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg)
262            {
263                let updated_ast = update_internal_sheet_references(
264                    &ast,
265                    &source_name,
266                    new_name,
267                    source_sheet_id,
268                    new_sheet_id,
269                );
270
271                let new_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
272                self.vertex_formulas.insert(new_id, new_ast_id);
273
274                if let Ok((deps, range_deps, _, name_vertices)) =
275                    self.extract_dependencies(&updated_ast, new_sheet_id)
276                {
277                    let mapped_deps: Vec<VertexId> = deps
278                        .iter()
279                        .map(|&dep_id| vertex_mapping.get(&dep_id).copied().unwrap_or(dep_id))
280                        .collect();
281
282                    self.add_dependent_edges(new_id, &mapped_deps);
283                    self.add_range_dependent_edges(new_id, &range_deps, new_sheet_id);
284
285                    if !name_vertices.is_empty() {
286                        self.attach_vertex_to_names(new_id, &name_vertices);
287                    }
288                }
289            }
290        }
291
292        let sheet_names: Vec<(String, NamedRange)> = self
293            .sheet_named_ranges
294            .iter()
295            .filter(|((sid, _), _)| *sid == source_sheet_id)
296            .map(|((_, name), range)| (name.clone(), range.clone()))
297            .collect();
298
299        for (name, mut named_range) in sheet_names {
300            named_range.scope = NameScope::Sheet(new_sheet_id);
301
302            match &mut named_range.definition {
303                NamedDefinition::Cell(cell_ref) if cell_ref.sheet_id == source_sheet_id => {
304                    cell_ref.sheet_id = new_sheet_id;
305                }
306                NamedDefinition::Range(range_ref) => {
307                    if range_ref.start.sheet_id == source_sheet_id {
308                        range_ref.start.sheet_id = new_sheet_id;
309                        range_ref.end.sheet_id = new_sheet_id;
310                    }
311                }
312                _ => {}
313            }
314
315            named_range.dependents.clear();
316            let name_vertex = self.allocate_name_vertex(named_range.scope);
317            if matches!(named_range.definition, NamedDefinition::Range(_)) {
318                self.store.set_kind(name_vertex, VertexKind::NamedArray);
319            } else {
320                self.store.set_kind(name_vertex, VertexKind::NamedScalar);
321            }
322            named_range.vertex = name_vertex;
323
324            let referenced_names = self.rebuild_name_dependencies(
325                name_vertex,
326                &named_range.definition,
327                named_range.scope,
328            );
329            if !referenced_names.is_empty() {
330                self.attach_vertex_to_names(name_vertex, &referenced_names);
331            }
332
333            self.sheet_named_ranges
334                .insert((new_sheet_id, name.clone()), named_range);
335            self.name_vertex_lookup
336                .insert(name_vertex, (NameScope::Sheet(new_sheet_id), name));
337        }
338
339        self.end_batch();
340
341        Ok(new_sheet_id)
342    }
343}