UPDATE: Update "generate locale" utility FIX: Minor fixes to UI and proper support for locales/timezones UPDATE: Adds "display language" setting to core
1139 lines
36 KiB
Rust
1139 lines
36 KiB
Rust
use super::{super::utils::quote_name, Node, Reference};
|
|
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
|
use crate::expressions::parser::move_formula::to_string_array_node;
|
|
use crate::expressions::parser::static_analysis::add_implicit_intersection;
|
|
use crate::expressions::token::{OpSum, OpUnary};
|
|
use crate::language::{get_language, Language};
|
|
use crate::locale::{get_locale, Locale};
|
|
use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
|
|
|
|
pub enum DisplaceData {
|
|
Column {
|
|
sheet: u32,
|
|
column: i32,
|
|
delta: i32,
|
|
},
|
|
Row {
|
|
sheet: u32,
|
|
row: i32,
|
|
delta: i32,
|
|
},
|
|
CellHorizontal {
|
|
sheet: u32,
|
|
row: i32,
|
|
column: i32,
|
|
delta: i32,
|
|
},
|
|
CellVertical {
|
|
sheet: u32,
|
|
row: i32,
|
|
column: i32,
|
|
delta: i32,
|
|
},
|
|
RowMove {
|
|
sheet: u32,
|
|
row: i32,
|
|
delta: i32,
|
|
},
|
|
ColumnMove {
|
|
sheet: u32,
|
|
column: i32,
|
|
delta: i32,
|
|
},
|
|
None,
|
|
}
|
|
|
|
/// This is the internal mode in IronCalc
|
|
pub fn to_rc_format(node: &Node) -> String {
|
|
#[allow(clippy::expect_used)]
|
|
let locale = get_locale("en").expect("");
|
|
#[allow(clippy::expect_used)]
|
|
let language = get_language("en").expect("");
|
|
stringify(node, None, &DisplaceData::None, false, locale, language)
|
|
}
|
|
|
|
/// This is the mode used to display the formula in the UI
|
|
pub fn to_localized_string(
|
|
node: &Node,
|
|
context: &CellReferenceRC,
|
|
locale: &Locale,
|
|
language: &Language,
|
|
) -> String {
|
|
stringify(
|
|
node,
|
|
Some(context),
|
|
&DisplaceData::None,
|
|
false,
|
|
locale,
|
|
language,
|
|
)
|
|
}
|
|
|
|
/// This is the mode used to export the formula to Excel
|
|
pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
|
|
#[allow(clippy::expect_used)]
|
|
let locale = get_locale("en").expect("");
|
|
#[allow(clippy::expect_used)]
|
|
let language = get_language("en").expect("");
|
|
stringify(
|
|
node,
|
|
Some(context),
|
|
&DisplaceData::None,
|
|
true,
|
|
locale,
|
|
language,
|
|
)
|
|
}
|
|
|
|
pub fn to_string_displaced(
|
|
node: &Node,
|
|
context: &CellReferenceRC,
|
|
displace_data: &DisplaceData,
|
|
) -> String {
|
|
#[allow(clippy::expect_used)]
|
|
let locale = get_locale("en").expect("");
|
|
#[allow(clippy::expect_used)]
|
|
let language = get_language("en").expect("");
|
|
stringify(node, Some(context), displace_data, false, locale, language)
|
|
}
|
|
|
|
/// Converts a local reference to a string applying some displacement if needed.
|
|
/// It uses A1 style if context is not None. If context is None it uses R1C1 style
|
|
/// If full_row is true then the row details will be omitted in the A1 case
|
|
/// If full_column is true then column details will be omitted.
|
|
pub(crate) fn stringify_reference(
|
|
context: Option<&CellReferenceRC>,
|
|
displace_data: &DisplaceData,
|
|
reference: &Reference,
|
|
full_row: bool,
|
|
full_column: bool,
|
|
) -> String {
|
|
let sheet_name = reference.sheet_name;
|
|
let sheet_index = reference.sheet_index;
|
|
let absolute_row = reference.absolute_row;
|
|
let absolute_column = reference.absolute_column;
|
|
let row = reference.row;
|
|
let column = reference.column;
|
|
match context {
|
|
Some(context) => {
|
|
let mut row = if absolute_row { row } else { row + context.row };
|
|
let mut column = if absolute_column {
|
|
column
|
|
} else {
|
|
column + context.column
|
|
};
|
|
match displace_data {
|
|
DisplaceData::Row {
|
|
sheet,
|
|
row: displace_row,
|
|
delta,
|
|
} => {
|
|
if sheet_index == *sheet && !full_row {
|
|
if *delta < 0 {
|
|
if &row >= displace_row {
|
|
if row < displace_row - *delta {
|
|
return "#REF!".to_string();
|
|
}
|
|
row += *delta;
|
|
}
|
|
} else if &row >= displace_row {
|
|
row += *delta;
|
|
}
|
|
}
|
|
}
|
|
DisplaceData::Column {
|
|
sheet,
|
|
column: displace_column,
|
|
delta,
|
|
} => {
|
|
if sheet_index == *sheet && !full_column {
|
|
if *delta < 0 {
|
|
if &column >= displace_column {
|
|
if column < displace_column - *delta {
|
|
return "#REF!".to_string();
|
|
}
|
|
column += *delta;
|
|
}
|
|
} else if &column >= displace_column {
|
|
column += *delta;
|
|
}
|
|
}
|
|
}
|
|
DisplaceData::CellHorizontal {
|
|
sheet,
|
|
row: displace_row,
|
|
column: displace_column,
|
|
delta,
|
|
} => {
|
|
if sheet_index == *sheet && displace_row == &row {
|
|
if *delta < 0 {
|
|
if &column >= displace_column {
|
|
if column < displace_column - *delta {
|
|
return "#REF!".to_string();
|
|
}
|
|
column += *delta;
|
|
}
|
|
} else if &column >= displace_column {
|
|
column += *delta;
|
|
}
|
|
}
|
|
}
|
|
DisplaceData::CellVertical {
|
|
sheet,
|
|
row: displace_row,
|
|
column: displace_column,
|
|
delta,
|
|
} => {
|
|
if sheet_index == *sheet && displace_column == &column {
|
|
if *delta < 0 {
|
|
if &row >= displace_row {
|
|
if row < displace_row - *delta {
|
|
return "#REF!".to_string();
|
|
}
|
|
row += *delta;
|
|
}
|
|
} else if &row >= displace_row {
|
|
row += *delta;
|
|
}
|
|
}
|
|
}
|
|
DisplaceData::RowMove {
|
|
sheet,
|
|
row: move_row,
|
|
delta,
|
|
} => {
|
|
if sheet_index == *sheet {
|
|
if row == *move_row {
|
|
row += *delta;
|
|
} else if *delta > 0 {
|
|
// Moving the row downwards
|
|
if row > *move_row && row <= *move_row + *delta {
|
|
// Intermediate rows move up by one position
|
|
row -= 1;
|
|
}
|
|
} else if *delta < 0 {
|
|
// Moving the row upwards
|
|
if row < *move_row && row >= *move_row + *delta {
|
|
// Intermediate rows move down by one position
|
|
row += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DisplaceData::ColumnMove {
|
|
sheet,
|
|
column: move_column,
|
|
delta,
|
|
} => {
|
|
if sheet_index == *sheet {
|
|
if column == *move_column {
|
|
column += *delta;
|
|
} else if *delta > 0 {
|
|
// Moving the column to the right
|
|
if column > *move_column && column <= *move_column + *delta {
|
|
// Intermediate columns move left by one position
|
|
column -= 1;
|
|
}
|
|
} else if *delta < 0 {
|
|
// Moving the column to the left
|
|
if column < *move_column && column >= *move_column + *delta {
|
|
// Intermediate columns move right by one position
|
|
column += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DisplaceData::None => {}
|
|
}
|
|
if row < 1 {
|
|
return "#REF!".to_string();
|
|
}
|
|
let mut row_abs = if absolute_row {
|
|
format!("${row}")
|
|
} else {
|
|
format!("{row}")
|
|
};
|
|
let column = match crate::expressions::utils::number_to_column(column) {
|
|
Some(s) => s,
|
|
None => return "#REF!".to_string(),
|
|
};
|
|
let mut col_abs = if absolute_column {
|
|
format!("${column}")
|
|
} else {
|
|
column
|
|
};
|
|
if full_row {
|
|
row_abs = "".to_string()
|
|
}
|
|
if full_column {
|
|
col_abs = "".to_string()
|
|
}
|
|
match &sheet_name {
|
|
Some(name) => {
|
|
format!("{}!{}{}", quote_name(name), col_abs, row_abs)
|
|
}
|
|
None => {
|
|
format!("{col_abs}{row_abs}")
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
let row_abs = if absolute_row {
|
|
format!("R{row}")
|
|
} else {
|
|
format!("R[{row}]")
|
|
};
|
|
let col_abs = if absolute_column {
|
|
format!("C{column}")
|
|
} else {
|
|
format!("C[{column}]")
|
|
};
|
|
match &sheet_name {
|
|
Some(name) => {
|
|
format!("{}!{}{}", quote_name(name), row_abs, col_abs)
|
|
}
|
|
None => {
|
|
format!("{row_abs}{col_abs}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_function(
|
|
name: &str,
|
|
args: &Vec<Node>,
|
|
context: Option<&CellReferenceRC>,
|
|
displace_data: &DisplaceData,
|
|
export_to_excel: bool,
|
|
locale: &Locale,
|
|
language: &Language,
|
|
) -> String {
|
|
let mut first = true;
|
|
let mut arguments = "".to_string();
|
|
let arg_separator = if locale.numbers.symbols.decimal == "." {
|
|
','
|
|
} else {
|
|
';'
|
|
};
|
|
for el in args {
|
|
if !first {
|
|
arguments = format!(
|
|
"{}{}{}",
|
|
arguments,
|
|
arg_separator,
|
|
stringify(
|
|
el,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
);
|
|
} else {
|
|
first = false;
|
|
arguments = stringify(
|
|
el,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
);
|
|
}
|
|
}
|
|
format!("{name}({arguments})")
|
|
}
|
|
|
|
// There is just one representation in the AST (Abstract Syntax Tree) of a formula.
|
|
// But three different ways to convert it to a string.
|
|
//
|
|
// To stringify a formula we need a "context", that is in which cell are we doing the "stringifying"
|
|
//
|
|
// But there are three ways to stringify a formula:
|
|
//
|
|
// * To show it to the IronCalc user
|
|
// * To store internally
|
|
// * To export to Excel
|
|
//
|
|
// There are, of course correspondingly three "modes" when parsing a formula.
|
|
//
|
|
// The internal representation is the more different as references are stored in the RC representation.
|
|
// The the AST of the formula is kept close to this representation we don't need a context
|
|
//
|
|
// In the export to Excel representation certain things are different:
|
|
// * We add a _xlfn. in front of some (more modern) functions
|
|
// * We remove the Implicit Intersection operator when it is automatic and add _xlfn.SINGLE when it is not
|
|
//
|
|
// Examples:
|
|
// * =A1+B2
|
|
// * =RC+R1C1
|
|
// * =A1+B1
|
|
|
|
fn stringify(
|
|
node: &Node,
|
|
context: Option<&CellReferenceRC>,
|
|
displace_data: &DisplaceData,
|
|
export_to_excel: bool,
|
|
locale: &Locale,
|
|
language: &Language,
|
|
) -> String {
|
|
use self::Node::*;
|
|
match node {
|
|
BooleanKind(value) => {
|
|
if *value {
|
|
language.booleans.r#true.to_string()
|
|
} else {
|
|
language.booleans.r#false.to_string()
|
|
}
|
|
}
|
|
NumberKind(number) => {
|
|
let s = to_excel_precision_str(*number);
|
|
if locale.numbers.symbols.decimal == "." {
|
|
s
|
|
} else {
|
|
s.replace(".", &locale.numbers.symbols.decimal)
|
|
}
|
|
}
|
|
StringKind(value) => format!("\"{value}\""),
|
|
WrongReferenceKind {
|
|
sheet_name,
|
|
column,
|
|
row,
|
|
absolute_row,
|
|
absolute_column,
|
|
} => stringify_reference(
|
|
context,
|
|
&DisplaceData::None,
|
|
&Reference {
|
|
sheet_name,
|
|
sheet_index: 0,
|
|
row: *row,
|
|
column: *column,
|
|
absolute_row: *absolute_row,
|
|
absolute_column: *absolute_column,
|
|
},
|
|
false,
|
|
false,
|
|
),
|
|
ReferenceKind {
|
|
sheet_name,
|
|
sheet_index,
|
|
column,
|
|
row,
|
|
absolute_row,
|
|
absolute_column,
|
|
} => stringify_reference(
|
|
context,
|
|
displace_data,
|
|
&Reference {
|
|
sheet_name,
|
|
sheet_index: *sheet_index,
|
|
row: *row,
|
|
column: *column,
|
|
absolute_row: *absolute_row,
|
|
absolute_column: *absolute_column,
|
|
},
|
|
false,
|
|
false,
|
|
),
|
|
RangeKind {
|
|
sheet_name,
|
|
sheet_index,
|
|
absolute_row1,
|
|
absolute_column1,
|
|
row1,
|
|
column1,
|
|
absolute_row2,
|
|
absolute_column2,
|
|
row2,
|
|
column2,
|
|
} => {
|
|
// Note that open ranges SUM(A:A) or SUM(1:1) will be treated as normal ranges in the R1C1 (internal) representation
|
|
// A:A will be R1C[0]:R1048576C[0]
|
|
// So when we are forming the A1 range we need to strip the irrelevant information
|
|
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
|
|
let full_column = *absolute_column1
|
|
&& *absolute_column2
|
|
&& (*column1 == 1)
|
|
&& (*column2 == LAST_COLUMN);
|
|
let s1 = stringify_reference(
|
|
context,
|
|
displace_data,
|
|
&Reference {
|
|
sheet_name,
|
|
sheet_index: *sheet_index,
|
|
row: *row1,
|
|
column: *column1,
|
|
absolute_row: *absolute_row1,
|
|
absolute_column: *absolute_column1,
|
|
},
|
|
full_row,
|
|
full_column,
|
|
);
|
|
let s2 = stringify_reference(
|
|
context,
|
|
displace_data,
|
|
&Reference {
|
|
sheet_name: &None,
|
|
sheet_index: *sheet_index,
|
|
row: *row2,
|
|
column: *column2,
|
|
absolute_row: *absolute_row2,
|
|
absolute_column: *absolute_column2,
|
|
},
|
|
full_row,
|
|
full_column,
|
|
);
|
|
format!("{s1}:{s2}")
|
|
}
|
|
WrongRangeKind {
|
|
sheet_name,
|
|
absolute_row1,
|
|
absolute_column1,
|
|
row1,
|
|
column1,
|
|
absolute_row2,
|
|
absolute_column2,
|
|
row2,
|
|
column2,
|
|
} => {
|
|
// Note that open ranges SUM(A:A) or SUM(1:1) will be treated as normal ranges in the R1C1 (internal) representation
|
|
// A:A will be R1C[0]:R1048576C[0]
|
|
// So when we are forming the A1 range we need to strip the irrelevant information
|
|
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
|
|
let full_column = *absolute_column1
|
|
&& *absolute_column2
|
|
&& (*column1 == 1)
|
|
&& (*column2 == LAST_COLUMN);
|
|
let s1 = stringify_reference(
|
|
context,
|
|
&DisplaceData::None,
|
|
&Reference {
|
|
sheet_name,
|
|
sheet_index: 0, // HACK
|
|
row: *row1,
|
|
column: *column1,
|
|
absolute_row: *absolute_row1,
|
|
absolute_column: *absolute_column1,
|
|
},
|
|
full_row,
|
|
full_column,
|
|
);
|
|
let s2 = stringify_reference(
|
|
context,
|
|
&DisplaceData::None,
|
|
&Reference {
|
|
sheet_name: &None,
|
|
sheet_index: 0, // HACK
|
|
row: *row2,
|
|
column: *column2,
|
|
absolute_row: *absolute_row2,
|
|
absolute_column: *absolute_column2,
|
|
},
|
|
full_row,
|
|
full_column,
|
|
);
|
|
format!("{s1}:{s2}")
|
|
}
|
|
OpRangeKind { left, right } => format!(
|
|
"{}:{}",
|
|
stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
),
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
OpConcatenateKind { left, right } => format!(
|
|
"{}&{}",
|
|
stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
),
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
CompareKind { kind, left, right } => format!(
|
|
"{}{}{}",
|
|
stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
),
|
|
kind,
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
OpSumKind { kind, left, right } => {
|
|
let left_str = stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
);
|
|
// if kind is minus then we need parentheses in the right side if they are OpSumKind or CompareKind
|
|
let right_str = if (matches!(kind, OpSum::Minus) && matches!(**right, OpSumKind { .. }))
|
|
| matches!(**right, CompareKind { .. })
|
|
{
|
|
format!(
|
|
"({})",
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
)
|
|
} else {
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
)
|
|
};
|
|
|
|
format!("{left_str}{kind}{right_str}")
|
|
}
|
|
OpProductKind { kind, left, right } => {
|
|
let x = match **left {
|
|
OpSumKind { .. } | CompareKind { .. } => format!(
|
|
"({})",
|
|
stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
_ => stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
),
|
|
};
|
|
let y = match **right {
|
|
OpSumKind { .. } | CompareKind { .. } | OpProductKind { .. } => format!(
|
|
"({})",
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
_ => stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
),
|
|
};
|
|
format!("{x}{kind}{y}")
|
|
}
|
|
OpPowerKind { left, right } => {
|
|
let x = match **left {
|
|
BooleanKind(_)
|
|
| NumberKind(_)
|
|
| UnaryKind { .. }
|
|
| StringKind(_)
|
|
| ReferenceKind { .. }
|
|
| RangeKind { .. }
|
|
| WrongReferenceKind { .. }
|
|
| DefinedNameKind(_)
|
|
| TableNameKind(_)
|
|
| WrongVariableKind(_)
|
|
| WrongRangeKind { .. } => stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
),
|
|
OpRangeKind { .. }
|
|
| OpConcatenateKind { .. }
|
|
| OpProductKind { .. }
|
|
| OpPowerKind { .. }
|
|
| FunctionKind { .. }
|
|
| InvalidFunctionKind { .. }
|
|
| ArrayKind(_)
|
|
| ErrorKind(_)
|
|
| ParseErrorKind { .. }
|
|
| OpSumKind { .. }
|
|
| CompareKind { .. }
|
|
| ImplicitIntersection { .. }
|
|
| EmptyArgKind => format!(
|
|
"({})",
|
|
stringify(
|
|
left,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
};
|
|
let y = match **right {
|
|
BooleanKind(_)
|
|
| NumberKind(_)
|
|
| StringKind(_)
|
|
| ReferenceKind { .. }
|
|
| RangeKind { .. }
|
|
| WrongReferenceKind { .. }
|
|
| DefinedNameKind(_)
|
|
| TableNameKind(_)
|
|
| WrongVariableKind(_)
|
|
| WrongRangeKind { .. } => stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
),
|
|
OpRangeKind { .. }
|
|
| OpConcatenateKind { .. }
|
|
| OpProductKind { .. }
|
|
| OpPowerKind { .. }
|
|
| FunctionKind { .. }
|
|
| InvalidFunctionKind { .. }
|
|
| ArrayKind(_)
|
|
| UnaryKind { .. }
|
|
| ErrorKind(_)
|
|
| ParseErrorKind { .. }
|
|
| OpSumKind { .. }
|
|
| CompareKind { .. }
|
|
| ImplicitIntersection { .. }
|
|
| EmptyArgKind => format!(
|
|
"({})",
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
),
|
|
};
|
|
format!("{x}^{y}")
|
|
}
|
|
InvalidFunctionKind { name, args } => format_function(
|
|
&name.to_ascii_lowercase(),
|
|
args,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
),
|
|
FunctionKind { kind, args } => {
|
|
let name = if export_to_excel {
|
|
kind.to_xlsx_string()
|
|
} else {
|
|
kind.to_localized_name(language)
|
|
};
|
|
format_function(
|
|
&name,
|
|
args,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
)
|
|
}
|
|
ArrayKind(args) => {
|
|
let mut first_row = true;
|
|
let mut matrix_string = String::new();
|
|
let row_separator = if locale.numbers.symbols.decimal == "." {
|
|
';'
|
|
} else {
|
|
'/'
|
|
};
|
|
let col_separator = if row_separator == ';' { ',' } else { ';' };
|
|
|
|
for row in args {
|
|
if !first_row {
|
|
matrix_string.push(row_separator);
|
|
} else {
|
|
first_row = false;
|
|
}
|
|
let mut first_column = true;
|
|
let mut row_string = String::new();
|
|
for el in row {
|
|
if !first_column {
|
|
row_string.push(col_separator);
|
|
} else {
|
|
first_column = false;
|
|
}
|
|
row_string.push_str(&to_string_array_node(el, locale, language));
|
|
}
|
|
matrix_string.push_str(&row_string);
|
|
}
|
|
format!("{{{matrix_string}}}")
|
|
}
|
|
TableNameKind(value) => value.to_string(),
|
|
DefinedNameKind((name, ..)) => name.to_string(),
|
|
WrongVariableKind(name) => name.to_string(),
|
|
UnaryKind { kind, right } => match kind {
|
|
OpUnary::Minus => {
|
|
let needs_parentheses = match **right {
|
|
BooleanKind(_)
|
|
| NumberKind(_)
|
|
| StringKind(_)
|
|
| ReferenceKind { .. }
|
|
| RangeKind { .. }
|
|
| WrongReferenceKind { .. }
|
|
| WrongRangeKind { .. }
|
|
| OpRangeKind { .. }
|
|
| OpConcatenateKind { .. }
|
|
| OpProductKind { .. }
|
|
| FunctionKind { .. }
|
|
| InvalidFunctionKind { .. }
|
|
| ArrayKind(_)
|
|
| DefinedNameKind(_)
|
|
| TableNameKind(_)
|
|
| WrongVariableKind(_)
|
|
| ImplicitIntersection { .. }
|
|
| CompareKind { .. }
|
|
| ErrorKind(_)
|
|
| ParseErrorKind { .. }
|
|
| EmptyArgKind => false,
|
|
|
|
OpPowerKind { .. } | OpSumKind { .. } | UnaryKind { .. } => true,
|
|
};
|
|
if needs_parentheses {
|
|
format!(
|
|
"-({})",
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
)
|
|
} else {
|
|
format!(
|
|
"-{}",
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
)
|
|
}
|
|
}
|
|
OpUnary::Percentage => {
|
|
format!(
|
|
"{}%",
|
|
stringify(
|
|
right,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
)
|
|
}
|
|
},
|
|
ErrorKind(kind) => format!("{kind}"),
|
|
ParseErrorKind {
|
|
formula,
|
|
position: _,
|
|
message: _,
|
|
} => formula.to_string(),
|
|
EmptyArgKind => "".to_string(),
|
|
ImplicitIntersection {
|
|
automatic: _,
|
|
child,
|
|
} => {
|
|
if export_to_excel {
|
|
// We need to check wether the II can be automatic or not
|
|
let mut new_node = child.as_ref().clone();
|
|
|
|
add_implicit_intersection(&mut new_node, true);
|
|
if matches!(&new_node, Node::ImplicitIntersection { .. }) {
|
|
return stringify(
|
|
child,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language,
|
|
);
|
|
}
|
|
|
|
return format!(
|
|
"_xlfn.SINGLE({})",
|
|
stringify(
|
|
child,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
);
|
|
}
|
|
format!(
|
|
"@{}",
|
|
stringify(
|
|
child,
|
|
context,
|
|
displace_data,
|
|
export_to_excel,
|
|
locale,
|
|
language
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn rename_sheet_in_node(node: &mut Node, sheet_index: u32, new_name: &str) {
|
|
match node {
|
|
// Rename
|
|
Node::ReferenceKind {
|
|
sheet_name,
|
|
sheet_index: index,
|
|
..
|
|
} => {
|
|
if *index == sheet_index && sheet_name.is_some() {
|
|
*sheet_name = Some(new_name.to_owned());
|
|
}
|
|
}
|
|
Node::RangeKind {
|
|
sheet_name,
|
|
sheet_index: index,
|
|
..
|
|
} => {
|
|
if *index == sheet_index && sheet_name.is_some() {
|
|
*sheet_name = Some(new_name.to_owned());
|
|
}
|
|
}
|
|
Node::WrongReferenceKind { sheet_name, .. } => {
|
|
if let Some(name) = sheet_name {
|
|
if name.to_uppercase() == new_name.to_uppercase() {
|
|
*sheet_name = Some(name.to_owned())
|
|
}
|
|
}
|
|
}
|
|
Node::WrongRangeKind { sheet_name, .. } => {
|
|
if sheet_name.is_some() {
|
|
*sheet_name = Some(new_name.to_owned());
|
|
}
|
|
}
|
|
|
|
// Go next level
|
|
Node::OpRangeKind { left, right } => {
|
|
rename_sheet_in_node(left, sheet_index, new_name);
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::OpConcatenateKind { left, right } => {
|
|
rename_sheet_in_node(left, sheet_index, new_name);
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::OpSumKind {
|
|
kind: _,
|
|
left,
|
|
right,
|
|
} => {
|
|
rename_sheet_in_node(left, sheet_index, new_name);
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::OpProductKind {
|
|
kind: _,
|
|
left,
|
|
right,
|
|
} => {
|
|
rename_sheet_in_node(left, sheet_index, new_name);
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::OpPowerKind { left, right } => {
|
|
rename_sheet_in_node(left, sheet_index, new_name);
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::FunctionKind { kind: _, args } => {
|
|
for arg in args {
|
|
rename_sheet_in_node(arg, sheet_index, new_name);
|
|
}
|
|
}
|
|
Node::InvalidFunctionKind { name: _, args } => {
|
|
for arg in args {
|
|
rename_sheet_in_node(arg, sheet_index, new_name);
|
|
}
|
|
}
|
|
Node::CompareKind {
|
|
kind: _,
|
|
left,
|
|
right,
|
|
} => {
|
|
rename_sheet_in_node(left, sheet_index, new_name);
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::UnaryKind { kind: _, right } => {
|
|
rename_sheet_in_node(right, sheet_index, new_name);
|
|
}
|
|
Node::ImplicitIntersection {
|
|
automatic: _,
|
|
child,
|
|
} => {
|
|
rename_sheet_in_node(child, sheet_index, new_name);
|
|
}
|
|
|
|
// Do nothing
|
|
Node::BooleanKind(_) => {}
|
|
Node::NumberKind(_) => {}
|
|
Node::StringKind(_) => {}
|
|
Node::ErrorKind(_) => {}
|
|
Node::ParseErrorKind { .. } => {}
|
|
Node::ArrayKind(_) => {}
|
|
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);
|
|
}
|
|
Node::ImplicitIntersection {
|
|
automatic: _,
|
|
child,
|
|
} => {
|
|
rename_defined_name_in_node(child, 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(_) => {}
|
|
}
|
|
}
|