Names: refuse R1C1-style names

valid_name refused R and C but took R1C1, R2, C3 and RC, which Excel
refuses because they read as R1C1 references.
This commit is contained in:
nihilist-narwhal
2026-10-09 23:27:38 -05:00
parent 13664fe66b
commit 8029433b1c
2 changed files with 26 additions and 4 deletions
+15 -4
View File
@@ -218,15 +218,26 @@ fn valid_name(n: &str) -> bool {
if !(first.is_alphabetic() || first == '_' || first == '\\') {
return false;
}
if n.len() > 255 || n.eq_ignore_ascii_case("R") || n.eq_ignore_ascii_case("C") {
return false;
}
if CellRef::parse(n).is_some() {
if n.len() > 255 || CellRef::parse(n).is_some() || is_r1c1(n) {
return false;
}
n.chars().all(|c| c.is_alphanumeric() || matches!(c, '_' | '.' | '\\' | '?'))
}
/// Looks like an R1C1 reference (`R`, `C`, `RC`, `R2`, `C3`, `R1C1`), which Excel doesn't
/// accept as a name.
fn is_r1c1(n: &str) -> bool {
let digits = |s: &str| s.bytes().take_while(u8::is_ascii_digit).count();
let mut rest = n;
if let Some(r) = rest.strip_prefix(['R', 'r']) {
rest = r.get(digits(r)..).unwrap_or("");
}
if let Some(c) = rest.strip_prefix(['C', 'c']) {
rest = c.get(digits(c)..).unwrap_or("");
}
rest.is_empty() && !n.is_empty()
}
fn define_name(s: &mut Session, p: &Json) -> Result<Json> {
let Some(name) = str_param(p, "name").map(str::to_string) else {
s.ui_requests.push(crate::UiRequest::Dialog("defineName".into(), json!({})));
+11
View File
@@ -121,6 +121,17 @@ fn tables_and_structured_refs() {
assert_eq!(v(&s, "B4"), Value::Number(7.0));
}
#[test]
fn names_that_look_like_references_are_refused() {
let mut s = s();
for bad in ["R1C1", "r2", "C3", "RC", "R", "c", "rc12", "A1", "XFD1048576", "1st"] {
assert!(s.execute("formulas.defineName", json!({"name": bad, "refersTo": "=1"})).is_err(), "{bad}");
}
for good in ["R1C1X", "Rate", "RCx", "Rx", "_R1", "Cost"] {
s.execute("formulas.defineName", json!({"name": good, "refersTo": "=1"})).unwrap();
}
}
#[test]
fn sheets() {
let mut s = s();