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:
@@ -9,11 +9,13 @@
|
||||
*/
|
||||
|
||||
import type {
|
||||
CellCoord,
|
||||
CellFormat,
|
||||
ChartObject,
|
||||
CustomFunction,
|
||||
DocumentState,
|
||||
HAlign,
|
||||
NamedRange,
|
||||
Sheet,
|
||||
SheetObject,
|
||||
TableObject,
|
||||
@@ -58,6 +60,46 @@ function normaliseCustomFunctions(x: unknown): CustomFunction[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
function isCellCoord(x: unknown): x is CellCoord {
|
||||
return (
|
||||
!!x &&
|
||||
typeof x === 'object' &&
|
||||
typeof (x as CellCoord).col === 'number' &&
|
||||
typeof (x as CellCoord).row === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
/** Defaults to `[]` -- absent in every document saved before this feature
|
||||
* existed. Drops a malformed entry, and any entry whose `tableId` doesn't
|
||||
* match a real table *in this document* (a dangling reference -- e.g. a
|
||||
* hand-edited file, or a table that no longer exists) -- rather than
|
||||
* leaving it to resolve to `#NAME?` forever; the live "delete a table"
|
||||
* path (`document.ts`) does leave such entries in place, for consistency
|
||||
* with how a deleted table's ordinary formula references are never
|
||||
* auto-pruned either -- this is stricter only because *importing* JSON is
|
||||
* already a validating boundary for everything else in the document. */
|
||||
function normaliseNamedRanges(x: unknown, tableIds: ReadonlySet<string>): NamedRange[] {
|
||||
if (!Array.isArray(x)) return [];
|
||||
const out: NamedRange[] = [];
|
||||
for (const r of x) {
|
||||
if (
|
||||
!r ||
|
||||
typeof r !== 'object' ||
|
||||
typeof (r as { id?: unknown }).id !== 'string' ||
|
||||
typeof (r as { name?: unknown }).name !== 'string' ||
|
||||
typeof (r as { tableId?: unknown }).tableId !== 'string' ||
|
||||
!isCellCoord((r as { start?: unknown }).start) ||
|
||||
!isCellCoord((r as { end?: unknown }).end)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const rec = r as NamedRange;
|
||||
if (!tableIds.has(rec.tableId)) continue;
|
||||
out.push({ id: rec.id, name: rec.name, tableId: rec.tableId, start: { ...rec.start }, end: { ...rec.end } });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
||||
function colorOrUndef(x: unknown): string | undefined {
|
||||
return typeof x === 'string' && HEX_COLOR.test(x) ? x : undefined;
|
||||
@@ -293,6 +335,7 @@ export function validateDocument(x: unknown): DocumentState {
|
||||
|
||||
const sheetIds = new Set<string>();
|
||||
const objectIds = new Set<string>();
|
||||
const tableIds = new Set<string>();
|
||||
for (const s of doc.sheets) {
|
||||
if (!s || typeof s.id !== 'string' || typeof s.name !== 'string' || !Array.isArray(s.objects))
|
||||
throw new JsonImportError('Mindestens ein Blatt ist fehlerhaft.');
|
||||
@@ -303,6 +346,7 @@ export function validateDocument(x: unknown): DocumentState {
|
||||
throw new JsonImportError('Mindestens ein Objekt ist fehlerhaft.');
|
||||
if (objectIds.has(o.id)) throw new JsonImportError('Doppelte Objekt-ID.');
|
||||
objectIds.add(o.id);
|
||||
if (isTableObject(o)) tableIds.add(o.id);
|
||||
}
|
||||
}
|
||||
if (typeof doc.activeSheetId !== 'string' || !sheetIds.has(doc.activeSheetId))
|
||||
@@ -330,6 +374,7 @@ export function validateDocument(x: unknown): DocumentState {
|
||||
locale: isNumberLocale(doc.locale) ? doc.locale : DEFAULT_LOCALE,
|
||||
csvDelimiter: doc.csvDelimiter === ';' ? ';' : ',',
|
||||
customFunctions: normaliseCustomFunctions(doc.customFunctions),
|
||||
namedRanges: normaliseNamedRanges(doc.namedRanges, tableIds),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -393,6 +438,7 @@ export function migrateToV2(x: unknown): DocumentState {
|
||||
locale: DEFAULT_LOCALE,
|
||||
csvDelimiter: ',',
|
||||
customFunctions: [],
|
||||
namedRanges: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user