diff --git a/crates/numfmt/src/lib.rs b/crates/numfmt/src/lib.rs index 52cada2c7..99c754521 100644 --- a/crates/numfmt/src/lib.rs +++ b/crates/numfmt/src/lib.rs @@ -425,6 +425,9 @@ impl NumberFormat { /// The `TEXT()` worksheet function: formats a value with a format code. Numeric text is /// converted to a number first; other text and booleans (as `TRUE`/`FALSE`) go through the text /// section, or are returned as is. +/// Longest text a cell holds, in characters (Excel's limit), and so the longest `TEXT` result. +const MAX_CELL_TEXT_CHARS: usize = 32_767; + pub fn text_function(v: &Value, code: &str, sys: DateSystem) -> Result { if code.is_empty() { return Ok(String::new()); @@ -440,7 +443,15 @@ pub fn text_function(v: &Value, code: &str, sys: DateSystem) -> Result return Ok(fmt.format_text(if b { "TRUE" } else { "FALSE" }).text), Value::Text(t) => match gridcraft_core::parse::parse_number_text(&t) { Some(n) => n, - None => return Ok(fmt.format_text(&t).text), + // Every `@` repeats the text: a result past a cell's limit is `#VALUE!`, so nested + // TEXT calls can't multiply it further. + None => { + let out = fmt.format_text(&t).text; + if out.len() > MAX_CELL_TEXT_CHARS && out.chars().count() > MAX_CELL_TEXT_CHARS { + return Err(CellError::Value); + } + return Ok(out); + } }, Value::Array(_) => return Err(CellError::Value), }; diff --git a/crates/numfmt/src/tests.rs b/crates/numfmt/src/tests.rs index 4b2ee089d..8a738aab7 100644 --- a/crates/numfmt/src/tests.rs +++ b/crates/numfmt/src/tests.rs @@ -396,6 +396,10 @@ fn text_fn() { assert_eq!(text_function(&Value::Number(1234.5), "$#,##0.00", S), Ok("$1,234.50".into())); assert_eq!(text_function(&Value::from("5"), "0.00", S), Ok("5.00".into())); assert_eq!(text_function(&Value::from("abc"), "0.00", S), Ok("abc".into())); + // Each `@` repeats the text: up to a cell's 32,767 characters, then #VALUE!. + let at = "@".repeat(255); + assert_eq!(text_function(&Value::from("x".repeat(128).as_str()), &at, S).map(|s| s.len()), Ok(128 * 255)); + assert_eq!(text_function(&Value::from("x".repeat(32_000).as_str()), &at, S), Err(CellError::Value)); assert_eq!(text_function(&Value::Error(CellError::NA), "0", S), Err(CellError::NA)); assert_eq!(text_function(&Value::Number(46302.0), "yyyy", S), Ok("2026".into())); assert_eq!(text_function(&Value::Number(0.5), "General", S), Ok("0.5".into()));