Add a sheet qualifier: "Blatt!Tabelle::Zelle", table names unique per sheet
Table names were document-unique; now they're unique per sheet only -- two different sheets may each hold a table called "Umsatz". A plain, unqualified reference (Umsatz!B2 / Umsatz::B2, unchanged from before) resolves to the table on the SAME sheet as the formula referencing it when the name is otherwise ambiguous, falling back to the single document-wide match when it isn't ambiguous at all -- exactly as before for every existing formula. The new explicit 3-part form (Analyse!Umsatz::B2, quoted where a name has spaces) always targets the named sheet's table regardless of where the formula itself lives, for the case that's still genuinely ambiguous. Engine: parser.ts's afterFirstQualifierToken() disambiguates via lookahead -- "X!Y::" (a name, then "::") is the new 3-part sheet+table form; a bare "X!Y" keeps its original single-qualifier meaning, so every formula written before this exists still parses identically. CellNode/RangeNode gain an optional sheetQualifier field. Workbook.resolveTableName(name, sheetName?) implements the resolution policy; TableModel gains sheetName. precedentsOf() now also sets homeTableId (previously only evaluateCell did), so graph edges use the same same-sheet-preference resolution an evaluation would. Store: table name uniqueness checks (addTable, renameObject) are now scoped to the target sheet instead of the whole document. Point-mode's qualifyRef() inserts the full "Blatt!Tabelle::" form only when the clicked table's name is genuinely ambiguous and lives on a different sheet than the editor; otherwise the simple form, unchanged. 200 tests passing (19 new), vue-tsc and eslint clean, verified live in the running app: renamed a Blatt-2 table to the same name as Analyse's seed "Umsatz", confirmed an unqualified reference resolves per-sheet, an explicit sheet-qualified one always targets the named sheet, and the original sheet's own formulas are unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+30
-5
@@ -35,8 +35,12 @@ import { evaluate } from './evaluator';
|
||||
|
||||
export interface TableModel {
|
||||
id: string;
|
||||
/** document-unique table name, used as the qualifier in formulas */
|
||||
/** table name, used as the qualifier in formulas -- unique *per sheet*
|
||||
* only; the same name may occur on more than one sheet (see
|
||||
* `sheetName` and {@link EngineHost.resolveTableName}). */
|
||||
name: string;
|
||||
/** name of the sheet this table lives on. */
|
||||
sheetName: string;
|
||||
cols: number;
|
||||
rows: number;
|
||||
/** key `${col},${row}` -> raw user input. Absent = empty cell. */
|
||||
@@ -78,10 +82,26 @@ export class Workbook implements EngineHost {
|
||||
|
||||
// ---- EngineHost ---------------------------------------------------------
|
||||
|
||||
resolveTableName(name: string): string | null {
|
||||
resolveTableName(name: string, sheetName?: string | null): string | null {
|
||||
const needle = name.trim().toLowerCase();
|
||||
const hit = this.tables.find((t) => t.name.trim().toLowerCase() === needle);
|
||||
return hit ? hit.id : null;
|
||||
const candidates = this.tables.filter((t) => t.name.trim().toLowerCase() === needle);
|
||||
if (candidates.length === 0) return null;
|
||||
|
||||
if (sheetName != null) {
|
||||
const sheetNeedle = sheetName.trim().toLowerCase();
|
||||
const hit = candidates.find((t) => t.sheetName.trim().toLowerCase() === sheetNeedle);
|
||||
return hit ? hit.id : null;
|
||||
}
|
||||
if (candidates.length === 1) return candidates[0].id;
|
||||
|
||||
// ambiguous (same name on more than one sheet) -- prefer a table on the
|
||||
// same sheet as the formula doing the referencing, if there is one
|
||||
const home = this.tableById(this.homeTableId);
|
||||
if (home) {
|
||||
const sameSheet = candidates.find((t) => t.sheetName === home.sheetName);
|
||||
if (sameSheet) return sameSheet.id;
|
||||
}
|
||||
return null; // still ambiguous -> #REF, needs an explicit sheet qualifier
|
||||
}
|
||||
|
||||
tableSize(tableId: string): { cols: number; rows: number } | null {
|
||||
@@ -167,9 +187,14 @@ export class Workbook implements EngineHost {
|
||||
|
||||
/** Resolve every reference in `ast` to concrete cell keys (home = tableId). */
|
||||
private precedentsOf(ast: Node, tableId: string): string[] {
|
||||
// resolveTableName()'s "prefer the home sheet" ambiguity fallback reads
|
||||
// this.homeTableId -- set it here too (not just in evaluateCell) so graph
|
||||
// edges are wired using the *same* resolution an evaluation would use.
|
||||
this.homeTableId = tableId;
|
||||
const out: string[] = [];
|
||||
for (const ref of collectRefs(ast)) {
|
||||
const target = ref.qualifier == null ? tableId : this.resolveTableName(ref.qualifier);
|
||||
const target =
|
||||
ref.qualifier == null ? tableId : this.resolveTableName(ref.qualifier, ref.sheetQualifier ?? null);
|
||||
if (!target) continue; // unknown table -> no edge; evaluator emits #REF
|
||||
if (ref.kind === 'cell') {
|
||||
out.push(keyOf(target, ref.col, ref.row));
|
||||
|
||||
Reference in New Issue
Block a user