From 34125d7e43f572e13b57ef19e0e6d168730ea08c Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:27:26 +0200 Subject: [PATCH] Quote sheet names when printing sheet-qualified defined names (#567) --- crates/formula/src/adjust.rs | 7 +++---- crates/formula/src/printer.rs | 10 +++++++++- crates/formula/src/tests.rs | 11 +++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/formula/src/adjust.rs b/crates/formula/src/adjust.rs index 6f01e05..d4881aa 100644 --- a/crates/formula/src/adjust.rs +++ b/crates/formula/src/adjust.rs @@ -264,11 +264,10 @@ pub fn rename_sheet(e: Expr, old: &str, new: &str) -> Expr { } Expr::Ref(r) } - // Sheet-qualified defined name `Old!MyName` is stored as one string. + // Sheet-qualified defined name `Old!MyName` is stored as one string, with the bare sheet name as the parser + // stores it (the printer adds the quotes). Expr::Name(n) => match n.split_at_checked(old.len()) { - Some((head, rest)) if head.eq_ignore_ascii_case(old) && rest.starts_with('!') => { - Expr::Name(format!("{}{rest}", crate::printer::quote_sheet(new))) - } + Some((head, rest)) if head.eq_ignore_ascii_case(old) && rest.starts_with('!') => Expr::Name(format!("{new}{rest}")), _ => Expr::Name(n), }, other => other, diff --git a/crates/formula/src/printer.rs b/crates/formula/src/printer.rs index a63599b..a1ff7a7 100644 --- a/crates/formula/src/printer.rs +++ b/crates/formula/src/printer.rs @@ -178,7 +178,15 @@ fn write_expr(s: &mut String, e: &Expr, r1c1: Option) { Some(at) => s.push_str(&reference_r1c1(r, at)), None => s.push_str(&reference_a1(r)), }, - Expr::Name(n) => s.push_str(n), + Expr::Name(n) => match n.rsplit_once('!') { + // 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("e_sheet(sheet)); + s.push('!'); + s.push_str(name); + } + _ => s.push_str(n), + }, Expr::Struct(st) => s.push_str(&struct_ref(st)), Expr::Unary(op, x) => match op { UnOp::Neg => { diff --git a/crates/formula/src/tests.rs b/crates/formula/src/tests.rs index d3dd4c2..66e1f01 100644 --- a/crates/formula/src/tests.rs +++ b/crates/formula/src/tests.rs @@ -5,6 +5,13 @@ fn rt(src: &str) -> String { print(&parse(src).unwrap_or_else(|e| panic!("{src}: {e}"))) } +#[test] +fn quoted_sheet_qualified_name_keeps_quotes() { + assert_eq!(rt("'My Sheet'!Total"), "'My Sheet'!Total"); + assert_eq!(rt("'Data'!Total"), "Data!Total"); + assert_eq!(rt("'It''s'!Total+1"), "'It''s'!Total+1"); +} + #[test] fn roundtrips() { for f in [ @@ -183,6 +190,10 @@ fn rename_sheet_updates_qualified_names() { assert_eq!(print(&rename_sheet(e, "data", "Numbers")), "Numbers!MyName+MyName+Other!MyName"); let e = parse("Data!MyName").unwrap(); assert_eq!(print(&rename_sheet(e, "Data", "New Name")), "'New Name'!MyName"); + // Stored bare, like a parsed name, so the evaluator finds the sheet and a second rename still matches. + let e = rename_sheet(parse("Data!MyName").unwrap(), "Data", "It's"); + assert_eq!(e, Expr::Name("It's!MyName".into())); + assert_eq!(print(&rename_sheet(e, "It's", "Totals")), "Totals!MyName"); } #[test]