Files
IronCalc/base/src/expressions/parser/stringify.rs
2024-05-11 14:28:48 +02:00

655 lines
22 KiB
Rust

#![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<Node>,
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 => {}
}
}