From 9699e45a707455a2a7cb87661b6b526da7a43335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Tue, 3 Dec 2024 21:30:46 +0100 Subject: [PATCH] BOB --- base/src/model.rs | 92 +++++++++++++++++++ base/src/test/user_model/mod.rs | 1 + .../src/test/user_model/test_defined_names.rs | 40 ++++++++ base/src/user_model/common.rs | 92 ++++++++++++++++++- base/src/user_model/history.rs | 21 ++++- bindings/wasm/src/lib.rs | 47 ++++++++++ 6 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 base/src/test/user_model/test_defined_names.rs diff --git a/base/src/model.rs b/base/src/model.rs index 422b068..afe8402 100644 --- a/base/src/model.rs +++ b/base/src/model.rs @@ -418,6 +418,7 @@ impl Model { CalcResult::new_error(Error::NIMPL, cell, "Arrays not implemented".to_string()) } VariableKind(defined_name) => { + println!("{:?}", defined_name); let parsed_defined_name = self .parsed_defined_names .get(&(Some(cell.sheet), defined_name.to_lowercase())) // try getting local defined name @@ -426,9 +427,11 @@ impl Model { .get(&(None, defined_name.to_lowercase())) }); // fallback to global + println!("Parsed: {:?}", defined_name); if let Some(parsed_defined_name) = parsed_defined_name { match parsed_defined_name { ParsedDefinedName::CellReference(reference) => { + println!("Here: {}", reference); self.evaluate_cell(*reference) } ParsedDefinedName::RangeReference(range) => CalcResult::Range { @@ -1986,6 +1989,95 @@ impl Model { .worksheet_mut(sheet)? .set_row_height(column, height) } + + /// Adds a new defined name + pub fn new_defined_name( + &mut self, + name: &str, + scope: Option, + formula: &str, + ) -> Result<(), String> { + let name_upper = name.to_uppercase(); + let defined_names = &self.workbook.defined_names; + // if the defined name already exist return error + for df in defined_names { + if df.name.to_uppercase() == name_upper && df.sheet_id == scope { + return Err("Defined name already exists".to_string()); + } + } + self.workbook.defined_names.push(DefinedName { + name: name.to_string(), + formula: formula.to_string(), + sheet_id: scope, + }); + self.reset_parsed_structures(); + Ok(()) + } + + /// Delete defined name of name and scope + pub fn delete_defined_name(&mut self, name: &str, scope: Option) -> Result<(), String> { + let name_upper = name.to_uppercase(); + let defined_names = &self.workbook.defined_names; + let mut index = None; + for (i, df) in defined_names.iter().enumerate() { + if df.name.to_uppercase() == name_upper && df.sheet_id == scope { + index = Some(i); + } + } + if let Some(i) = index { + self.workbook.defined_names.remove(i); + self.reset_parsed_structures(); + Ok(()) + } else { + Err("Defined name not found".to_string()) + } + } + + /// returns the formula for a defined name + pub fn get_defined_name_formula( + &self, + name: &str, + scope: Option, + ) -> Result { + let name_upper = name.to_uppercase(); + let defined_names = &self.workbook.defined_names; + for df in defined_names { + if df.name.to_uppercase() == name_upper && df.sheet_id == scope { + return Ok(df.formula.clone()); + } + } + Err("Defined name not found".to_string()) + } + + /// update defined name + pub fn update_defined_name( + &mut self, + name: &str, + scope: Option, + new_name: &str, + new_scope: Option, + new_formula: &str, + ) -> Result<(), String> { + let name_upper = name.to_uppercase(); + let defined_names = &self.workbook.defined_names; + let mut index = None; + for (i, df) in defined_names.iter().enumerate() { + if df.name.to_uppercase() == name_upper && df.sheet_id == scope { + index = Some(i); + } + } + if let Some(i) = index { + if let Some(df) = self.workbook.defined_names.get_mut(i) { + df.name = new_name.to_string(); + df.sheet_id = new_scope; + df.formula = new_formula.to_string(); + self.reset_parsed_structures(); + } + Ok(()) + } else { + Err("Defined name not found".to_string()) + } + } } #[cfg(test)] diff --git a/base/src/test/user_model/mod.rs b/base/src/test/user_model/mod.rs index c557104..86c8f18 100644 --- a/base/src/test/user_model/mod.rs +++ b/base/src/test/user_model/mod.rs @@ -3,6 +3,7 @@ mod test_autofill_columns; mod test_autofill_rows; mod test_border; mod test_clear_cells; +mod test_defined_names; mod test_diff_queue; mod test_evaluation; mod test_general; diff --git a/base/src/test/user_model/test_defined_names.rs b/base/src/test/user_model/test_defined_names.rs new file mode 100644 index 0000000..bb77fd3 --- /dev/null +++ b/base/src/test/user_model/test_defined_names.rs @@ -0,0 +1,40 @@ +#![allow(clippy::unwrap_used)] + +use crate::UserModel; + +#[test] +fn create_defined_name() { + let mut model = UserModel::new_empty("model", "en", "UTC").unwrap(); + model.set_user_input(0, 1, 1, "42").unwrap(); + model.new_defined_name("myName", None, "$A$1").unwrap(); + model.set_user_input(0, 5, 7, "=myName").unwrap(); + assert_eq!( + model.get_formatted_cell_value(0, 5, 7), + Ok("42".to_string()) + ); + + // rename it + model + .update_defined_name("myName", None, "myName", None, "$A$1*2") + .unwrap(); + assert_eq!( + model.get_formatted_cell_value(0, 5, 7), + Ok("42".to_string()) + ); + + // delete it + model.delete_defined_name("myName", None).unwrap(); + assert_eq!( + model.get_formatted_cell_value(0, 5, 7), + Ok("#REF!".to_string()) + ); +} + +#[test] +fn rename_defined_name() {} + +#[test] +fn delete_sheet() {} + +#[test] +fn change_scope() {} diff --git a/base/src/user_model/common.rs b/base/src/user_model/common.rs index ad48528..1c87b73 100644 --- a/base/src/user_model/common.rs +++ b/base/src/user_model/common.rs @@ -13,8 +13,8 @@ use crate::{ }, model::Model, types::{ - Alignment, BorderItem, CellType, Col, HorizontalAlignment, SheetProperties, Style, - VerticalAlignment, + Alignment, BorderItem, CellType, Col, DefinedName, HorizontalAlignment, SheetProperties, + Style, VerticalAlignment, }, utils::is_valid_hex_color, }; @@ -1689,6 +1689,66 @@ impl UserModel { Ok(()) } + /// Returns the list of defined names + pub fn get_defined_name_list(&self) -> Vec { + self.model.workbook.defined_names.clone() + } + + /// Delete an existing defined name + pub fn delete_defined_name(&mut self, name: &str, scope: Option) -> Result<(), String> { + let old_value = self.model.get_defined_name_formula(name, scope)?; + let diff_list = vec![Diff::DeleteDefinedName { + name: name.to_string(), + scope, + old_value, + }]; + self.push_diff_list(diff_list); + self.model.delete_defined_name(name, scope)?; + self.evaluate_if_not_paused(); + Ok(()) + } + + /// Create a new defined name + pub fn new_defined_name( + &mut self, + name: &str, + scope: Option, + formula: &str, + ) -> Result<(), String> { + self.model.new_defined_name(name, scope, formula)?; + let diff_list = vec![Diff::CreateDefinedName { + name: name.to_string(), + scope, + value: formula.to_string(), + }]; + self.push_diff_list(diff_list); + self.evaluate_if_not_paused(); + Ok(()) + } + + /// Updates a defined name + pub fn update_defined_name( + &mut self, + name: &str, + scope: Option, + new_name: &str, + new_scope: Option, + new_formula: &str, + ) -> Result<(), String> { + let old_formula = self.model.get_defined_name_formula(name, scope)?; + let diff_list = vec![Diff::UpdateDefinedName { + name: name.to_string(), + scope, + old_formula: old_formula.to_string(), + new_name: new_name.to_string(), + new_scope, + new_formula: new_formula.to_string(), + }]; + self.push_diff_list(diff_list); + self.evaluate_if_not_paused(); + Ok(()) + } + // **** Private methods ****** // fn push_diff_list(&mut self, diff_list: DiffList) { @@ -1859,6 +1919,20 @@ impl UserModel { } => { self.model.set_show_grid_lines(*sheet, *old_value)?; } + Diff::CreateDefinedName { name, scope, value } => todo!(), + Diff::DeleteDefinedName { + name, + scope, + old_value, + } => todo!(), + Diff::UpdateDefinedName { + name, + scope, + old_formula, + new_name, + new_scope, + new_formula, + } => todo!(), } } if needs_evaluation { @@ -1986,6 +2060,20 @@ impl UserModel { } => { self.model.set_show_grid_lines(*sheet, *new_value)?; } + Diff::CreateDefinedName { name, scope, value } => todo!(), + Diff::DeleteDefinedName { + name, + scope, + old_value, + } => todo!(), + Diff::UpdateDefinedName { + name, + scope, + old_formula, + new_name, + new_scope, + new_formula, + } => todo!(), } } diff --git a/base/src/user_model/history.rs b/base/src/user_model/history.rs index 4403e43..949059c 100644 --- a/base/src/user_model/history.rs +++ b/base/src/user_model/history.rs @@ -108,7 +108,26 @@ pub(crate) enum Diff { sheet: u32, old_value: bool, new_value: bool, - }, // FIXME: we are missing SetViewDiffs + }, + CreateDefinedName { + name: String, + scope: Option, + value: String, + }, + DeleteDefinedName { + name: String, + scope: Option, + old_value: String, + }, + UpdateDefinedName { + name: String, + scope: Option, + old_formula: String, + new_name: String, + new_scope: Option, + new_formula: String, + }, + // FIXME: we are missing SetViewDiffs } pub(crate) type DiffList = Vec; diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index d333573..4c845ee 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,3 +1,4 @@ +use serde::Serialize; use wasm_bindgen::{ prelude::{wasm_bindgen, JsError}, JsValue, @@ -29,6 +30,13 @@ pub fn column_name_from_number(column: i32) -> Result { } } +#[derive(Serialize)] +struct DefinedName { + name: String, + scope: Option, + formula: String, +} + #[wasm_bindgen] pub struct Model { model: BaseModel, @@ -541,4 +549,43 @@ impl Model { .paste_csv_string(&range, csv) .map_err(|e| to_js_error(e.to_string())) } + + #[wasm_bindgen(js_name = "getDefinedNameList")] + pub fn get_defined_name_list(&self) -> Result { + let data: DefinedNameList = + self.model + .get_defined_name_list() + .iter() + .map(|s| DefinedName { + name: s.name.to_string(), + scope: s.sheet_id, + formula: s.formula.to_string(), + }); + // Ok(data) + serde_wasm_bindgen::to_value(&data).map_err(|e| to_js_error(e.to_string())) + } + + #[wasm_bindgen(js_name = "newDefinedName")] + pub fn new_defined_name( + &mut self, + name: &str, + scope: Option, + formula: &str, + ) -> Result<(), JsError> { + self.model + .new_defined_name(name, scope, formula) + .map_err(|e| to_js_error(e.to_string())) + } + + #[wasm_bindgen(js_name = "updateDefinedName")] + pub fn update_defined_name( + &mut self, + name: &str, + scope: Option, + new_name: &str, + new_scope: Option, + new_formula: &str, + ) -> Result<(), JsError> { + todo!() + } }