From 5ff4774c5a5ce2915036837cf9024d805fe60770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Sun, 16 Nov 2025 17:17:35 +0100 Subject: [PATCH] FIX: Cast to string now checks for dates, currencies or percentages Fixes part of #535 --- base/src/cast.rs | 26 ++++++++++++++----- base/src/formatter/format.rs | 7 ++--- base/src/functions/mathematical.rs | 27 ++++++++++++++++++-- xlsx/tests/calc_tests/MROUND_edgecases.xlsx | Bin 0 -> 12495 bytes 4 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 xlsx/tests/calc_tests/MROUND_edgecases.xlsx diff --git a/base/src/cast.rs b/base/src/cast.rs index 06cb2c9..6f8a528 100644 --- a/base/src/cast.rs +++ b/base/src/cast.rs @@ -5,6 +5,7 @@ use crate::{ token::Error, types::CellReferenceIndex, }, + formatter::format::parse_formatted_number, model::Model, }; @@ -89,20 +90,31 @@ impl Model { self.cast_to_number(result, cell) } - fn cast_to_number( + pub(crate) fn cast_to_number( &mut self, result: CalcResult, cell: CellReferenceIndex, ) -> Result { match result { CalcResult::Number(f) => Ok(f), - CalcResult::String(s) => match s.parse::() { + CalcResult::String(s) => match s.trim().parse::() { Ok(f) => Ok(f), - _ => Err(CalcResult::new_error( - Error::VALUE, - cell, - "Expecting number".to_string(), - )), + _ => { + let mut currencies = vec!["$", "€"]; + let currency = &self.locale.currency.symbol; + if !currencies.iter().any(|e| e == currency) { + currencies.push(currency); + } + // We try to parse as number + if let Ok((v, _number_format)) = parse_formatted_number(&s, ¤cies) { + return Ok(v); + } + Err(CalcResult::new_error( + Error::VALUE, + cell, + "Expecting number".to_string(), + )) + } }, CalcResult::Boolean(f) => { if f { diff --git a/base/src/formatter/format.rs b/base/src/formatter/format.rs index cfa7d01..1e0865a 100644 --- a/base/src/formatter/format.rs +++ b/base/src/formatter/format.rs @@ -744,10 +744,10 @@ fn parse_date(value: &str) -> Result<(i32, String), String> { /// "30.34%" => (0.3034, "0.00%") /// 100€ => (100, "100€") pub(crate) fn parse_formatted_number( - value: &str, + original: &str, currencies: &[&str], ) -> Result<(f64, Option), String> { - let value = value.trim(); + let value = original.trim(); let scientific_format = "0.00E+00"; // Check if it is a percentage @@ -799,7 +799,8 @@ pub(crate) fn parse_formatted_number( } } - if let Ok((serial_number, format)) = parse_date(value) { + // check if it is a date. NOTE: we don't trim the original here + if let Ok((serial_number, format)) = parse_date(original) { return Ok((serial_number as f64, Some(format))); } diff --git a/base/src/functions/mathematical.rs b/base/src/functions/mathematical.rs index a03dc0b..fa35669 100644 --- a/base/src/functions/mathematical.rs +++ b/base/src/functions/mathematical.rs @@ -1163,11 +1163,34 @@ impl Model { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } - let value = match self.get_number(&args[0], cell) { + + let value = self.evaluate_node_in_context(&args[0], cell); + + let multiple = self.evaluate_node_in_context(&args[1], cell); + + // if both are empty => #N/A + if matches!(value, CalcResult::EmptyArg) || matches!(multiple, CalcResult::EmptyArg) { + return CalcResult::Error { + error: Error::NA, + origin: cell, + message: "Bad argument for MROUND".to_string(), + }; + } + + // Booleans are not cast + if matches!(value, CalcResult::Boolean(_)) { + return CalcResult::new_error(Error::VALUE, cell, "Expecting number".to_string()); + } + + if matches!(multiple, CalcResult::Boolean(_)) { + return CalcResult::new_error(Error::VALUE, cell, "Expecting number".to_string()); + } + + let value = match self.cast_to_number(value, cell) { Ok(f) => f, Err(s) => return s, }; - let multiple = match self.get_number(&args[1], cell) { + let multiple = match self.cast_to_number(multiple, cell) { Ok(f) => f, Err(s) => return s, }; diff --git a/xlsx/tests/calc_tests/MROUND_edgecases.xlsx b/xlsx/tests/calc_tests/MROUND_edgecases.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..757ac8a0e0a066644f21f96b9dbd8d2897df59bf GIT binary patch literal 12495 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 TSO5UR>+9(?S#>h}_UZos%Z@G4 literal 0 HcmV?d00001