XLSX: a Python script's length is measured after its escapes are decoded

A script's code was cut at the character limit before its ST_Xstring escapes
were decoded, so a script whose escapes (`_x0009_`, `_x000D_`) made the file's
text longer than the limit lost its end without a warning, although its code
fits. The code is now decoded first and then cut one character past the limit,
so a script that is too long once decoded still fails the check and its cell
keeps only its value, as before. Decoding is linear and reads at most a bounded
number of encoded characters, enough for any code that fits.
This commit is contained in:
prxnvt
2026-11-01 12:58:13 -05:00
parent e82e91b451
commit 84d4f6aa84
2 changed files with 35 additions and 3 deletions
+19
View File
@@ -317,6 +317,25 @@ fn a_python_cell_without_its_code_keeps_its_whole_cached_result_as_values() {
}
}
#[test]
fn a_script_is_measured_after_its_escapes_are_decoded() {
let max = gridcraft_model::python::MAX_FORMULA_CHARS;
// 2,000 tabs written as `_x0009_`: 14,000 characters in the file, 2,004 as code, which fits.
let escaped = format!("x = 1{}end", "_x0009_".repeat(2_000));
assert!(escaped.chars().count() > max);
// Code longer than a cell may hold once decoded is still refused.
let long = format!("{}{}", "_x0009_".repeat(10), "y".repeat(max));
let data = "<sheetData><row r=\"1\"><c r=\"A1\"><f>_xlfn._xlws.PY(0,0)</f><v>1</v></c><c r=\"B1\"><f>_xlfn._xlws.PY(1,0)</f><v>2</v></c></row></sheetData>";
let (wb, warnings) = crate::io::open_bytes("escapes.xlsx", &excel_shaped(data, Some(&[&escaped, &long]))).unwrap();
let sh = wb.sheet(0).unwrap();
let a1 = sh.cell(CellRef::parse("A1").unwrap()).unwrap().py.as_ref().map(|p| p.code.clone());
assert_eq!(a1, Some(format!("x = 1{}end", "\t".repeat(2_000))), "read in full");
let b1 = sh.cell(CellRef::parse("B1").unwrap()).unwrap();
assert!(b1.py.is_none(), "too long once decoded: only the value");
assert_eq!(b1.value, Value::Number(2.0));
assert!(warnings.iter().any(|w| w.contains("B1")), "{warnings:?}");
}
#[test]
fn many_cells_naming_one_long_script_take_from_one_budget() {
// 20,000 cells name one script of 8,000 characters: 160 MB of code if each took a copy.