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 { 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 { 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!("{}TRUE", result); } else { result = format!("{}FALSE", result); } } 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!("{}TRUE", result); } else { result = format!("{}FALSE", result); } } 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::()) } 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) } }