Merge pull request #749 from marcoarnulfo:fix/structured-ref-column-spaces

This commit is contained in:
Brandon Thomas 2026-10-13 00:08:04 -04:00
commit 773bd6c1ae
4 changed files with 51 additions and 2 deletions

View File

@ -1059,6 +1059,33 @@ fn tables_and_structured_refs() {
}
#[test]
fn structured_refs_to_columns_with_edge_spaces() {
let mut s = s();
s.execute("range.setValues", json!({"range": "A1", "values": [["Item", "Qty "], ["a", 2], ["b", 3]]})).unwrap();
s.execute("insert.table", json!({"range": "A1:B3", "hasHeaders": true})).unwrap();
s.execute("table.totalRow", json!({"on": true, "table": "Table1"})).unwrap();
assert_eq!(v(&s, "B4"), Value::Number(5.0));
for (cell, input) in [
("F1", "=SUM(Table1[Qty ])"),
("F2", "=Table1[[#Totals],[Qty ]]"),
("F3", "=SUBTOTAL(109,Table1[Qty ])"),
("F4", "=SUM(Table1[[Qty ]])"),
("F5", "=SUM(Table1[Qty])"),
("C2", "=Table1[@[Qty ]]*10"),
] {
s.execute("cell.set", json!({"cell": cell, "input": input})).unwrap();
}
let n = Value::Number;
assert_eq!([v(&s, "F1"), v(&s, "F2"), v(&s, "F3"), v(&s, "F4")], [n(5.0), n(5.0), n(5.0), n(5.0)]);
assert_eq!(v(&s, "F5"), Value::Error(gridcraft_core::CellError::Ref));
assert_eq!(v(&s, "C2"), n(20.0));
assert_eq!(s.execute("cell.get", json!({"cell": "F3"})).unwrap()["formula"], "=SUBTOTAL(109,Table1[[Qty ]])");
let r = s.execute("file.saveBytes", json!({"format": "xlsx"})).unwrap();
let b64 = r["base64"].as_str().unwrap().to_string();
s.execute("file.open", json!({"name": "x.xlsx", "base64": b64})).unwrap();
assert_eq!(s.execute("cell.get", json!({"cell": "F3"})).unwrap()["formula"], "=SUBTOTAL(109,Table1[[Qty ]])");
assert_eq!([v(&s, "F1"), v(&s, "F2"), v(&s, "F3")], [n(5.0), n(5.0), n(5.0)]);
fn table_rename_keeps_a_defined_name_with_the_same_name() {
let mut s = s();
s.execute("range.setValues", json!({"range": "A1", "values": [["Qty"], [7]]})).unwrap();

View File

@ -426,6 +426,9 @@ fn combine(op: BinOp, lhs: Expr, rhs: Expr) -> Expr {
pub fn parse_struct(table: &str, body: &str) -> Result<StructRef, String> {
let mut specifiers = Vec::new();
let mut cols: Vec<String> = Vec::new();
// Spaces at the edges of a column name are part of it (`Table1[Qty ]` names `Qty `), so the
// untrimmed body is what a lone column is read from.
let raw = body;
let body = body.trim();
let unescape = |s: &str| {
let mut out = String::new();
@ -477,7 +480,7 @@ pub fn parse_struct(table: &str, body: &str) -> Result<StructRef, String> {
} else if let Some(i) = item(body) {
specifiers.push(i);
} else if !body.is_empty() {
cols.push(column(body)?);
cols.push(column(raw)?);
}
} else {
// [[#Headers],[Col1]:[Col3]], [[#This Row],[Col 1]] or [[#This Row],Col1]

View File

@ -119,7 +119,12 @@ fn write_struct_ref(s: &StructRef, at_shorthand: bool) -> String {
let _ = write!(out, "[{}]", s.specifiers.first().map(item).unwrap_or(""));
}
(Some(c), None) if s.specifiers.is_empty() => {
let _ = write!(out, "[{}]", esc(c));
// Excel writes a name with spaces at its edges in the double-bracket form.
if c.trim() == c {
let _ = write!(out, "[{}]", esc(c));
} else {
let _ = write!(out, "[[{}]]", esc(c));
}
}
(Some(c), None) if this_row => {
if simple_col(c) {

View File

@ -274,6 +274,20 @@ fn unbracketed_this_row_column() {
}
}
#[test]
fn struct_column_keeps_edge_spaces() {
// The spaces are part of the column name: `Table1[Qty ]` names `Qty `, never `Qty`.
let Expr::Struct(s) = parse("Table1[Qty ]").unwrap() else { panic!() };
assert_eq!(s.col_start.as_deref(), Some("Qty "));
let Expr::Struct(s) = parse("Table1[ Qty]").unwrap() else { panic!() };
assert_eq!(s.col_start.as_deref(), Some(" Qty"));
// Excel writes such a column in the double-bracket form.
assert_eq!(rt("Table1[Qty ]"), "Table1[[Qty ]]");
for f in ["Table1[[Qty ]]", "Table1[[ Sum for Q2 ]]", "Table1[[#Totals],[Qty ]]", "Table1[@[Qty ]]", "Table1[Unit Price]"] {
assert_eq!(rt(f), f);
}
}
#[test]
fn trim_references() {
for f in [