@visactor/vtable-sheet 1.22.7 → 1.22.8-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/cjs/components/sheet-tab-event-handler.js +3 -2
  2. package/cjs/components/sheet-tab-event-handler.js.map +1 -1
  3. package/cjs/components/vtable-sheet.d.ts +2 -1
  4. package/cjs/components/vtable-sheet.js +9 -1
  5. package/cjs/components/vtable-sheet.js.map +1 -1
  6. package/cjs/core/WorkSheet.d.ts +1 -1
  7. package/cjs/core/WorkSheet.js.map +1 -1
  8. package/cjs/core/table-plugins.js +7 -5
  9. package/cjs/core/table-plugins.js.map +1 -1
  10. package/cjs/formula/cell-highlight-manager.d.ts +1 -0
  11. package/cjs/formula/cell-highlight-manager.js +27 -22
  12. package/cjs/formula/cell-highlight-manager.js.map +1 -1
  13. package/cjs/formula/cross-sheet-data-synchronizer.d.ts +48 -0
  14. package/cjs/formula/cross-sheet-data-synchronizer.js +159 -0
  15. package/cjs/formula/cross-sheet-data-synchronizer.js.map +1 -0
  16. package/cjs/formula/cross-sheet-formula-handler.d.ts +58 -0
  17. package/cjs/formula/cross-sheet-formula-handler.js +240 -0
  18. package/cjs/formula/cross-sheet-formula-handler.js.map +1 -0
  19. package/cjs/formula/cross-sheet-formula-manager.d.ts +52 -0
  20. package/cjs/formula/cross-sheet-formula-manager.js +235 -0
  21. package/cjs/formula/cross-sheet-formula-manager.js.map +1 -0
  22. package/cjs/formula/cross-sheet-formula-validator.d.ts +56 -0
  23. package/cjs/formula/cross-sheet-formula-validator.js +271 -0
  24. package/cjs/formula/cross-sheet-formula-validator.js.map +1 -0
  25. package/cjs/formula/formula-engine.d.ts +4 -1
  26. package/cjs/formula/formula-engine.js +132 -25
  27. package/cjs/formula/formula-engine.js.map +1 -1
  28. package/cjs/formula/formula-paste-processor.d.ts +3 -3
  29. package/cjs/formula/formula-paste-processor.js.map +1 -1
  30. package/cjs/formula/formula-reference-adjustor.d.ts +1 -1
  31. package/cjs/formula/formula-reference-adjustor.js.map +1 -1
  32. package/cjs/formula/index.d.ts +8 -0
  33. package/cjs/formula/index.js +40 -3
  34. package/cjs/formula/index.js.map +1 -1
  35. package/cjs/index.d.ts +1 -1
  36. package/cjs/index.js +1 -1
  37. package/cjs/index.js.map +1 -1
  38. package/cjs/managers/formula-manager.d.ts +17 -2
  39. package/cjs/managers/formula-manager.js +150 -12
  40. package/cjs/managers/formula-manager.js.map +1 -1
  41. package/cjs/styles/menu.js +2 -1
  42. package/cjs/styles/sheet-tab.js +1 -2
  43. package/cjs/tools/index.js +2 -1
  44. package/dist/vtable-sheet.js +11322 -331
  45. package/dist/vtable-sheet.min.js +1 -1
  46. package/es/components/sheet-tab-event-handler.js +3 -2
  47. package/es/components/sheet-tab-event-handler.js.map +1 -1
  48. package/es/components/vtable-sheet.d.ts +2 -1
  49. package/es/components/vtable-sheet.js +9 -1
  50. package/es/components/vtable-sheet.js.map +1 -1
  51. package/es/core/WorkSheet.d.ts +1 -1
  52. package/es/core/WorkSheet.js.map +1 -1
  53. package/es/core/table-plugins.js +7 -5
  54. package/es/core/table-plugins.js.map +1 -1
  55. package/es/formula/cell-highlight-manager.d.ts +1 -0
  56. package/es/formula/cell-highlight-manager.js +27 -22
  57. package/es/formula/cell-highlight-manager.js.map +1 -1
  58. package/es/formula/cross-sheet-data-synchronizer.d.ts +48 -0
  59. package/es/formula/cross-sheet-data-synchronizer.js +151 -0
  60. package/es/formula/cross-sheet-data-synchronizer.js.map +1 -0
  61. package/es/formula/cross-sheet-formula-handler.d.ts +58 -0
  62. package/es/formula/cross-sheet-formula-handler.js +236 -0
  63. package/es/formula/cross-sheet-formula-handler.js.map +1 -0
  64. package/es/formula/cross-sheet-formula-manager.d.ts +52 -0
  65. package/es/formula/cross-sheet-formula-manager.js +227 -0
  66. package/es/formula/cross-sheet-formula-manager.js.map +1 -0
  67. package/es/formula/cross-sheet-formula-validator.d.ts +56 -0
  68. package/es/formula/cross-sheet-formula-validator.js +263 -0
  69. package/es/formula/cross-sheet-formula-validator.js.map +1 -0
  70. package/es/formula/formula-engine.d.ts +4 -1
  71. package/es/formula/formula-engine.js +132 -25
  72. package/es/formula/formula-engine.js.map +1 -1
  73. package/es/formula/formula-paste-processor.d.ts +3 -3
  74. package/es/formula/formula-paste-processor.js.map +1 -1
  75. package/es/formula/formula-reference-adjustor.d.ts +1 -1
  76. package/es/formula/formula-reference-adjustor.js.map +1 -1
  77. package/es/formula/index.d.ts +8 -0
  78. package/es/formula/index.js +8 -0
  79. package/es/formula/index.js.map +1 -1
  80. package/es/index.d.ts +1 -1
  81. package/es/index.js +1 -1
  82. package/es/index.js.map +1 -1
  83. package/es/managers/formula-manager.d.ts +17 -2
  84. package/es/managers/formula-manager.js +152 -11
  85. package/es/managers/formula-manager.js.map +1 -1
  86. package/es/styles/menu.js +2 -1
  87. package/es/styles/sheet-tab.js +1 -2
  88. package/es/tools/index.js +2 -1
  89. package/package.json +5 -5
@@ -0,0 +1,52 @@
1
+ import type { FormulaCell } from '../ts-types/formula';
2
+ import type { FormulaEngine } from './formula-engine';
3
+ export interface CrossSheetReference {
4
+ sourceSheet: string;
5
+ targetSheet: string;
6
+ sourceCell: FormulaCell;
7
+ targetCells: FormulaCell[];
8
+ formula: string;
9
+ }
10
+ export interface CrossSheetDependency {
11
+ dependentSheet: string;
12
+ precedentSheet: string;
13
+ dependentCells: FormulaCell[];
14
+ precedentCells: FormulaCell[];
15
+ }
16
+ export declare class CrossSheetFormulaManager {
17
+ private formulaEngine;
18
+ private sheetManager;
19
+ private crossSheetDependencies;
20
+ private reverseCrossSheetDependencies;
21
+ private calculationCache;
22
+ private readonly CACHE_TTL;
23
+ constructor(formulaEngine: FormulaEngine, sheetManager?: {
24
+ getAllSheets: () => Array<{
25
+ sheetKey: string;
26
+ sheetTitle: string;
27
+ }>;
28
+ });
29
+ registerCrossSheetReference(formula: string, sourceCell: FormulaCell): CrossSheetReference[];
30
+ private isValidSheet;
31
+ private extractTargetSheets;
32
+ private extractTargetCells;
33
+ private updateCrossSheetDependency;
34
+ getCrossSheetDependencies(sheet: string): CrossSheetDependency[];
35
+ private getDependentCells;
36
+ private getPrecedentCells;
37
+ updateCrossSheetReferences(targetSheet: string, changedCells: FormulaCell[]): Promise<void>;
38
+ private formulaReferencesCells;
39
+ private recalculateFormula;
40
+ private recalculateDependents;
41
+ validateCrossSheetReferences(sheet: string): {
42
+ valid: boolean;
43
+ errors: string[];
44
+ };
45
+ getCachedValue(cell: FormulaCell): any;
46
+ clearCache(): void;
47
+ clearSheetDependencies(sheet: string): void;
48
+ private parseA1Notation;
49
+ private getA1Notation;
50
+ private getCacheKey;
51
+ destroy(): void;
52
+ }
@@ -0,0 +1,227 @@
1
+ var __awaiter = this && this.__awaiter || function(thisArg, _arguments, P, generator) {
2
+ return new (P || (P = Promise))((function(resolve, reject) {
3
+ function fulfilled(value) {
4
+ try {
5
+ step(generator.next(value));
6
+ } catch (e) {
7
+ reject(e);
8
+ }
9
+ }
10
+ function rejected(value) {
11
+ try {
12
+ step(generator.throw(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ }
17
+ function step(result) {
18
+ var value;
19
+ result.done ? resolve(result.value) : (value = result.value, value instanceof P ? value : new P((function(resolve) {
20
+ resolve(value);
21
+ }))).then(fulfilled, rejected);
22
+ }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ }));
25
+ };
26
+
27
+ export class CrossSheetFormulaManager {
28
+ constructor(formulaEngine, sheetManager) {
29
+ this.crossSheetDependencies = new Map, this.reverseCrossSheetDependencies = new Map,
30
+ this.calculationCache = new Map, this.CACHE_TTL = 1e3, this.formulaEngine = formulaEngine,
31
+ this.sheetManager = sheetManager;
32
+ }
33
+ registerCrossSheetReference(formula, sourceCell) {
34
+ const references = [], targetSheets = this.extractTargetSheets(formula);
35
+ for (const targetSheet of targetSheets) {
36
+ const targetCells = this.extractTargetCells(formula, targetSheet);
37
+ targetCells.length > 0 && (references.push({
38
+ sourceSheet: sourceCell.sheet,
39
+ targetSheet: targetSheet,
40
+ sourceCell: sourceCell,
41
+ targetCells: targetCells,
42
+ formula: formula
43
+ }), this.updateCrossSheetDependency(sourceCell.sheet, targetSheet, sourceCell, targetCells));
44
+ }
45
+ return references;
46
+ }
47
+ isValidSheet(sheetName) {
48
+ if (this.sheetManager) {
49
+ return this.sheetManager.getAllSheets().some((sheet => sheet.sheetTitle.toLowerCase() === sheetName.toLowerCase()));
50
+ }
51
+ return this.formulaEngine.getAllSheets().some((sheet => sheet.title.toLowerCase() === sheetName.toLowerCase()));
52
+ }
53
+ extractTargetSheets(formula) {
54
+ const sheets = new Set, quotedSheetPattern = /'([^']+)'[!!]/g, unquotedSheetPattern = /([A-Za-z0-9_\s一-龥]+)[!!]/g;
55
+ let match;
56
+ for (;null !== (match = quotedSheetPattern.exec(formula)); ) {
57
+ const sheetName = match[1];
58
+ sheetName && this.isValidSheet(sheetName) && sheets.add(sheetName);
59
+ }
60
+ for (;null !== (match = unquotedSheetPattern.exec(formula)); ) {
61
+ const sheetName = match[1];
62
+ sheetName && this.isValidSheet(sheetName) && sheets.add(sheetName);
63
+ }
64
+ return Array.from(sheets);
65
+ }
66
+ extractTargetCells(formula, targetSheet) {
67
+ const cells = [], escapedSheetName = targetSheet.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), cellPattern = new RegExp(`'?${escapedSheetName}'?[!!]([A-Z]+[0-9]+)(?:\\s*:\\s*(?:'?${escapedSheetName}'?[!!])?([A-Z]+[0-9]+))?`, "g");
68
+ let match;
69
+ for (;null !== (match = cellPattern.exec(formula)); ) if (match[2]) {
70
+ const startCell = this.parseA1Notation(match[1]), endCell = this.parseA1Notation(match[2]);
71
+ for (let row = Math.min(startCell.row, endCell.row); row <= Math.max(startCell.row, endCell.row); row++) for (let col = Math.min(startCell.col, endCell.col); col <= Math.max(startCell.col, endCell.col); col++) cells.push({
72
+ sheet: targetSheet,
73
+ row: row,
74
+ col: col
75
+ });
76
+ } else {
77
+ const cell = this.parseA1Notation(match[1]);
78
+ cells.push({
79
+ sheet: targetSheet,
80
+ row: cell.row,
81
+ col: cell.col
82
+ });
83
+ }
84
+ return cells;
85
+ }
86
+ updateCrossSheetDependency(sourceSheet, targetSheet, _sourceCell, _targetCells) {
87
+ this.crossSheetDependencies.has(sourceSheet) || this.crossSheetDependencies.set(sourceSheet, new Set);
88
+ const sourceDeps = this.crossSheetDependencies.get(sourceSheet);
89
+ sourceDeps && sourceDeps.add(targetSheet), this.reverseCrossSheetDependencies.has(targetSheet) || this.reverseCrossSheetDependencies.set(targetSheet, new Set);
90
+ const targetDeps = this.reverseCrossSheetDependencies.get(targetSheet);
91
+ targetDeps && targetDeps.add(sourceSheet);
92
+ }
93
+ getCrossSheetDependencies(sheet) {
94
+ const dependencies = [], targetSheets = this.crossSheetDependencies.get(sheet);
95
+ if (targetSheets) for (const targetSheet of targetSheets) {
96
+ const dependentCells = this.getDependentCells(sheet, targetSheet), precedentCells = this.getPrecedentCells(targetSheet, sheet);
97
+ dependencies.push({
98
+ dependentSheet: sheet,
99
+ precedentSheet: targetSheet,
100
+ dependentCells: dependentCells,
101
+ precedentCells: precedentCells
102
+ });
103
+ }
104
+ return dependencies;
105
+ }
106
+ getDependentCells(dependentSheet, precedentSheet) {
107
+ const cells = [], allSheets = this.formulaEngine.getAllSheets();
108
+ for (const sheetInfo of allSheets) if (sheetInfo.key === dependentSheet) {
109
+ const formulas = this.formulaEngine.exportFormulas(sheetInfo.key);
110
+ for (const [cellRef, formula] of Object.entries(formulas)) if (formula.includes(`${precedentSheet}!`)) {
111
+ const cell = this.parseA1Notation(cellRef);
112
+ cells.push({
113
+ sheet: dependentSheet,
114
+ row: cell.row,
115
+ col: cell.col
116
+ });
117
+ }
118
+ }
119
+ return cells;
120
+ }
121
+ getPrecedentCells(precedentSheet, dependentSheet) {
122
+ const cells = [], formulas = this.formulaEngine.exportFormulas(dependentSheet);
123
+ for (const formula of Object.values(formulas)) if (formula.includes(`${precedentSheet}!`)) {
124
+ const targetCells = this.extractTargetCells(formula, precedentSheet);
125
+ cells.push(...targetCells);
126
+ }
127
+ return cells;
128
+ }
129
+ updateCrossSheetReferences(targetSheet, changedCells) {
130
+ return __awaiter(this, void 0, void 0, (function*() {
131
+ const dependentSheets = this.reverseCrossSheetDependencies.get(targetSheet);
132
+ if (!dependentSheets || 0 === dependentSheets.size) return;
133
+ const cellsToRecalculate = [];
134
+ for (const dependentSheet of dependentSheets) {
135
+ const formulas = this.formulaEngine.exportFormulas(dependentSheet);
136
+ for (const [cellRef, formula] of Object.entries(formulas)) if (this.formulaReferencesCells(formula, targetSheet, changedCells)) {
137
+ const cell = this.parseA1Notation(cellRef);
138
+ cellsToRecalculate.push({
139
+ sheet: dependentSheet,
140
+ row: cell.row,
141
+ col: cell.col
142
+ });
143
+ }
144
+ }
145
+ for (const cell of cellsToRecalculate) yield this.recalculateFormula(cell);
146
+ }));
147
+ }
148
+ formulaReferencesCells(formula, targetSheet, targetCells) {
149
+ return this.extractTargetCells(formula, targetSheet).some((refCell => targetCells.some((targetCell => refCell.sheet === targetCell.sheet && refCell.row === targetCell.row && refCell.col === targetCell.col))));
150
+ }
151
+ recalculateFormula(cell) {
152
+ return __awaiter(this, void 0, void 0, (function*() {
153
+ try {
154
+ const formula = this.formulaEngine.getCellFormula(cell);
155
+ if (formula) {
156
+ const result = this.formulaEngine.calculateFormula(formula), cacheKey = this.getCacheKey(cell);
157
+ this.calculationCache.set(cacheKey, {
158
+ value: result.value,
159
+ timestamp: Date.now()
160
+ }), this.recalculateDependents(cell);
161
+ }
162
+ } catch (error) {}
163
+ }));
164
+ }
165
+ recalculateDependents(cell) {
166
+ const dependents = this.formulaEngine.getCellDependents(cell);
167
+ for (const dependent of dependents) this.recalculateFormula(dependent);
168
+ }
169
+ validateCrossSheetReferences(sheet) {
170
+ const errors = [], formulas = this.formulaEngine.exportFormulas(sheet);
171
+ for (const [cellRef, formula] of Object.entries(formulas)) {
172
+ const targetSheets = this.extractTargetSheets(formula);
173
+ for (const targetSheet of targetSheets) {
174
+ if (!this.formulaEngine.getAllSheets().some((s => s.key === targetSheet))) {
175
+ errors.push(`Invalid sheet reference in ${cellRef}: ${targetSheet}`);
176
+ continue;
177
+ }
178
+ const targetCells = this.extractTargetCells(formula, targetSheet);
179
+ for (const targetCell of targetCells) {
180
+ this.formulaEngine.getCellValue(targetCell).error && errors.push(`Invalid cell reference in ${cellRef}: ${targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`);
181
+ }
182
+ }
183
+ }
184
+ return {
185
+ valid: 0 === errors.length,
186
+ errors: errors
187
+ };
188
+ }
189
+ getCachedValue(cell) {
190
+ const cacheKey = this.getCacheKey(cell), cached = this.calculationCache.get(cacheKey);
191
+ return cached && Date.now() - cached.timestamp < this.CACHE_TTL ? cached.value : null;
192
+ }
193
+ clearCache() {
194
+ this.calculationCache.clear();
195
+ }
196
+ clearSheetDependencies(sheet) {
197
+ this.crossSheetDependencies.delete(sheet);
198
+ for (const [targetSheet, sourceSheets] of this.reverseCrossSheetDependencies.entries()) sourceSheets.delete(sheet),
199
+ 0 === sourceSheets.size && this.reverseCrossSheetDependencies.delete(targetSheet);
200
+ for (const [cacheKey] of this.calculationCache.entries()) cacheKey.startsWith(`${sheet}!`) && this.calculationCache.delete(cacheKey);
201
+ }
202
+ parseA1Notation(a1Notation) {
203
+ const match = a1Notation.match(/^([A-Z]+)([0-9]+)$/);
204
+ if (!match) throw new Error(`Invalid cell reference: ${a1Notation}`);
205
+ const colLetters = match[1], rowNumber = parseInt(match[2], 10);
206
+ let col = 0;
207
+ for (let i = 0; i < colLetters.length; i++) col = 26 * col + (colLetters.charCodeAt(i) - 65);
208
+ return {
209
+ row: rowNumber - 1,
210
+ col: col
211
+ };
212
+ }
213
+ getA1Notation(row, col) {
214
+ let colStr = "", tempCol = col;
215
+ do {
216
+ colStr = String.fromCharCode(65 + tempCol % 26) + colStr, tempCol = Math.floor(tempCol / 26) - 1;
217
+ } while (tempCol >= 0);
218
+ return `${colStr}${row + 1}`;
219
+ }
220
+ getCacheKey(cell) {
221
+ return `${cell.sheet}!${this.getA1Notation(cell.row, cell.col)}`;
222
+ }
223
+ destroy() {
224
+ this.clearCache(), this.crossSheetDependencies.clear(), this.reverseCrossSheetDependencies.clear();
225
+ }
226
+ }
227
+ //# sourceMappingURL=cross-sheet-formula-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/formula/cross-sheet-formula-manager.ts"],"names":[],"mappings":"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","file":"cross-sheet-formula-manager.js","sourcesContent":["/**\n * 跨sheet公式管理器\n * 专门处理跨sheet tab的公式引用和计算\n */\n\nimport type { FormulaCell } from '../ts-types/formula';\nimport type { FormulaEngine } from './formula-engine';\n\nexport interface CrossSheetReference {\n sourceSheet: string;\n targetSheet: string;\n sourceCell: FormulaCell;\n targetCells: FormulaCell[];\n formula: string;\n}\n\nexport interface CrossSheetDependency {\n dependentSheet: string;\n precedentSheet: string;\n dependentCells: FormulaCell[];\n precedentCells: FormulaCell[];\n}\n\nexport class CrossSheetFormulaManager {\n private formulaEngine: FormulaEngine;\n private sheetManager: { getAllSheets: () => Array<{ sheetKey: string; sheetTitle: string }> } | undefined;\n\n // 跨sheet依赖关系映射\n private crossSheetDependencies: Map<string, Set<string>> = new Map();\n\n // 反向依赖映射(用于快速查找)\n private reverseCrossSheetDependencies: Map<string, Set<string>> = new Map();\n\n // 缓存机制\n private calculationCache: Map<string, { value: any; timestamp: number }> = new Map();\n private readonly CACHE_TTL = 1000; // 1秒缓存\n\n constructor(\n formulaEngine: FormulaEngine,\n sheetManager?: { getAllSheets: () => Array<{ sheetKey: string; sheetTitle: string }> }\n ) {\n this.formulaEngine = formulaEngine;\n this.sheetManager = sheetManager;\n }\n\n /**\n * 注册跨sheet公式引用\n */\n registerCrossSheetReference(formula: string, sourceCell: FormulaCell): CrossSheetReference[] {\n const references: CrossSheetReference[] = [];\n const targetSheets = this.extractTargetSheets(formula);\n\n for (const targetSheet of targetSheets) {\n const targetCells = this.extractTargetCells(formula, targetSheet);\n if (targetCells.length > 0) {\n references.push({\n sourceSheet: sourceCell.sheet,\n targetSheet,\n sourceCell,\n targetCells,\n formula\n });\n\n // 更新依赖关系\n this.updateCrossSheetDependency(sourceCell.sheet, targetSheet, sourceCell, targetCells);\n }\n }\n\n return references;\n }\n\n /**\n * 验证sheet是否存在(使用sheetTitle而不是sheetKey)\n * 检查所有存在的sheet,包括未激活的sheet\n */\n private isValidSheet(sheetName: string): boolean {\n // 优先使用sheetManager检查所有存在的sheet(包括未激活的)\n if (this.sheetManager) {\n const allSheets = this.sheetManager.getAllSheets();\n return allSheets.some(sheet => sheet.sheetTitle.toLowerCase() === sheetName.toLowerCase());\n }\n\n // 回退到formulaEngine中已注册的sheet\n const allSheets = this.formulaEngine.getAllSheets();\n return allSheets.some(sheet => sheet.title.toLowerCase() === sheetName.toLowerCase());\n }\n\n /**\n * 提取公式中引用的目标sheet\n * 检查所有存在的sheet,包括未激活的sheet\n */\n private extractTargetSheets(formula: string): string[] {\n const sheets = new Set<string>();\n // 支持:\n // - Sheet1!A1\n // - Sheet1!A1\n // - 'My Sheet'!A1\n // - 'My Sheet'!A1\n const quotedSheetPattern = /'([^']+)'[!!]/g;\n const unquotedSheetPattern = /([A-Za-z0-9_\\s一-龥]+)[!!]/g;\n\n let match: RegExpExecArray | null;\n while ((match = quotedSheetPattern.exec(formula)) !== null) {\n const sheetName = match[1];\n if (sheetName && this.isValidSheet(sheetName)) {\n sheets.add(sheetName);\n }\n }\n while ((match = unquotedSheetPattern.exec(formula)) !== null) {\n const sheetName = match[1];\n if (sheetName && this.isValidSheet(sheetName)) {\n sheets.add(sheetName);\n }\n }\n\n return Array.from(sheets);\n }\n\n /**\n * 提取公式中引用的目标单元格\n */\n private extractTargetCells(formula: string, targetSheet: string): FormulaCell[] {\n const cells: FormulaCell[] = [];\n\n // 匹配带sheet前缀的单元格引用,如 Sheet1!A1 或 Sheet1!A1:B2 - 支持中英文sheet名称\n const escapedSheetName = targetSheet.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const sepPattern = '[!!]';\n // 支持:\n // - Sheet1!A1\n // - Sheet1!A1:B2\n // - Sheet1!A1:Sheet1!B2\n // - 'My Sheet'!A1:'My Sheet'!B2\n const cellPattern = new RegExp(\n `'?${escapedSheetName}'?${sepPattern}([A-Z]+[0-9]+)` +\n `(?:\\\\s*:\\\\s*(?:'?${escapedSheetName}'?${sepPattern})?([A-Z]+[0-9]+))?`,\n 'g'\n );\n let match;\n\n while ((match = cellPattern.exec(formula)) !== null) {\n if (match[2]) {\n // 范围引用,如 A1:B2\n const startCell = this.parseA1Notation(match[1]);\n const endCell = this.parseA1Notation(match[2]);\n\n // 展开范围到所有单元格\n for (let row = Math.min(startCell.row, endCell.row); row <= Math.max(startCell.row, endCell.row); row++) {\n for (let col = Math.min(startCell.col, endCell.col); col <= Math.max(startCell.col, endCell.col); col++) {\n cells.push({ sheet: targetSheet, row, col });\n }\n }\n } else {\n // 单个单元格引用,如 A1\n const cell = this.parseA1Notation(match[1]);\n cells.push({ sheet: targetSheet, row: cell.row, col: cell.col });\n }\n }\n\n return cells;\n }\n\n /**\n * 更新跨sheet依赖关系\n */\n private updateCrossSheetDependency(\n sourceSheet: string,\n targetSheet: string,\n _sourceCell: FormulaCell,\n _targetCells: FormulaCell[]\n ): void {\n // 正向依赖\n if (!this.crossSheetDependencies.has(sourceSheet)) {\n this.crossSheetDependencies.set(sourceSheet, new Set());\n }\n const sourceDeps = this.crossSheetDependencies.get(sourceSheet);\n if (sourceDeps) {\n sourceDeps.add(targetSheet);\n }\n\n // 反向依赖\n if (!this.reverseCrossSheetDependencies.has(targetSheet)) {\n this.reverseCrossSheetDependencies.set(targetSheet, new Set());\n }\n const targetDeps = this.reverseCrossSheetDependencies.get(targetSheet);\n if (targetDeps) {\n targetDeps.add(sourceSheet);\n }\n }\n\n /**\n * 获取跨sheet依赖关系\n */\n getCrossSheetDependencies(sheet: string): CrossSheetDependency[] {\n const dependencies: CrossSheetDependency[] = [];\n const targetSheets = this.crossSheetDependencies.get(sheet);\n\n if (targetSheets) {\n for (const targetSheet of targetSheets) {\n const dependentCells = this.getDependentCells(sheet, targetSheet);\n const precedentCells = this.getPrecedentCells(targetSheet, sheet);\n\n dependencies.push({\n dependentSheet: sheet,\n precedentSheet: targetSheet,\n dependentCells,\n precedentCells\n });\n }\n }\n\n return dependencies;\n }\n\n /**\n * 获取依赖于指定sheet的单元格\n */\n private getDependentCells(dependentSheet: string, precedentSheet: string): FormulaCell[] {\n const cells: FormulaCell[] = [];\n\n // 遍历所有公式,找到引用precedentSheet的单元格\n const allSheets = this.formulaEngine.getAllSheets();\n for (const sheetInfo of allSheets) {\n if (sheetInfo.key === dependentSheet) {\n const formulas = this.formulaEngine.exportFormulas(sheetInfo.key);\n\n for (const [cellRef, formula] of Object.entries(formulas)) {\n if (formula.includes(`${precedentSheet}!`)) {\n const cell = this.parseA1Notation(cellRef);\n cells.push({ sheet: dependentSheet, row: cell.row, col: cell.col });\n }\n }\n }\n }\n\n return cells;\n }\n\n /**\n * 获取被指定sheet依赖的单元格\n */\n private getPrecedentCells(precedentSheet: string, dependentSheet: string): FormulaCell[] {\n const cells: FormulaCell[] = [];\n\n // 从dependentSheet的公式中提取引用precedentSheet的单元格\n const formulas = this.formulaEngine.exportFormulas(dependentSheet);\n\n for (const formula of Object.values(formulas)) {\n if (formula.includes(`${precedentSheet}!`)) {\n const targetCells = this.extractTargetCells(formula, precedentSheet);\n cells.push(...targetCells);\n }\n }\n\n return cells;\n }\n\n /**\n * 当目标sheet数据变化时,更新依赖的公式\n */\n async updateCrossSheetReferences(targetSheet: string, changedCells: FormulaCell[]): Promise<void> {\n const dependentSheets = this.reverseCrossSheetDependencies.get(targetSheet);\n\n if (!dependentSheets || dependentSheets.size === 0) {\n return;\n }\n\n // 收集所有需要重新计算的公式单元格\n const cellsToRecalculate: FormulaCell[] = [];\n\n for (const dependentSheet of dependentSheets) {\n const formulas = this.formulaEngine.exportFormulas(dependentSheet);\n\n for (const [cellRef, formula] of Object.entries(formulas)) {\n // 检查公式是否引用了任何变化的单元格\n if (this.formulaReferencesCells(formula, targetSheet, changedCells)) {\n const cell = this.parseA1Notation(cellRef);\n cellsToRecalculate.push({ sheet: dependentSheet, row: cell.row, col: cell.col });\n }\n }\n }\n\n // 重新计算所有受影响的公式\n for (const cell of cellsToRecalculate) {\n await this.recalculateFormula(cell);\n }\n }\n\n /**\n * 检查公式是否引用了指定的单元格\n */\n private formulaReferencesCells(formula: string, targetSheet: string, targetCells: FormulaCell[]): boolean {\n const referencedCells = this.extractTargetCells(formula, targetSheet);\n\n return referencedCells.some(refCell =>\n targetCells.some(\n targetCell =>\n refCell.sheet === targetCell.sheet && refCell.row === targetCell.row && refCell.col === targetCell.col\n )\n );\n }\n\n /**\n * 重新计算公式单元格\n */\n private async recalculateFormula(cell: FormulaCell): Promise<void> {\n try {\n const formula = this.formulaEngine.getCellFormula(cell);\n if (formula) {\n const result = this.formulaEngine.calculateFormula(formula);\n\n // 更新缓存\n const cacheKey = this.getCacheKey(cell);\n this.calculationCache.set(cacheKey, {\n value: result.value,\n timestamp: Date.now()\n });\n\n // 触发重新计算依赖\n this.recalculateDependents(cell);\n }\n } catch (error) {\n console.error(`Failed to recalculate formula at ${cell.sheet}!${this.getA1Notation(cell.row, cell.col)}:`, error);\n }\n }\n\n /**\n * 重新计算依赖的公式\n */\n private recalculateDependents(cell: FormulaCell): void {\n const dependents = this.formulaEngine.getCellDependents(cell);\n\n for (const dependent of dependents) {\n // 递归重新计算\n this.recalculateFormula(dependent);\n }\n }\n\n /**\n * 验证跨sheet引用的有效性\n */\n validateCrossSheetReferences(sheet: string): { valid: boolean; errors: string[] } {\n const errors: string[] = [];\n const formulas = this.formulaEngine.exportFormulas(sheet);\n\n for (const [cellRef, formula] of Object.entries(formulas)) {\n const targetSheets = this.extractTargetSheets(formula);\n\n for (const targetSheet of targetSheets) {\n if (!this.formulaEngine.getAllSheets().some(s => s.key === targetSheet)) {\n errors.push(`Invalid sheet reference in ${cellRef}: ${targetSheet}`);\n continue;\n }\n\n const targetCells = this.extractTargetCells(formula, targetSheet);\n for (const targetCell of targetCells) {\n const cellValue = this.formulaEngine.getCellValue(targetCell);\n if (cellValue.error) {\n errors.push(\n `Invalid cell reference in ${cellRef}: ${targetSheet}!${this.getA1Notation(\n targetCell.row,\n targetCell.col\n )}`\n );\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors\n };\n }\n\n /**\n * 获取缓存的计算结果\n */\n getCachedValue(cell: FormulaCell): any {\n const cacheKey = this.getCacheKey(cell);\n const cached = this.calculationCache.get(cacheKey);\n\n if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {\n return cached.value;\n }\n\n return null;\n }\n\n /**\n * 清除缓存\n */\n clearCache(): void {\n this.calculationCache.clear();\n }\n\n /**\n * 清除指定sheet的依赖关系\n */\n clearSheetDependencies(sheet: string): void {\n // 清除正向依赖\n this.crossSheetDependencies.delete(sheet);\n\n // 清除反向依赖\n for (const [targetSheet, sourceSheets] of this.reverseCrossSheetDependencies.entries()) {\n sourceSheets.delete(sheet);\n if (sourceSheets.size === 0) {\n this.reverseCrossSheetDependencies.delete(targetSheet);\n }\n }\n\n // 清除相关缓存\n for (const [cacheKey] of this.calculationCache.entries()) {\n if (cacheKey.startsWith(`${sheet}!`)) {\n this.calculationCache.delete(cacheKey);\n }\n }\n }\n\n /**\n * 工具方法:A1表示法解析\n */\n private parseA1Notation(a1Notation: string): { row: number; col: number } {\n const match = a1Notation.match(/^([A-Z]+)([0-9]+)$/);\n if (!match) {\n throw new Error(`Invalid cell reference: ${a1Notation}`);\n }\n\n const colLetters = match[1];\n const rowNumber = parseInt(match[2], 10);\n\n let col = 0;\n for (let i = 0; i < colLetters.length; i++) {\n col = col * 26 + (colLetters.charCodeAt(i) - 65);\n }\n\n return { row: rowNumber - 1, col };\n }\n\n /**\n * 工具方法:A1表示法生成\n */\n private getA1Notation(row: number, col: number): string {\n let colStr = '';\n let tempCol = col;\n\n do {\n colStr = String.fromCharCode(65 + (tempCol % 26)) + colStr;\n tempCol = Math.floor(tempCol / 26) - 1;\n } while (tempCol >= 0);\n\n return `${colStr}${row + 1}`;\n }\n\n /**\n * 工具方法:缓存键生成\n */\n private getCacheKey(cell: FormulaCell): string {\n return `${cell.sheet}!${this.getA1Notation(cell.row, cell.col)}`;\n }\n\n /**\n * 销毁管理器\n */\n destroy(): void {\n this.clearCache();\n this.crossSheetDependencies.clear();\n this.reverseCrossSheetDependencies.clear();\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import type { FormulaCell } from '../ts-types/formula';
2
+ import type { FormulaEngine } from './formula-engine';
3
+ import type { CrossSheetFormulaManager } from './cross-sheet-formula-manager';
4
+ export interface ValidationError {
5
+ type: 'INVALID_SHEET' | 'INVALID_CELL' | 'CIRCULAR_REFERENCE' | 'MISSING_REFERENCE';
6
+ message: string;
7
+ sheet: string;
8
+ cell: FormulaCell;
9
+ target?: string;
10
+ }
11
+ export interface ValidationResult {
12
+ valid: boolean;
13
+ errors: ValidationError[];
14
+ warnings: string[];
15
+ }
16
+ export interface ValidationOptions {
17
+ checkCircularReferences: boolean;
18
+ checkMissingReferences: boolean;
19
+ checkCellBounds: boolean;
20
+ maxRecursionDepth: number;
21
+ }
22
+ export declare class CrossSheetFormulaValidator {
23
+ private formulaEngine;
24
+ private crossSheetManager;
25
+ private sheetManager;
26
+ private validationCache;
27
+ private readonly CACHE_TTL;
28
+ private defaultOptions;
29
+ constructor(formulaEngine: FormulaEngine, crossSheetManager: CrossSheetFormulaManager, sheetManager?: {
30
+ getAllSheets: () => Array<{
31
+ sheetKey: string;
32
+ sheetTitle: string;
33
+ }>;
34
+ });
35
+ validateSheet(sheet: string, options?: Partial<ValidationOptions>): ValidationResult;
36
+ private validateFormula;
37
+ private extractCrossSheetReferences;
38
+ private validateCellReferences;
39
+ private checkCircularReferences;
40
+ private checkMissingReferences;
41
+ validateAllSheets(options?: Partial<ValidationOptions>): Map<string, ValidationResult>;
42
+ validateFormulaSyntax(formula: string): {
43
+ valid: boolean;
44
+ error?: string;
45
+ };
46
+ private validateExpressionStructure;
47
+ private isValidSheetName;
48
+ private isValidCellReference;
49
+ private isValidSheet;
50
+ private parseA1Notation;
51
+ private getA1Notation;
52
+ private getCacheKey;
53
+ private getCacheTimestamp;
54
+ clearCache(): void;
55
+ destroy(): void;
56
+ }
@@ -0,0 +1,263 @@
1
+ export class CrossSheetFormulaValidator {
2
+ constructor(formulaEngine, crossSheetManager, sheetManager) {
3
+ this.validationCache = new Map, this.CACHE_TTL = 5e3, this.defaultOptions = {
4
+ checkCircularReferences: !0,
5
+ checkMissingReferences: !0,
6
+ checkCellBounds: !0,
7
+ maxRecursionDepth: 100
8
+ }, this.formulaEngine = formulaEngine, this.crossSheetManager = crossSheetManager,
9
+ this.sheetManager = sheetManager;
10
+ }
11
+ validateSheet(sheet, options) {
12
+ const validationOptions = Object.assign(Object.assign({}, this.defaultOptions), options), cacheKey = this.getCacheKey(sheet, validationOptions), cached = this.validationCache.get(cacheKey);
13
+ if (cached && Date.now() - this.getCacheTimestamp(cacheKey) < this.CACHE_TTL) return cached;
14
+ const errors = [];
15
+ try {
16
+ const formulas = this.formulaEngine.exportFormulas(sheet);
17
+ for (const [cellRef, formula] of Object.entries(formulas)) {
18
+ const cell = this.parseA1Notation(cellRef), cellWithSheet = {
19
+ sheet: sheet,
20
+ row: cell.row,
21
+ col: cell.col
22
+ }, formulaErrors = this.validateFormula(formula, cellWithSheet, validationOptions);
23
+ errors.push(...formulaErrors);
24
+ }
25
+ if (validationOptions.checkCircularReferences) {
26
+ const circularErrors = this.checkCircularReferences(sheet);
27
+ errors.push(...circularErrors);
28
+ }
29
+ if (validationOptions.checkMissingReferences) {
30
+ const missingErrors = this.checkMissingReferences(sheet);
31
+ errors.push(...missingErrors);
32
+ }
33
+ } catch (error) {
34
+ errors.push({
35
+ type: "MISSING_REFERENCE",
36
+ message: `Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
37
+ sheet: sheet,
38
+ cell: {
39
+ sheet: sheet,
40
+ row: 0,
41
+ col: 0
42
+ }
43
+ });
44
+ }
45
+ const result = {
46
+ valid: 0 === errors.length,
47
+ errors: errors,
48
+ warnings: []
49
+ };
50
+ return this.validationCache.set(cacheKey, result), result;
51
+ }
52
+ validateFormula(formula, cell, options) {
53
+ const errors = [];
54
+ if (!formula.startsWith("=")) return errors;
55
+ try {
56
+ const crossSheetRefs = this.extractCrossSheetReferences(formula);
57
+ for (const ref of crossSheetRefs) if (this.isValidSheet(ref.targetSheet)) {
58
+ if (options.checkCellBounds) {
59
+ const cellErrors = this.validateCellReferences(ref, cell);
60
+ errors.push(...cellErrors);
61
+ }
62
+ } else errors.push({
63
+ type: "INVALID_SHEET",
64
+ message: `Invalid sheet reference: ${ref.targetSheet}`,
65
+ sheet: cell.sheet,
66
+ cell: cell,
67
+ target: ref.targetSheet
68
+ });
69
+ } catch (error) {
70
+ errors.push({
71
+ type: "MISSING_REFERENCE",
72
+ message: `Formula validation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
73
+ sheet: cell.sheet,
74
+ cell: cell
75
+ });
76
+ }
77
+ return errors;
78
+ }
79
+ extractCrossSheetReferences(formula) {
80
+ const references = [], sheetRefPattern = /'?([^'!!]+)'?[!!]([A-Z]+[0-9]+)(?:\s*:\s*(?:'?([^'!!]+)'?[!!])?([A-Z]+[0-9]+))?/g;
81
+ let match;
82
+ for (;null !== (match = sheetRefPattern.exec(formula)); ) {
83
+ const targetSheet = match[1], startRef = match[2], endSheetMaybe = match[3], endRef = match[4], targetCells = [];
84
+ if (endRef) {
85
+ if (endSheetMaybe && endSheetMaybe.toLowerCase() !== targetSheet.toLowerCase()) continue;
86
+ const startCell = this.parseA1Notation(startRef), endCell = this.parseA1Notation(endRef);
87
+ for (let row = Math.min(startCell.row, endCell.row); row <= Math.max(startCell.row, endCell.row); row++) for (let col = Math.min(startCell.col, endCell.col); col <= Math.max(startCell.col, endCell.col); col++) targetCells.push({
88
+ sheet: targetSheet,
89
+ row: row,
90
+ col: col
91
+ });
92
+ } else {
93
+ const cell = this.parseA1Notation(startRef);
94
+ targetCells.push({
95
+ sheet: targetSheet,
96
+ row: cell.row,
97
+ col: cell.col
98
+ });
99
+ }
100
+ references.push({
101
+ targetSheet: targetSheet,
102
+ targetCells: targetCells
103
+ });
104
+ }
105
+ return references;
106
+ }
107
+ validateCellReferences(ref, sourceCell) {
108
+ const errors = [];
109
+ for (const targetCell of ref.targetCells) try {
110
+ const cellValue = this.formulaEngine.getCellValue(targetCell);
111
+ if (cellValue.error) {
112
+ const targetRef = `${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`;
113
+ errors.push({
114
+ type: "INVALID_CELL",
115
+ message: `Invalid cell reference: ${targetRef} - ${cellValue.error}`,
116
+ sheet: sourceCell.sheet,
117
+ cell: sourceCell,
118
+ target: targetRef
119
+ });
120
+ }
121
+ } catch (error) {
122
+ errors.push({
123
+ type: "INVALID_CELL",
124
+ message: `Cell validation failed: ${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`,
125
+ sheet: sourceCell.sheet,
126
+ cell: sourceCell,
127
+ target: `${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`
128
+ });
129
+ }
130
+ return errors;
131
+ }
132
+ checkCircularReferences(sheet) {
133
+ const errors = [], visited = new Set, recursionStack = new Set, dfs = (currentSheet, path) => {
134
+ const key = currentSheet;
135
+ if (recursionStack.has(key)) {
136
+ const cycleStart = path.indexOf(currentSheet), cycle = path.slice(cycleStart).concat([ currentSheet ]);
137
+ return errors.push({
138
+ type: "CIRCULAR_REFERENCE",
139
+ message: `Circular reference detected: ${cycle.join(" -> ")}`,
140
+ sheet: currentSheet,
141
+ cell: {
142
+ sheet: currentSheet,
143
+ row: 0,
144
+ col: 0
145
+ }
146
+ }), !0;
147
+ }
148
+ if (visited.has(key)) return !1;
149
+ visited.add(key), recursionStack.add(key);
150
+ const dependencies = this.crossSheetManager.getCrossSheetDependencies(currentSheet);
151
+ for (const dep of dependencies) if (dfs(dep.precedentSheet, [ ...path, currentSheet ])) return !0;
152
+ return recursionStack.delete(key), !1;
153
+ };
154
+ return dfs(sheet, []), errors;
155
+ }
156
+ checkMissingReferences(sheet) {
157
+ const errors = [], allSheets = this.formulaEngine.getAllSheets(), existingSheets = new Set(allSheets.map((s => s.key))), formulas = this.formulaEngine.exportFormulas(sheet);
158
+ for (const [cellRef, formula] of Object.entries(formulas)) {
159
+ const cell = this.parseA1Notation(cellRef), cellWithSheet = {
160
+ sheet: sheet,
161
+ row: cell.row,
162
+ col: cell.col
163
+ }, crossSheetRefs = this.extractCrossSheetReferences(formula);
164
+ for (const ref of crossSheetRefs) existingSheets.has(ref.targetSheet) || errors.push({
165
+ type: "MISSING_REFERENCE",
166
+ message: `Missing sheet reference: ${ref.targetSheet}`,
167
+ sheet: sheet,
168
+ cell: cellWithSheet,
169
+ target: ref.targetSheet
170
+ });
171
+ }
172
+ return errors;
173
+ }
174
+ validateAllSheets(options) {
175
+ const results = new Map, allSheets = this.formulaEngine.getAllSheets();
176
+ for (const sheetInfo of allSheets) {
177
+ const result = this.validateSheet(sheetInfo.key, options);
178
+ results.set(sheetInfo.key, result);
179
+ }
180
+ return results;
181
+ }
182
+ validateFormulaSyntax(formula) {
183
+ try {
184
+ if (!formula.startsWith("=")) return {
185
+ valid: !0
186
+ };
187
+ const expression = formula.substring(1).trim();
188
+ this.validateExpressionStructure(expression);
189
+ const crossSheetPattern = /([A-Za-z0-9_一-龥]+)[!!]([A-Z]+[0-9]+(?::[A-Z]+[0-9]+)?)/g;
190
+ let match;
191
+ for (;null !== (match = crossSheetPattern.exec(expression)); ) {
192
+ const sheetName = match[1], cellRef = match[2];
193
+ if (!this.isValidSheetName(sheetName)) return {
194
+ valid: !1,
195
+ error: `Invalid sheet name: ${sheetName}`
196
+ };
197
+ if (!this.isValidSheet(sheetName)) return {
198
+ valid: !1,
199
+ error: `Invalid sheet name: ${sheetName}`
200
+ };
201
+ if (!this.isValidCellReference(cellRef)) return {
202
+ valid: !1,
203
+ error: `Invalid cell reference: ${cellRef}`
204
+ };
205
+ }
206
+ return {
207
+ valid: !0
208
+ };
209
+ } catch (error) {
210
+ return {
211
+ valid: !1,
212
+ error: error instanceof Error ? error.message : "Syntax validation failed"
213
+ };
214
+ }
215
+ }
216
+ validateExpressionStructure(expression) {
217
+ if ((expression.match(/\(/g) || []).length !== (expression.match(/\)/g) || []).length) throw new Error("Unmatched parentheses");
218
+ if ((expression.match(/"/g) || []).length % 2 != 0) throw new Error("Unmatched quotes");
219
+ }
220
+ isValidSheetName(sheetName) {
221
+ return /^[A-Za-z_一-龥][A-Za-z0-9_一-龥]*$/.test(sheetName);
222
+ }
223
+ isValidCellReference(cellRef) {
224
+ return /^[A-Z]+[0-9]+$/.test(cellRef) || /^[A-Z]+[0-9]+:[A-Z]+[0-9]+$/.test(cellRef);
225
+ }
226
+ isValidSheet(sheetName) {
227
+ if (this.sheetManager) {
228
+ return this.sheetManager.getAllSheets().some((sheet => sheet.sheetTitle.toLowerCase() === sheetName.toLowerCase()));
229
+ }
230
+ return this.formulaEngine.getAllSheets().some((sheet => sheet.title.toLowerCase() === sheetName.toLowerCase()));
231
+ }
232
+ parseA1Notation(a1Notation) {
233
+ const match = a1Notation.match(/^([A-Z]+)([0-9]+)$/);
234
+ if (!match) throw new Error(`Invalid cell reference: ${a1Notation}`);
235
+ const colLetters = match[1], rowNumber = parseInt(match[2], 10);
236
+ let col = 0;
237
+ for (let i = 0; i < colLetters.length; i++) col = 26 * col + (colLetters.charCodeAt(i) - 65);
238
+ return {
239
+ row: rowNumber - 1,
240
+ col: col
241
+ };
242
+ }
243
+ getA1Notation(row, col) {
244
+ let colStr = "", tempCol = col;
245
+ do {
246
+ colStr = String.fromCharCode(65 + tempCol % 26) + colStr, tempCol = Math.floor(tempCol / 26) - 1;
247
+ } while (tempCol >= 0);
248
+ return `${colStr}${row + 1}`;
249
+ }
250
+ getCacheKey(sheet, options) {
251
+ return `${sheet}_${JSON.stringify(options)}`;
252
+ }
253
+ getCacheTimestamp(_cacheKey) {
254
+ return Date.now();
255
+ }
256
+ clearCache() {
257
+ this.validationCache.clear();
258
+ }
259
+ destroy() {
260
+ this.clearCache();
261
+ }
262
+ }
263
+ //# sourceMappingURL=cross-sheet-formula-validator.js.map