Keep '!' in structured-reference columns intact on XLSX save

Found while testing crates/xlsx/src/fmla.rs:101 (with crates/formula/src/printer.rs:181)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-22 09:47:21 +02:00
co-authored by Claude Opus 5.5
parent 06920de8da
commit c39d2ad197
2 changed files with 35 additions and 1 deletions
+23 -1
View File
@@ -85,6 +85,28 @@ pub fn struct_ref_file(s: &StructRef) -> String {
write_struct_ref(s, false)
}
/// Splits a sheet-qualified name at its last `!` outside brackets. A `!` inside brackets
/// belongs to a structured reference's column (`Table1[Done!]`, `Table1[#REF!]`).
fn sheet_split(n: &str) -> Option<(&str, &str)> {
let mut depth = 0usize;
let mut escaped = false;
let mut at = None;
for (i, c) in n.char_indices() {
if escaped {
escaped = false;
continue;
}
match c {
'\'' if depth > 0 => escaped = true,
'[' => depth = depth.saturating_add(1),
']' => depth = depth.saturating_sub(1),
'!' if depth == 0 => at = Some(i),
_ => {}
}
}
at.and_then(|i| Some((n.get(..i)?, n.get(i + 1..)?)))
}
fn write_struct_ref(s: &StructRef, at_shorthand: bool) -> String {
let esc = |c: &str| c.replace('\'', "''").replace('[', "'[").replace(']', "']").replace('#', "'#");
let item = |i: &StructItem| match i {
@@ -178,7 +200,7 @@ fn write_expr(s: &mut String, e: &Expr, r1c1: Option<CellRef>) {
Some(at) => s.push_str(&reference_r1c1(r, at)),
None => s.push_str(&reference_a1(r)),
},
Expr::Name(n) => match n.rsplit_once('!') {
Expr::Name(n) => match sheet_split(n) {
// A sheet-qualified name is stored with the bare sheet name (`My Sheet!Total`): quote it so it parses back.
Some((sheet, name)) => {
s.push_str(&quote_sheet(sheet));
+12
View File
@@ -183,6 +183,18 @@ mod tests {
}
}
#[test]
fn struct_ref_column_with_bang() {
// A `!` inside the brackets is part of the column name, not a sheet separator.
assert_eq!(to_file(&Formula::new("SUM(Table1[Done!])")), "SUM(Table1[Done!])");
assert_eq!(text_to_file("=Table1[[#This Row],[a!b]]"), "Table1[[#This Row],[a!b]]");
// `Table1[#REF!]` was found by the command-sequence fuzzer (seed 40).
for text in ["SUM(Table1[Done!])", "Table1[#REF!]", "Table1[@[x!]]", "Sheet2!A1+Table1[[#All],[a!b]]"] {
let f = Formula::new(text);
assert_eq!(from_file(&to_file(&f)), f.text);
}
}
#[test]
fn prefixes() {
assert_eq!(to_file(&Formula::new("XLOOKUP(1,A:A,B:B)+SUM(A1)")), "_xlfn.XLOOKUP(1,A:A,B:B)+SUM(A1)");