#![deny(missing_docs)] use super::{super::utils::quote_name, Node, Reference}; use crate::constants::{LAST_COLUMN, LAST_ROW}; use crate::expressions::token::OpUnary; use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str}; /// Displaced data pub enum DisplaceData { /// Displaces columns (inserting or deleting columns) Column { /// Sheet in which the displace data applies sheet: u32, /// Column from which the data is displaced column: i32, /// Number of columns displaced (might be negative, e.g. when deleting columns) delta: i32, }, /// Displaces rows (Inserting or deleting rows) Row { /// Sheet in which the displace data applies sheet: u32, /// Row from which the data is displaced row: i32, /// Number of rows displaced (might be negative, e.g. when deleting rows) delta: i32, }, /// Displaces cells horizontally ShiftCellsRight { /// Sheet in which the displace data applies sheet: u32, /// Row of the to left corner row: i32, /// Column of the top left corner column: i32, /// Number of rows to be displaced row_delta: i32, /// Number of columns to be displaced (might be negative) column_delta: i32, }, /// Displaces cells vertically ShiftCellsDown { /// Sheet in which the displace data applies sheet: u32, /// Row of the to left corner row: i32, /// Column of the top left corner column: i32, /// Number of rows displaced (might be negative) row_delta: i32, /// Number of columns to be displaced column_delta: i32, }, /// Displaces data due to a column move from column to column + delta ColumnMove { /// Sheet in which the displace data applies sheet: u32, /// Column that is moved column: i32, /// The position of the new column is column + delta (might be negative) delta: i32, }, /// Doesn't do any cell displacement None, } /// Stringifies the AST formula in its internal R1C1 format pub fn to_rc_format(node: &Node) -> String { stringify(node, None, &DisplaceData::None, false) } /// Stringifies the formula applying the _displace_data_. pub fn to_string_displaced( node: &Node, context: &CellReferenceRC, displace_data: &DisplaceData, ) -> String { stringify(node, Some(context), displace_data, false) } /// Stringifies a formula from the AST pub fn to_string(node: &Node, context: &CellReferenceRC) -> String { stringify(node, Some(context), &DisplaceData::None, false) } /// Stringifies the formula for Excel compatibility pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String { stringify(node, Some(context), &DisplaceData::None, true) } /// 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_colum 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::ShiftCellsRight { sheet, row: displace_row, column: displace_column, column_delta, row_delta, } => { if sheet_index == *sheet && displace_row >= &row && *displace_row < row + *row_delta { if *column_delta < 0 { if &column >= displace_column { if column < displace_column - *column_delta { return "#REF!".to_string(); } column += *column_delta; } } else if &column >= displace_column { column += *column_delta; } } } DisplaceData::ShiftCellsDown { sheet, row: displace_row, column: displace_column, row_delta, column_delta, } => { if sheet_index == *sheet && displace_column >= &column && *displace_column < column + *column_delta { if *row_delta < 0 { if &row >= displace_row { if row < displace_row - *row_delta { return "#REF!".to_string(); } row += *row_delta; } } else if &row >= displace_row { row += *row_delta; } } } DisplaceData::ColumnMove { sheet, column: move_column, delta, } => { if sheet_index == *sheet { if column == *move_column { column += *delta; } else if (*delta > 0 && column > *move_column && column <= *move_column + *delta) || (*delta < 0 && column < *move_column && column >= *move_column + *delta) { column -= *delta; } } } 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, use_original_name: 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, use_original_name) ); } else { first = false; arguments = stringify(el, context, displace_data, use_original_name); } } format!("{}({})", name, arguments) } fn stringify( node: &Node, context: Option<&CellReferenceRC>, displace_data: &DisplaceData, use_original_name: 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, use_original_name), stringify(right, context, displace_data, use_original_name) ), OpConcatenateKind { left, right } => format!( "{}&{}", stringify(left, context, displace_data, use_original_name), stringify(right, context, displace_data, use_original_name) ), CompareKind { kind, left, right } => format!( "{}{}{}", stringify(left, context, displace_data, use_original_name), kind, stringify(right, context, displace_data, use_original_name) ), OpSumKind { kind, left, right } => format!( "{}{}{}", stringify(left, context, displace_data, use_original_name), kind, stringify(right, context, displace_data, use_original_name) ), OpProductKind { kind, left, right } => { let x = match **left { OpSumKind { .. } => format!( "({})", stringify(left, context, displace_data, use_original_name) ), CompareKind { .. } => format!( "({})", stringify(left, context, displace_data, use_original_name) ), _ => stringify(left, context, displace_data, use_original_name), }; let y = match **right { OpSumKind { .. } => format!( "({})", stringify(right, context, displace_data, use_original_name) ), CompareKind { .. } => format!( "({})", stringify(right, context, displace_data, use_original_name) ), OpProductKind { .. } => format!( "({})", stringify(right, context, displace_data, use_original_name) ), _ => stringify(right, context, displace_data, use_original_name), }; format!("{}{}{}", x, kind, y) } OpPowerKind { left, right } => format!( "{}^{}", stringify(left, context, displace_data, use_original_name), stringify(right, context, displace_data, use_original_name) ), InvalidFunctionKind { name, args } => { format_function(name, args, context, displace_data, use_original_name) } FunctionKind { kind, args } => { let name = if use_original_name { kind.to_xlsx_string() } else { kind.to_string() }; format_function(&name, args, context, displace_data, use_original_name) } ArrayKind(args) => { let mut first = true; let mut arguments = "".to_string(); for el in args { if !first { arguments = format!( "{},{}", arguments, stringify(el, context, displace_data, use_original_name) ); } else { first = false; arguments = stringify(el, context, displace_data, use_original_name); } } format!("{{{}}}", arguments) } VariableKind(value) => value.to_string(), UnaryKind { kind, right } => match kind { OpUnary::Minus => { format!( "-{}", stringify(right, context, displace_data, use_original_name) ) } OpUnary::Percentage => { format!( "{}%", stringify(right, context, displace_data, use_original_name) ) } }, ErrorKind(kind) => format!("{}", kind), ParseErrorKind { formula, position: _, message: _, } => formula.to_string(), EmptyArgKind => "".to_string(), } } 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); } // Do nothing Node::BooleanKind(_) => {} Node::NumberKind(_) => {} Node::StringKind(_) => {} Node::ErrorKind(_) => {} Node::ParseErrorKind { .. } => {} Node::ArrayKind(_) => {} Node::VariableKind(_) => {} Node::EmptyArgKind => {} } }