UPDATE: Dump of initial files

This commit is contained in:
Nicolás Hatcher
2023-11-18 21:26:18 +01:00
commit c5b8efd83d
279 changed files with 42654 additions and 0 deletions

View File

@@ -0,0 +1,877 @@
/*!
# GRAMAR
<pre class="rust">
opComp => '=' | '<' | '>' | '<=' } '>=' | '<>'
opFactor => '*' | '/'
unaryOp => '-' | '+'
expr => concat (opComp concat)*
concat => term ('&' term)*
term => factor (opFactor factor)*
factor => prod (opProd prod)*
prod => power ('^' power)*
power => (unaryOp)* range '%'*
range => primary (':' primary)?
primary => '(' expr ')'
=> number
=> function '(' f_args ')'
=> name
=> string
=> '{' a_args '}'
=> bool
=> bool()
=> error
f_args => e (',' e)*
</pre>
*/
use std::collections::HashMap;
use crate::functions::Function;
use crate::language::get_language;
use crate::locale::get_locale;
use crate::types::Table;
use super::lexer;
use super::token;
use super::token::OpUnary;
use super::token::TableReference;
use super::token::TokenType;
use super::types::*;
use super::utils::number_to_column;
use token::OpCompare;
pub mod move_formula;
pub mod stringify;
pub mod walk;
#[cfg(test)]
mod test;
#[cfg(test)]
mod test_ranges;
#[cfg(test)]
mod test_move_formula;
#[cfg(test)]
mod test_tables;
pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> {
let mut lexer = lexer::Lexer::new(
formula,
lexer::LexerMode::A1,
get_locale("en").expect(""),
get_language("en").expect(""),
);
if let TokenType::Range {
left,
right,
sheet: _,
} = lexer.next_token()
{
Ok((left.column, left.row, right.column, right.row))
} else {
Err("Not a range".to_string())
}
}
fn get_table_column_by_name(table_column_name: &str, table: &Table) -> Option<i32> {
for (index, table_column) in table.columns.iter().enumerate() {
if table_column.name == table_column_name {
return Some(index as i32);
}
}
None
}
pub(crate) struct Reference<'a> {
sheet_name: &'a Option<String>,
sheet_index: u32,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
}
#[derive(PartialEq, Clone, Debug)]
pub enum Node {
BooleanKind(bool),
NumberKind(f64),
StringKind(String),
ReferenceKind {
sheet_name: Option<String>,
sheet_index: u32,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
},
RangeKind {
sheet_name: Option<String>,
sheet_index: u32,
absolute_row1: bool,
absolute_column1: bool,
row1: i32,
column1: i32,
absolute_row2: bool,
absolute_column2: bool,
row2: i32,
column2: i32,
},
WrongReferenceKind {
sheet_name: Option<String>,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
},
WrongRangeKind {
sheet_name: Option<String>,
absolute_row1: bool,
absolute_column1: bool,
row1: i32,
column1: i32,
absolute_row2: bool,
absolute_column2: bool,
row2: i32,
column2: i32,
},
OpRangeKind {
left: Box<Node>,
right: Box<Node>,
},
OpConcatenateKind {
left: Box<Node>,
right: Box<Node>,
},
OpSumKind {
kind: token::OpSum,
left: Box<Node>,
right: Box<Node>,
},
OpProductKind {
kind: token::OpProduct,
left: Box<Node>,
right: Box<Node>,
},
OpPowerKind {
left: Box<Node>,
right: Box<Node>,
},
FunctionKind {
kind: Function,
args: Vec<Node>,
},
InvalidFunctionKind {
name: String,
args: Vec<Node>,
},
ArrayKind(Vec<Node>),
VariableKind(String),
CompareKind {
kind: OpCompare,
left: Box<Node>,
right: Box<Node>,
},
UnaryKind {
kind: OpUnary,
right: Box<Node>,
},
ErrorKind(token::Error),
ParseErrorKind {
formula: String,
message: String,
position: usize,
},
EmptyArgKind,
}
#[derive(Clone)]
pub struct Parser {
lexer: lexer::Lexer,
worksheets: Vec<String>,
context: Option<CellReferenceRC>,
tables: HashMap<String, Table>,
}
impl Parser {
pub fn new(worksheets: Vec<String>, tables: HashMap<String, Table>) -> Parser {
let lexer = lexer::Lexer::new(
"",
lexer::LexerMode::A1,
get_locale("en").expect(""),
get_language("en").expect(""),
);
Parser {
lexer,
worksheets,
context: None,
tables,
}
}
pub fn set_lexer_mode(&mut self, mode: lexer::LexerMode) {
self.lexer.set_lexer_mode(mode)
}
pub fn set_worksheets(&mut self, worksheets: Vec<String>) {
self.worksheets = worksheets;
}
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
self.lexer.set_formula(formula);
self.context = context.clone();
self.parse_expr()
}
fn get_sheet_index_by_name(&self, name: &str) -> Option<u32> {
let worksheets = &self.worksheets;
for (i, sheet) in worksheets.iter().enumerate() {
if sheet == name {
return Some(i as u32);
}
}
None
}
fn parse_expr(&mut self) -> Node {
let mut t = self.parse_concat();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while let TokenType::Compare(op) = next_token {
self.lexer.advance_token();
let p = self.parse_concat();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::CompareKind {
kind: op,
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_concat(&mut self) -> Node {
let mut t = self.parse_term();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while next_token == TokenType::And {
self.lexer.advance_token();
let p = self.parse_term();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpConcatenateKind {
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_term(&mut self) -> Node {
let mut t = self.parse_factor();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while let TokenType::Addition(op) = next_token {
self.lexer.advance_token();
let p = self.parse_factor();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpSumKind {
kind: op,
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_factor(&mut self) -> Node {
let mut t = self.parse_prod();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while let TokenType::Product(op) = next_token {
self.lexer.advance_token();
let p = self.parse_prod();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpProductKind {
kind: op,
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_prod(&mut self) -> Node {
let mut t = self.parse_power();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while next_token == TokenType::Power {
self.lexer.advance_token();
let p = self.parse_power();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpPowerKind {
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_power(&mut self) -> Node {
let mut next_token = self.lexer.peek_token();
let mut sign = 1;
while let TokenType::Addition(op) = next_token {
self.lexer.advance_token();
if op == token::OpSum::Minus {
sign = -sign;
}
next_token = self.lexer.peek_token();
}
let mut t = self.parse_range();
if let Node::ParseErrorKind { .. } = t {
return t;
}
if sign == -1 {
t = Node::UnaryKind {
kind: token::OpUnary::Minus,
right: Box::new(t),
}
}
next_token = self.lexer.peek_token();
while next_token == TokenType::Percent {
self.lexer.advance_token();
t = Node::UnaryKind {
kind: token::OpUnary::Percentage,
right: Box::new(t),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_range(&mut self) -> Node {
let t = self.parse_primary();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let next_token = self.lexer.peek_token();
if next_token == TokenType::Colon {
self.lexer.advance_token();
let p = self.parse_primary();
if let Node::ParseErrorKind { .. } = p {
return p;
}
return Node::OpRangeKind {
left: Box::new(t),
right: Box::new(p),
};
}
t
}
fn parse_primary(&mut self) -> Node {
let next_token = self.lexer.next_token();
match next_token {
TokenType::LeftParenthesis => {
let t = self.parse_expr();
if let Node::ParseErrorKind { .. } = t {
return t;
}
if let Err(err) = self.lexer.expect(TokenType::RightParenthesis) {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: err.position,
message: err.message,
};
}
t
}
TokenType::Number(s) => Node::NumberKind(s),
TokenType::String(s) => Node::StringKind(s),
TokenType::LeftBrace => {
let t = self.parse_expr();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
let mut args: Vec<Node> = vec![t];
while next_token == TokenType::Semicolon {
self.lexer.advance_token();
let p = self.parse_expr();
if let Node::ParseErrorKind { .. } = p {
return p;
}
next_token = self.lexer.peek_token();
args.push(p);
}
if let Err(err) = self.lexer.expect(TokenType::RightBrace) {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: err.position,
message: err.message,
};
}
Node::ArrayKind(args)
}
TokenType::Reference {
sheet,
row,
column,
absolute_column,
absolute_row,
} => {
let context = match &self.context {
Some(c) => c,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: "Expected context for the reference".to_string(),
}
}
};
let sheet_index = match &sheet {
Some(name) => self.get_sheet_index_by_name(name),
None => self.get_sheet_index_by_name(&context.sheet),
};
let a1_mode = self.lexer.is_a1_mode();
let row = if absolute_row || !a1_mode {
row
} else {
row - context.row
};
let column = if absolute_column || !a1_mode {
column
} else {
column - context.column
};
match sheet_index {
Some(index) => Node::ReferenceKind {
sheet_name: sheet,
sheet_index: index,
row,
column,
absolute_row,
absolute_column,
},
None => Node::WrongReferenceKind {
sheet_name: sheet,
row,
column,
absolute_row,
absolute_column,
},
}
}
TokenType::Range { sheet, left, right } => {
let context = match &self.context {
Some(c) => c,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: "Expected context for the reference".to_string(),
}
}
};
let sheet_index = match &sheet {
Some(name) => self.get_sheet_index_by_name(name),
None => self.get_sheet_index_by_name(&context.sheet),
};
let mut row1 = left.row;
let mut column1 = left.column;
let mut row2 = right.row;
let mut column2 = right.column;
let mut absolute_column1 = left.absolute_column;
let mut absolute_column2 = right.absolute_column;
let mut absolute_row1 = left.absolute_row;
let mut absolute_row2 = right.absolute_row;
if self.lexer.is_a1_mode() {
if !left.absolute_row {
row1 -= context.row
};
if !left.absolute_column {
column1 -= context.column
};
if !right.absolute_row {
row2 -= context.row
};
if !right.absolute_column {
column2 -= context.column
};
}
if row1 > row2 {
(row2, row1) = (row1, row2);
(absolute_row2, absolute_row1) = (absolute_row1, absolute_row2);
}
if column1 > column2 {
(column2, column1) = (column1, column2);
(absolute_column2, absolute_column1) = (absolute_column1, absolute_column2);
}
match sheet_index {
Some(index) => Node::RangeKind {
sheet_name: sheet,
sheet_index: index,
row1,
column1,
row2,
column2,
absolute_column1,
absolute_column2,
absolute_row1,
absolute_row2,
},
None => Node::WrongRangeKind {
sheet_name: sheet,
row1,
column1,
row2,
column2,
absolute_column1,
absolute_column2,
absolute_row1,
absolute_row2,
},
}
}
TokenType::Ident(name) => {
let next_token = self.lexer.peek_token();
if next_token == TokenType::LeftParenthesis {
// It's a function call "SUM(.."
self.lexer.advance_token();
let args = match self.parse_function_args() {
Ok(s) => s,
Err(e) => return e,
};
if let Err(err) = self.lexer.expect(TokenType::RightParenthesis) {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: err.position,
message: err.message,
};
}
if let Some(function_kind) = Function::get_function(&name) {
return Node::FunctionKind {
kind: function_kind,
args,
};
} else {
return Node::InvalidFunctionKind { name, args };
}
}
Node::VariableKind(name)
}
TokenType::Error(kind) => Node::ErrorKind(kind),
TokenType::Illegal(error) => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: error.position,
message: error.message,
},
TokenType::EOF => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected end of input.".to_string(),
},
TokenType::Boolean(value) => Node::BooleanKind(value),
TokenType::Compare(_) => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'COMPARE'".to_string(),
}
}
TokenType::Addition(_) => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'SUM'".to_string(),
}
}
TokenType::Product(_) => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'PRODUCT'".to_string(),
}
}
TokenType::Power => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'POWER'".to_string(),
}
}
TokenType::RightParenthesis
| TokenType::RightBracket
| TokenType::Colon
| TokenType::Semicolon
| TokenType::RightBrace
| TokenType::Comma
| TokenType::Bang
| TokenType::And
| TokenType::Percent => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: format!("Unexpected token: '{}'", next_token),
},
TokenType::LeftBracket => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: '['".to_string(),
},
TokenType::StructuredReference {
table_name,
specifier,
table_reference,
} => {
// We will try to convert to a normal reference
// table_name[column_name] => cell1:cell2
// table_name[[#This Row], [column_name]:[column_name]] => cell1:cell2
if let Some(context) = &self.context {
let context_sheet_index = match self.get_sheet_index_by_name(&context.sheet) {
Some(i) => i,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "sheet not found".to_string(),
};
}
};
// table-name => table
let table = self.tables.get(&table_name).unwrap_or_else(|| {
panic!(
"Table not found: '{table_name}' at '{}!{}{}'",
context.sheet,
number_to_column(context.column).expect(""),
context.row
)
});
let table_sheet_index = match self.get_sheet_index_by_name(&table.sheet_name) {
Some(i) => i,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "sheet not found".to_string(),
};
}
};
let sheet_name = if table_sheet_index == context_sheet_index {
None
} else {
Some(table.sheet_name.clone())
};
// context must be with tables.reference
let (column_start, mut row_start, column_end, mut row_end) =
parse_range(&table.reference).expect("Failed parsing range");
let totals_row_count = table.totals_row_count as i32;
let header_row_count = table.header_row_count as i32;
row_end -= totals_row_count;
match specifier {
Some(token::TableSpecifier::ThisRow) => {
row_start = context.row;
row_end = context.row;
}
Some(token::TableSpecifier::Totals) => {
if totals_row_count != 0 {
row_start = row_end + 1;
row_end = row_start;
} else {
// Table1[#Totals] is #REF! if Table1 does not have totals
return Node::ErrorKind(token::Error::REF);
}
}
Some(token::TableSpecifier::Headers) => {
row_end = row_start;
}
Some(token::TableSpecifier::Data) => {
row_start += header_row_count;
}
Some(token::TableSpecifier::All) => {
if totals_row_count != 0 {
row_end += 1;
}
}
None => {
// skip the headers
row_start += header_row_count;
}
}
match table_reference {
None => {
return Node::RangeKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row1: true,
absolute_column1: true,
row1: row_start,
column1: column_start,
absolute_row2: true,
absolute_column2: true,
row2: row_end,
column2: column_end,
};
}
Some(TableReference::ColumnReference(s)) => {
let column_index = match get_table_column_by_name(&s, table) {
Some(s) => s + column_start,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: format!(
"Expecting column: {s} in table {table_name}"
),
};
}
};
if row_start == row_end {
return Node::ReferenceKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row: true,
absolute_column: true,
row: row_start,
column: column_index,
};
}
return Node::RangeKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row1: true,
absolute_column1: true,
row1: row_start,
column1: column_index,
absolute_row2: true,
absolute_column2: true,
row2: row_end,
column2: column_index,
};
}
Some(TableReference::RangeReference((left, right))) => {
let left_column_index = match get_table_column_by_name(&left, table) {
Some(f) => f + column_start,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: format!(
"Expecting column: {left} in table {table_name}"
),
};
}
};
let right_column_index = match get_table_column_by_name(&right, table) {
Some(f) => f + column_start,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: format!(
"Expecting column: {right} in table {table_name}"
),
};
}
};
return Node::RangeKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row1: true,
absolute_column1: true,
row1: row_start,
column1: left_column_index,
absolute_row2: true,
absolute_column2: true,
row2: row_end,
column2: right_column_index,
};
}
}
}
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Structured references not supported in R1C1 mode".to_string(),
}
}
}
}
fn parse_function_args(&mut self) -> Result<Vec<Node>, Node> {
let mut args: Vec<Node> = Vec::new();
let mut next_token = self.lexer.peek_token();
if next_token == TokenType::RightParenthesis {
return Ok(args);
}
if self.lexer.peek_token() == TokenType::Comma {
args.push(Node::EmptyArgKind);
} else {
let t = self.parse_expr();
if let Node::ParseErrorKind { .. } = t {
return Err(t);
}
args.push(t);
}
next_token = self.lexer.peek_token();
while next_token == TokenType::Comma {
self.lexer.advance_token();
if self.lexer.peek_token() == TokenType::Comma {
args.push(Node::EmptyArgKind);
next_token = TokenType::Comma;
} else if self.lexer.peek_token() == TokenType::RightParenthesis {
args.push(Node::EmptyArgKind);
return Ok(args);
} else {
let p = self.parse_expr();
if let Node::ParseErrorKind { .. } = p {
return Err(p);
}
next_token = self.lexer.peek_token();
args.push(p);
}
}
Ok(args)
}
}

View File

@@ -0,0 +1,397 @@
use super::{
stringify::{stringify_reference, DisplaceData},
Node, Reference,
};
use crate::{
constants::{LAST_COLUMN, LAST_ROW},
expressions::token::OpUnary,
};
use crate::{
expressions::types::{Area, CellReferenceRC},
number_format::to_excel_precision_str,
};
pub(crate) fn ref_is_in_area(sheet: u32, row: i32, column: i32, area: &Area) -> bool {
if area.sheet != sheet {
return false;
}
if row < area.row || row > area.row + area.height - 1 {
return false;
}
if column < area.column || column > area.column + area.width - 1 {
return false;
}
true
}
pub(crate) struct MoveContext<'a> {
pub source_sheet_name: &'a str,
pub row: i32,
pub column: i32,
pub area: &'a Area,
pub target_sheet_name: &'a str,
pub row_delta: i32,
pub column_delta: i32,
}
/// This implements Excel's cut && paste
/// We are moving a formula in (row, column) to (row+row_delta, column + column_delta).
/// All references that do not point to a cell in area will be left untouched.
/// All references that point to a cell in area will be displaced
pub(crate) fn move_formula(node: &Node, move_context: &MoveContext) -> String {
to_string_moved(node, move_context)
}
fn move_function(name: &str, args: &Vec<Node>, move_context: &MoveContext) -> String {
let mut first = true;
let mut arguments = "".to_string();
for el in args {
if !first {
arguments = format!("{},{}", arguments, to_string_moved(el, move_context));
} else {
first = false;
arguments = to_string_moved(el, move_context);
}
}
format!("{}({})", name, arguments)
}
fn to_string_moved(node: &Node, move_context: &MoveContext) -> String {
use self::Node::*;
match node {
BooleanKind(value) => format!("{}", value).to_ascii_uppercase(),
NumberKind(number) => to_excel_precision_str(*number),
StringKind(value) => format!("\"{}\"", value),
ReferenceKind {
sheet_name,
sheet_index,
absolute_row,
absolute_column,
row,
column,
} => {
let reference_row = if *absolute_row {
*row
} else {
row + move_context.row
};
let reference_column = if *absolute_column {
*column
} else {
column + move_context.column
};
let new_row;
let new_column;
let mut ref_sheet_name = sheet_name;
let source_sheet_name = &Some(move_context.source_sheet_name.to_string());
if ref_is_in_area(
*sheet_index,
reference_row,
reference_column,
move_context.area,
) {
// if the reference is in the area we are moving we want to displace the reference
new_row = row + move_context.row_delta;
new_column = column + move_context.column_delta;
} else {
// If the reference is not in the area we are moving the reference remains unchanged
new_row = *row;
new_column = *column;
if move_context.target_sheet_name != move_context.source_sheet_name
&& sheet_name.is_none()
{
ref_sheet_name = source_sheet_name;
}
};
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: ref_sheet_name,
sheet_index: *sheet_index,
absolute_row: *absolute_row,
absolute_column: *absolute_column,
row: new_row,
column: new_column,
},
false,
false,
)
}
RangeKind {
sheet_name,
sheet_index,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
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 reference_row1 = if *absolute_row1 {
*row1
} else {
row1 + move_context.row
};
let reference_column1 = if *absolute_column1 {
*column1
} else {
column1 + move_context.column
};
let reference_row2 = if *absolute_row2 {
*row2
} else {
row2 + move_context.row
};
let reference_column2 = if *absolute_column2 {
*column2
} else {
column2 + move_context.column
};
let new_row1;
let new_column1;
let new_row2;
let new_column2;
let mut ref_sheet_name = sheet_name;
let source_sheet_name = &Some(move_context.source_sheet_name.to_string());
if ref_is_in_area(
*sheet_index,
reference_row1,
reference_column1,
move_context.area,
) && ref_is_in_area(
*sheet_index,
reference_row2,
reference_column2,
move_context.area,
) {
// if the whole range is inside the area we are moving we want to displace the context
new_row1 = row1 + move_context.row_delta;
new_column1 = column1 + move_context.column_delta;
new_row2 = row2 + move_context.row_delta;
new_column2 = column2 + move_context.column_delta;
} else {
// If the reference is not in the area we are moving the context remains unchanged
new_row1 = *row1;
new_column1 = *column1;
new_row2 = *row2;
new_column2 = *column2;
if move_context.target_sheet_name != move_context.source_sheet_name
&& sheet_name.is_none()
{
ref_sheet_name = source_sheet_name;
}
};
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
let s1 = stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: ref_sheet_name,
sheet_index: *sheet_index,
absolute_row: *absolute_row1,
absolute_column: *absolute_column1,
row: new_row1,
column: new_column1,
},
full_row,
full_column,
);
let s2 = stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: &None,
sheet_index: *sheet_index,
absolute_row: *absolute_row2,
absolute_column: *absolute_column2,
row: new_row2,
column: new_column2,
},
full_row,
full_column,
);
format!("{}:{}", s1, s2)
}
WrongReferenceKind {
sheet_name,
absolute_row,
absolute_column,
row,
column,
} => {
// NB: Excel does not displace wrong references but Google Docs does. We follow Excel
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
// It's a wrong reference, so there is no valid `sheet_index`.
// We don't need it, since the `sheet_index` is only used if `displace_data` is not `None`.
// I should fix it, maybe putting the `sheet_index` inside the `displace_data`
stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name,
sheet_index: 0, // HACK
row: *row,
column: *column,
absolute_row: *absolute_row,
absolute_column: *absolute_column,
},
false,
false,
)
}
WrongRangeKind {
sheet_name,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
let full_column = *absolute_column1
&& *absolute_column2
&& (*column1 == 1)
&& (*column2 == LAST_COLUMN);
// NB: Excel does not displace wrong references but Google Docs does. We follow Excel
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
let s1 = stringify_reference(
Some(&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(
Some(&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!(
"{}:{}",
to_string_moved(left, move_context),
to_string_moved(right, move_context),
),
OpConcatenateKind { left, right } => format!(
"{}&{}",
to_string_moved(left, move_context),
to_string_moved(right, move_context),
),
OpSumKind { kind, left, right } => format!(
"{}{}{}",
to_string_moved(left, move_context),
kind,
to_string_moved(right, move_context),
),
OpProductKind { kind, left, right } => {
let x = match **left {
OpSumKind { .. } => format!("({})", to_string_moved(left, move_context)),
CompareKind { .. } => format!("({})", to_string_moved(left, move_context)),
_ => to_string_moved(left, move_context),
};
let y = match **right {
OpSumKind { .. } => format!("({})", to_string_moved(right, move_context)),
CompareKind { .. } => format!("({})", to_string_moved(right, move_context)),
OpProductKind { .. } => format!("({})", to_string_moved(right, move_context)),
UnaryKind { .. } => {
format!("({})", to_string_moved(right, move_context))
}
_ => to_string_moved(right, move_context),
};
format!("{}{}{}", x, kind, y)
}
OpPowerKind { left, right } => format!(
"{}^{}",
to_string_moved(left, move_context),
to_string_moved(right, move_context),
),
InvalidFunctionKind { name, args } => move_function(name, args, move_context),
FunctionKind { kind, args } => {
let name = &kind.to_string();
move_function(name, args, move_context)
}
ArrayKind(args) => {
// This code is a placeholder. Arrays are not yet implemented
let mut first = true;
let mut arguments = "".to_string();
for el in args {
if !first {
arguments = format!("{},{}", arguments, to_string_moved(el, move_context));
} else {
first = false;
arguments = to_string_moved(el, move_context);
}
}
format!("{{{}}}", arguments)
}
VariableKind(value) => value.to_string(),
CompareKind { kind, left, right } => format!(
"{}{}{}",
to_string_moved(left, move_context),
kind,
to_string_moved(right, move_context),
),
UnaryKind { kind, right } => match kind {
OpUnary::Minus => format!("-{}", to_string_moved(right, move_context)),
OpUnary::Percentage => format!("{}%", to_string_moved(right, move_context)),
},
ErrorKind(kind) => format!("{}", kind),
ParseErrorKind {
formula,
message: _,
position: _,
} => formula.to_string(),
EmptyArgKind => "".to_string(),
}
}

View File

@@ -0,0 +1,612 @@
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};
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,
},
ColumnMove {
sheet: u32,
column: i32,
delta: i32,
},
None,
}
pub fn to_rc_format(node: &Node) -> String {
stringify(node, None, &DisplaceData::None, false)
}
pub fn to_string_displaced(
node: &Node,
context: &CellReferenceRC,
displace_data: &DisplaceData,
) -> String {
stringify(node, Some(context), displace_data, false)
}
pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
stringify(node, Some(context), &DisplaceData::None, false)
}
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::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::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 => {}
}
}

View File

@@ -0,0 +1,497 @@
use std::collections::HashMap;
use crate::expressions::lexer::LexerMode;
use crate::expressions::parser::stringify::DisplaceData;
use super::super::types::CellReferenceRC;
use super::Parser;
use super::{
super::parser::{
stringify::{to_rc_format, to_string},
Node,
},
stringify::to_string_displaced,
};
struct Formula<'a> {
initial: &'a str,
expected: &'a str,
}
#[test]
fn test_parser_reference() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("A2", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[1]C[0]");
}
#[test]
fn test_parser_absolute_column() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("$A1", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[0]C1");
}
#[test]
fn test_parser_absolute_row_col() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("$C$5", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R5C3");
}
#[test]
fn test_parser_absolute_row_col_1() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("$A$1", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R1C1");
}
#[test]
fn test_parser_simple_formula() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("C3+Sheet2!D4", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[2]C[2]+Sheet2!R[3]C[3]");
}
#[test]
fn test_parser_boolean() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("true", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "TRUE");
}
#[test]
fn test_parser_bad_formula() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("#Value", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "#Value");
assert_eq!(message, "Invalid error.");
assert_eq!(*position, 1);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "#Value");
}
#[test]
fn test_parser_bad_formula_1() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("<5", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "<5");
assert_eq!(message, "Unexpected token: 'COMPARE'");
assert_eq!(*position, 0);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "<5");
}
#[test]
fn test_parser_bad_formula_2() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("*5", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "*5");
assert_eq!(message, "Unexpected token: 'PRODUCT'");
assert_eq!(*position, 0);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "*5");
}
#[test]
fn test_parser_bad_formula_3() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("SUM(#VALVE!)", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "SUM(#VALVE!)");
assert_eq!(message, "Invalid error.");
assert_eq!(*position, 5);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "SUM(#VALVE!)");
}
#[test]
fn test_parser_formulas() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let formulas = vec![
Formula {
initial: "IF(C3:D4>2,B5,SUM(D1:D7))",
expected: "IF(R[2]C[2]:R[3]C[3]>2,R[4]C[1],SUM(R[0]C[3]:R[6]C[3]))",
},
Formula {
initial: "-A1",
expected: "-R[0]C[0]",
},
Formula {
initial: "#VALUE!",
expected: "#VALUE!",
},
Formula {
initial: "SUM(C3:D4)",
expected: "SUM(R[2]C[2]:R[3]C[3])",
},
Formula {
initial: "A1/(B1-C1)",
expected: "R[0]C[0]/(R[0]C[1]-R[0]C[2])",
},
];
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
for formula in formulas {
let t = parser.parse(
formula.initial,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_rc_format(&t), formula.expected);
assert_eq!(to_string(&t, &cell_reference), formula.initial);
}
}
#[test]
fn test_parser_r1c1_formulas() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
parser.set_lexer_mode(LexerMode::R1C1);
let formulas = vec![
Formula {
initial: "IF(R[2]C[2]:R[3]C[3]>2,R[4]C[1],SUM(R[0]C[3]:R[6]C[3]))",
expected: "IF(E5:F6>2,D7,SUM(F3:F9))",
},
Formula {
initial: "-R[0]C[0]",
expected: "-C3",
},
Formula {
initial: "R[1]C[-1]+1",
expected: "B4+1",
},
Formula {
initial: "#VALUE!",
expected: "#VALUE!",
},
Formula {
initial: "SUM(R[2]C[2]:R[3]C[3])",
expected: "SUM(E5:F6)",
},
Formula {
initial: "R[-3]C[0]",
expected: "#REF!",
},
Formula {
initial: "R[0]C[-3]",
expected: "#REF!",
},
Formula {
initial: "R[-2]C[-2]",
expected: "A1",
},
Formula {
initial: "SIN(R[-3]C[-3])",
expected: "SIN(#REF!)",
},
];
// Reference cell is Sheet1!C3
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 3,
column: 3,
};
for formula in formulas {
let t = parser.parse(
formula.initial,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_string(&t, &cell_reference), formula.expected);
assert_eq!(to_rc_format(&t), formula.initial);
}
}
#[test]
fn test_parser_quotes() {
let worksheets = vec!["Sheet1".to_string(), "Second Sheet".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("C3+'Second Sheet'!D4", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[2]C[2]+'Second Sheet'!R[3]C[3]");
}
#[test]
fn test_parser_escape_quotes() {
let worksheets = vec!["Sheet1".to_string(), "Second '2' Sheet".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("C3+'Second ''2'' Sheet'!D4", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[2]C[2]+'Second ''2'' Sheet'!R[3]C[3]");
}
#[test]
fn test_parser_parenthesis() {
let worksheets = vec!["Sheet1".to_string(), "Second2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("(C3=\"Yes\")*5", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "(R[2]C[2]=\"Yes\")*5");
}
#[test]
fn test_parser_excel_xlfn() {
let worksheets = vec!["Sheet1".to_string(), "Second2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("_xlfn.CONCAT(C3)", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "CONCAT(R[2]C[2])");
}
#[test]
fn test_to_string_displaced() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("C3", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: 4,
};
let t = to_string_displaced(&node, context, &displace_data);
assert_eq!(t, "G3".to_string());
}
#[test]
fn test_to_string_displaced_full_ranges() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("SUM(3:3)", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: 4,
};
assert_eq!(
to_string_displaced(&node, context, &displace_data),
"SUM(3:3)".to_string()
);
let node = parser.parse("SUM(D:D)", &Some(context.clone()));
let displace_data = DisplaceData::Row {
sheet: 0,
row: 3,
delta: 4,
};
assert_eq!(
to_string_displaced(&node, context, &displace_data),
"SUM(D:D)".to_string()
);
}
#[test]
fn test_to_string_displaced_too_low() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("C3", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: -40,
};
let t = to_string_displaced(&node, context, &displace_data);
assert_eq!(t, "#REF!".to_string());
}
#[test]
fn test_to_string_displaced_too_high() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("C3", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: 4000000,
};
let t = to_string_displaced(&node, context, &displace_data);
assert_eq!(t, "#REF!".to_string());
}

View File

@@ -0,0 +1,482 @@
use std::collections::HashMap;
use crate::expressions::parser::move_formula::{move_formula, MoveContext};
use crate::expressions::types::Area;
use super::super::types::CellReferenceRC;
use super::Parser;
#[test]
fn test_move_formula() {
// top left corner C2
let row = 2;
let column = 3;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row,
column,
width: 4,
height: 5,
};
// formula AB31 will not change
let node = parser.parse("AB31", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "AB31");
// formula $AB$31 will not change
let node = parser.parse("AB31", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "AB31");
// but formula D5 will change to N15 (N = D + 10)
let node = parser.parse("D5", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "N15");
// Also formula $D$5 will change to N15 (N = D + 10)
let node = parser.parse("$D$5", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "$N$15");
}
#[test]
fn test_move_formula_context_offset() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
let node = parser.parse("-X9+C2%", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "-X9+M12%");
}
#[test]
fn test_move_formula_area_limits() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
// Outside of the area. Not moved
let node = parser.parse("B2+B3+C1+G6+H5", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "B2+B3+C1+G6+H5");
// In the area. Moved
let node = parser.parse("C2+F4+F5+F6", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "M12+P14+P15+P16");
}
#[test]
fn test_move_formula_ranges() {
// top left corner C2
let row = 2;
let column = 3;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let area = &Area {
sheet: 0,
row,
column,
width: 4,
height: 5,
};
// Ranges inside the area are fully displaced (absolute or not)
let node = parser.parse("SUM(C2:F5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(M12:P15)");
let node = parser.parse("SUM($C$2:$F$5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM($M$12:$P$15)");
// Ranges completely outside of the area are not touched
let node = parser.parse("SUM(A1:B3)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(A1:B3)");
let node = parser.parse("SUM($A$1:$B$3)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM($A$1:$B$3)");
// Ranges that overlap with the area are also NOT displaced
let node = parser.parse("SUM(A1:F5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(A1:F5)");
// Ranges that contain the area are also NOT displaced
let node = parser.parse("SUM(A1:X50)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(A1:X50)");
}
#[test]
fn test_move_formula_wrong_reference() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
// Area is C2:G5
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Wrong formulas will NOT be displaced
let node = parser.parse("Sheet3!AB31", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "Sheet3!AB31");
let node = parser.parse("Sheet3!$X$9", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "Sheet3!$X$9");
let node = parser.parse("SUM(Sheet3!D2:D3)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(Sheet3!D2:D3)");
}
#[test]
fn test_move_formula_misc() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
let node = parser.parse("X9^C2-F4*H2", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "X9^M12-P14*H2");
let node = parser.parse("F5*(-D5)*SUM(A1, X9, $D$5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "P15*(-N15)*SUM(A1,X9,$N$15)");
let node = parser.parse("IF(F5 < -D5, X9 & F5, FALSE)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "IF(P15<-N15,X9&P15,FALSE)");
}
#[test]
fn test_move_formula_another_sheet() {
// top left corner C2
let row = 2;
let column = 3;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
// we add two sheets and we cut/paste from Sheet1 to Sheet2
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row,
column,
width: 4,
height: 5,
};
// Formula AB31 and JJ3:JJ4 refers to original Sheet1!AB31 and Sheet1!JJ3:JJ4
let node = parser.parse(
"AB31*SUM(JJ3:JJ4)+SUM(Sheet2!C2:F6)*SUM(C2:F6)",
&Some(context.clone()),
);
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet2",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(
t,
"Sheet1!AB31*SUM(Sheet1!JJ3:JJ4)+SUM(Sheet2!C2:F6)*SUM(M12:P16)"
);
}

View File

@@ -0,0 +1,102 @@
use std::collections::HashMap;
use crate::expressions::lexer::LexerMode;
use super::super::parser::stringify::{to_rc_format, to_string};
use super::super::types::CellReferenceRC;
use super::Parser;
struct Formula<'a> {
formula_a1: &'a str,
formula_r1c1: &'a str,
}
#[test]
fn test_parser_formulas_with_full_ranges() {
let worksheets = vec!["Sheet1".to_string(), "Second Sheet".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let formulas = vec![
Formula {
formula_a1: "IF(C:D>2,B5,SUM(D:D))",
formula_r1c1: "IF(R1C[2]:R1048576C[3]>2,R[4]C[1],SUM(R1C[3]:R1048576C[3]))",
},
Formula {
formula_a1: "A:A",
formula_r1c1: "R1C[0]:R1048576C[0]",
},
Formula {
formula_a1: "SUM(3:3)",
formula_r1c1: "SUM(R[2]C1:R[2]C16384)",
},
Formula {
formula_a1: "SUM($3:$3)",
formula_r1c1: "SUM(R3C1:R3C16384)",
},
Formula {
formula_a1: "SUM(Sheet1!3:$3)",
formula_r1c1: "SUM(Sheet1!R[2]C1:R3C16384)",
},
Formula {
formula_a1: "SUM('Second Sheet'!C:D)",
formula_r1c1: "SUM('Second Sheet'!R1C[2]:R1048576C[3])",
},
];
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
for formula in &formulas {
let t = parser.parse(
formula.formula_a1,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_rc_format(&t), formula.formula_r1c1);
assert_eq!(to_string(&t, &cell_reference), formula.formula_a1);
}
// Now the inverse
parser.set_lexer_mode(LexerMode::R1C1);
for formula in &formulas {
let t = parser.parse(
formula.formula_r1c1,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_rc_format(&t), formula.formula_r1c1);
assert_eq!(to_string(&t, &cell_reference), formula.formula_a1);
}
}
#[test]
fn test_range_inverse_order() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
// D4:C2 => C2:D4
let t = parser.parse(
"SUM(D4:C2)*SUM(Sheet2!D4:C20)*SUM($C$20:D4)",
&Some(cell_reference.clone()),
);
assert_eq!(
to_string(&t, &cell_reference),
"SUM(C2:D4)*SUM(Sheet2!C4:D20)*SUM($C4:D$20)".to_string()
);
}

View File

@@ -0,0 +1,100 @@
#![allow(clippy::unwrap_used)]
use std::collections::HashMap;
use crate::expressions::parser::stringify::to_string;
use crate::expressions::utils::{number_to_column, parse_reference_a1};
use crate::types::{Table, TableColumn, TableStyleInfo};
use super::super::types::CellReferenceRC;
use super::Parser;
fn create_test_table(
table_name: &str,
column_names: &[&str],
cell_ref: &str,
row_count: i32,
) -> HashMap<String, Table> {
let mut table = HashMap::new();
let mut columns = Vec::new();
for (id, name) in column_names.iter().enumerate() {
columns.push(TableColumn {
id: id as u32,
name: name.to_string(),
..Default::default()
})
}
let init_cell = parse_reference_a1(cell_ref).unwrap();
let start_row = init_cell.row;
let start_column = number_to_column(init_cell.column).unwrap();
let end_column = number_to_column(init_cell.column + column_names.len() as i32).unwrap();
let end_row = start_row + row_count - 1;
let area_ref = format!("{start_column}{start_row}:{end_column}{end_row}");
table.insert(
table_name.to_string(),
Table {
name: table_name.to_string(),
display_name: table_name.to_string(),
sheet_name: "Sheet One".to_string(),
reference: area_ref,
totals_row_count: 0,
header_row_count: 1,
header_row_dxf_id: None,
data_dxf_id: None,
columns,
style_info: TableStyleInfo {
..Default::default()
},
totals_row_dxf_id: None,
has_filters: false,
},
);
table
}
#[test]
fn simple_table() {
let worksheets = vec!["Sheet One".to_string(), "Second Sheet".to_string()];
// This is a table A1:F3, the column F has a formula
let column_names = ["Jan", "Feb", "Mar", "Apr", "Dec", "Year"];
let row_count = 3;
let tables = create_test_table("tblIncome", &column_names, "A1", row_count);
let mut parser = Parser::new(worksheets, tables);
// Reference cell is 'Sheet One'!F2
let cell_reference = CellReferenceRC {
sheet: "Sheet One".to_string(),
row: 2,
column: 6,
};
let formula = "SUM(tblIncome[[#This Row],[Jan]:[Dec]])";
let t = parser.parse(formula, &Some(cell_reference.clone()));
assert_eq!(to_string(&t, &cell_reference), "SUM($A$2:$E$2)");
// Cell A3
let cell_reference = CellReferenceRC {
sheet: "Sheet One".to_string(),
row: 4,
column: 1,
};
let formula = "SUBTOTAL(109, tblIncome[Jan])";
let t = parser.parse(formula, &Some(cell_reference.clone()));
assert_eq!(to_string(&t, &cell_reference), "SUBTOTAL(109,$A$2:$A$3)");
// Cell A3 in 'Second Sheet'
let cell_reference = CellReferenceRC {
sheet: "Second Sheet".to_string(),
row: 4,
column: 1,
};
let formula = "SUBTOTAL(109, tblIncome[Jan])";
let t = parser.parse(formula, &Some(cell_reference.clone()));
assert_eq!(
to_string(&t, &cell_reference),
"SUBTOTAL(109,'Sheet One'!$A$2:$A$3)"
);
}

View File

@@ -0,0 +1,276 @@
use super::{move_formula::ref_is_in_area, Node};
use crate::expressions::types::{Area, CellReferenceIndex};
pub(crate) fn forward_references(
node: &mut Node,
context: &CellReferenceIndex,
source_area: &Area,
target_sheet: u32,
target_sheet_name: &str,
target_row: i32,
target_column: i32,
) {
match node {
Node::ReferenceKind {
sheet_name,
sheet_index: reference_sheet,
absolute_row,
absolute_column,
row: reference_row,
column: reference_column,
} => {
let reference_row_absolute = if *absolute_row {
*reference_row
} else {
*reference_row + context.row
};
let reference_column_absolute = if *absolute_column {
*reference_column
} else {
*reference_column + context.column
};
if ref_is_in_area(
*reference_sheet,
reference_row_absolute,
reference_column_absolute,
source_area,
) {
if *reference_sheet != target_sheet {
*sheet_name = Some(target_sheet_name.to_string());
*reference_sheet = target_sheet;
}
*reference_row = target_row + *reference_row - source_area.row;
*reference_column = target_column + *reference_column - source_area.column;
}
}
Node::RangeKind {
sheet_name,
sheet_index,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
let reference_row1 = if *absolute_row1 {
*row1
} else {
*row1 + context.row
};
let reference_column1 = if *absolute_column1 {
*column1
} else {
*column1 + context.column
};
let reference_row2 = if *absolute_row2 {
*row2
} else {
*row2 + context.row
};
let reference_column2 = if *absolute_column2 {
*column2
} else {
*column2 + context.column
};
if ref_is_in_area(*sheet_index, reference_row1, reference_column1, source_area)
&& ref_is_in_area(*sheet_index, reference_row2, reference_column2, source_area)
{
if *sheet_index != target_sheet {
*sheet_index = target_sheet;
*sheet_name = Some(target_sheet_name.to_string());
}
*row1 = target_row + *row1 - source_area.row;
*column1 = target_column + *column1 - source_area.column;
*row2 = target_row + *row2 - source_area.row;
*column2 = target_column + *column2 - source_area.column;
}
}
// Recurse
Node::OpRangeKind { left, right } => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpConcatenateKind { left, right } => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpSumKind {
kind: _,
left,
right,
} => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpProductKind {
kind: _,
left,
right,
} => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpPowerKind { left, right } => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::FunctionKind { kind: _, args } => {
for arg in args {
forward_references(
arg,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
}
Node::InvalidFunctionKind { name: _, args } => {
for arg in args {
forward_references(
arg,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
}
Node::CompareKind {
kind: _,
left,
right,
} => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::UnaryKind { kind: _, right } => {
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
// TODO: Not implemented
Node::ArrayKind(_) => {}
// Do nothing. Note: we could do a blanket _ => {}
Node::VariableKind(_) => {}
Node::ErrorKind(_) => {}
Node::ParseErrorKind { .. } => {}
Node::EmptyArgKind => {}
Node::BooleanKind(_) => {}
Node::NumberKind(_) => {}
Node::StringKind(_) => {}
Node::WrongReferenceKind { .. } => {}
Node::WrongRangeKind { .. } => {}
}
}