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::{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 { stringify(node, None, &DisplaceData::None, false) } /// This is the mode used to display the formula in the UI pub fn to_string(node: &Node, context: &CellReferenceRC) -> String { stringify(node, Some(context), &DisplaceData::None, false) } /// This is the mode used to export the formula to Excel pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String { stringify(node, Some(context), &DisplaceData::None, true) } pub fn to_string_displaced( node: &Node, context: &CellReferenceRC, displace_data: &DisplaceData, ) -> String { stringify(node, Some(context), displace_data, false) } /// 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, context: Option<&CellReferenceRC>, displace_data: &DisplaceData, export_to_excel: bool, ) -> String { let mut first = true; let mut arguments = "".to_string(); for el in args { if !first { arguments = format!( "{},{}", arguments, stringify(el, context, displace_data, export_to_excel) ); } else { first = false; arguments = stringify(el, context, displace_data, export_to_excel); } } 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, ) -> String { use self::Node::*; match node { BooleanKind(value) => format!("{value}").to_ascii_uppercase(), NumberKind(number) => to_excel_precision_str(*number), 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), stringify(right, context, displace_data, export_to_excel) ), OpConcatenateKind { left, right } => format!( "{}&{}", stringify(left, context, displace_data, export_to_excel), stringify(right, context, displace_data, export_to_excel) ), CompareKind { kind, left, right } => format!( "{}{}{}", stringify(left, context, displace_data, export_to_excel), kind, stringify(right, context, displace_data, export_to_excel) ), OpSumKind { kind, left, right } => { let left_str = stringify(left, context, displace_data, export_to_excel); // 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) ) } else { stringify(right, context, displace_data, export_to_excel) }; 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) ), _ => stringify(left, context, displace_data, export_to_excel), }; let y = match **right { OpSumKind { .. } | CompareKind { .. } | OpProductKind { .. } => format!( "({})", stringify(right, context, displace_data, export_to_excel) ), _ => stringify(right, context, displace_data, export_to_excel), }; 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), OpRangeKind { .. } | OpConcatenateKind { .. } | OpProductKind { .. } | OpPowerKind { .. } | FunctionKind { .. } | InvalidFunctionKind { .. } | ArrayKind(_) | ErrorKind(_) | ParseErrorKind { .. } | OpSumKind { .. } | CompareKind { .. } | ImplicitIntersection { .. } | EmptyArgKind => format!( "({})", stringify(left, context, displace_data, export_to_excel) ), }; let y = match **right { BooleanKind(_) | NumberKind(_) | StringKind(_) | ReferenceKind { .. } | RangeKind { .. } | WrongReferenceKind { .. } | DefinedNameKind(_) | TableNameKind(_) | WrongVariableKind(_) | WrongRangeKind { .. } => { stringify(right, context, displace_data, export_to_excel) } OpRangeKind { .. } | OpConcatenateKind { .. } | OpProductKind { .. } | OpPowerKind { .. } | FunctionKind { .. } | InvalidFunctionKind { .. } | ArrayKind(_) | UnaryKind { .. } | ErrorKind(_) | ParseErrorKind { .. } | OpSumKind { .. } | CompareKind { .. } | ImplicitIntersection { .. } | EmptyArgKind => format!( "({})", stringify(right, context, displace_data, export_to_excel) ), }; format!("{x}^{y}") } InvalidFunctionKind { name, args } => { format_function(name, args, context, displace_data, export_to_excel) } FunctionKind { kind, args } => { let name = if export_to_excel { kind.to_xlsx_string() } else { kind.to_string() }; format_function(&name, args, context, displace_data, export_to_excel) } ArrayKind(args) => { let mut first_row = true; let mut matrix_string = String::new(); for row in args { if !first_row { matrix_string.push(';'); } 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(','); } else { first_column = false; } row_string.push_str(&to_string_array_node(el)); } 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) ) } else { format!( "-{}", stringify(right, context, displace_data, export_to_excel) ) } } OpUnary::Percentage => { format!( "{}%", stringify(right, context, displace_data, export_to_excel) ) } }, 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); } return format!( "_xlfn.SINGLE({})", stringify(child, context, displace_data, export_to_excel) ); } format!( "@{}", stringify(child, context, displace_data, export_to_excel) ) } } } 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, 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(_) => {} } }