diff --git a/base/src/calc_result.rs b/base/src/calc_result.rs index 5e57872..a1db54a 100644 --- a/base/src/calc_result.rs +++ b/base/src/calc_result.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use crate::expressions::{token::Error, types::CellReferenceIndex}; +#[derive(Clone)] pub struct Range { pub left: CellReferenceIndex, pub right: CellReferenceIndex, diff --git a/base/src/expressions/parser/mod.rs b/base/src/expressions/parser/mod.rs index d8de57d..3233c2e 100644 --- a/base/src/expressions/parser/mod.rs +++ b/base/src/expressions/parser/mod.rs @@ -164,7 +164,9 @@ pub enum Node { args: Vec, }, ArrayKind(Vec), - VariableKind(String), + DefinedNameKind((String, Option)), + TableNameKind(String), + WrongVariableKind(String), CompareKind { kind: OpCompare, left: Box, @@ -187,12 +189,17 @@ pub enum Node { pub struct Parser { lexer: lexer::Lexer, worksheets: Vec, + defined_names: Vec<(String, Option)>, context: Option, tables: HashMap, } impl Parser { - pub fn new(worksheets: Vec, tables: HashMap) -> Parser { + pub fn new( + worksheets: Vec, + defined_names: Vec<(String, Option)>, + tables: HashMap, + ) -> Parser { let lexer = lexer::Lexer::new( "", lexer::LexerMode::A1, @@ -204,6 +211,7 @@ impl Parser { Parser { lexer, worksheets, + defined_names, context: None, tables, } @@ -212,8 +220,13 @@ impl Parser { self.lexer.set_lexer_mode(mode) } - pub fn set_worksheets(&mut self, worksheets: Vec) { + pub fn set_worksheets_and_names( + &mut self, + worksheets: Vec, + defined_names: Vec<(String, Option)>, + ) { self.worksheets = worksheets; + self.defined_names = defined_names; } pub fn parse(&mut self, formula: &str, context: &Option) -> Node { @@ -232,6 +245,24 @@ impl Parser { None } + // Returns: + // * None: If there is no defined name by that name + // * Some(Some(index)): If there is a defined name local to that sheet + // * Some(None): If there is a global defined name + fn get_defined_name(&self, name: &str, sheet: u32) -> Option> { + for (df_name, df_scope) in &self.defined_names { + if name.to_lowercase() == df_name.to_lowercase() && df_scope == &Some(sheet) { + return Some(*df_scope); + } + } + for (df_name, df_scope) in &self.defined_names { + if name.to_lowercase() == df_name.to_lowercase() && df_scope.is_none() { + return Some(None); + } + } + None + } + fn parse_expr(&mut self) -> Node { let mut t = self.parse_concat(); if let Node::ParseErrorKind { .. } = t { @@ -585,11 +616,42 @@ impl Parser { kind: function_kind, args, }; - } else { - return Node::InvalidFunctionKind { name, args }; + } + return Node::InvalidFunctionKind { name, args }; + } + let context = match &self.context { + Some(c) => c, + None => { + return Node::ParseErrorKind { + formula: self.lexer.get_formula(), + position: self.lexer.get_position() as usize, + message: "Expected context for the reference".to_string(), + } + } + }; + + let context_sheet_index = match self.get_sheet_index_by_name(&context.sheet) { + Some(i) => i, + None => { + return Node::ParseErrorKind { + formula: self.lexer.get_formula(), + position: 0, + message: "sheet not found".to_string(), + }; + } + }; + + // Could be a defined name or a table + if let Some(scope) = self.get_defined_name(&name, context_sheet_index) { + return Node::DefinedNameKind((name, scope)); + } + let name_lower = name.to_lowercase(); + for table_name in self.tables.keys() { + if table_name.to_lowercase() == name_lower { + return Node::TableNameKind(name); } } - Node::VariableKind(name) + Node::WrongVariableKind(name) } TokenType::Error(kind) => Node::ErrorKind(kind), TokenType::Illegal(error) => Node::ParseErrorKind { diff --git a/base/src/expressions/parser/move_formula.rs b/base/src/expressions/parser/move_formula.rs index b6336cc..5453cb0 100644 --- a/base/src/expressions/parser/move_formula.rs +++ b/base/src/expressions/parser/move_formula.rs @@ -375,7 +375,9 @@ fn to_string_moved(node: &Node, move_context: &MoveContext) -> String { } format!("{{{}}}", arguments) } - VariableKind(value) => value.to_string(), + DefinedNameKind((name, _)) => name.to_string(), + TableNameKind(name) => name.to_string(), + WrongVariableKind(name) => name.to_string(), CompareKind { kind, left, right } => format!( "{}{}{}", to_string_moved(left, move_context), diff --git a/base/src/expressions/parser/stringify.rs b/base/src/expressions/parser/stringify.rs index f9ed35c..00f2d5f 100644 --- a/base/src/expressions/parser/stringify.rs +++ b/base/src/expressions/parser/stringify.rs @@ -464,7 +464,9 @@ fn stringify( | ReferenceKind { .. } | RangeKind { .. } | WrongReferenceKind { .. } - | VariableKind(_) + | DefinedNameKind(_) + | TableNameKind(_) + | WrongVariableKind(_) | WrongRangeKind { .. } => { stringify(left, context, displace_data, use_original_name) } @@ -492,7 +494,9 @@ fn stringify( | ReferenceKind { .. } | RangeKind { .. } | WrongReferenceKind { .. } - | VariableKind(_) + | DefinedNameKind(_) + | TableNameKind(_) + | WrongVariableKind(_) | WrongRangeKind { .. } => { stringify(right, context, displace_data, use_original_name) } @@ -543,7 +547,9 @@ fn stringify( } format!("{{{}}}", arguments) } - VariableKind(value) => value.to_string(), + TableNameKind(value) => value.to_string(), + DefinedNameKind((name, _)) => name.to_string(), + WrongVariableKind(name) => name.to_string(), UnaryKind { kind, right } => match kind { OpUnary::Minus => { format!( @@ -660,7 +666,90 @@ pub(crate) fn rename_sheet_in_node(node: &mut Node, sheet_index: u32, new_name: Node::ErrorKind(_) => {} Node::ParseErrorKind { .. } => {} Node::ArrayKind(_) => {} - Node::VariableKind(_) => {} + Node::DefinedNameKind(_) => {} + Node::TableNameKind(_) => {} + Node::WrongVariableKind(_) => {} Node::EmptyArgKind => {} } } + +pub(crate) fn rename_defined_name_in_node( + node: &mut Node, + name: &str, + scope: Option, + new_name: &str, +) { + match node { + // Rename + Node::DefinedNameKind((n, s)) => { + if name.to_lowercase() == n.to_lowercase() && *s == scope { + *n = new_name.to_string(); + } + } + // Go next level + Node::OpRangeKind { left, right } => { + rename_defined_name_in_node(left, name, scope, new_name); + rename_defined_name_in_node(right, name, scope, new_name); + } + Node::OpConcatenateKind { left, right } => { + rename_defined_name_in_node(left, name, scope, new_name); + rename_defined_name_in_node(right, name, scope, new_name); + } + Node::OpSumKind { + kind: _, + left, + right, + } => { + rename_defined_name_in_node(left, name, scope, new_name); + rename_defined_name_in_node(right, name, scope, new_name); + } + Node::OpProductKind { + kind: _, + left, + right, + } => { + rename_defined_name_in_node(left, name, scope, new_name); + rename_defined_name_in_node(right, name, scope, new_name); + } + Node::OpPowerKind { left, right } => { + rename_defined_name_in_node(left, name, scope, new_name); + rename_defined_name_in_node(right, name, scope, new_name); + } + Node::FunctionKind { kind: _, args } => { + for arg in args { + rename_defined_name_in_node(arg, name, scope, new_name); + } + } + Node::InvalidFunctionKind { name: _, args } => { + for arg in args { + rename_defined_name_in_node(arg, name, scope, new_name); + } + } + Node::CompareKind { + kind: _, + left, + right, + } => { + rename_defined_name_in_node(left, name, scope, new_name); + rename_defined_name_in_node(right, name, scope, new_name); + } + Node::UnaryKind { kind: _, right } => { + rename_defined_name_in_node(right, name, scope, new_name); + } + + // Do nothing + Node::BooleanKind(_) => {} + Node::NumberKind(_) => {} + Node::StringKind(_) => {} + Node::ErrorKind(_) => {} + Node::ParseErrorKind { .. } => {} + Node::ArrayKind(_) => {} + Node::EmptyArgKind => {} + Node::ReferenceKind { .. } => {} + Node::RangeKind { .. } => {} + Node::WrongReferenceKind { .. } => {} + Node::WrongRangeKind { .. } => {} + Node::TableNameKind(_) => {} + Node::WrongVariableKind(_) => {} + } +} diff --git a/base/src/expressions/parser/tests/test_general.rs b/base/src/expressions/parser/tests/test_general.rs index 2882b34..7c7c842 100644 --- a/base/src/expressions/parser/tests/test_general.rs +++ b/base/src/expressions/parser/tests/test_general.rs @@ -17,7 +17,7 @@ struct Formula<'a> { #[test] fn test_parser_reference() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -32,7 +32,7 @@ fn test_parser_reference() { #[test] fn test_parser_absolute_column() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -47,7 +47,7 @@ fn test_parser_absolute_column() { #[test] fn test_parser_absolute_row_col() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -62,7 +62,7 @@ fn test_parser_absolute_row_col() { #[test] fn test_parser_absolute_row_col_1() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -77,7 +77,7 @@ fn test_parser_absolute_row_col_1() { #[test] fn test_parser_simple_formula() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -93,7 +93,7 @@ fn test_parser_simple_formula() { #[test] fn test_parser_boolean() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -109,7 +109,7 @@ fn test_parser_boolean() { #[test] fn test_parser_bad_formula() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -138,7 +138,7 @@ fn test_parser_bad_formula() { #[test] fn test_parser_bad_formula_1() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -167,7 +167,7 @@ fn test_parser_bad_formula_1() { #[test] fn test_parser_bad_formula_2() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -196,7 +196,7 @@ fn test_parser_bad_formula_2() { #[test] fn test_parser_bad_formula_3() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -225,7 +225,7 @@ fn test_parser_bad_formula_3() { #[test] fn test_parser_formulas() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let formulas = vec![ Formula { @@ -273,7 +273,7 @@ fn test_parser_formulas() { #[test] fn test_parser_r1c1_formulas() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); parser.set_lexer_mode(LexerMode::R1C1); let formulas = vec![ @@ -338,7 +338,7 @@ fn test_parser_r1c1_formulas() { #[test] fn test_parser_quotes() { let worksheets = vec!["Sheet1".to_string(), "Second Sheet".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -354,7 +354,7 @@ fn test_parser_quotes() { #[test] fn test_parser_escape_quotes() { let worksheets = vec!["Sheet1".to_string(), "Second '2' Sheet".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -370,7 +370,7 @@ fn test_parser_escape_quotes() { #[test] fn test_parser_parenthesis() { let worksheets = vec!["Sheet1".to_string(), "Second2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -386,7 +386,7 @@ fn test_parser_parenthesis() { #[test] fn test_parser_excel_xlfn() { let worksheets = vec!["Sheet1".to_string(), "Second2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -407,7 +407,7 @@ fn test_to_string_displaced() { column: 1, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let node = parser.parse("C3", &Some(context.clone())); let displace_data = DisplaceData::Column { @@ -427,7 +427,7 @@ fn test_to_string_displaced_full_ranges() { column: 1, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let node = parser.parse("SUM(3:3)", &Some(context.clone())); let displace_data = DisplaceData::Column { @@ -460,7 +460,7 @@ fn test_to_string_displaced_too_low() { column: 1, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let node = parser.parse("C3", &Some(context.clone())); let displace_data = DisplaceData::Column { @@ -480,7 +480,7 @@ fn test_to_string_displaced_too_high() { column: 1, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let node = parser.parse("C3", &Some(context.clone())); let displace_data = DisplaceData::Column { diff --git a/base/src/expressions/parser/tests/test_issue_155.rs b/base/src/expressions/parser/tests/test_issue_155.rs index 5c0191d..8d26346 100644 --- a/base/src/expressions/parser/tests/test_issue_155.rs +++ b/base/src/expressions/parser/tests/test_issue_155.rs @@ -9,7 +9,7 @@ use crate::expressions::types::CellReferenceRC; #[test] fn issue_155_parser() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -24,7 +24,7 @@ fn issue_155_parser() { #[test] fn issue_155_parser_case_2() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -39,7 +39,7 @@ fn issue_155_parser_case_2() { #[test] fn issue_155_parser_only_row() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { @@ -55,7 +55,7 @@ fn issue_155_parser_only_row() { #[test] fn issue_155_parser_only_column() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { diff --git a/base/src/expressions/parser/tests/test_move_formula.rs b/base/src/expressions/parser/tests/test_move_formula.rs index 63196f0..5aa43ad 100644 --- a/base/src/expressions/parser/tests/test_move_formula.rs +++ b/base/src/expressions/parser/tests/test_move_formula.rs @@ -15,7 +15,7 @@ fn test_move_formula() { column, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Area is C2:F6 let area = &Area { @@ -102,7 +102,7 @@ fn test_move_formula_context_offset() { column, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Area is C2:F6 let area = &Area { @@ -140,7 +140,7 @@ fn test_move_formula_area_limits() { column, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Area is C2:F6 let area = &Area { @@ -195,7 +195,7 @@ fn test_move_formula_ranges() { column, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let area = &Area { sheet: 0, @@ -318,7 +318,7 @@ fn test_move_formula_wrong_reference() { height: 5, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Wrong formulas will NOT be displaced let node = parser.parse("Sheet3!AB31", &Some(context.clone())); @@ -377,7 +377,7 @@ fn test_move_formula_misc() { column, }; let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Area is C2:F6 let area = &Area { @@ -445,7 +445,7 @@ fn test_move_formula_another_sheet() { }; // we add two sheets and we cut/paste from Sheet1 to Sheet2 let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Area is C2:F6 let area = &Area { diff --git a/base/src/expressions/parser/tests/test_ranges.rs b/base/src/expressions/parser/tests/test_ranges.rs index adc0f5e..3f05ab3 100644 --- a/base/src/expressions/parser/tests/test_ranges.rs +++ b/base/src/expressions/parser/tests/test_ranges.rs @@ -14,7 +14,7 @@ struct Formula<'a> { #[test] fn test_parser_formulas_with_full_ranges() { let worksheets = vec!["Sheet1".to_string(), "Second Sheet".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); let formulas = vec![ Formula { @@ -81,7 +81,7 @@ fn test_parser_formulas_with_full_ranges() { #[test] fn test_range_inverse_order() { let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { diff --git a/base/src/expressions/parser/tests/test_stringify.rs b/base/src/expressions/parser/tests/test_stringify.rs index 3d6d50d..dbdd53b 100644 --- a/base/src/expressions/parser/tests/test_stringify.rs +++ b/base/src/expressions/parser/tests/test_stringify.rs @@ -9,7 +9,7 @@ use crate::expressions::types::CellReferenceRC; #[test] fn exp_order() { let worksheets = vec!["Sheet1".to_string()]; - let mut parser = Parser::new(worksheets, HashMap::new()); + let mut parser = Parser::new(worksheets, vec![], HashMap::new()); // Reference cell is Sheet1!A1 let cell_reference = CellReferenceRC { diff --git a/base/src/expressions/parser/tests/test_tables.rs b/base/src/expressions/parser/tests/test_tables.rs index ebb177a..a31aaaf 100644 --- a/base/src/expressions/parser/tests/test_tables.rs +++ b/base/src/expressions/parser/tests/test_tables.rs @@ -63,7 +63,7 @@ fn simple_table() { let row_count = 3; let tables = create_test_table("tblIncome", &column_names, "A1", row_count); - let mut parser = Parser::new(worksheets, tables); + let mut parser = Parser::new(worksheets, vec![], tables); // Reference cell is 'Sheet One'!F2 let cell_reference = CellReferenceRC { sheet: "Sheet One".to_string(), diff --git a/base/src/expressions/parser/walk.rs b/base/src/expressions/parser/walk.rs index 8cef6af..746e379 100644 --- a/base/src/expressions/parser/walk.rs +++ b/base/src/expressions/parser/walk.rs @@ -263,7 +263,9 @@ pub(crate) fn forward_references( // TODO: Not implemented Node::ArrayKind(_) => {} // Do nothing. Note: we could do a blanket _ => {} - Node::VariableKind(_) => {} + Node::DefinedNameKind(_) => {} + Node::TableNameKind(_) => {} + Node::WrongVariableKind(_) => {} Node::ErrorKind(_) => {} Node::ParseErrorKind { .. } => {} Node::EmptyArgKind => {} diff --git a/base/src/functions/information.rs b/base/src/functions/information.rs index 6d2aaa7..44d847c 100644 --- a/base/src/functions/information.rs +++ b/base/src/functions/information.rs @@ -247,45 +247,67 @@ impl Model { return CalcResult::Number(cell.sheet as f64 + 1.0); } // The arg could be a defined name or a table - let arg = &args[0]; - if let Node::VariableKind(name) = arg { - // Let's see if it is a defined name - if let Some(defined_name) = self.parsed_defined_names.get(&(None, name.to_lowercase())) - { - match defined_name { - ParsedDefinedName::CellReference(reference) => { - return CalcResult::Number(reference.sheet as f64 + 1.0) + // let = &args[0]; + match &args[0] { + Node::DefinedNameKind((name, scope)) => { + // Let's see if it is a defined name + if let Some(defined_name) = self + .parsed_defined_names + .get(&(*scope, name.to_lowercase())) + { + match defined_name { + ParsedDefinedName::CellReference(reference) => { + return CalcResult::Number(reference.sheet as f64 + 1.0) + } + ParsedDefinedName::RangeReference(range) => { + return CalcResult::Number(range.left.sheet as f64 + 1.0) + } + ParsedDefinedName::InvalidDefinedNameFormula => { + return CalcResult::Error { + error: Error::ERROR, + origin: cell, + message: "Invalid name".to_string(), + }; + } } - ParsedDefinedName::RangeReference(range) => { - return CalcResult::Number(range.left.sheet as f64 + 1.0) - } - ParsedDefinedName::InvalidDefinedNameFormula => { - return CalcResult::Error { - error: Error::NA, - origin: cell, - message: "Invalid name".to_string(), - }; + } else { + // This should never happen + return CalcResult::Error { + error: Error::ERROR, + origin: cell, + message: "Invalid name".to_string(), + }; + } + } + Node::TableNameKind(name) => { + // Now let's see if it is a table + for (table_name, table) in &self.workbook.tables { + if table_name == name { + if let Some(sheet_index) = self.get_sheet_index_by_name(&table.sheet_name) { + return CalcResult::Number(sheet_index as f64 + 1.0); + } else { + break; + } } } } - // Now let's see if it is a table - for (table_name, table) in &self.workbook.tables { - if table_name == name { - if let Some(sheet_index) = self.get_sheet_index_by_name(&table.sheet_name) { - return CalcResult::Number(sheet_index as f64 + 1.0); - } else { - break; - } + Node::WrongVariableKind(name) => { + return CalcResult::Error { + error: Error::NAME, + origin: cell, + message: format!("Name not found: {name}"), + } + } + arg => { + // Now it should be the name of a sheet + let sheet_name = match self.get_string(arg, cell) { + Ok(s) => s, + Err(e) => return e, + }; + if let Some(sheet_index) = self.get_sheet_index_by_name(&sheet_name) { + return CalcResult::Number(sheet_index as f64 + 1.0); } } - } - // Now it should be the name of a sheet - let sheet_name = match self.get_string(arg, cell) { - Ok(s) => s, - Err(e) => return e, - }; - if let Some(sheet_index) = self.get_sheet_index_by_name(&sheet_name) { - return CalcResult::Number(sheet_index as f64 + 1.0); } CalcResult::Error { error: Error::NA, diff --git a/base/src/lib.rs b/base/src/lib.rs index b235ebf..840a969 100644 --- a/base/src/lib.rs +++ b/base/src/lib.rs @@ -25,6 +25,8 @@ #![doc = include_str!("../examples/formulas_and_errors.rs")] //! ``` +#![warn(clippy::print_stdout)] + pub mod calc_result; pub mod cell; pub mod expressions; diff --git a/base/src/model.rs b/base/src/model.rs index 5ed01af..3152a82 100644 --- a/base/src/model.rs +++ b/base/src/model.rs @@ -8,9 +8,10 @@ use crate::{ cell::CellValue, constants::{self, LAST_COLUMN, LAST_ROW}, expressions::{ + lexer::LexerMode, parser::{ move_formula::{move_formula, MoveContext}, - stringify::{to_rc_format, to_string}, + stringify::{rename_defined_name_in_node, to_rc_format, to_string}, Node, Parser, }, token::{get_error_by_name, Error, OpCompare, OpProduct, OpSum, OpUnary}, @@ -72,6 +73,7 @@ pub(crate) enum CellState { } /// A parsed formula for a defined name +#[derive(Clone)] pub(crate) enum ParsedDefinedName { /// CellReference (`=C4`) CellReference(CellReferenceIndex), @@ -79,9 +81,6 @@ pub(crate) enum ParsedDefinedName { RangeReference(Range), /// `=SomethingElse` InvalidDefinedNameFormula, - // TODO: Support constants in defined names - // TODO: Support formulas in defined names - // TODO: Support tables in defined names } /// A dynamical IronCalc model. @@ -417,38 +416,40 @@ impl Model { // TODO: NOT IMPLEMENTED CalcResult::new_error(Error::NIMPL, cell, "Arrays not implemented".to_string()) } - VariableKind(defined_name) => { - let parsed_defined_name = self - .parsed_defined_names - .get(&(Some(cell.sheet), defined_name.to_lowercase())) // try getting local defined name - .or_else(|| { - self.parsed_defined_names - .get(&(None, defined_name.to_lowercase())) - }); // fallback to global - - if let Some(parsed_defined_name) = parsed_defined_name { + DefinedNameKind((name, scope)) => { + if let Ok(Some(parsed_defined_name)) = self.get_parsed_defined_name(name, *scope) { match parsed_defined_name { ParsedDefinedName::CellReference(reference) => { - self.evaluate_cell(*reference) + self.evaluate_cell(reference) } ParsedDefinedName::RangeReference(range) => CalcResult::Range { left: range.left, right: range.right, }, ParsedDefinedName::InvalidDefinedNameFormula => CalcResult::new_error( - Error::NIMPL, + Error::NAME, cell, - format!("Defined name \"{}\" is not a reference.", defined_name), + format!("Defined name \"{}\" is not a reference.", name), ), } } else { CalcResult::new_error( Error::NAME, cell, - format!("Defined name \"{}\" not found.", defined_name), + format!("Defined name \"{}\" not found.", name), ) } } + TableNameKind(s) => CalcResult::new_error( + Error::NAME, + cell, + format!("table name \"{}\" not supported.", s), + ), + WrongVariableKind(s) => CalcResult::new_error( + Error::NAME, + cell, + format!("Variable name \"{}\" not found.", s), + ), CompareKind { kind, left, right } => { let l = self.evaluate_node_in_context(left, cell); if l.is_error() { @@ -864,6 +865,7 @@ impl Model { let worksheet_names = worksheets.iter().map(|s| s.get_name()).collect(); + let defined_names = workbook.get_defined_names_with_scope(); // add all tables // let mut tables = Vec::new(); // for worksheet in worksheets { @@ -873,7 +875,7 @@ impl Model { // } // tables.push(tables_in_sheet); // } - let parser = Parser::new(worksheet_names, workbook.tables.clone()); + let parser = Parser::new(worksheet_names, defined_names, workbook.tables.clone()); let cells = HashMap::new(); let locale = get_locale(&workbook.settings.locale) .map_err(|_| "Invalid locale".to_string())? @@ -1603,6 +1605,42 @@ impl Model { .set_cell_with_number(row, column, value, style) } + // Helper function that returns a defined name given the name and scope + fn get_parsed_defined_name( + &self, + name: &str, + scope: Option, + ) -> Result, String> { + let name_upper = name.to_uppercase(); + + for (key, df) in &self.parsed_defined_names { + if key.1.to_uppercase() == name_upper && key.0 == scope { + return Ok(Some(df.clone())); + } + } + Ok(None) + } + + // Returns the formula for a defined name + pub(crate) fn get_defined_name_formula( + &self, + name: &str, + scope: Option, + ) -> Result { + let name_upper = name.to_uppercase(); + let defined_names = &self.workbook.defined_names; + let sheet_id = match scope { + Some(index) => Some(self.workbook.worksheet(index)?.sheet_id), + None => None, + }; + for df in defined_names { + if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id { + return Ok(df.formula.clone()); + } + } + Err("Defined name not found".to_string()) + } + /// Gets the Excel Value (Bool, Number, String) of a cell /// /// See also: @@ -1993,6 +2031,122 @@ 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; + let sheet_id = match scope { + Some(index) => Some(self.workbook.worksheet(index)?.sheet_id), + None => None, + }; + // if the defined name already exist return error + for df in defined_names { + if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id { + return Err("Defined name already exists".to_string()); + } + } + self.workbook.defined_names.push(DefinedName { + name: name.to_string(), + formula: formula.to_string(), + sheet_id, + }); + 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 sheet_id = match scope { + Some(index) => Some(self.workbook.worksheet(index)?.sheet_id), + None => None, + }; + let mut index = None; + for (i, df) in defined_names.iter().enumerate() { + if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id { + 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()) + } + } + + /// 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 sheet_id = match scope { + Some(index) => Some(self.workbook.worksheet(index)?.sheet_id), + None => None, + }; + + let new_sheet_id = match new_scope { + Some(index) => Some(self.workbook.worksheet(index)?.sheet_id), + None => None, + }; + + let mut index = None; + for (i, df) in defined_names.iter().enumerate() { + if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id { + index = Some(i); + } + } + if let Some(i) = index { + if let Some(df) = self.workbook.defined_names.get_mut(i) { + if new_name != df.name { + // We need to rename the name in every formula: + + // Parse all formulas with the old name + // All internal formulas are R1C1 + self.parser.set_lexer_mode(LexerMode::R1C1); + let worksheets = &mut self.workbook.worksheets; + for worksheet in worksheets { + let cell_reference = &Some(CellReferenceRC { + sheet: worksheet.get_name(), + row: 1, + column: 1, + }); + let mut formulas = Vec::new(); + for formula in &worksheet.shared_formulas { + let mut t = self.parser.parse(formula, cell_reference); + rename_defined_name_in_node(&mut t, name, scope, new_name); + formulas.push(to_rc_format(&t)); + } + worksheet.shared_formulas = formulas; + } + // Se the mode back to A1 + self.parser.set_lexer_mode(LexerMode::A1); + } + df.name = new_name.to_string(); + df.sheet_id = new_sheet_id; + 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/new_empty.rs b/base/src/new_empty.rs index 285d088..92f0e2e 100644 --- a/base/src/new_empty.rs +++ b/base/src/new_empty.rs @@ -144,8 +144,9 @@ impl Model { /// Reparses all formulas and defined names pub(crate) fn reset_parsed_structures(&mut self) { + let defined_names = self.workbook.get_defined_names_with_scope(); self.parser - .set_worksheets(self.workbook.get_worksheet_names()); + .set_worksheets_and_names(self.workbook.get_worksheet_names(), defined_names); self.parsed_formulas = vec![]; self.parse_formulas(); self.parsed_defined_names = HashMap::new(); @@ -388,7 +389,7 @@ impl Model { let parsed_formulas = Vec::new(); let worksheets = &workbook.worksheets; let worksheet_names = worksheets.iter().map(|s| s.get_name()).collect(); - let parser = Parser::new(worksheet_names, HashMap::new()); + let parser = Parser::new(worksheet_names, vec![], HashMap::new()); let cells = HashMap::new(); // FIXME: Add support for display languages diff --git a/base/src/test/user_model/mod.rs b/base/src/test/user_model/mod.rs index 0dcc989..77d649b 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..5b0dadb --- /dev/null +++ b/base/src/test/user_model/test_defined_names.rs @@ -0,0 +1,167 @@ +#![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, "Sheet1!$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()) + ); + + // delete it + model.delete_defined_name("myName", None).unwrap(); + assert_eq!( + model.get_formatted_cell_value(0, 5, 7), + Ok("#NAME?".to_string()) + ); + + model.undo().unwrap(); + assert_eq!( + model.get_formatted_cell_value(0, 5, 7), + Ok("42".to_string()) + ); +} + +#[test] +fn scopes() { + let mut model = UserModel::new_empty("model", "en", "UTC").unwrap(); + model.set_user_input(0, 1, 1, "42").unwrap(); + + // Global + model + .new_defined_name("myName", None, "Sheet1!$A$1") + .unwrap(); + model.set_user_input(0, 5, 7, "=myName").unwrap(); + + // Local to Sheet2 + model.new_sheet().unwrap(); + model.set_user_input(1, 2, 1, "145").unwrap(); + model + .new_defined_name("myName", Some(1), "Sheet2!$A$2") + .unwrap(); + model.set_user_input(1, 8, 8, "=myName").unwrap(); + + // Sheet 3 + model.new_sheet().unwrap(); + model.set_user_input(2, 2, 2, "=myName").unwrap(); + + // Global + assert_eq!( + model.get_formatted_cell_value(0, 5, 7), + Ok("42".to_string()) + ); + + assert_eq!( + model.get_formatted_cell_value(1, 8, 8), + Ok("145".to_string()) + ); + + assert_eq!( + model.get_formatted_cell_value(2, 2, 2), + Ok("42".to_string()) + ); +} + +#[test] +fn delete_sheet() { + let mut model = UserModel::new_empty("model", "en", "UTC").unwrap(); + model.set_user_input(0, 1, 1, "Hello").unwrap(); + model + .set_user_input(0, 2, 1, r#"=CONCATENATE(MyName, " world!")"#) + .unwrap(); + model.new_sheet().unwrap(); + model + .new_defined_name("myName", Some(1), "Sheet1!$A$1") + .unwrap(); + model + .set_user_input(1, 2, 1, r#"=CONCATENATE(MyName, " my world!")"#) + .unwrap(); + + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok("#NAME?".to_string()) + ); + + assert_eq!( + model.get_formatted_cell_value(1, 2, 1), + Ok("Hello my world!".to_string()) + ); + + model.delete_sheet(0).unwrap(); + + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok("#NAME?".to_string()) + ); + + assert_eq!( + model.get_cell_content(0, 2, 1), + Ok(r#"=CONCATENATE(MyName," my world!")"#.to_string()) + ); +} + +#[test] +fn change_scope() { + let mut model = UserModel::new_empty("model", "en", "UTC").unwrap(); + model.set_user_input(0, 1, 1, "Hello").unwrap(); + model + .set_user_input(0, 2, 1, r#"=CONCATENATE(MyName, " world!")"#) + .unwrap(); + model.new_sheet().unwrap(); + model + .new_defined_name("myName", Some(1), "Sheet1!$A$1") + .unwrap(); + + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok("#NAME?".to_string()) + ); + + model + .update_defined_name("myName", Some(1), "myName", None, "Sheet1!$A$1") + .unwrap(); + + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok("Hello world!".to_string()) + ); +} + +#[test] +fn rename_defined_name() { + let mut model = UserModel::new_empty("model", "en", "UTC").unwrap(); + model.set_user_input(0, 1, 1, "Hello").unwrap(); + model + .set_user_input(0, 2, 1, r#"=CONCATENATE(MyName, " world!")"#) + .unwrap(); + model.new_sheet().unwrap(); + model + .new_defined_name("myName", None, "Sheet1!$A$1") + .unwrap(); + + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok("Hello world!".to_string()) + ); + + model + .update_defined_name("myName", None, "newName", None, "Sheet1!$A$1") + .unwrap(); + + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok("Hello world!".to_string()) + ); + + assert_eq!( + model.get_cell_content(0, 2, 1), + Ok(r#"=CONCATENATE(newName," world!")"#.to_string()) + ); +} diff --git a/base/src/test/user_model/test_general.rs b/base/src/test/user_model/test_general.rs index 86302dd..3882f2c 100644 --- a/base/src/test/user_model/test_general.rs +++ b/base/src/test/user_model/test_general.rs @@ -91,7 +91,6 @@ fn insert_remove_columns() { let mut model = UserModel::from_model(model); // column E let column_width = model.get_column_width(0, 5).unwrap(); - println!("{column_width}"); // Insert some data in row 5 (and change the style) in E1 assert!(model.set_user_input(0, 1, 5, "100$").is_ok()); diff --git a/base/src/units.rs b/base/src/units.rs index 3c82e14..f313991 100644 --- a/base/src/units.rs +++ b/base/src/units.rs @@ -293,7 +293,9 @@ impl Model { Node::EmptyArgKind => None, Node::InvalidFunctionKind { .. } => None, Node::ArrayKind(_) => None, - Node::VariableKind(_) => None, + Node::DefinedNameKind(_) => None, + Node::TableNameKind(_) => None, + Node::WrongVariableKind(_) => None, Node::CompareKind { .. } => None, Node::OpPowerKind { .. } => None, } diff --git a/base/src/user_model/common.rs b/base/src/user_model/common.rs index def3771..601eeab 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, SheetState, - Style, VerticalAlignment, + Alignment, BorderItem, CellType, Col, DefinedName, HorizontalAlignment, SheetProperties, + SheetState, Style, VerticalAlignment, }, utils::is_valid_hex_color, }; @@ -1734,6 +1734,68 @@ 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.model + .update_defined_name(name, scope, new_name, new_scope, new_formula)?; + self.evaluate_if_not_paused(); + Ok(()) + } + // **** Private methods ****** // fn push_diff_list(&mut self, diff_list: DiffList) { @@ -1904,6 +1966,36 @@ impl UserModel { } => { self.model.set_show_grid_lines(*sheet, *old_value)?; } + Diff::CreateDefinedName { + name, + scope, + value: _, + } => { + self.model.delete_defined_name(name, *scope)?; + } + Diff::DeleteDefinedName { + name, + scope, + old_value, + } => { + self.model.new_defined_name(name, *scope, old_value)?; + } + Diff::UpdateDefinedName { + name, + scope, + old_formula, + new_name, + new_scope, + new_formula: _, + } => { + self.model.update_defined_name( + new_name, + *new_scope, + name, + *scope, + old_formula, + )?; + } Diff::SetSheetState { index, old_value, @@ -2036,6 +2128,28 @@ impl UserModel { } => { self.model.set_show_grid_lines(*sheet, *new_value)?; } + Diff::CreateDefinedName { name, scope, value } => { + self.model.new_defined_name(name, *scope, value)? + } + Diff::DeleteDefinedName { + name, + scope, + old_value: _, + } => self.model.delete_defined_name(name, *scope)?, + Diff::UpdateDefinedName { + name, + scope, + old_formula: _, + new_name, + new_scope, + new_formula, + } => self.model.update_defined_name( + name, + *scope, + new_name, + *new_scope, + new_formula, + )?, Diff::SetSheetState { index, old_value: _, diff --git a/base/src/user_model/history.rs b/base/src/user_model/history.rs index e9b3490..b9f8c75 100644 --- a/base/src/user_model/history.rs +++ b/base/src/user_model/history.rs @@ -113,7 +113,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/base/src/workbook.rs b/base/src/workbook.rs index 791acb1..606ee81 100644 --- a/base/src/workbook.rs +++ b/base/src/workbook.rs @@ -27,4 +27,27 @@ impl Workbook { .get_mut(worksheet_index as usize) .ok_or_else(|| "Invalid sheet index".to_string()) } + + /// Returns the a list of defined names in the workbook with their scope + pub(crate) fn get_defined_names_with_scope(&self) -> Vec<(String, Option)> { + let sheet_id_index: Vec = self.worksheets.iter().map(|s| s.sheet_id).collect(); + + let defined_names = self + .defined_names + .iter() + .map(|dn| { + let index = dn + .sheet_id + .and_then(|sheet_id| { + // returns an Option + sheet_id_index.iter().position(|&x| x == sheet_id) + }) + // convert Option to Option + .map(|pos| pos as u32); + + (dn.name.clone(), index) + }) + .collect::>(); + defined_names + } } diff --git a/bindings/wasm/fix_types.py b/bindings/wasm/fix_types.py index d3f663b..fd466ee 100644 --- a/bindings/wasm/fix_types.py +++ b/bindings/wasm/fix_types.py @@ -187,6 +187,20 @@ paste_from_clipboard_types = r""" pasteFromClipboard(source_sheet: number, source_range: [number, number, number, number], clipboard: ClipboardData, is_cut: boolean): void; """ +defined_name_list = r""" +/** +* @returns {any} +*/ + getDefinedNameList(): any; +""" + +defined_name_list_types = r""" +/** +* @returns {DefinedName[]} +*/ + getDefinedNameList(): DefinedName[]; +""" + def fix_types(text): text = text.replace(get_tokens_str, get_tokens_str_types) text = text.replace(update_style_str, update_style_str_types) @@ -200,6 +214,7 @@ def fix_types(text): text = text.replace(paste_csv_string, paste_csv_string_types) text = text.replace(clipboard, clipboard_types) text = text.replace(paste_from_clipboard, paste_from_clipboard_types) + text = text.replace(defined_name_list, defined_name_list_types) with open("types.ts") as f: types_str = f.read() header_types = "{}\n\n{}".format(header, types_str) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 17f25ee..ec23c2d 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, @@ -552,4 +560,53 @@ 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: Vec = self + .model + .get_defined_name_list() + .iter() + .map(|s| DefinedName { + name: s.name.to_string(), + scope: s.sheet_id, + formula: s.formula.to_string(), + }) + .collect(); + // 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> { + self.model + .update_defined_name(name, scope, new_name, new_scope, new_formula) + .map_err(|e| to_js_error(e.to_string())) + } + + #[wasm_bindgen(js_name = "deleteDefinedName")] + pub fn delete_definedname(&mut self, name: &str, scope: Option) -> Result<(), JsError> { + self.model + .delete_defined_name(name, scope) + .map_err(|e| to_js_error(e.to_string())) + } } diff --git a/bindings/wasm/types.ts b/bindings/wasm/types.ts index c66d168..7af55b8 100644 --- a/bindings/wasm/types.ts +++ b/bindings/wasm/types.ts @@ -227,4 +227,10 @@ export interface Clipboard { csv: string; data: ClipboardData; range: [number, number, number, number]; +} + +export interface DefinedName { + name: string; + scope?: number; + formula: string; } \ No newline at end of file diff --git a/xlsx/src/import/worksheets.rs b/xlsx/src/import/worksheets.rs index 3d14b44..836f233 100644 --- a/xlsx/src/import/worksheets.rs +++ b/xlsx/src/import/worksheets.rs @@ -41,6 +41,30 @@ pub(crate) struct Relationship { pub(crate) rel_type: String, } +impl WorkbookXML { + fn get_defined_names_with_scope(&self) -> Vec<(String, Option)> { + let sheet_id_index: Vec = self.worksheets.iter().map(|s| s.sheet_id).collect(); + + let defined_names = self + .defined_names + .iter() + .map(|dn| { + let index = dn + .sheet_id + .and_then(|sheet_id| { + // returns an Option + sheet_id_index.iter().position(|&x| x == sheet_id) + }) + // convert Option to Option + .map(|pos| pos as u32); + + (dn.name.clone(), index) + }) + .collect::>(); + defined_names + } +} + fn get_column_from_ref(s: &str) -> String { let cs = s.chars(); let mut column = Vec::::new(); @@ -280,8 +304,9 @@ fn from_a1_to_rc( worksheets: &[String], context: String, tables: HashMap, + defined_names: Vec<(String, Option)>, ) -> Result { - let mut parser = Parser::new(worksheets.to_owned(), tables); + let mut parser = Parser::new(worksheets.to_owned(), defined_names, tables); let cell_reference = parse_reference(&context).map_err(|error| XlsxError::Xml(error.to_string()))?; let t = parser.parse(&formula, &Some(cell_reference)); @@ -681,6 +706,7 @@ pub(super) fn load_sheet( worksheets: &[String], tables: &HashMap, shared_strings: &mut Vec, + defined_names: Vec<(String, Option)>, ) -> Result<(Worksheet, bool), XlsxError> { let sheet_name = &settings.name; let sheet_id = settings.id; @@ -855,8 +881,13 @@ pub(super) fn load_sheet( // It's the mother cell. We do not use the ref attribute in IronCalc let formula = fs[0].text().unwrap_or("").to_string(); let context = format!("{}!{}", sheet_name, cell_ref); - let formula = - from_a1_to_rc(formula, worksheets, context, tables.clone())?; + let formula = from_a1_to_rc( + formula, + worksheets, + context, + tables.clone(), + defined_names.clone(), + )?; match index_map.get(&si) { Some(index) => { // The index for that formula already exists meaning we bumped into a daughter cell first @@ -910,7 +941,13 @@ pub(super) fn load_sheet( // Its a cell with a simple formula let formula = fs[0].text().unwrap_or("").to_string(); let context = format!("{}!{}", sheet_name, cell_ref); - let formula = from_a1_to_rc(formula, worksheets, context, tables.clone())?; + let formula = from_a1_to_rc( + formula, + worksheets, + context, + tables.clone(), + defined_names.clone(), + )?; match get_formula_index(&formula, &shared_formulas) { Some(index) => formula_index = index, @@ -1023,6 +1060,9 @@ pub(super) fn load_sheets( let mut sheets = Vec::new(); let mut selected_sheet = 0; let mut sheet_index = 0; + + let defined_names = workbook.get_defined_names_with_scope(); + for sheet in &workbook.worksheets { let sheet_name = &sheet.name; let rel_id = &sheet.id; @@ -1044,8 +1084,15 @@ pub(super) fn load_sheets( .ok_or_else(|| XlsxError::Xml("Corrupt XML structure".to_string()))? .to_vec(), }; - let (s, is_selected) = - load_sheet(archive, &path, settings, worksheets, tables, shared_strings)?; + let (s, is_selected) = load_sheet( + archive, + &path, + settings, + worksheets, + tables, + shared_strings, + defined_names.clone(), + )?; if is_selected { selected_sheet = sheet_index; }