1274 lines
49 KiB
Rust
1274 lines
49 KiB
Rust
use crate::{
|
|
calc_result::CalcResult,
|
|
constants::{LAST_COLUMN, LAST_ROW},
|
|
expressions::{parser::Node, token::Error, types::CellReferenceIndex},
|
|
formatter::format::{format_number, parse_formatted_number},
|
|
model::Model,
|
|
number_format::to_precision,
|
|
};
|
|
|
|
use super::{
|
|
text_util::{substitute, text_after, text_before, Case},
|
|
util::from_wildcard_to_regex,
|
|
};
|
|
|
|
/// Finds the first instance of 'search_for' in text starting at char index start
|
|
fn find(search_for: &str, text: &str, start: usize) -> Option<i32> {
|
|
let ch = text.chars();
|
|
let mut byte_index = 0;
|
|
for (char_index, c) in ch.enumerate() {
|
|
if char_index + 1 >= start && text[byte_index..].starts_with(search_for) {
|
|
return Some((char_index + 1) as i32);
|
|
}
|
|
byte_index += c.len_utf8();
|
|
}
|
|
None
|
|
}
|
|
|
|
/// You can use the wildcard characters — the question mark (?) and asterisk (*) — in the find_text argument.
|
|
/// * A question mark matches any single character.
|
|
/// * An asterisk matches any sequence of characters.
|
|
/// * If you want to find an actual question mark or asterisk, type a tilde (~) before the character.
|
|
fn search(search_for: &str, text: &str, start: usize) -> Option<i32> {
|
|
let re = match from_wildcard_to_regex(search_for, false) {
|
|
Ok(r) => r,
|
|
Err(_) => return None,
|
|
};
|
|
|
|
let ch = text.chars();
|
|
let mut byte_index = 0;
|
|
for (char_index, c) in ch.enumerate() {
|
|
if char_index + 1 >= start {
|
|
if let Some(m) = re.find(&text[byte_index..]) {
|
|
return Some((text[0..(m.start() + byte_index)].chars().count() as i32) + 1);
|
|
} else {
|
|
return None;
|
|
}
|
|
}
|
|
byte_index += c.len_utf8();
|
|
}
|
|
None
|
|
}
|
|
|
|
impl Model {
|
|
pub(crate) fn fn_concat(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let mut result = "".to_string();
|
|
for arg in args {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::String(value) => result = format!("{result}{value}"),
|
|
CalcResult::Number(value) => result = format!("{result}{value}"),
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
|
|
CalcResult::Boolean(value) => {
|
|
if value {
|
|
result = format!("{result}TRUE");
|
|
} else {
|
|
result = format!("{result}FALSE");
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
for row in left.row..(right.row + 1) {
|
|
for column in left.column..(right.column + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::String(value) => {
|
|
result = format!("{result}{value}");
|
|
}
|
|
CalcResult::Number(value) => result = format!("{result}{value}"),
|
|
CalcResult::Boolean(value) => {
|
|
if value {
|
|
result = format!("{result}TRUE");
|
|
} else {
|
|
result = format!("{result}FALSE");
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
|
|
CalcResult::Range { .. } => {}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
}
|
|
CalcResult::String(result)
|
|
}
|
|
pub(crate) fn fn_text(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 2 {
|
|
let value = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(f) => f,
|
|
CalcResult::String(s) => {
|
|
return CalcResult::String(s);
|
|
}
|
|
CalcResult::Boolean(b) => {
|
|
return CalcResult::Boolean(b);
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => 0.0,
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
let format_code = match self.get_string(&args[1], cell) {
|
|
Ok(s) => s,
|
|
Err(s) => return s,
|
|
};
|
|
let d = format_number(value, &format_code, &self.locale);
|
|
if let Some(_e) = d.error {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Invalid format code".to_string(),
|
|
};
|
|
}
|
|
CalcResult::String(d.text)
|
|
} else {
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
}
|
|
|
|
/// FIND(find_text, within_text, [start_num])
|
|
/// * FIND and FINDB are case sensitive and don't allow wildcard characters.
|
|
/// * If find_text is "" (empty text), FIND matches the first character in the search string (that is, the character numbered start_num or 1).
|
|
/// * Find_text cannot contain any wildcard characters.
|
|
/// * If find_text does not appear in within_text, FIND and FINDB return the #VALUE! error value.
|
|
/// * If start_num is not greater than zero, FIND and FINDB return the #VALUE! error value.
|
|
/// * If start_num is greater than the length of within_text, FIND and FINDB return the #VALUE! error value.
|
|
/// NB: FINDB is not implemented. It is the same as FIND function unless locale is a DBCS (Double Byte Character Set)
|
|
pub(crate) fn fn_find(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() < 2 || args.len() > 3 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let find_text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(s) => return s,
|
|
};
|
|
let within_text = match self.get_string(&args[1], cell) {
|
|
Ok(s) => s,
|
|
Err(s) => return s,
|
|
};
|
|
let start_num = if args.len() == 3 {
|
|
match self.get_number(&args[2], cell) {
|
|
Ok(s) => s.floor(),
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
if start_num < 1.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Start num must be >= 1".to_string(),
|
|
};
|
|
}
|
|
let start_num = start_num as usize;
|
|
|
|
if start_num > within_text.len() {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Start num greater than length".to_string(),
|
|
};
|
|
}
|
|
if let Some(s) = find(&find_text, &within_text, start_num) {
|
|
CalcResult::Number(s as f64)
|
|
} else {
|
|
CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Text not found".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Same API as FIND but:
|
|
/// * Allows wildcards
|
|
/// * It is case insensitive
|
|
/// SEARCH(find_text, within_text, [start_num])
|
|
pub(crate) fn fn_search(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() < 2 || args.len() > 3 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let find_text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(s) => return s,
|
|
};
|
|
let within_text = match self.get_string(&args[1], cell) {
|
|
Ok(s) => s,
|
|
Err(s) => return s,
|
|
};
|
|
let start_num = if args.len() == 3 {
|
|
match self.get_number(&args[2], cell) {
|
|
Ok(s) => s.floor(),
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
if start_num < 1.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Start num must be >= 1".to_string(),
|
|
};
|
|
}
|
|
let start_num = start_num as usize;
|
|
|
|
if start_num > within_text.len() {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Start num greater than length".to_string(),
|
|
};
|
|
}
|
|
// SEARCH is case insensitive
|
|
if let Some(s) = search(
|
|
&find_text.to_lowercase(),
|
|
&within_text.to_lowercase(),
|
|
start_num,
|
|
) {
|
|
CalcResult::Number(s as f64)
|
|
} else {
|
|
CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Text not found".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// LEN, LEFT, RIGHT, MID, LOWER, UPPER, TRIM
|
|
pub(crate) fn fn_len(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 1 {
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
return CalcResult::Number(s.chars().count() as f64);
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_trim(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 1 {
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
return CalcResult::String(s.trim().to_owned());
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_lower(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 1 {
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
return CalcResult::String(s.to_lowercase());
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_unicode(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 1 {
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Empty cell".to_string(),
|
|
}
|
|
}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
|
|
match s.chars().next() {
|
|
Some(c) => {
|
|
let unicode_number = c as u32;
|
|
return CalcResult::Number(unicode_number as f64);
|
|
}
|
|
None => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Empty cell".to_string(),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_upper(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 1 {
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
return CalcResult::String(s.to_uppercase());
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_left(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() > 2 || args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
let num_chars = if args.len() == 2 {
|
|
match self.evaluate_node_in_context(&args[1], cell) {
|
|
CalcResult::Number(v) => {
|
|
if v < 0.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Number must be >= 0".to_string(),
|
|
};
|
|
}
|
|
v.floor() as usize
|
|
}
|
|
CalcResult::Boolean(_) | CalcResult::String(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Expecting number".to_string(),
|
|
};
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => 0,
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
1
|
|
};
|
|
let mut result = "".to_string();
|
|
for (index, ch) in s.chars().enumerate() {
|
|
if index >= num_chars {
|
|
break;
|
|
}
|
|
result.push(ch);
|
|
}
|
|
CalcResult::String(result)
|
|
}
|
|
|
|
pub(crate) fn fn_right(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() > 2 || args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
let num_chars = if args.len() == 2 {
|
|
match self.evaluate_node_in_context(&args[1], cell) {
|
|
CalcResult::Number(v) => {
|
|
if v < 0.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Number must be >= 0".to_string(),
|
|
};
|
|
}
|
|
v.floor() as usize
|
|
}
|
|
CalcResult::Boolean(_) | CalcResult::String(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Expecting number".to_string(),
|
|
};
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => 0,
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
1
|
|
};
|
|
let mut result = "".to_string();
|
|
for (index, ch) in s.chars().rev().enumerate() {
|
|
if index >= num_chars {
|
|
break;
|
|
}
|
|
result.push(ch);
|
|
}
|
|
CalcResult::String(result.chars().rev().collect::<String>())
|
|
}
|
|
|
|
pub(crate) fn fn_mid(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() != 3 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::Number(v) => format!("{v}"),
|
|
CalcResult::String(v) => v,
|
|
CalcResult::Boolean(b) => {
|
|
if b {
|
|
"TRUE".to_string()
|
|
} else {
|
|
"FALSE".to_string()
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => "".to_string(),
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
let start_num = match self.evaluate_node_in_context(&args[1], cell) {
|
|
CalcResult::Number(v) => {
|
|
if v < 1.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Number must be >= 1".to_string(),
|
|
};
|
|
}
|
|
v.floor() as usize
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
_ => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Expecting number".to_string(),
|
|
};
|
|
}
|
|
};
|
|
let num_chars = match self.evaluate_node_in_context(&args[2], cell) {
|
|
CalcResult::Number(v) => {
|
|
if v < 0.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Number must be >= 0".to_string(),
|
|
};
|
|
}
|
|
v.floor() as usize
|
|
}
|
|
CalcResult::String(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Expecting number".to_string(),
|
|
};
|
|
}
|
|
CalcResult::Boolean(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Expecting number".to_string(),
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::Range { .. } => {
|
|
// Implicit Intersection not implemented
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Implicit Intersection not implemented".to_string(),
|
|
};
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => 0,
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
let mut result = "".to_string();
|
|
let mut count: usize = 0;
|
|
for (index, ch) in s.chars().enumerate() {
|
|
if count >= num_chars {
|
|
break;
|
|
}
|
|
if index + 1 >= start_num {
|
|
result.push(ch);
|
|
count += 1;
|
|
}
|
|
}
|
|
CalcResult::String(result)
|
|
}
|
|
|
|
// REPT(text, number_times)
|
|
pub(crate) fn fn_rept(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() != 2 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let number_times = match self.get_number(&args[1], cell) {
|
|
Ok(f) => f.floor() as i32,
|
|
Err(s) => return s,
|
|
};
|
|
let text_len = text.len() as i32;
|
|
|
|
// We normally don't follow Excel's sometimes archaic size's restrictions
|
|
// But this might be a security issue
|
|
if text_len * number_times > 32767 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "number times too high".to_string(),
|
|
};
|
|
}
|
|
if number_times < 0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "number times too high".to_string(),
|
|
};
|
|
}
|
|
if number_times == 0 {
|
|
return CalcResult::String("".to_string());
|
|
}
|
|
CalcResult::String(text.repeat(number_times as usize))
|
|
}
|
|
|
|
// TEXTAFTER(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found])
|
|
pub(crate) fn fn_textafter(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let arg_count = args.len();
|
|
if !(2..=6).contains(&arg_count) {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let delimiter = match self.get_string(&args[1], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let instance_num = if arg_count > 2 {
|
|
match self.get_number(&args[2], cell) {
|
|
Ok(f) => f.floor() as i32,
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
1
|
|
};
|
|
let match_mode = if arg_count > 3 {
|
|
match self.get_number(&args[3], cell) {
|
|
Ok(f) => {
|
|
if f == 0.0 {
|
|
Case::Sensitive
|
|
} else {
|
|
Case::Insensitive
|
|
}
|
|
}
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
Case::Sensitive
|
|
};
|
|
|
|
let match_end = if arg_count > 4 {
|
|
match self.get_number(&args[4], cell) {
|
|
Ok(f) => f,
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
// disabled by default
|
|
// the delimiter is specified in the formula
|
|
0.0
|
|
};
|
|
if instance_num == 0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "instance_num must be <> 0".to_string(),
|
|
};
|
|
}
|
|
if delimiter.len() > text.len() {
|
|
// so this is fun(!)
|
|
// if the function was provided with two arguments is a #VALUE!
|
|
// if it had more is a #N/A (irrespective of their values)
|
|
if arg_count > 2 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "The delimiter is longer than the text is trying to match".to_string(),
|
|
};
|
|
} else {
|
|
return CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "The delimiter is longer than the text is trying to match".to_string(),
|
|
};
|
|
}
|
|
}
|
|
if match_end != 0.0 && match_end != 1.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "argument must be 0 or 1".to_string(),
|
|
};
|
|
};
|
|
match text_after(&text, &delimiter, instance_num, match_mode) {
|
|
Some(s) => CalcResult::String(s),
|
|
None => {
|
|
if match_end == 1.0 {
|
|
if instance_num == 1 {
|
|
return CalcResult::String("".to_string());
|
|
} else if instance_num == -1 {
|
|
return CalcResult::String(text);
|
|
}
|
|
}
|
|
if arg_count == 6 {
|
|
// An empty cell is converted to empty string (not 0)
|
|
match self.evaluate_node_in_context(&args[5], cell) {
|
|
CalcResult::EmptyCell => CalcResult::String("".to_string()),
|
|
result => result,
|
|
}
|
|
} else {
|
|
CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "Value not found".to_string(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn fn_textbefore(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let arg_count = args.len();
|
|
if !(2..=6).contains(&arg_count) {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let delimiter = match self.get_string(&args[1], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let instance_num = if arg_count > 2 {
|
|
match self.get_number(&args[2], cell) {
|
|
Ok(f) => f.floor() as i32,
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
1
|
|
};
|
|
let match_mode = if arg_count > 3 {
|
|
match self.get_number(&args[3], cell) {
|
|
Ok(f) => {
|
|
if f == 0.0 {
|
|
Case::Sensitive
|
|
} else {
|
|
Case::Insensitive
|
|
}
|
|
}
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
Case::Sensitive
|
|
};
|
|
|
|
let match_end = if arg_count > 4 {
|
|
match self.get_number(&args[4], cell) {
|
|
Ok(f) => f,
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
// disabled by default
|
|
// the delimiter is specified in the formula
|
|
0.0
|
|
};
|
|
if instance_num == 0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "instance_num must be <> 0".to_string(),
|
|
};
|
|
}
|
|
if delimiter.len() > text.len() {
|
|
// so this is fun(!)
|
|
// if the function was provided with two arguments is a #VALUE!
|
|
// if it had more is a #N/A (irrespective of their values)
|
|
if arg_count > 2 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "The delimiter is longer than the text is trying to match".to_string(),
|
|
};
|
|
} else {
|
|
return CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "The delimiter is longer than the text is trying to match".to_string(),
|
|
};
|
|
}
|
|
}
|
|
if match_end != 0.0 && match_end != 1.0 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "argument must be 0 or 1".to_string(),
|
|
};
|
|
};
|
|
match text_before(&text, &delimiter, instance_num, match_mode) {
|
|
Some(s) => CalcResult::String(s),
|
|
None => {
|
|
if match_end == 1.0 {
|
|
if instance_num == -1 {
|
|
return CalcResult::String("".to_string());
|
|
} else if instance_num == 1 {
|
|
return CalcResult::String(text);
|
|
}
|
|
}
|
|
if arg_count == 6 {
|
|
// An empty cell is converted to empty string (not 0)
|
|
match self.evaluate_node_in_context(&args[5], cell) {
|
|
CalcResult::EmptyCell => CalcResult::String("".to_string()),
|
|
result => result,
|
|
}
|
|
} else {
|
|
CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "Value not found".to_string(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TEXTJOIN(delimiter, ignore_empty, text1, [text2], …)
|
|
pub(crate) fn fn_textjoin(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let arg_count = args.len();
|
|
if arg_count < 3 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let delimiter = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let ignore_empty = match self.get_boolean(&args[1], cell) {
|
|
Ok(b) => b,
|
|
Err(error) => return error,
|
|
};
|
|
let mut values = Vec::new();
|
|
for arg in &args[2..] {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Number(value) => values.push(format!("{value}")),
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
let row1 = left.row;
|
|
let mut row2 = right.row;
|
|
let column1 = left.column;
|
|
let mut column2 = right.column;
|
|
if row1 == 1 && row2 == LAST_ROW {
|
|
row2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_row,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
if column1 == 1 && column2 == LAST_COLUMN {
|
|
column2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_column,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
for row in row1..row2 + 1 {
|
|
for column in column1..(column2 + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::Number(value) => {
|
|
values.push(format!("{value}"));
|
|
}
|
|
CalcResult::String(value) => values.push(value),
|
|
CalcResult::Boolean(value) => {
|
|
if value {
|
|
values.push("TRUE".to_string())
|
|
} else {
|
|
values.push("FALSE".to_string())
|
|
}
|
|
}
|
|
CalcResult::EmptyCell => {
|
|
if !ignore_empty {
|
|
values.push("".to_string())
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::EmptyArg | CalcResult::Range { .. } => {}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::String(value) => values.push(value),
|
|
CalcResult::Boolean(value) => {
|
|
if value {
|
|
values.push("TRUE".to_string())
|
|
} else {
|
|
values.push("FALSE".to_string())
|
|
}
|
|
}
|
|
CalcResult::EmptyCell => {
|
|
if !ignore_empty {
|
|
values.push("".to_string())
|
|
}
|
|
}
|
|
CalcResult::EmptyArg => {}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
};
|
|
}
|
|
let result = values.join(&delimiter);
|
|
CalcResult::String(result)
|
|
}
|
|
|
|
// SUBSTITUTE(text, old_text, new_text, [instance_num])
|
|
pub(crate) fn fn_substitute(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let arg_count = args.len();
|
|
if !(2..=4).contains(&arg_count) {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let old_text = match self.get_string(&args[1], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let new_text = match self.get_string(&args[2], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let instance_num = if arg_count > 3 {
|
|
match self.get_number(&args[3], cell) {
|
|
Ok(f) => Some(f.floor() as i32),
|
|
Err(s) => return s,
|
|
}
|
|
} else {
|
|
// means every instance is replaced
|
|
None
|
|
};
|
|
if let Some(num) = instance_num {
|
|
if num < 1 {
|
|
return CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Invalid value".to_string(),
|
|
};
|
|
}
|
|
if old_text.is_empty() {
|
|
return CalcResult::String(text);
|
|
}
|
|
CalcResult::String(substitute(&text, &old_text, &new_text, num))
|
|
} else {
|
|
if old_text.is_empty() {
|
|
return CalcResult::String(text);
|
|
}
|
|
CalcResult::String(text.replace(&old_text, &new_text))
|
|
}
|
|
}
|
|
pub(crate) fn fn_concatenate(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let arg_count = args.len();
|
|
if arg_count == 0 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let mut text_array = Vec::new();
|
|
for arg in args {
|
|
let text = match self.get_string(arg, cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
text_array.push(text)
|
|
}
|
|
CalcResult::String(text_array.join(""))
|
|
}
|
|
|
|
pub(crate) fn fn_exact(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() != 2 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let result1 = &self.evaluate_node_in_context(&args[0], cell);
|
|
let result2 = &self.evaluate_node_in_context(&args[1], cell);
|
|
// FIXME: Implicit intersection
|
|
if let (CalcResult::Number(number1), CalcResult::Number(number2)) = (result1, result2) {
|
|
// In Excel two numbers are the same if they are the same up to 15 digits.
|
|
CalcResult::Boolean(to_precision(*number1, 15) == to_precision(*number2, 15))
|
|
} else {
|
|
let string1 = match self.cast_to_string(result1.clone(), cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
let string2 = match self.cast_to_string(result2.clone(), cell) {
|
|
Ok(s) => s,
|
|
Err(error) => return error,
|
|
};
|
|
CalcResult::Boolean(string1 == string2)
|
|
}
|
|
}
|
|
// VALUE(text)
|
|
pub(crate) fn fn_value(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() != 1 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
match self.evaluate_node_in_context(&args[0], cell) {
|
|
CalcResult::String(text) => {
|
|
let currencies = vec!["$", "€"];
|
|
if let Ok((value, _)) = parse_formatted_number(&text, ¤cies) {
|
|
return CalcResult::Number(value);
|
|
};
|
|
CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Invalid number".to_string(),
|
|
}
|
|
}
|
|
CalcResult::Number(f) => CalcResult::Number(f),
|
|
CalcResult::Boolean(_) => CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Invalid number".to_string(),
|
|
},
|
|
error @ CalcResult::Error { .. } => error,
|
|
CalcResult::Range { .. } => {
|
|
// TODO Implicit Intersection
|
|
CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: "Invalid number".to_string(),
|
|
}
|
|
}
|
|
CalcResult::EmptyCell | CalcResult::EmptyArg => CalcResult::Number(0.0),
|
|
CalcResult::Array(_) => CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn fn_t(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() != 1 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
// FIXME: Implicit intersection
|
|
let result = self.evaluate_node_in_context(&args[0], cell);
|
|
match result {
|
|
CalcResult::String(_) => result,
|
|
error @ CalcResult::Error { .. } => error,
|
|
_ => CalcResult::String("".to_string()),
|
|
}
|
|
}
|
|
|
|
// VALUETOTEXT(value)
|
|
pub(crate) fn fn_valuetotext(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() != 1 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let text = match self.get_string(&args[0], cell) {
|
|
Ok(s) => s,
|
|
Err(error) => match error {
|
|
CalcResult::Error { error, .. } => error.to_string(),
|
|
_ => "".to_string(),
|
|
},
|
|
};
|
|
CalcResult::String(text)
|
|
}
|
|
}
|