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:
+48
-4
@@ -4,8 +4,11 @@ Selbst geschriebene, framework-agnostische Formel-Engine. Kein HyperFormula, kei
|
||||
`formula-parser`, keine Spreadsheet-Library. Reines ES-Modul, in Vitest getestet.
|
||||
|
||||
Die adressierbare Einheit ist eine **Tabelle** – ein Tabellen-Objekt auf einer
|
||||
Numbers-artigen Blattfläche. Tabellennamen sind dokumentweit eindeutig, deshalb
|
||||
löst `Umsatz!B2` (bzw. `Umsatz::B2`) von jedem Blatt aus identisch auf.
|
||||
Numbers-artigen Blattfläche. Tabellennamen sind nur **pro Blatt** eindeutig
|
||||
(zwei Blätter dürfen je eine Tabelle "Umsatz" haben) – `Umsatz!B2` (bzw.
|
||||
`Umsatz::B2`) löst dann zur Tabelle auf dem **gleichen Blatt wie die Formel**
|
||||
auf, sofern eindeutig; die explizite Form `Blatt!Tabelle::B2` trifft immer
|
||||
genau die genannte Tabelle. Siehe "Blatt-Qualifier" weiter unten.
|
||||
|
||||
## Module
|
||||
|
||||
@@ -45,10 +48,18 @@ primary = NUMBER
|
||||
|
||||
funcall = IDENT "(" [ expression { ";" expression } ] ")" ;
|
||||
|
||||
reference = [ qualifier ] CELL [ ":" CELL ] ;
|
||||
reference = [ sheetQualifier ] [ qualifier ] CELL [ ":" CELL ] ;
|
||||
qualifier = (IDENT | QUOTED) ("!" | "::") ; (* Tabellenname; "!" und der
|
||||
Numbers-Stil "::" sind
|
||||
austauschbar *)
|
||||
sheetQualifier = (IDENT | QUOTED) "!" ; (* Blattname -- nur erkannt,
|
||||
wenn direkt gefolgt von
|
||||
vollem "Tabelle::" (die
|
||||
3-teilige Form); ein
|
||||
einzelnes "X!Y" bleibt
|
||||
die alte, rückwärts-
|
||||
kompatible 2-teilige
|
||||
Form (X = Tabelle) *)
|
||||
|
||||
NUMBER = /[0-9]+ ("." [0-9]+)? ([eE] [+-]? [0-9]+)?/ | /"." [0-9]+/ ;
|
||||
STRING = /" ... "/ (Backslash-Escapes) ;
|
||||
@@ -81,6 +92,39 @@ eine Zelle aussieht) zusätzlich geprüft wird — ein Treffer erzeugt statt ein
|
||||
verwendet (`userFunctions.ts`); ohne das Argument ist das Verhalten exakt wie
|
||||
zuvor, jeder bare Bezeichner ohne Zellform bleibt ein `ParseError`.
|
||||
|
||||
## Blatt-Qualifier ("Blatt!Tabelle::Zelle")
|
||||
|
||||
`parser.ts#Parser.afterFirstQualifierToken` entscheidet per Lookahead, ob ein
|
||||
`!` einen (alten) Tabellen-Qualifier einleitet oder einen **Blatt**-Qualifier:
|
||||
nur wenn direkt danach `IDENT/QUOTED "::"` folgt, ist es ein Blattname und der
|
||||
Parser konsumiert drei Segmente (`Blatt`, `Tabelle`, `Zelle`) statt zwei —
|
||||
sonst bleibt die alte, einstufige Bedeutung (`X!Y` = Tabelle `X`, Zelle `Y`)
|
||||
exakt erhalten. `CellNode`/`RangeNode` bekommen dafür ein optionales
|
||||
`sheetQualifier?: string`-Feld neben dem bestehenden `qualifier`.
|
||||
|
||||
Die Auflösung passiert in `workbook.ts#Workbook.resolveTableName(name,
|
||||
sheetName?)` (Teil von `EngineHost`):
|
||||
|
||||
- `sheetName` gegeben (3-teilige Form) → nur eine Tabelle mit diesem Namen
|
||||
**auf diesem Blatt** trifft; sonst `null` (→ `#REF`).
|
||||
- `sheetName` fehlt und der Tabellenname ist **eindeutig** im Dokument → wie
|
||||
bisher, unverändert.
|
||||
- `sheetName` fehlt und der Name kommt auf **mehreren** Blättern vor →
|
||||
bevorzugt die Tabelle auf demselben Blatt wie `this.homeTableId` (dafür
|
||||
setzen sowohl `evaluateCell` als auch `precedentsOf` `homeTableId`, bevor
|
||||
sie referenzieren — Letzteres, damit auch die **Graph-Kanten** beim Parsen
|
||||
schon dieselbe Auflösung wie später die Auswertung verwenden). Bleibt es
|
||||
danach mehrdeutig: `null` (→ `#REF`).
|
||||
|
||||
`TableModel` (workbook.ts) trägt dafür zusätzlich `sheetName: string`. Die
|
||||
Store-Seite (`document.ts#toTableModels`) befüllt es aus dem Blatt, dem die
|
||||
Tabelle gehört; `tableNamesOnSheet(sheetId)`/`objectNamesOnSheet(sheetId)`
|
||||
ersetzen dort die früher dokumentweiten Eindeutigkeits-Prüfungen beim Anlegen/
|
||||
Umbenennen einer Tabelle. `qualifyRef()` (Point-Mode) fügt die volle
|
||||
3-teilige Form nur dann ein, wenn der angeklickte Tabellenname wirklich
|
||||
mehrdeutig **und** auf einem anderen Blatt als der Editor liegt — sonst reicht
|
||||
weiterhin die einfache Form.
|
||||
|
||||
## Benutzerdefinierte Funktionen
|
||||
|
||||
Kein Laufzeit-Closure, keine Änderung an `evaluator.ts`/`graph.ts`: ein Aufruf
|
||||
@@ -132,7 +176,7 @@ rufen `rebuild()` für einen vollständigen – trotzdem billigen – Durchlauf.
|
||||
|
||||
| Wert | Bedeutung |
|
||||
|------------|-----------|
|
||||
| `#REF` | ungültiger/gelöschter Bezug **oder** unbekannter Tabellenname |
|
||||
| `#REF` | ungültiger/gelöschter Bezug, unbekannter Tabellen-/Blattname, **oder** ein unqualifizierter Tabellenname, der auf mehreren Blättern existiert und auch über die "gleiches Blatt wie die Formel"-Regel nicht eindeutig wird |
|
||||
| `#DIV/0!` | Division durch 0 (auch `AVG` ohne Zahlen) |
|
||||
| `#NAME?` | unbekannte Funktion (eingebaut oder benutzerdefiniert) |
|
||||
| `#ERR` | Parse-Fehler (`=A1+`, `=SUM(`); auch Arity-Mismatch oder Expansionstiefen-Limit bei einer benutzerdefinierten Funktion |
|
||||
|
||||
Reference in New Issue
Block a user