UPDATE: API for defined names
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
ad2efad3ae
commit
e455ed14ea
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user