Tabellen-Umbenennung schreibt Referenzen dokumentweit um
Bisher speicherte eine Formel den Tabellennamen nur als Text im
Qualifier ("Umsatz!B2") – beim Umbenennen der Tabelle wurde nirgendwo
danach gesucht, sodass jede andere Formel, die noch den alten Namen
nannte, beim nächsten Neuberechnen #REF zeigte.
renameObject() ruft jetzt vor der eigentlichen Umbenennung eine neue
renameDocumentRefs()/rewriteRefsForRename() auf, die dieselbe
Token-Splice-Technik wiederverwendet, die für das dokumentweite
Umschreiben bei Zeilen-/Spalten-Einfügen bereits existiert: jede Formel
im Dokument wird tokenisiert, jeder Qualifier über die echte
wb.resolveTableName-Auflösung (nicht per String-Vergleich) auf die
umbenannte Tabelle geprüft, und nur die Tabellen-Namens-Textspanne
ersetzt – bei der 3-teiligen "Blatt!Tabelle::Zelle"-Form bleibt der
Blatt-Teil unangetastet, ein neuer Name mit Leerzeichen wird korrekt
gequotet. Umbenennen + Umschreiben zählen als ein Undo-Schritt.
Bewusst auf Tabellenzellen begrenzt, wie schon beim Zeilen-/Spalten-
Umschreiben (keine Makro-Aktionen, keine Funktionskörper).
Live per Playwright verifiziert: "Umsatz" per Doppelklick zu "Verkauf"
umbenannt, =SUM(Umsatz!B2:E2) in einer anderen Tabelle wurde automatisch
zu =SUM(Verkauf!B2:E2), Wert blieb unverändert (kein #REF).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -206,9 +206,11 @@ eingebauter Funktionsname: `=MARGE(Umsatz!B2; Umsatz!C2)`.
|
||||
Name/Parameter, ein nicht parsender Formelkörper, und ein **Funktions-
|
||||
Zyklus** (A ruft B, B ruft A, auch über mehrere Stufen) — wird abgelehnt,
|
||||
bevor je eine `#ERR`-Zelle entsteht.
|
||||
- **Umbenennen/Löschen** aktualisiert **keine** bestehenden Formeltexte —
|
||||
genau wie beim Umbenennen/Löschen einer Tabelle. Zellen, die die
|
||||
betroffene Funktion aufrufen, zeigen danach `#NAME?`.
|
||||
- **Umbenennen/Löschen** aktualisiert **keine** bestehenden Formeltexte.
|
||||
Zellen, die die betroffene Funktion aufrufen, zeigen danach `#NAME?`.
|
||||
Anders als bei einer **Tabelle** (s. u. „Referenzen bleiben nach dem
|
||||
Umbenennen konsistent") — eine Funktion hat keine Referenz-Syntax, die
|
||||
sich automatisch umschreiben ließe, nur ihren Aufruf-Namen.
|
||||
- Ein Aufruf wird **beim Parsen der aufrufenden Zelle in ihre AST eingesetzt**
|
||||
(Inlining, keine Laufzeit-Closures) — siehe
|
||||
[`src/engine/userFunctions.ts`](src/engine/userFunctions.ts) und die
|
||||
@@ -765,6 +767,27 @@ Die Formel-**Point-Mode**-Einfügung (s. o.) erkennt eine mehrdeutige
|
||||
Zieltabelle selbst und fügt automatisch die volle 3-teilige Form ein, statt
|
||||
einer Formel, die versehentlich die falsche Tabelle träfe.
|
||||
|
||||
**Referenzen bleiben nach dem Umbenennen konsistent.** Wird eine Tabelle
|
||||
umbenannt (Doppelklick auf ihren Namen in der Kopfleiste), schreibt der
|
||||
Store **dokumentweit jede** `Tabelle!Zelle`-/`Tabelle::Zelle`-Referenz auf
|
||||
sie um — egal in welcher anderen Tabelle sie steht, egal ob 2- oder
|
||||
3-teilige Form (bei der 3-teiligen Form wird nur der Tabellen-Teil
|
||||
ersetzt, der Blatt-Teil bleibt unangetastet), egal ob Einzelzelle oder
|
||||
Bereich. Nur die betroffene Qualifier-Textspanne wird ersetzt (nötigenfalls
|
||||
mit `'...'` gequotet, wenn der neue Name Leerzeichen/Sonderzeichen
|
||||
enthält) — der Rest der Formel, inklusive Whitespace, bleibt byte-identisch,
|
||||
dieselbe Technik wie beim dokumentweiten Umschreiben durch Zeilen-/
|
||||
Spalten-Einfügen (s. u.). Umbenennen + Umschreiben zählen zusammen als
|
||||
**ein** Undo-Schritt. Damit bleibt eine Formel nach dem Umbenennen exakt
|
||||
so gültig wie vorher — ohne dieses Umschreiben würde jede Formel, die noch
|
||||
den alten Namen nennt, beim nächsten Neuberechnen `#REF` zeigen (der
|
||||
Tabellenname existiert für sie dann schlicht nicht mehr). Bewusst
|
||||
**begrenzt auf Tabellenzellen** — Makro-Aktionen adressieren eine Tabelle
|
||||
über einen zur Laufzeit aufgelösten Text-String (s. „Makros" oben), nicht
|
||||
über eine Formel-Qualifier-Syntax, und ein benutzerdefinierter
|
||||
Funktionskörper wird ebenfalls nicht durchsucht (dieselbe, bereits
|
||||
bestehende Begrenzung wie beim dokumentweiten Zeilen-/Spalten-Umschreiben).
|
||||
|
||||
**`$`-Bezüge sperren** (relativ/gemischt/absolut): `A1` (relativ) vs. `$A1` /
|
||||
`A$1` (Spalte bzw. Zeile gesperrt) vs. `$A$1` (beide gesperrt). Für die
|
||||
Auswertung selbst ist `$` irrelevant — sie ist rein positionsbasiert. Es zählt
|
||||
@@ -1133,6 +1156,16 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
|
||||
Bereich, `$`-Sperren bleiben wirkungslos (anders als beim Kopieren),
|
||||
unveränderte Referenzen bleiben byte-identisch, Undo/Redo, Schutz gegen
|
||||
Löschen der letzten verbleibenden Zeile/Spalte.
|
||||
- **Tabellen-Umbenennung schreibt Referenzen um** — ein `Tabelle!Zelle`-
|
||||
und ein `Tabelle::Zelle`-Bezug aus einer *anderen* Tabelle werden beide
|
||||
umgeschrieben und liefern danach weiterhin denselben Wert (kein `#REF`),
|
||||
ein Bereichsbezug behält seinen Bereich (nur der Qualifier ändert sich),
|
||||
bei der 3-teiligen `Blatt!Tabelle::Zelle`-Form wird ausschließlich der
|
||||
Tabellen-Teil ersetzt, ein neuer Name mit Leerzeichen wird korrekt
|
||||
gequotet, eine Formel, die eine *andere*, nicht umbenannte Tabelle
|
||||
referenziert, bleibt byte-identisch, ein redundanter Selbst-Qualifier
|
||||
innerhalb der umbenannten Tabelle selbst wird ebenfalls erfasst,
|
||||
Umbenennen + Umschreiben zählen als ein Undo-Schritt.
|
||||
- **Abhängigkeiten erkunden** (`graphPrecedents`/`graphDependents` in
|
||||
`workbook.spec.ts`, `cellGraphSelf`/`cellGraphNeighbors`/
|
||||
`openDependencyGraph`/`closeDependencyGraph` in `document.spec.ts`) —
|
||||
@@ -1314,10 +1347,11 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
|
||||
- **Benutzerdefinierte Funktionen** (Phase 1, siehe oben) bewusst ohne:
|
||||
Live-Vorschau des Ergebnisses im Bearbeiten-Dialog, variadische Parameter/
|
||||
Default-Werte, und automatisches Nachführen bestehender Aufrufe bei
|
||||
Umbenennung (Verhalten entspricht bewusst dem einer Tabellen-Umbenennung,
|
||||
s. o.) — der Funktionsname selbst wird beim Formeltippen aber inzwischen
|
||||
vervollständigt (s. o. „Funktions-Autovervollständigung"), auch für eigene
|
||||
Funktionen.
|
||||
Umbenennung — anders als bei einer Tabelle inzwischen (s. o.) bewusst
|
||||
nicht nachgezogen, da eine Funktion nur einen Aufruf-Namen hat, keine
|
||||
umschreibbare Referenz-Syntax. Der Funktionsname selbst wird beim
|
||||
Formeltippen aber inzwischen vervollständigt (s. o. „Funktions-
|
||||
Autovervollständigung"), auch für eigene Funktionen.
|
||||
- **Funktions-Autovervollständigung** bewusst ohne: Zell-/Tabellennamen-
|
||||
Vorschläge (dafür existiert der Point-Mode, s. o.), Vervollständigung, die
|
||||
auch Text *nach* dem Cursor berücksichtigt (nur der Text bis zum Cursor
|
||||
|
||||
@@ -1244,6 +1244,129 @@ describe('insertRow/deleteRow/insertColumn/deleteColumn (mid-grid, not just edge
|
||||
});
|
||||
});
|
||||
|
||||
describe('renaming a table rewrites every other formula\'s qualifier (document-wide, not just names re-resolving)', () => {
|
||||
const rawInput = (s: DocumentStore, table: string, a1: string) => {
|
||||
const { col, row } = coords(a1);
|
||||
return s.cellInput(tableId(s, table), col, row);
|
||||
};
|
||||
|
||||
it('a plain "Alt!B2" reference in ANOTHER table becomes "Neu!B2" and keeps evaluating correctly', () => {
|
||||
const s = fresh();
|
||||
const src = s.addTable(s.doc.activeSheetId);
|
||||
const srcName = s.tableById(src)!.name;
|
||||
const dst = s.addTable(s.doc.activeSheetId);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
setCell(s, srcName, 'B2', '10');
|
||||
setCell(s, dstName, 'A1', `=${srcName}!B2`);
|
||||
expect(val(s, dstName, 'A1')).toBe(10);
|
||||
|
||||
s.renameObject(src, 'Neu');
|
||||
expect(rawInput(s, dstName, 'A1')).toBe('=Neu!B2');
|
||||
expect(val(s, dstName, 'A1')).toBe(10); // still resolves -- no #REF
|
||||
});
|
||||
|
||||
it('the "::" qualifier form is rewritten too', () => {
|
||||
const s = fresh();
|
||||
const src = s.addTable(s.doc.activeSheetId);
|
||||
const srcName = s.tableById(src)!.name;
|
||||
const dst = s.addTable(s.doc.activeSheetId);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
setCell(s, dstName, 'A1', `=${srcName}::B2+1`);
|
||||
|
||||
s.renameObject(src, 'Neu');
|
||||
expect(rawInput(s, dstName, 'A1')).toBe('=Neu::B2+1');
|
||||
});
|
||||
|
||||
it('a range reference keeps its range, only the qualifier changes', () => {
|
||||
const s = fresh();
|
||||
const src = s.addTable(s.doc.activeSheetId);
|
||||
const srcName = s.tableById(src)!.name;
|
||||
const dst = s.addTable(s.doc.activeSheetId);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
setCell(s, dstName, 'A1', `=SUM(${srcName}!B2:B6)`);
|
||||
|
||||
s.renameObject(src, 'Neu');
|
||||
expect(rawInput(s, dstName, 'A1')).toBe('=SUM(Neu!B2:B6)');
|
||||
});
|
||||
|
||||
it('only the TABLE part of a 3-part "Blatt!Tabelle::Zelle" reference is rewritten, the sheet stays', () => {
|
||||
const s = fresh();
|
||||
const sheet2 = s.addSheet('Zweites');
|
||||
const src = s.addTable(sheet2);
|
||||
const srcName = s.tableById(src)!.name;
|
||||
s.setActiveSheet(s.doc.sheets[0].id);
|
||||
const dst = s.addTable(s.doc.sheets[0].id);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
const sheet2Name = s.sheetById(sheet2)!.name;
|
||||
setCell(s, dstName, 'A1', `=${sheet2Name}!${srcName}::B2`);
|
||||
|
||||
s.renameObject(src, 'Neu');
|
||||
expect(rawInput(s, dstName, 'A1')).toBe(`=${sheet2Name}!Neu::B2`);
|
||||
});
|
||||
|
||||
it('renaming to a name that needs quoting produces a properly quoted qualifier', () => {
|
||||
const s = fresh();
|
||||
const src = s.addTable(s.doc.activeSheetId);
|
||||
const srcName = s.tableById(src)!.name;
|
||||
const dst = s.addTable(s.doc.activeSheetId);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
setCell(s, dstName, 'A1', `=${srcName}!B2`);
|
||||
|
||||
s.renameObject(src, 'Neuer Name');
|
||||
expect(rawInput(s, dstName, 'A1')).toBe("='Neuer Name'!B2");
|
||||
});
|
||||
|
||||
it('a formula referencing a DIFFERENT, un-renamed table stays byte-identical', () => {
|
||||
const s = fresh();
|
||||
const src = s.addTable(s.doc.activeSheetId);
|
||||
const other = s.addTable(s.doc.activeSheetId);
|
||||
const otherName = s.tableById(other)!.name;
|
||||
const dst = s.addTable(s.doc.activeSheetId);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
const untouched = `=${otherName}!B2 + 1`;
|
||||
setCell(s, dstName, 'A1', untouched);
|
||||
|
||||
s.renameObject(src, 'Neu');
|
||||
expect(rawInput(s, dstName, 'A1')).toBe(untouched);
|
||||
});
|
||||
|
||||
it('a redundant self-qualified reference inside the renamed table itself is rewritten too', () => {
|
||||
const s = fresh();
|
||||
const id = s.addTable(s.doc.activeSheetId);
|
||||
const name = s.tableById(id)!.name;
|
||||
setCell(s, name, 'B2', '10');
|
||||
setCell(s, name, 'A1', `=${name}!B2`); // redundant explicit self-qualifier
|
||||
|
||||
s.renameObject(id, 'Neu');
|
||||
expect(rawInput(s, 'Neu', 'A1')).toBe('=Neu!B2');
|
||||
expect(val(s, 'Neu', 'A1')).toBe(10);
|
||||
});
|
||||
|
||||
it('the rewrite and the rename coalesce into a single undo step', () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const s = fresh();
|
||||
const src = s.addTable(s.doc.activeSheetId);
|
||||
const srcName = s.tableById(src)!.name;
|
||||
const dst = s.addTable(s.doc.activeSheetId);
|
||||
const dstName = s.tableById(dst)!.name;
|
||||
setCell(s, dstName, 'A1', `=${srcName}!B2`);
|
||||
vi.advanceTimersByTime(600); // let the setup settle first
|
||||
|
||||
s.renameObject(src, 'Neu');
|
||||
vi.advanceTimersByTime(600);
|
||||
expect(rawInput(s, dstName, 'A1')).toBe('=Neu!B2');
|
||||
|
||||
s.undo(); // one Ctrl+Z undoes BOTH the rename and the ref rewrite together
|
||||
expect(s.tableById(src)!.name).toBe(srcName);
|
||||
expect(rawInput(s, dstName, 'A1')).toBe(`=${srcName}!B2`);
|
||||
} finally {
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('dependency-graph view (cellGraphSelf/cellGraphNeighbors)', () => {
|
||||
it('cellGraphSelf describes a formula cell: label, raw text, value, table/sheet name', () => {
|
||||
const s = fresh();
|
||||
|
||||
+140
-6
@@ -413,6 +413,113 @@ function rewriteRefsForStructuralChange(
|
||||
return '=' + out;
|
||||
}
|
||||
|
||||
/** `'` needs quoting whenever a name isn't a bare identifier (spaces,
|
||||
* punctuation, ...) -- shared by the table- and sheet-qualifier cases.
|
||||
* Module-level (not a store method) since it's pure and both
|
||||
* `rewriteRefsForRename` below and the store's own `qualifyRef` need it. */
|
||||
function quoteQualifier(name: string): string {
|
||||
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite every reference to `targetTableId` inside one formula so its
|
||||
* qualifier text uses `newName` instead of whatever name it was written
|
||||
* with -- called when a table is renamed (see `renameObject`), so a
|
||||
* formula elsewhere that still says `AlterName!B2` keeps pointing at the
|
||||
* SAME table instead of becoming `#REF` the next time the workbook
|
||||
* rebuilds ("Referenzen in Formeln müssen immer konsistent sein").
|
||||
*
|
||||
* Same token-splice technique and qualifier-detection shape as
|
||||
* `rewriteRefsForStructuralChange` above (only the qualifier token's own
|
||||
* text is touched, everything else -- including whitespace and the cell/
|
||||
* range part -- stays byte-identical), but simpler: no coordinate
|
||||
* shifting, and only the TABLE-name token is ever replaced -- a 3-part
|
||||
* `Blatt!Tabelle::Zelle` form keeps its own sheet qualifier untouched,
|
||||
* only `Tabelle` becomes `newName`. `homeTableId` is the formula's own
|
||||
* table, needed the same way as in the structural-change rewriter, to
|
||||
* resolve a same-name-on-two-sheets ambiguity via the real
|
||||
* `wb.resolveTableName` rules rather than a naive string match.
|
||||
*/
|
||||
function rewriteRefsForRename(
|
||||
raw: string,
|
||||
homeTableId: string,
|
||||
targetTableId: string,
|
||||
newName: string,
|
||||
wb: Workbook,
|
||||
): string {
|
||||
if (!raw.startsWith('=')) return raw;
|
||||
const body = raw.slice(1);
|
||||
let toks: Token[];
|
||||
try {
|
||||
toks = tokenize(body);
|
||||
} catch {
|
||||
return raw; // unparsable -- leave as-is, the evaluator already surfaces #ERR
|
||||
}
|
||||
|
||||
const savedHome = wb.homeTableId;
|
||||
wb.homeTableId = homeTableId;
|
||||
const resolve = (name: string, sheetName: string | null): string | null =>
|
||||
wb.resolveTableName(name, sheetName);
|
||||
|
||||
interface Repl {
|
||||
pos: number;
|
||||
end: number;
|
||||
text: string;
|
||||
}
|
||||
const repls: Repl[] = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < toks.length && toks[i].type !== 'EOF') {
|
||||
const tok = toks[i];
|
||||
let cellIdx = -1;
|
||||
let resolvedTableId: string | null = null;
|
||||
let qualTok: Token | null = null;
|
||||
|
||||
if ((tok.type === 'IDENT' || tok.type === 'QUOTED') && toks[i + 1]) {
|
||||
if (toks[i + 1].type === 'BANG' && looksLikeSheetThenTable(toks, i + 1)) {
|
||||
qualTok = toks[i + 2]; // the TABLE token of the 3-part form -- toks[i]/tok is the sheet, left alone
|
||||
resolvedTableId = resolve(qualTok.value, tok.value);
|
||||
cellIdx = i + 4; // consumed: IDENT/QUOTED "!" IDENT/QUOTED "::"
|
||||
} else if (toks[i + 1].type === 'BANG' || toks[i + 1].type === 'DBLCOLON') {
|
||||
qualTok = tok;
|
||||
resolvedTableId = resolve(tok.value, null);
|
||||
cellIdx = i + 2;
|
||||
}
|
||||
}
|
||||
if (cellIdx === -1) {
|
||||
i++; // a bare, unqualified cell never needs rewriting on rename -- it's already "this table"
|
||||
continue;
|
||||
}
|
||||
|
||||
const cellTokA = toks[cellIdx];
|
||||
if (!cellTokA || cellTokA.type !== 'IDENT' || !looksLikeCell(cellTokA.value)) {
|
||||
i++; // qualifier not actually followed by a cell -- malformed, leave alone
|
||||
continue;
|
||||
}
|
||||
const isRangeStart = toks[cellIdx + 1]?.type === 'COLON';
|
||||
const cellTokB = isRangeStart ? toks[cellIdx + 2] : undefined;
|
||||
const validRange = !!cellTokB && cellTokB.type === 'IDENT' && looksLikeCell(cellTokB.value);
|
||||
|
||||
if (resolvedTableId === targetTableId && qualTok) {
|
||||
repls.push({ pos: qualTok.pos, end: qualTok.pos + qualTok.value.length, text: quoteQualifier(newName) });
|
||||
}
|
||||
|
||||
i = validRange ? cellIdx + 3 : cellIdx + 1;
|
||||
}
|
||||
|
||||
wb.homeTableId = savedHome;
|
||||
if (repls.length === 0) return raw;
|
||||
repls.sort((x, y) => x.pos - y.pos);
|
||||
let out = '';
|
||||
let cursor = 0;
|
||||
for (const r of repls) {
|
||||
out += body.slice(cursor, r.pos) + r.text;
|
||||
cursor = r.end;
|
||||
}
|
||||
out += body.slice(cursor);
|
||||
return '=' + out;
|
||||
}
|
||||
|
||||
function loadInitial(): DocumentState {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
@@ -927,7 +1034,15 @@ function createStore() {
|
||||
.filter((t) => t.id !== objId)
|
||||
.map((t) => t.name.toLowerCase()),
|
||||
);
|
||||
o.name = others.has(clean.toLowerCase()) ? uniqueName(clean, others) : clean;
|
||||
const newName = others.has(clean.toLowerCase()) ? uniqueName(clean, others) : clean;
|
||||
// Rewrite every OTHER formula's qualifier BEFORE renaming -- wb still
|
||||
// resolves the old name at this point, and rewriteRefsForRename needs
|
||||
// that to identify which references actually point at this table.
|
||||
// "Referenzen in Formeln müssen immer konsistent sein" (user request):
|
||||
// without this, a formula elsewhere still saying the old name would
|
||||
// silently become #REF the moment the workbook rebuilds below.
|
||||
if (newName !== o.name) renameDocumentRefs(objId, newName);
|
||||
o.name = newName;
|
||||
syncWorkbook(); // formulas referencing the old/new name must re-resolve
|
||||
} else {
|
||||
o.name = clean;
|
||||
@@ -1860,6 +1975,30 @@ function createStore() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Same driver shape as `rewriteDocumentRefs` above, for a table rename
|
||||
* (see `rewriteRefsForRename`'s own doc comment) -- every OTHER table's
|
||||
* cells are walked too, not just the renamed table's own, since a
|
||||
* qualified reference to it can live anywhere in the document. Scoped
|
||||
* to table cells only, same as the structural-change rewriter (macro
|
||||
* bodies address a table by a plain string, not a formula qualifier, and
|
||||
* are resolved fresh at each run anyway -- see `resolveMacroTableRef` --
|
||||
* so they need no rewriting; a custom function's own body is likewise
|
||||
* left alone, matching the existing, established scope of the sibling
|
||||
* row/col-insert/delete rewrite this mirrors). Called BEFORE the
|
||||
* object's `.name` is actually changed, so `wb` (not yet rebuilt) still
|
||||
* resolves the OLD name to `targetTableId`. */
|
||||
function renameDocumentRefs(targetTableId: string, newName: string) {
|
||||
for (const s of doc.sheets) {
|
||||
for (const o of s.objects) {
|
||||
if (o.kind !== 'table') continue;
|
||||
for (const [key, raw] of Object.entries(o.cells)) {
|
||||
const next = rewriteRefsForRename(raw, o.id, targetTableId, newName, wb);
|
||||
if (next !== raw) o.cells[key] = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Re-key every entry of a "col,row" map, shifting the given axis at/after
|
||||
* `at` by `delta` (dropping entries whose axis value === `at` when
|
||||
* `delta` is -1 -- that row/col's own cells are gone). Shared by
|
||||
@@ -2360,11 +2499,6 @@ function createStore() {
|
||||
.map((f) => ({ name: f.name, hint: `${f.name}(${f.params.join('; ')})`, kind: 'custom' }));
|
||||
return [...builtins, ...custom].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
/** `'` needs quoting whenever a name isn't a bare identifier (spaces,
|
||||
* punctuation, ...) -- shared by the table- and sheet-qualifier cases. */
|
||||
function quoteQualifier(name: string): string {
|
||||
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "''")}'`;
|
||||
}
|
||||
function qualifyRef(homeTableId: string, targetTableId: string, body: string): string {
|
||||
if (targetTableId === homeTableId) return body;
|
||||
const target = tableById(targetTableId);
|
||||
|
||||
Reference in New Issue
Block a user