diff --git a/crates/engine/src/cmd/insert.rs b/crates/engine/src/cmd/insert.rs index d878e4469..9f044613d 100644 --- a/crates/engine/src/cmd/insert.rs +++ b/crates/engine/src/cmd/insert.rs @@ -466,10 +466,11 @@ fn table_style(s: &mut Session, p: &Json) -> Result { } /// Table names follow the Microsoft name rules, as the xlsx writer does: letters, digits, `_` and `.`, -/// starting with a letter, `_` or `\`. Anything else (a hyphen, say) would read as an operator inside a -/// structured reference, and the writer would rename the table on save. +/// starting with a letter, `_` or `\`, and at most 255 characters. Anything else (a hyphen, say) would +/// read as an operator inside a structured reference, and the writer would rename the table on save. fn valid_table_name(name: &str) -> bool { - name.starts_with(|c: char| c.is_alphabetic() || c == '_' || c == '\\') + name.chars().count() <= 255 + && name.starts_with(|c: char| c.is_alphabetic() || c == '_' || c == '\\') && CellRef::parse(name).is_none() && name.chars().enumerate().all(|(i, c)| c.is_alphanumeric() || c == '_' || c == '.' || (i == 0 && c == '\\')) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 513366848..db6ad1e2a 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1222,6 +1222,26 @@ fn table_rename_keeps_a_defined_name_with_the_same_name() { assert_eq!((v(&s, "E1"), v(&s, "F1")), (Value::Number(100.0), Value::Number(7.0))); } +#[test] +fn table_names_are_limited_to_255_characters() { + let mut s = s(); + s.execute("range.setValues", json!({"range": "A1", "values": [["Qty"], [7]]})).unwrap(); + s.execute("insert.table", json!({"range": "A1:A2", "header": true, "name": "Original"})).unwrap(); + + let name_255 = "ع".repeat(255); + s.execute("table.rename", json!({"table": "Original", "name": name_255})).unwrap(); + assert!(s.doc().unwrap().wb.table(&"ع".repeat(255)).is_some()); + + let name_256 = "ع".repeat(256); + let error = s.execute("table.rename", json!({"table": &"ع".repeat(255), "name": name_256})).unwrap_err(); + assert!(error.to_string().contains("not valid"), "{error}"); + assert!(s.doc().unwrap().wb.table(&"ع".repeat(255)).is_some()); + + let name_200 = "ع".repeat(200); + s.execute("table.rename", json!({"table": &"ع".repeat(255), "name": name_200})).unwrap(); + assert!(s.doc().unwrap().wb.table(&"ع".repeat(200)).is_some()); +} + #[test] fn slicer_filters_table() { let mut s = s(); diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 6885a595a..a05ad69f1 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,6 +1,7 @@ # Localization parity > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (defined-name length limit counts characters, so long Arabic names are accepted) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (Table names are limited to 255 characters) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 Per-language status against Excel, which ships its UI in dozens of languages (Windows language packs cover 100+), with localized function names, argument separators, number and date formats, @@ -82,6 +83,7 @@ language (none so far) and of localized function names. | Date | Change | Summary | |---|---|---| | 2026-10-19 | minor | Defined-name length counts characters, not bytes: Arabic names up to 255 letters are accepted, and Create from Selection keeps long Arabic headers | +| 2026-10-19 | minor | Table names are limited to 255 Unicode characters | | 2026-10-12 | minor | Format Cells looks up its tabs, labels, options and note through `Language::tr` (Fill tab keyed `Format Cells\|Fill`); dialog titles and OK/Cancel too. Labels already in a table (Number, Currency, Fill…) show translated; the rest fall back to English until the tables add them | | 2026-10-12 | minor | Brazilian Portuguese catalog (`i18n/pt.rs`) grows from 377 to 923 entries: every key the other catalogs translate, including the dialog strings #669 starts looking up (~42%) | | 2026-10-11 | minor | German ribbon catalog (`i18n/de.rs`, 266 labels, ~12%); `de` locale tags (de-DE, de-AT, de-CH…) resolve to German. PR #163 (localized German formulas/numbers) still open |