Text-Vergleich, &-Verkettung und benannte Bereiche
- Vergleichsoperatoren (= <> > < >= <=) unterscheiden jetzt Text- von Zahlenvergleich statt jede Seite zwangsweise durch toNumber() zu schicken (=A1="Nord" gab vorher #VALUE! statt 1); ermöglicht Text-Kriterien in selbstgebauten COUNTIF/SUMIF/AVERAGEIF-Funktionen (neue README-Sektion "SUMIF/COUNTIF/AVERAGEIF nachbauen"). - Neuer &-Operator (Excel-Präzedenz zwischen Vergleich und +/-) plus CONCAT/CONCATENATE als Funktions-Äquivalent. - Benannte Bereiche: neuer Toolbar-Button "🏷" verwaltet dokumentweite Namen für eine feste Zelle/einen festen Bereich (z. B. Umsatz_Q1statt Umsatz!B2:B6), unqualifiziert in jeder Top-Level-Formel nutzbar. Live gegen die Tabellen-ID aufgelöst (übersteht Tabellen-Umbenennung), bewusst nicht innerhalb einer benutzerdefinierten Funktion selbst nutzbar (Aufrufer können sie aber als Argument übergeben). - Nebenbei: COUNT2s Autovervollständigungs-Hint korrigiert (zeigte "COUNT(...)" statt "COUNT2(...)"). 374 Tests, vue-tsc/eslint/build sauber, Playwright-live-verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+31
-5
@@ -31,6 +31,7 @@ import {
|
||||
parseKey,
|
||||
type CellValue,
|
||||
type EngineHost,
|
||||
type NamedRangeDef,
|
||||
} from './types';
|
||||
import { evaluate } from './evaluator';
|
||||
|
||||
@@ -81,10 +82,17 @@ export class Workbook implements EngineHost {
|
||||
private readonly values = new Map<string, CellValue>();
|
||||
/** user-defined function library for this document, name.toUpperCase() -> def. */
|
||||
private readonly functionIndex: Map<string, FunctionDef>;
|
||||
/** document-wide named ranges (see NamedRangeDef's doc comment). */
|
||||
private readonly namedRanges: NamedRangeDef[];
|
||||
|
||||
constructor(tables: TableModel[], functionDefs: FunctionDef[] = []) {
|
||||
constructor(
|
||||
tables: TableModel[],
|
||||
functionDefs: FunctionDef[] = [],
|
||||
namedRanges: NamedRangeDef[] = [],
|
||||
) {
|
||||
this.tables = tables;
|
||||
this.functionIndex = buildFunctionIndex(functionDefs);
|
||||
this.namedRanges = namedRanges;
|
||||
this.rebuild();
|
||||
}
|
||||
|
||||
@@ -112,6 +120,17 @@ export class Workbook implements EngineHost {
|
||||
return null; // still ambiguous -> #REF, needs an explicit sheet qualifier
|
||||
}
|
||||
|
||||
resolveNamedRange(
|
||||
name: string,
|
||||
): { qualifier: string; sheetQualifier?: string; start: { col: number; row: number }; end: { col: number; row: number } } | null {
|
||||
const needle = name.trim().toLowerCase();
|
||||
const def = this.namedRanges.find((d) => d.name.trim().toLowerCase() === needle);
|
||||
if (!def) return null;
|
||||
const table = this.tableById(def.tableId);
|
||||
if (!table) return null; // target table was since deleted -- #NAME?, same as a stale table rename
|
||||
return { qualifier: table.name, sheetQualifier: table.sheetName, start: def.start, end: def.end };
|
||||
}
|
||||
|
||||
tableSize(tableId: string): { cols: number; rows: number } | null {
|
||||
const t = this.tableById(tableId);
|
||||
return t ? { cols: t.cols, rows: t.rows } : null;
|
||||
@@ -201,13 +220,20 @@ export class Workbook implements EngineHost {
|
||||
this.homeTableId = tableId;
|
||||
const out: string[] = [];
|
||||
for (const ref of collectRefs(ast)) {
|
||||
// a named range isn't itself a qualifier/coords pair -- resolve it to
|
||||
// one first (same shape as a 'cell'/'range' ref), then fall through to
|
||||
// the identical edge-wiring below.
|
||||
const resolved = ref.kind === 'namedref' ? this.resolveNamedRange(ref.name) : ref;
|
||||
if (!resolved) continue; // unknown name -> no edge; evaluator emits #NAME?
|
||||
const target =
|
||||
ref.qualifier == null ? tableId : this.resolveTableName(ref.qualifier, ref.sheetQualifier ?? null);
|
||||
resolved.qualifier == null
|
||||
? tableId
|
||||
: this.resolveTableName(resolved.qualifier, resolved.sheetQualifier ?? null);
|
||||
if (!target) continue; // unknown table -> no edge; evaluator emits #REF
|
||||
if (ref.kind === 'cell') {
|
||||
out.push(keyOf(target, ref.col, ref.row));
|
||||
if ('col' in resolved) {
|
||||
out.push(keyOf(target, resolved.col, resolved.row));
|
||||
} else {
|
||||
for (const c of expandRange(ref.start, ref.end)) {
|
||||
for (const c of expandRange(resolved.start, resolved.end)) {
|
||||
out.push(keyOf(target, c.col, c.row));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user