UPDATE: API for defined names
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
ad2efad3ae
commit
e455ed14ea
@@ -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,
|
||||
|
||||
@@ -164,7 +164,9 @@ pub enum Node {
|
||||
args: Vec<Node>,
|
||||
},
|
||||
ArrayKind(Vec<Node>),
|
||||
VariableKind(String),
|
||||
DefinedNameKind((String, Option<u32>)),
|
||||
TableNameKind(String),
|
||||
WrongVariableKind(String),
|
||||
CompareKind {
|
||||
kind: OpCompare,
|
||||
left: Box<Node>,
|
||||
@@ -187,12 +189,17 @@ pub enum Node {
|
||||
pub struct Parser {
|
||||
lexer: lexer::Lexer,
|
||||
worksheets: Vec<String>,
|
||||
defined_names: Vec<(String, Option<u32>)>,
|
||||
context: Option<CellReferenceRC>,
|
||||
tables: HashMap<String, Table>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(worksheets: Vec<String>, tables: HashMap<String, Table>) -> Parser {
|
||||
pub fn new(
|
||||
worksheets: Vec<String>,
|
||||
defined_names: Vec<(String, Option<u32>)>,
|
||||
tables: HashMap<String, Table>,
|
||||
) -> 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<String>) {
|
||||
pub fn set_worksheets_and_names(
|
||||
&mut self,
|
||||
worksheets: Vec<String>,
|
||||
defined_names: Vec<(String, Option<u32>)>,
|
||||
) {
|
||||
self.worksheets = worksheets;
|
||||
self.defined_names = defined_names;
|
||||
}
|
||||
|
||||
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> 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<Option<u32>> {
|
||||
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 };
|
||||
}
|
||||
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(),
|
||||
}
|
||||
Node::VariableKind(name)
|
||||
}
|
||||
};
|
||||
|
||||
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::WrongVariableKind(name)
|
||||
}
|
||||
TokenType::Error(kind) => Node::ErrorKind(kind),
|
||||
TokenType::Illegal(error) => Node::ParseErrorKind {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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<u32>,
|
||||
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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 => {}
|
||||
|
||||
@@ -247,10 +247,13 @@ 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 = &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(&(None, name.to_lowercase()))
|
||||
if let Some(defined_name) = self
|
||||
.parsed_defined_names
|
||||
.get(&(*scope, name.to_lowercase()))
|
||||
{
|
||||
match defined_name {
|
||||
ParsedDefinedName::CellReference(reference) => {
|
||||
@@ -261,13 +264,22 @@ impl Model {
|
||||
}
|
||||
ParsedDefinedName::InvalidDefinedNameFormula => {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
error: Error::ERROR,
|
||||
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 {
|
||||
@@ -279,6 +291,14 @@ impl Model {
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
@@ -287,6 +307,8 @@ impl Model {
|
||||
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,
|
||||
origin: cell,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<u32>,
|
||||
) -> Result<Option<ParsedDefinedName>, 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<u32>,
|
||||
) -> Result<String, 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,
|
||||
};
|
||||
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<u32>,
|
||||
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<u32>) -> 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<u32>,
|
||||
new_name: &str,
|
||||
new_scope: Option<u32>,
|
||||
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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
167
base/src/test/user_model/test_defined_names.rs
Normal file
167
base/src/test/user_model/test_defined_names.rs
Normal file
@@ -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())
|
||||
);
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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<DefinedName> {
|
||||
self.model.workbook.defined_names.clone()
|
||||
}
|
||||
|
||||
/// Delete an existing defined name
|
||||
pub fn delete_defined_name(&mut self, name: &str, scope: Option<u32>) -> 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<u32>,
|
||||
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<u32>,
|
||||
new_name: &str,
|
||||
new_scope: Option<u32>,
|
||||
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: _,
|
||||
|
||||
@@ -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<u32>,
|
||||
value: String,
|
||||
},
|
||||
DeleteDefinedName {
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
old_value: String,
|
||||
},
|
||||
UpdateDefinedName {
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
old_formula: String,
|
||||
new_name: String,
|
||||
new_scope: Option<u32>,
|
||||
new_formula: String,
|
||||
},
|
||||
// FIXME: we are missing SetViewDiffs
|
||||
}
|
||||
|
||||
pub(crate) type DiffList = Vec<Diff>;
|
||||
|
||||
@@ -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<u32>)> {
|
||||
let sheet_id_index: Vec<u32> = 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<usize>
|
||||
sheet_id_index.iter().position(|&x| x == sheet_id)
|
||||
})
|
||||
// convert Option<usize> to Option<u32>
|
||||
.map(|pos| pos as u32);
|
||||
|
||||
(dn.name.clone(), index)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
defined_names
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<String, JsError> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DefinedName {
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
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<JsValue, JsError> {
|
||||
let data: Vec<DefinedName> = 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<u32>,
|
||||
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<u32>,
|
||||
new_name: &str,
|
||||
new_scope: Option<u32>,
|
||||
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<u32>) -> Result<(), JsError> {
|
||||
self.model
|
||||
.delete_defined_name(name, scope)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,3 +228,9 @@ export interface Clipboard {
|
||||
data: ClipboardData;
|
||||
range: [number, number, number, number];
|
||||
}
|
||||
|
||||
export interface DefinedName {
|
||||
name: string;
|
||||
scope?: number;
|
||||
formula: string;
|
||||
}
|
||||
@@ -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<u32>)> {
|
||||
let sheet_id_index: Vec<u32> = 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<usize>
|
||||
sheet_id_index.iter().position(|&x| x == sheet_id)
|
||||
})
|
||||
// convert Option<usize> to Option<u32>
|
||||
.map(|pos| pos as u32);
|
||||
|
||||
(dn.name.clone(), index)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
defined_names
|
||||
}
|
||||
}
|
||||
|
||||
fn get_column_from_ref(s: &str) -> String {
|
||||
let cs = s.chars();
|
||||
let mut column = Vec::<char>::new();
|
||||
@@ -280,8 +304,9 @@ fn from_a1_to_rc(
|
||||
worksheets: &[String],
|
||||
context: String,
|
||||
tables: HashMap<String, Table>,
|
||||
defined_names: Vec<(String, Option<u32>)>,
|
||||
) -> Result<String, XlsxError> {
|
||||
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<R: Read + std::io::Seek>(
|
||||
worksheets: &[String],
|
||||
tables: &HashMap<String, Table>,
|
||||
shared_strings: &mut Vec<String>,
|
||||
defined_names: Vec<(String, Option<u32>)>,
|
||||
) -> Result<(Worksheet, bool), XlsxError> {
|
||||
let sheet_name = &settings.name;
|
||||
let sheet_id = settings.id;
|
||||
@@ -855,8 +881,13 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
||||
// 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<R: Read + std::io::Seek>(
|
||||
// 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<R: Read + std::io::Seek>(
|
||||
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<R: Read + std::io::Seek>(
|
||||
.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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user