@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.
- package/cjs/components/sheet-tab-event-handler.js +3 -2
- package/cjs/components/sheet-tab-event-handler.js.map +1 -1
- package/cjs/components/vtable-sheet.d.ts +2 -1
- package/cjs/components/vtable-sheet.js +9 -1
- package/cjs/components/vtable-sheet.js.map +1 -1
- package/cjs/core/WorkSheet.d.ts +1 -1
- package/cjs/core/WorkSheet.js.map +1 -1
- package/cjs/core/table-plugins.js +7 -5
- package/cjs/core/table-plugins.js.map +1 -1
- package/cjs/formula/cell-highlight-manager.d.ts +1 -0
- package/cjs/formula/cell-highlight-manager.js +27 -22
- package/cjs/formula/cell-highlight-manager.js.map +1 -1
- package/cjs/formula/cross-sheet-data-synchronizer.d.ts +48 -0
- package/cjs/formula/cross-sheet-data-synchronizer.js +159 -0
- package/cjs/formula/cross-sheet-data-synchronizer.js.map +1 -0
- package/cjs/formula/cross-sheet-formula-handler.d.ts +58 -0
- package/cjs/formula/cross-sheet-formula-handler.js +240 -0
- package/cjs/formula/cross-sheet-formula-handler.js.map +1 -0
- package/cjs/formula/cross-sheet-formula-manager.d.ts +52 -0
- package/cjs/formula/cross-sheet-formula-manager.js +235 -0
- package/cjs/formula/cross-sheet-formula-manager.js.map +1 -0
- package/cjs/formula/cross-sheet-formula-validator.d.ts +56 -0
- package/cjs/formula/cross-sheet-formula-validator.js +271 -0
- package/cjs/formula/cross-sheet-formula-validator.js.map +1 -0
- package/cjs/formula/formula-engine.d.ts +4 -1
- package/cjs/formula/formula-engine.js +132 -25
- package/cjs/formula/formula-engine.js.map +1 -1
- package/cjs/formula/formula-paste-processor.d.ts +3 -3
- package/cjs/formula/formula-paste-processor.js.map +1 -1
- package/cjs/formula/formula-reference-adjustor.d.ts +1 -1
- package/cjs/formula/formula-reference-adjustor.js.map +1 -1
- package/cjs/formula/index.d.ts +8 -0
- package/cjs/formula/index.js +40 -3
- package/cjs/formula/index.js.map +1 -1
- package/cjs/index.d.ts +1 -1
- package/cjs/index.js +1 -1
- package/cjs/index.js.map +1 -1
- package/cjs/managers/formula-manager.d.ts +17 -2
- package/cjs/managers/formula-manager.js +150 -12
- package/cjs/managers/formula-manager.js.map +1 -1
- package/cjs/styles/menu.js +2 -1
- package/cjs/styles/sheet-tab.js +1 -2
- package/cjs/tools/index.js +2 -1
- package/dist/vtable-sheet.js +11322 -331
- package/dist/vtable-sheet.min.js +1 -1
- package/es/components/sheet-tab-event-handler.js +3 -2
- package/es/components/sheet-tab-event-handler.js.map +1 -1
- package/es/components/vtable-sheet.d.ts +2 -1
- package/es/components/vtable-sheet.js +9 -1
- package/es/components/vtable-sheet.js.map +1 -1
- package/es/core/WorkSheet.d.ts +1 -1
- package/es/core/WorkSheet.js.map +1 -1
- package/es/core/table-plugins.js +7 -5
- package/es/core/table-plugins.js.map +1 -1
- package/es/formula/cell-highlight-manager.d.ts +1 -0
- package/es/formula/cell-highlight-manager.js +27 -22
- package/es/formula/cell-highlight-manager.js.map +1 -1
- package/es/formula/cross-sheet-data-synchronizer.d.ts +48 -0
- package/es/formula/cross-sheet-data-synchronizer.js +151 -0
- package/es/formula/cross-sheet-data-synchronizer.js.map +1 -0
- package/es/formula/cross-sheet-formula-handler.d.ts +58 -0
- package/es/formula/cross-sheet-formula-handler.js +236 -0
- package/es/formula/cross-sheet-formula-handler.js.map +1 -0
- package/es/formula/cross-sheet-formula-manager.d.ts +52 -0
- package/es/formula/cross-sheet-formula-manager.js +227 -0
- package/es/formula/cross-sheet-formula-manager.js.map +1 -0
- package/es/formula/cross-sheet-formula-validator.d.ts +56 -0
- package/es/formula/cross-sheet-formula-validator.js +263 -0
- package/es/formula/cross-sheet-formula-validator.js.map +1 -0
- package/es/formula/formula-engine.d.ts +4 -1
- package/es/formula/formula-engine.js +132 -25
- package/es/formula/formula-engine.js.map +1 -1
- package/es/formula/formula-paste-processor.d.ts +3 -3
- package/es/formula/formula-paste-processor.js.map +1 -1
- package/es/formula/formula-reference-adjustor.d.ts +1 -1
- package/es/formula/formula-reference-adjustor.js.map +1 -1
- package/es/formula/index.d.ts +8 -0
- package/es/formula/index.js +8 -0
- package/es/formula/index.js.map +1 -1
- package/es/index.d.ts +1 -1
- package/es/index.js +1 -1
- package/es/index.js.map +1 -1
- package/es/managers/formula-manager.d.ts +17 -2
- package/es/managers/formula-manager.js +152 -11
- package/es/managers/formula-manager.js.map +1 -1
- package/es/styles/menu.js +2 -1
- package/es/styles/sheet-tab.js +1 -2
- package/es/tools/index.js +2 -1
- package/package.json +5 -5
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: !0
|
|
5
|
+
}), exports.CrossSheetFormulaValidator = void 0;
|
|
6
|
+
|
|
7
|
+
class CrossSheetFormulaValidator {
|
|
8
|
+
constructor(formulaEngine, crossSheetManager, sheetManager) {
|
|
9
|
+
this.validationCache = new Map, this.CACHE_TTL = 5e3, this.defaultOptions = {
|
|
10
|
+
checkCircularReferences: !0,
|
|
11
|
+
checkMissingReferences: !0,
|
|
12
|
+
checkCellBounds: !0,
|
|
13
|
+
maxRecursionDepth: 100
|
|
14
|
+
}, this.formulaEngine = formulaEngine, this.crossSheetManager = crossSheetManager,
|
|
15
|
+
this.sheetManager = sheetManager;
|
|
16
|
+
}
|
|
17
|
+
validateSheet(sheet, options) {
|
|
18
|
+
const validationOptions = Object.assign(Object.assign({}, this.defaultOptions), options), cacheKey = this.getCacheKey(sheet, validationOptions), cached = this.validationCache.get(cacheKey);
|
|
19
|
+
if (cached && Date.now() - this.getCacheTimestamp(cacheKey) < this.CACHE_TTL) return cached;
|
|
20
|
+
const errors = [];
|
|
21
|
+
try {
|
|
22
|
+
const formulas = this.formulaEngine.exportFormulas(sheet);
|
|
23
|
+
for (const [cellRef, formula] of Object.entries(formulas)) {
|
|
24
|
+
const cell = this.parseA1Notation(cellRef), cellWithSheet = {
|
|
25
|
+
sheet: sheet,
|
|
26
|
+
row: cell.row,
|
|
27
|
+
col: cell.col
|
|
28
|
+
}, formulaErrors = this.validateFormula(formula, cellWithSheet, validationOptions);
|
|
29
|
+
errors.push(...formulaErrors);
|
|
30
|
+
}
|
|
31
|
+
if (validationOptions.checkCircularReferences) {
|
|
32
|
+
const circularErrors = this.checkCircularReferences(sheet);
|
|
33
|
+
errors.push(...circularErrors);
|
|
34
|
+
}
|
|
35
|
+
if (validationOptions.checkMissingReferences) {
|
|
36
|
+
const missingErrors = this.checkMissingReferences(sheet);
|
|
37
|
+
errors.push(...missingErrors);
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
errors.push({
|
|
41
|
+
type: "MISSING_REFERENCE",
|
|
42
|
+
message: `Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
43
|
+
sheet: sheet,
|
|
44
|
+
cell: {
|
|
45
|
+
sheet: sheet,
|
|
46
|
+
row: 0,
|
|
47
|
+
col: 0
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const result = {
|
|
52
|
+
valid: 0 === errors.length,
|
|
53
|
+
errors: errors,
|
|
54
|
+
warnings: []
|
|
55
|
+
};
|
|
56
|
+
return this.validationCache.set(cacheKey, result), result;
|
|
57
|
+
}
|
|
58
|
+
validateFormula(formula, cell, options) {
|
|
59
|
+
const errors = [];
|
|
60
|
+
if (!formula.startsWith("=")) return errors;
|
|
61
|
+
try {
|
|
62
|
+
const crossSheetRefs = this.extractCrossSheetReferences(formula);
|
|
63
|
+
for (const ref of crossSheetRefs) if (this.isValidSheet(ref.targetSheet)) {
|
|
64
|
+
if (options.checkCellBounds) {
|
|
65
|
+
const cellErrors = this.validateCellReferences(ref, cell);
|
|
66
|
+
errors.push(...cellErrors);
|
|
67
|
+
}
|
|
68
|
+
} else errors.push({
|
|
69
|
+
type: "INVALID_SHEET",
|
|
70
|
+
message: `Invalid sheet reference: ${ref.targetSheet}`,
|
|
71
|
+
sheet: cell.sheet,
|
|
72
|
+
cell: cell,
|
|
73
|
+
target: ref.targetSheet
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
errors.push({
|
|
77
|
+
type: "MISSING_REFERENCE",
|
|
78
|
+
message: `Formula validation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
79
|
+
sheet: cell.sheet,
|
|
80
|
+
cell: cell
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return errors;
|
|
84
|
+
}
|
|
85
|
+
extractCrossSheetReferences(formula) {
|
|
86
|
+
const references = [], sheetRefPattern = /'?([^'!!]+)'?[!!]([A-Z]+[0-9]+)(?:\s*:\s*(?:'?([^'!!]+)'?[!!])?([A-Z]+[0-9]+))?/g;
|
|
87
|
+
let match;
|
|
88
|
+
for (;null !== (match = sheetRefPattern.exec(formula)); ) {
|
|
89
|
+
const targetSheet = match[1], startRef = match[2], endSheetMaybe = match[3], endRef = match[4], targetCells = [];
|
|
90
|
+
if (endRef) {
|
|
91
|
+
if (endSheetMaybe && endSheetMaybe.toLowerCase() !== targetSheet.toLowerCase()) continue;
|
|
92
|
+
const startCell = this.parseA1Notation(startRef), endCell = this.parseA1Notation(endRef);
|
|
93
|
+
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({
|
|
94
|
+
sheet: targetSheet,
|
|
95
|
+
row: row,
|
|
96
|
+
col: col
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
const cell = this.parseA1Notation(startRef);
|
|
100
|
+
targetCells.push({
|
|
101
|
+
sheet: targetSheet,
|
|
102
|
+
row: cell.row,
|
|
103
|
+
col: cell.col
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
references.push({
|
|
107
|
+
targetSheet: targetSheet,
|
|
108
|
+
targetCells: targetCells
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return references;
|
|
112
|
+
}
|
|
113
|
+
validateCellReferences(ref, sourceCell) {
|
|
114
|
+
const errors = [];
|
|
115
|
+
for (const targetCell of ref.targetCells) try {
|
|
116
|
+
const cellValue = this.formulaEngine.getCellValue(targetCell);
|
|
117
|
+
if (cellValue.error) {
|
|
118
|
+
const targetRef = `${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`;
|
|
119
|
+
errors.push({
|
|
120
|
+
type: "INVALID_CELL",
|
|
121
|
+
message: `Invalid cell reference: ${targetRef} - ${cellValue.error}`,
|
|
122
|
+
sheet: sourceCell.sheet,
|
|
123
|
+
cell: sourceCell,
|
|
124
|
+
target: targetRef
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
errors.push({
|
|
129
|
+
type: "INVALID_CELL",
|
|
130
|
+
message: `Cell validation failed: ${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`,
|
|
131
|
+
sheet: sourceCell.sheet,
|
|
132
|
+
cell: sourceCell,
|
|
133
|
+
target: `${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
checkCircularReferences(sheet) {
|
|
139
|
+
const errors = [], visited = new Set, recursionStack = new Set, dfs = (currentSheet, path) => {
|
|
140
|
+
const key = currentSheet;
|
|
141
|
+
if (recursionStack.has(key)) {
|
|
142
|
+
const cycleStart = path.indexOf(currentSheet), cycle = path.slice(cycleStart).concat([ currentSheet ]);
|
|
143
|
+
return errors.push({
|
|
144
|
+
type: "CIRCULAR_REFERENCE",
|
|
145
|
+
message: `Circular reference detected: ${cycle.join(" -> ")}`,
|
|
146
|
+
sheet: currentSheet,
|
|
147
|
+
cell: {
|
|
148
|
+
sheet: currentSheet,
|
|
149
|
+
row: 0,
|
|
150
|
+
col: 0
|
|
151
|
+
}
|
|
152
|
+
}), !0;
|
|
153
|
+
}
|
|
154
|
+
if (visited.has(key)) return !1;
|
|
155
|
+
visited.add(key), recursionStack.add(key);
|
|
156
|
+
const dependencies = this.crossSheetManager.getCrossSheetDependencies(currentSheet);
|
|
157
|
+
for (const dep of dependencies) if (dfs(dep.precedentSheet, [ ...path, currentSheet ])) return !0;
|
|
158
|
+
return recursionStack.delete(key), !1;
|
|
159
|
+
};
|
|
160
|
+
return dfs(sheet, []), errors;
|
|
161
|
+
}
|
|
162
|
+
checkMissingReferences(sheet) {
|
|
163
|
+
const errors = [], allSheets = this.formulaEngine.getAllSheets(), existingSheets = new Set(allSheets.map((s => s.key))), formulas = this.formulaEngine.exportFormulas(sheet);
|
|
164
|
+
for (const [cellRef, formula] of Object.entries(formulas)) {
|
|
165
|
+
const cell = this.parseA1Notation(cellRef), cellWithSheet = {
|
|
166
|
+
sheet: sheet,
|
|
167
|
+
row: cell.row,
|
|
168
|
+
col: cell.col
|
|
169
|
+
}, crossSheetRefs = this.extractCrossSheetReferences(formula);
|
|
170
|
+
for (const ref of crossSheetRefs) existingSheets.has(ref.targetSheet) || errors.push({
|
|
171
|
+
type: "MISSING_REFERENCE",
|
|
172
|
+
message: `Missing sheet reference: ${ref.targetSheet}`,
|
|
173
|
+
sheet: sheet,
|
|
174
|
+
cell: cellWithSheet,
|
|
175
|
+
target: ref.targetSheet
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return errors;
|
|
179
|
+
}
|
|
180
|
+
validateAllSheets(options) {
|
|
181
|
+
const results = new Map, allSheets = this.formulaEngine.getAllSheets();
|
|
182
|
+
for (const sheetInfo of allSheets) {
|
|
183
|
+
const result = this.validateSheet(sheetInfo.key, options);
|
|
184
|
+
results.set(sheetInfo.key, result);
|
|
185
|
+
}
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
188
|
+
validateFormulaSyntax(formula) {
|
|
189
|
+
try {
|
|
190
|
+
if (!formula.startsWith("=")) return {
|
|
191
|
+
valid: !0
|
|
192
|
+
};
|
|
193
|
+
const expression = formula.substring(1).trim();
|
|
194
|
+
this.validateExpressionStructure(expression);
|
|
195
|
+
const crossSheetPattern = /([A-Za-z0-9_一-龥]+)[!!]([A-Z]+[0-9]+(?::[A-Z]+[0-9]+)?)/g;
|
|
196
|
+
let match;
|
|
197
|
+
for (;null !== (match = crossSheetPattern.exec(expression)); ) {
|
|
198
|
+
const sheetName = match[1], cellRef = match[2];
|
|
199
|
+
if (!this.isValidSheetName(sheetName)) return {
|
|
200
|
+
valid: !1,
|
|
201
|
+
error: `Invalid sheet name: ${sheetName}`
|
|
202
|
+
};
|
|
203
|
+
if (!this.isValidSheet(sheetName)) return {
|
|
204
|
+
valid: !1,
|
|
205
|
+
error: `Invalid sheet name: ${sheetName}`
|
|
206
|
+
};
|
|
207
|
+
if (!this.isValidCellReference(cellRef)) return {
|
|
208
|
+
valid: !1,
|
|
209
|
+
error: `Invalid cell reference: ${cellRef}`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
valid: !0
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
valid: !1,
|
|
218
|
+
error: error instanceof Error ? error.message : "Syntax validation failed"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
validateExpressionStructure(expression) {
|
|
223
|
+
if ((expression.match(/\(/g) || []).length !== (expression.match(/\)/g) || []).length) throw new Error("Unmatched parentheses");
|
|
224
|
+
if ((expression.match(/"/g) || []).length % 2 != 0) throw new Error("Unmatched quotes");
|
|
225
|
+
}
|
|
226
|
+
isValidSheetName(sheetName) {
|
|
227
|
+
return /^[A-Za-z_一-龥][A-Za-z0-9_一-龥]*$/.test(sheetName);
|
|
228
|
+
}
|
|
229
|
+
isValidCellReference(cellRef) {
|
|
230
|
+
return /^[A-Z]+[0-9]+$/.test(cellRef) || /^[A-Z]+[0-9]+:[A-Z]+[0-9]+$/.test(cellRef);
|
|
231
|
+
}
|
|
232
|
+
isValidSheet(sheetName) {
|
|
233
|
+
if (this.sheetManager) {
|
|
234
|
+
return this.sheetManager.getAllSheets().some((sheet => sheet.sheetTitle.toLowerCase() === sheetName.toLowerCase()));
|
|
235
|
+
}
|
|
236
|
+
return this.formulaEngine.getAllSheets().some((sheet => sheet.title.toLowerCase() === sheetName.toLowerCase()));
|
|
237
|
+
}
|
|
238
|
+
parseA1Notation(a1Notation) {
|
|
239
|
+
const match = a1Notation.match(/^([A-Z]+)([0-9]+)$/);
|
|
240
|
+
if (!match) throw new Error(`Invalid cell reference: ${a1Notation}`);
|
|
241
|
+
const colLetters = match[1], rowNumber = parseInt(match[2], 10);
|
|
242
|
+
let col = 0;
|
|
243
|
+
for (let i = 0; i < colLetters.length; i++) col = 26 * col + (colLetters.charCodeAt(i) - 65);
|
|
244
|
+
return {
|
|
245
|
+
row: rowNumber - 1,
|
|
246
|
+
col: col
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
getA1Notation(row, col) {
|
|
250
|
+
let colStr = "", tempCol = col;
|
|
251
|
+
do {
|
|
252
|
+
colStr = String.fromCharCode(65 + tempCol % 26) + colStr, tempCol = Math.floor(tempCol / 26) - 1;
|
|
253
|
+
} while (tempCol >= 0);
|
|
254
|
+
return `${colStr}${row + 1}`;
|
|
255
|
+
}
|
|
256
|
+
getCacheKey(sheet, options) {
|
|
257
|
+
return `${sheet}_${JSON.stringify(options)}`;
|
|
258
|
+
}
|
|
259
|
+
getCacheTimestamp(_cacheKey) {
|
|
260
|
+
return Date.now();
|
|
261
|
+
}
|
|
262
|
+
clearCache() {
|
|
263
|
+
this.validationCache.clear();
|
|
264
|
+
}
|
|
265
|
+
destroy() {
|
|
266
|
+
this.clearCache();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
exports.CrossSheetFormulaValidator = CrossSheetFormulaValidator;
|
|
271
|
+
//# sourceMappingURL=cross-sheet-formula-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/formula/cross-sheet-formula-validator.ts"],"names":[],"mappings":"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","file":"cross-sheet-formula-validator.js","sourcesContent":["/**\n * 跨sheet公式验证器\n * 验证跨sheet引用的有效性和完整性\n */\n\nimport type { FormulaCell } from '../ts-types/formula';\nimport type { FormulaEngine } from './formula-engine';\nimport type { CrossSheetFormulaManager } from './cross-sheet-formula-manager';\n\nexport interface ValidationError {\n type: 'INVALID_SHEET' | 'INVALID_CELL' | 'CIRCULAR_REFERENCE' | 'MISSING_REFERENCE';\n message: string;\n sheet: string;\n cell: FormulaCell;\n target?: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: ValidationError[];\n warnings: string[];\n}\n\nexport interface ValidationOptions {\n checkCircularReferences: boolean;\n checkMissingReferences: boolean;\n checkCellBounds: boolean;\n maxRecursionDepth: number;\n}\n\nexport class CrossSheetFormulaValidator {\n private formulaEngine: FormulaEngine;\n private crossSheetManager: CrossSheetFormulaManager;\n private sheetManager: { getAllSheets: () => Array<{ sheetKey: string; sheetTitle: string }> } | undefined; // SheetManager reference\n\n private validationCache: Map<string, ValidationResult> = new Map();\n private readonly CACHE_TTL = 5000; // 5秒缓存\n\n private defaultOptions: ValidationOptions = {\n checkCircularReferences: true,\n checkMissingReferences: true,\n checkCellBounds: true,\n maxRecursionDepth: 100\n };\n\n constructor(\n formulaEngine: FormulaEngine,\n crossSheetManager: CrossSheetFormulaManager,\n sheetManager?: { getAllSheets: () => Array<{ sheetKey: string; sheetTitle: string }> }\n ) {\n this.formulaEngine = formulaEngine;\n this.crossSheetManager = crossSheetManager;\n this.sheetManager = sheetManager;\n }\n\n /**\n * 验证指定sheet的所有跨sheet公式\n */\n validateSheet(sheet: string, options?: Partial<ValidationOptions>): ValidationResult {\n const validationOptions = { ...this.defaultOptions, ...options };\n const cacheKey = this.getCacheKey(sheet, validationOptions);\n\n // 检查缓存\n const cached = this.validationCache.get(cacheKey);\n if (cached && Date.now() - this.getCacheTimestamp(cacheKey) < this.CACHE_TTL) {\n return cached;\n }\n\n const errors: ValidationError[] = [];\n const warnings: string[] = [];\n\n try {\n // 获取sheet的所有公式\n const formulas = this.formulaEngine.exportFormulas(sheet);\n\n // 验证每个公式\n for (const [cellRef, formula] of Object.entries(formulas)) {\n const cell = this.parseA1Notation(cellRef);\n const cellWithSheet: FormulaCell = { sheet, row: cell.row, col: cell.col };\n\n const formulaErrors = this.validateFormula(formula, cellWithSheet, validationOptions);\n errors.push(...formulaErrors);\n }\n\n // 检查循环依赖\n if (validationOptions.checkCircularReferences) {\n const circularErrors = this.checkCircularReferences(sheet);\n errors.push(...circularErrors);\n }\n\n // 检查缺失的引用\n if (validationOptions.checkMissingReferences) {\n const missingErrors = this.checkMissingReferences(sheet);\n errors.push(...missingErrors);\n }\n } catch (error) {\n errors.push({\n type: 'MISSING_REFERENCE',\n message: `Validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n sheet,\n cell: { sheet, row: 0, col: 0 }\n });\n }\n\n const result: ValidationResult = {\n valid: errors.length === 0,\n errors,\n warnings\n };\n\n // 缓存结果\n this.validationCache.set(cacheKey, result);\n\n return result;\n }\n\n /**\n * 验证单个公式\n */\n private validateFormula(formula: string, cell: FormulaCell, options: ValidationOptions): ValidationError[] {\n const errors: ValidationError[] = [];\n\n if (!formula.startsWith('=')) {\n return errors;\n }\n\n try {\n // 提取跨sheet引用\n const crossSheetRefs = this.extractCrossSheetReferences(formula);\n\n for (const ref of crossSheetRefs) {\n // 验证sheet是否存在\n if (!this.isValidSheet(ref.targetSheet)) {\n errors.push({\n type: 'INVALID_SHEET',\n message: `Invalid sheet reference: ${ref.targetSheet}`,\n sheet: cell.sheet,\n cell,\n target: ref.targetSheet\n });\n continue;\n }\n\n // 验证单元格是否存在\n if (options.checkCellBounds) {\n const cellErrors = this.validateCellReferences(ref, cell);\n errors.push(...cellErrors);\n }\n }\n } catch (error) {\n errors.push({\n type: 'MISSING_REFERENCE',\n message: `Formula validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n sheet: cell.sheet,\n cell\n });\n }\n\n return errors;\n }\n\n /**\n * 提取跨sheet引用\n */\n private extractCrossSheetReferences(formula: string): Array<{\n targetSheet: string;\n targetCells: FormulaCell[];\n }> {\n const references: Array<{\n targetSheet: string;\n targetCells: FormulaCell[];\n }> = [];\n\n // 匹配带sheet前缀的引用,如:\n // - Sheet1!A1\n // - Sheet1!A1:B2\n // - Sheet1!A1:Sheet1!B2\n // - Sheet1!A1:Sheet1!B2\n // - 'My Sheet'!A1:'My Sheet'!B2\n const sheetRefPattern = /'?([^'!!]+)'?[!!]([A-Z]+[0-9]+)(?:\\s*:\\s*(?:'?([^'!!]+)'?[!!])?([A-Z]+[0-9]+))?/g;\n let match: RegExpExecArray | null;\n\n while ((match = sheetRefPattern.exec(formula)) !== null) {\n const targetSheet = match[1];\n const startRef = match[2];\n const endSheetMaybe = match[3];\n const endRef = match[4];\n\n const targetCells: FormulaCell[] = [];\n\n if (endRef) {\n // 范围引用\n // 若右侧带了sheet前缀,仅支持与左侧相同(否则为 3D 引用,当前不展开)\n if (endSheetMaybe && endSheetMaybe.toLowerCase() !== targetSheet.toLowerCase()) {\n // 3D 引用:Sheet1!A1:Sheet2!B2(当前不支持)\n continue;\n }\n const startCell = this.parseA1Notation(startRef);\n const endCell = this.parseA1Notation(endRef);\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 targetCells.push({ sheet: targetSheet, row, col });\n }\n }\n } else {\n // 单个单元格引用,如 A1\n const cell = this.parseA1Notation(startRef);\n targetCells.push({ sheet: targetSheet, row: cell.row, col: cell.col });\n }\n\n references.push({\n targetSheet,\n targetCells\n });\n }\n\n return references;\n }\n\n /**\n * 验证单元格引用\n */\n private validateCellReferences(\n ref: {\n targetSheet: string;\n targetCells: FormulaCell[];\n },\n sourceCell: FormulaCell\n ): ValidationError[] {\n const errors: ValidationError[] = [];\n\n for (const targetCell of ref.targetCells) {\n try {\n // 检查单元格是否有效\n const cellValue = this.formulaEngine.getCellValue(targetCell);\n\n if (cellValue.error) {\n const targetRef = `${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`;\n errors.push({\n type: 'INVALID_CELL',\n message: `Invalid cell reference: ${targetRef} - ${cellValue.error}`,\n sheet: sourceCell.sheet,\n cell: sourceCell,\n target: targetRef\n });\n }\n } catch (error) {\n errors.push({\n type: 'INVALID_CELL',\n message: `Cell validation failed: ${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`,\n sheet: sourceCell.sheet,\n cell: sourceCell,\n target: `${ref.targetSheet}!${this.getA1Notation(targetCell.row, targetCell.col)}`\n });\n }\n }\n\n return errors;\n }\n\n /**\n * 检查循环依赖\n */\n private checkCircularReferences(sheet: string): ValidationError[] {\n const errors: ValidationError[] = [];\n const visited = new Set<string>();\n const recursionStack = new Set<string>();\n\n const dfs = (currentSheet: string, path: string[]): boolean => {\n const key = currentSheet;\n\n if (recursionStack.has(key)) {\n // 发现循环依赖\n const cycleStart = path.indexOf(currentSheet);\n const cycle = path.slice(cycleStart).concat([currentSheet]);\n\n errors.push({\n type: 'CIRCULAR_REFERENCE',\n message: `Circular reference detected: ${cycle.join(' -> ')}`,\n sheet: currentSheet,\n cell: { sheet: currentSheet, row: 0, col: 0 }\n });\n\n return true;\n }\n\n if (visited.has(key)) {\n return false;\n }\n\n visited.add(key);\n recursionStack.add(key);\n\n const dependencies = this.crossSheetManager.getCrossSheetDependencies(currentSheet);\n\n for (const dep of dependencies) {\n if (dfs(dep.precedentSheet, [...path, currentSheet])) {\n return true;\n }\n }\n\n recursionStack.delete(key);\n return false;\n };\n\n dfs(sheet, []);\n return errors;\n }\n\n /**\n * 检查缺失的引用\n */\n private checkMissingReferences(sheet: string): ValidationError[] {\n const errors: ValidationError[] = [];\n const allSheets = this.formulaEngine.getAllSheets();\n const existingSheets = new Set(allSheets.map(s => s.key));\n\n const formulas = this.formulaEngine.exportFormulas(sheet);\n\n for (const [cellRef, formula] of Object.entries(formulas)) {\n const cell = this.parseA1Notation(cellRef);\n const cellWithSheet: FormulaCell = { sheet, row: cell.row, col: cell.col };\n\n const crossSheetRefs = this.extractCrossSheetReferences(formula);\n\n for (const ref of crossSheetRefs) {\n if (!existingSheets.has(ref.targetSheet)) {\n errors.push({\n type: 'MISSING_REFERENCE',\n message: `Missing sheet reference: ${ref.targetSheet}`,\n sheet,\n cell: cellWithSheet,\n target: ref.targetSheet\n });\n }\n }\n }\n\n return errors;\n }\n\n /**\n * 验证所有sheet的跨sheet公式\n */\n validateAllSheets(options?: Partial<ValidationOptions>): Map<string, ValidationResult> {\n const results = new Map<string, ValidationResult>();\n const allSheets = this.formulaEngine.getAllSheets();\n\n for (const sheetInfo of allSheets) {\n const result = this.validateSheet(sheetInfo.key, options);\n results.set(sheetInfo.key, result);\n }\n\n return results;\n }\n\n /**\n * 验证公式语法(不执行计算)\n */\n validateFormulaSyntax(formula: string): { valid: boolean; error?: string } {\n try {\n if (!formula.startsWith('=')) {\n return { valid: true };\n }\n\n const expression = formula.substring(1).trim();\n\n // 基本语法检查\n this.validateExpressionStructure(expression);\n\n // 检查跨sheet引用格式 - 支持中英文、数字、下划线,以及中英文感叹号\n const crossSheetPattern = /([A-Za-z0-9_一-龥]+)[!!]([A-Z]+[0-9]+(?::[A-Z]+[0-9]+)?)/g;\n let match;\n\n while ((match = crossSheetPattern.exec(expression)) !== null) {\n const sheetName = match[1];\n const cellRef = match[2];\n\n // 验证sheet名称格式\n if (!this.isValidSheetName(sheetName)) {\n return { valid: false, error: `Invalid sheet name: ${sheetName}` };\n }\n\n // 验证sheet是否存在\n if (!this.isValidSheet(sheetName)) {\n return { valid: false, error: `Invalid sheet name: ${sheetName}` };\n }\n\n // 验证单元格引用格式\n if (!this.isValidCellReference(cellRef)) {\n return { valid: false, error: `Invalid cell reference: ${cellRef}` };\n }\n }\n\n return { valid: true };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : 'Syntax validation failed'\n };\n }\n }\n\n /**\n * 验证表达式结构\n */\n private validateExpressionStructure(expression: string): void {\n // 检查括号匹配\n const openParenCount = (expression.match(/\\(/g) || []).length;\n const closeParenCount = (expression.match(/\\)/g) || []).length;\n\n if (openParenCount !== closeParenCount) {\n throw new Error('Unmatched parentheses');\n }\n\n // 检查引号匹配\n const doubleQuoteCount = (expression.match(/\"/g) || []).length;\n if (doubleQuoteCount % 2 !== 0) {\n throw new Error('Unmatched quotes');\n }\n }\n\n /**\n * 验证sheet名称\n */\n private isValidSheetName(sheetName: string): boolean {\n // Sheet名称规则:支持中英文、数字、下划线,不能以数字开头\n const validPattern = /^[A-Za-z_一-龥][A-Za-z0-9_一-龥]*$/;\n return validPattern.test(sheetName);\n }\n\n /**\n * 验证单元格引用格式\n */\n private isValidCellReference(cellRef: string): boolean {\n // 支持单个单元格如 A1 或范围如 A1:B2\n const singleCellPattern = /^[A-Z]+[0-9]+$/;\n const rangePattern = /^[A-Z]+[0-9]+:[A-Z]+[0-9]+$/;\n\n return singleCellPattern.test(cellRef) || rangePattern.test(cellRef);\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 * 工具方法: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(sheet: string, options: ValidationOptions): string {\n return `${sheet}_${JSON.stringify(options)}`;\n }\n\n /**\n * 获取缓存时间戳\n */\n private getCacheTimestamp(_cacheKey: string): number {\n // 简单的实现,实际应该存储时间戳\n return Date.now();\n }\n\n /**\n * 清除验证缓存\n */\n clearCache(): void {\n this.validationCache.clear();\n }\n\n /**\n * 销毁验证器\n */\n destroy(): void {\n this.clearCache();\n }\n}\n"]}
|
|
@@ -14,6 +14,7 @@ export interface FormulaEngineConfig {
|
|
|
14
14
|
export declare class FormulaEngine {
|
|
15
15
|
private sheets;
|
|
16
16
|
private reverseSheets;
|
|
17
|
+
private sheetTitles;
|
|
17
18
|
private sheetData;
|
|
18
19
|
private formulaCells;
|
|
19
20
|
private dependencies;
|
|
@@ -24,6 +25,7 @@ export declare class FormulaEngine {
|
|
|
24
25
|
setActiveSheet(sheetKey: string): void;
|
|
25
26
|
getActiveSheet(): string | null;
|
|
26
27
|
addSheet(sheetKey: string, data?: unknown[][]): number;
|
|
28
|
+
setSheetTitle(sheetKey: string, sheetTitle: string): void;
|
|
27
29
|
updateSheetData(sheetKey: string, data: unknown[][]): void;
|
|
28
30
|
private normalizeData;
|
|
29
31
|
getSheetId(sheetKey: string): number;
|
|
@@ -87,10 +89,11 @@ export declare class FormulaEngine {
|
|
|
87
89
|
private validateExpressionStructure;
|
|
88
90
|
release(): void;
|
|
89
91
|
exportFormulas(sheetKey: string): Record<string, string>;
|
|
92
|
+
getSheetTitle(sheetKey: string): string | undefined;
|
|
90
93
|
getAllSheets(): Array<{
|
|
91
94
|
key: string;
|
|
92
95
|
id: number;
|
|
93
|
-
|
|
96
|
+
title: string;
|
|
94
97
|
}>;
|
|
95
98
|
sortFormulasByDependency(sheetKey: string, formulas: Record<string, string>): [string, string][];
|
|
96
99
|
removeSheet(sheetKey: string): void;
|
|
@@ -8,8 +8,9 @@ const formula_helper_1 = require("./formula-helper");
|
|
|
8
8
|
|
|
9
9
|
class FormulaEngine {
|
|
10
10
|
constructor(_config = {}) {
|
|
11
|
-
this.sheets = new Map, this.reverseSheets = new Map, this.
|
|
12
|
-
this.
|
|
11
|
+
this.sheets = new Map, this.reverseSheets = new Map, this.sheetTitles = new Map,
|
|
12
|
+
this.sheetData = new Map, this.formulaCells = new Map, this.dependencies = new Map,
|
|
13
|
+
this.dependents = new Map, this.nextSheetId = 0, this.activeSheetKey = null;
|
|
13
14
|
}
|
|
14
15
|
setActiveSheet(sheetKey) {
|
|
15
16
|
this.sheets.has(sheetKey) && (this.activeSheetKey = sheetKey);
|
|
@@ -24,6 +25,9 @@ class FormulaEngine {
|
|
|
24
25
|
const sheetData = data || [ [ "" ] ];
|
|
25
26
|
return this.sheetData.set(sheetId, sheetData), sheetId;
|
|
26
27
|
}
|
|
28
|
+
setSheetTitle(sheetKey, sheetTitle) {
|
|
29
|
+
this.sheets.has(sheetKey) && this.sheetTitles.set(sheetKey, sheetTitle);
|
|
30
|
+
}
|
|
27
31
|
updateSheetData(sheetKey, data) {
|
|
28
32
|
const sheetId = this.sheets.get(sheetKey);
|
|
29
33
|
null != sheetId && this.sheetData.set(sheetId, data);
|
|
@@ -120,7 +124,11 @@ class FormulaEngine {
|
|
|
120
124
|
};
|
|
121
125
|
}
|
|
122
126
|
parseCellKey(cellKey) {
|
|
123
|
-
|
|
127
|
+
let parts;
|
|
128
|
+
if (cellKey.includes("!")) parts = cellKey.split("!"); else {
|
|
129
|
+
if (!cellKey.includes("!")) return null;
|
|
130
|
+
parts = cellKey.split("!");
|
|
131
|
+
}
|
|
124
132
|
if (2 !== parts.length) return null;
|
|
125
133
|
const [sheet, a1Notation] = parts;
|
|
126
134
|
try {
|
|
@@ -159,7 +167,26 @@ class FormulaEngine {
|
|
|
159
167
|
const expression = formula.substring(1);
|
|
160
168
|
let inQuotes = !1, quoteChar = "", i = 0;
|
|
161
169
|
for (;i < expression.length; ) {
|
|
162
|
-
const char = expression[i];
|
|
170
|
+
const char = expression[i], quotedSheetCellMatchCN = expression.substring(i).match(/^'([A-Za-z0-9_\s一-龥]+)'\s*!\s*([A-Za-z]+[0-9]+)/);
|
|
171
|
+
if (quotedSheetCellMatchCN) {
|
|
172
|
+
const fullRef = quotedSheetCellMatchCN[0], sheetNameMatch = fullRef.match(/^'([^']+)'\s*!\s*(.+)$/);
|
|
173
|
+
if (sheetNameMatch) {
|
|
174
|
+
const originalSheetName = sheetNameMatch[1], cellRef = sheetNameMatch[2], correctedSheetName = this.findOriginalSheetName(originalSheetName), letters = cellRef.replace(/[0-9]/g, ""), numbers = cellRef.replace(/[A-Za-z]/g, "");
|
|
175
|
+
corrected += "'" + (correctedSheetName || originalSheetName) + "'!" + letters.toUpperCase() + numbers,
|
|
176
|
+
i += fullRef.length;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const quotedSheetCellMatch = expression.substring(i).match(/^'([A-Za-z0-9_\s一-龥]+)'![A-Za-z]+[0-9]+/);
|
|
181
|
+
if (quotedSheetCellMatch) {
|
|
182
|
+
const fullRef = quotedSheetCellMatch[0], sheetNameMatch = fullRef.match(/^'([^']+)'!(.+)$/);
|
|
183
|
+
if (sheetNameMatch) {
|
|
184
|
+
const originalSheetName = sheetNameMatch[1], cellRef = sheetNameMatch[2], correctedSheetName = this.findOriginalSheetName(originalSheetName), letters = cellRef.replace(/[0-9]/g, ""), numbers = cellRef.replace(/[A-Za-z]/g, "");
|
|
185
|
+
corrected += "'" + (correctedSheetName || originalSheetName) + "'!" + letters.toUpperCase() + numbers,
|
|
186
|
+
i += fullRef.length;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
163
190
|
if (!('"' !== char && "'" !== char || 0 !== i && "\\" === expression[i - 1])) {
|
|
164
191
|
inQuotes ? char === quoteChar && (inQuotes = !1, quoteChar = "") : (inQuotes = !0,
|
|
165
192
|
quoteChar = char), corrected += char, i++;
|
|
@@ -175,13 +202,37 @@ class FormulaEngine {
|
|
|
175
202
|
for (corrected += funcName.toUpperCase() + "(", i += funcName.length + 1; i < expression.length && " " === expression[i]; ) i++;
|
|
176
203
|
continue;
|
|
177
204
|
}
|
|
178
|
-
const
|
|
205
|
+
const sheetCellMatchCN = expression.substring(i).match(/^[A-Za-z0-9_\s一-龥]+!([A-Za-z]+[0-9]+)/);
|
|
206
|
+
if (sheetCellMatchCN) {
|
|
207
|
+
const fullRef = sheetCellMatchCN[0], [sheetName, cellRef] = fullRef.split("!"), originalSheetName = this.findOriginalSheetName(sheetName), letters = cellRef.replace(/[0-9]/g, ""), numbers = cellRef.replace(/[A-Za-z]/g, "");
|
|
208
|
+
corrected += (originalSheetName || sheetName) + "!" + letters.toUpperCase() + numbers,
|
|
209
|
+
i += fullRef.length;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const sheetCellMatch = expression.substring(i).match(/^[A-Za-z0-9_\s一-龥]+![A-Za-z]+[0-9]+/);
|
|
179
213
|
if (sheetCellMatch) {
|
|
180
214
|
const fullRef = sheetCellMatch[0], [sheetName, cellRef] = fullRef.split("!"), originalSheetName = this.findOriginalSheetName(sheetName), letters = cellRef.replace(/[0-9]/g, ""), numbers = cellRef.replace(/[A-Za-z]/g, "");
|
|
181
215
|
corrected += (originalSheetName || sheetName) + "!" + letters.toUpperCase() + numbers,
|
|
182
216
|
i += fullRef.length;
|
|
183
217
|
continue;
|
|
184
218
|
}
|
|
219
|
+
const quotedSheetRangeMatchCN = expression.substring(i).match(/^'([A-Za-z0-9_\s一-龥]+)'![A-Za-z]+[0-9]+:[A-Za-z]+[0-9]+/);
|
|
220
|
+
if (quotedSheetRangeMatchCN) {
|
|
221
|
+
const fullRangeRef = quotedSheetRangeMatchCN[0], sheetNameMatch = fullRangeRef.match(/^'([^']+)'!(.+)$/);
|
|
222
|
+
if (sheetNameMatch) {
|
|
223
|
+
const originalSheetName = sheetNameMatch[1], rangePart = sheetNameMatch[2], [startCell, endCell] = rangePart.split(":"), correctedSheetName = this.findOriginalSheetName(originalSheetName), startLetters = startCell.replace(/[0-9]/g, ""), startNumbers = startCell.replace(/[A-Za-z]/g, ""), newStartCell = startLetters.toUpperCase() + startNumbers, endLetters = endCell.replace(/[0-9]/g, ""), endNumbers = endCell.replace(/[A-Za-z]/g, "");
|
|
224
|
+
corrected += "'" + (correctedSheetName || originalSheetName) + "'!" + newStartCell + ":" + (endLetters.toUpperCase() + endNumbers),
|
|
225
|
+
i += fullRangeRef.length;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const sheetRangeMatchCN = expression.substring(i).match(/^[A-Za-z0-9_\s一-龥]+![A-Za-z]+[0-9]+:[A-Za-z]+[0-9]+/);
|
|
230
|
+
if (sheetRangeMatchCN) {
|
|
231
|
+
const fullRangeRef = sheetRangeMatchCN[0], [sheetPart, rangePart] = fullRangeRef.split("!"), [startCell, endCell] = rangePart.split(":"), originalSheetName = this.findOriginalSheetName(sheetPart), startLetters = startCell.replace(/[0-9]/g, ""), startNumbers = startCell.replace(/[A-Za-z]/g, ""), newStartCell = startLetters.toUpperCase() + startNumbers, endLetters = endCell.replace(/[0-9]/g, ""), endNumbers = endCell.replace(/[A-Za-z]/g, "");
|
|
232
|
+
corrected += (originalSheetName || sheetPart) + "!" + newStartCell + ":" + (endLetters.toUpperCase() + endNumbers),
|
|
233
|
+
i += fullRangeRef.length;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
185
236
|
const sheetRangeMatch = expression.substring(i).match(/^[A-Za-z0-9_]+![A-Za-z]+[0-9]+:[A-Za-z]+[0-9]+/);
|
|
186
237
|
if (sheetRangeMatch) {
|
|
187
238
|
const fullRangeRef = sheetRangeMatch[0], [sheetPart, rangePart] = fullRangeRef.split("!"), [startCell, endCell] = rangePart.split(":"), originalSheetName = this.findOriginalSheetName(sheetPart), startLetters = startCell.replace(/[0-9]/g, ""), startNumbers = startCell.replace(/[A-Za-z]/g, ""), newStartCell = startLetters.toUpperCase() + startNumbers, endLetters = endCell.replace(/[0-9]/g, ""), endNumbers = endCell.replace(/[A-Za-z]/g, "");
|
|
@@ -204,8 +255,10 @@ class FormulaEngine {
|
|
|
204
255
|
return corrected;
|
|
205
256
|
}
|
|
206
257
|
findOriginalSheetName(sheetName) {
|
|
207
|
-
|
|
258
|
+
for (const sheetTitle of this.sheetTitles.values()) if (sheetTitle === sheetName) return sheetTitle;
|
|
208
259
|
const lowerSheetName = sheetName.toLowerCase();
|
|
260
|
+
for (const sheetTitle of this.sheetTitles.values()) if (sheetTitle.toLowerCase() === lowerSheetName) return sheetTitle;
|
|
261
|
+
if (this.sheets.has(sheetName)) return sheetName;
|
|
209
262
|
for (const [existingSheetName] of this.sheets.entries()) if (existingSheetName.toLowerCase() === lowerSheetName) return existingSheetName;
|
|
210
263
|
return null;
|
|
211
264
|
}
|
|
@@ -296,7 +349,7 @@ class FormulaEngine {
|
|
|
296
349
|
value: expr.slice(1, -1),
|
|
297
350
|
error: void 0
|
|
298
351
|
};
|
|
299
|
-
if (/^([A-Za-z0-9_]+!)?[A-Z]+[0-9]+$/.test(expr)) return {
|
|
352
|
+
if (/^([A-Za-z0-9_\s一-龥]+!)?[A-Z]+[0-9]+$/.test(expr)) return {
|
|
300
353
|
value: this.getCellValueByA1(expr),
|
|
301
354
|
error: void 0
|
|
302
355
|
};
|
|
@@ -304,7 +357,7 @@ class FormulaEngine {
|
|
|
304
357
|
value: Number(expr),
|
|
305
358
|
error: void 0
|
|
306
359
|
};
|
|
307
|
-
if (/^([A-Za-z0-9_]+!)?[A-Z]+[0-9]+:[A-Z]+[0-9]+$/.test(expr)) {
|
|
360
|
+
if (/^([A-Za-z0-9_\s一-龥]+!)?[A-Z]+[0-9]+:[A-Z]+[0-9]+$/.test(expr)) {
|
|
308
361
|
return {
|
|
309
362
|
value: this.getRangeValuesFromExpr(expr),
|
|
310
363
|
error: void 0
|
|
@@ -729,7 +782,7 @@ class FormulaEngine {
|
|
|
729
782
|
};
|
|
730
783
|
functionValues.push(funcResult.value), processedExpr = processedExpr.replace(funcMatch.match, `__FUNC_${functionValues.length - 1}__`);
|
|
731
784
|
}
|
|
732
|
-
const cellRefs = processedExpr.match(/[A-Z]+[0-9]+/g) || [];
|
|
785
|
+
const cellRefs = processedExpr.match(/('[^']+'!)?([A-Za-z0-9_\s一-龥]+!)?[A-Z]+[0-9]+/g) || [];
|
|
733
786
|
for (const cellRef of cellRefs) {
|
|
734
787
|
const value = this.getCellValueByA1(cellRef);
|
|
735
788
|
processedExpr = processedExpr.replace(cellRef, String(value));
|
|
@@ -747,11 +800,21 @@ class FormulaEngine {
|
|
|
747
800
|
}
|
|
748
801
|
}
|
|
749
802
|
getCellValueByA1(a1Notation) {
|
|
803
|
+
var _a, _b;
|
|
750
804
|
try {
|
|
751
805
|
let sheetKey = this.activeSheetKey || this.reverseSheets.get(0) || "Sheet1", cellRef = a1Notation;
|
|
752
|
-
if (a1Notation.includes("!")) {
|
|
753
|
-
|
|
754
|
-
|
|
806
|
+
if (a1Notation.includes("!") || a1Notation.includes("!")) {
|
|
807
|
+
let parts;
|
|
808
|
+
if (parts = a1Notation.includes("!") ? a1Notation.split("!") : a1Notation.split("!"),
|
|
809
|
+
2 === parts.length) {
|
|
810
|
+
let sheetTitle = parts[0];
|
|
811
|
+
sheetTitle.startsWith("'") && sheetTitle.endsWith("'") && (sheetTitle = sheetTitle.slice(1, -1));
|
|
812
|
+
let foundSheetKey = null === (_a = Array.from(this.sheetTitles.entries()).find((([_, value]) => value.toLowerCase() === sheetTitle.toLowerCase()))) || void 0 === _a ? void 0 : _a[0];
|
|
813
|
+
foundSheetKey || (foundSheetKey = null === (_b = Array.from(this.sheets.entries()).find((([key, _]) => key.toLowerCase() === sheetTitle.toLowerCase()))) || void 0 === _b ? void 0 : _b[0]),
|
|
814
|
+
!foundSheetKey && this.sheets && this.sheets.has(sheetTitle) && (foundSheetKey = sheetTitle),
|
|
815
|
+
foundSheetKey ? (sheetKey = foundSheetKey, cellRef = parts[1]) : (sheetKey = sheetTitle,
|
|
816
|
+
cellRef = parts[1]);
|
|
817
|
+
}
|
|
755
818
|
}
|
|
756
819
|
const {row: row, col: col} = this.parseA1Notation(cellRef), cell = {
|
|
757
820
|
sheet: sheetKey,
|
|
@@ -759,19 +822,38 @@ class FormulaEngine {
|
|
|
759
822
|
col: col
|
|
760
823
|
};
|
|
761
824
|
return this.getCellValue(cell).value;
|
|
762
|
-
} catch (
|
|
825
|
+
} catch (_c) {
|
|
763
826
|
return 0;
|
|
764
827
|
}
|
|
765
828
|
}
|
|
766
829
|
getRangeValuesFromExpr(expr) {
|
|
767
830
|
try {
|
|
768
831
|
if (!expr.includes(":")) return [ this.getCellValueByA1(expr) ];
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
832
|
+
const defaultSheetKey = this.activeSheetKey || this.reverseSheets.get(0) || "Sheet1", parseSheetAndCell = part => {
|
|
833
|
+
var _a, _b;
|
|
834
|
+
let sheetKey = defaultSheetKey, cellRef = part.trim(), hasSheetPrefix = !1;
|
|
835
|
+
const hasEn = cellRef.includes("!"), hasCn = cellRef.includes("!");
|
|
836
|
+
if (hasEn || hasCn) {
|
|
837
|
+
const sep = hasEn ? "!" : "!", parts = cellRef.split(sep);
|
|
838
|
+
if (2 === parts.length) {
|
|
839
|
+
hasSheetPrefix = !0;
|
|
840
|
+
let sheetTitle = parts[0].trim();
|
|
841
|
+
cellRef = parts[1].trim(), sheetTitle.startsWith("'") && sheetTitle.endsWith("'") && (sheetTitle = sheetTitle.slice(1, -1));
|
|
842
|
+
const foundSheetKeyFromTitles = null === (_a = Array.from(this.sheetTitles.entries()).find((([_, value]) => value.toLowerCase() === sheetTitle.toLowerCase()))) || void 0 === _a ? void 0 : _a[0], foundSheetKeyFromKeys = null === (_b = Array.from(this.sheets.entries()).find((([sheetKey]) => sheetKey.toLowerCase() === sheetTitle.toLowerCase()))) || void 0 === _b ? void 0 : _b[0];
|
|
843
|
+
let foundSheetKey = foundSheetKeyFromTitles || foundSheetKeyFromKeys;
|
|
844
|
+
!foundSheetKey && this.sheets && this.sheets.has(sheetTitle) && (foundSheetKey = sheetTitle),
|
|
845
|
+
sheetKey = foundSheetKey || sheetTitle;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
sheetKey: sheetKey,
|
|
850
|
+
cellRef: cellRef,
|
|
851
|
+
hasSheetPrefix: hasSheetPrefix
|
|
852
|
+
};
|
|
853
|
+
}, [startRaw, endRaw] = expr.split(":"), startParsed = parseSheetAndCell(startRaw), endParsed = parseSheetAndCell(endRaw);
|
|
854
|
+
if (startParsed.hasSheetPrefix && !endParsed.hasSheetPrefix ? endParsed.sheetKey = startParsed.sheetKey : !startParsed.hasSheetPrefix && endParsed.hasSheetPrefix && (startParsed.sheetKey = endParsed.sheetKey),
|
|
855
|
+
startParsed.sheetKey.toLowerCase() !== endParsed.sheetKey.toLowerCase()) return [];
|
|
856
|
+
const sheetKey = startParsed.sheetKey, startCell = this.parseA1Notation(startParsed.cellRef), endCell = this.parseA1Notation(endParsed.cellRef), values = [];
|
|
775
857
|
for (let row = startCell.row; row <= endCell.row; row++) for (let col = startCell.col; col <= endCell.col; col++) {
|
|
776
858
|
const cell = {
|
|
777
859
|
sheet: sheetKey,
|
|
@@ -787,6 +869,10 @@ class FormulaEngine {
|
|
|
787
869
|
}
|
|
788
870
|
getCellValue(cell) {
|
|
789
871
|
try {
|
|
872
|
+
if (!this.sheets) return {
|
|
873
|
+
value: null,
|
|
874
|
+
error: "FormulaEngine not properly initialized: sheets Map is undefined"
|
|
875
|
+
};
|
|
790
876
|
const sheetId = this.sheets.get(cell.sheet);
|
|
791
877
|
if (void 0 === sheetId) return {
|
|
792
878
|
value: "",
|
|
@@ -897,14 +983,17 @@ class FormulaEngine {
|
|
|
897
983
|
}
|
|
898
984
|
return result;
|
|
899
985
|
}
|
|
986
|
+
getSheetTitle(sheetKey) {
|
|
987
|
+
return this.sheetTitles.get(sheetKey);
|
|
988
|
+
}
|
|
900
989
|
getAllSheets() {
|
|
901
990
|
const result = [];
|
|
902
991
|
for (const entry of Array.from(this.sheets.entries())) {
|
|
903
|
-
const [sheetKey, sheetId] = entry;
|
|
992
|
+
const [sheetKey, sheetId] = entry, sheetTitle = this.sheetTitles.get(sheetKey) || sheetKey;
|
|
904
993
|
result.push({
|
|
905
994
|
key: sheetKey,
|
|
906
995
|
id: sheetId,
|
|
907
|
-
|
|
996
|
+
title: sheetTitle
|
|
908
997
|
});
|
|
909
998
|
}
|
|
910
999
|
return result;
|
|
@@ -925,7 +1014,7 @@ class FormulaEngine {
|
|
|
925
1014
|
if (tempVisited.has(cellKey)) return;
|
|
926
1015
|
tempVisited.add(cellKey);
|
|
927
1016
|
const deps = tempDependencies.get(cellKey) || new Set;
|
|
928
|
-
for (const dep of deps) tempDependencies.has(dep) && visit(dep);
|
|
1017
|
+
for (const dep of deps) tempDependencies && tempDependencies.has(dep) && visit(dep);
|
|
929
1018
|
tempVisited.delete(cellKey), visited.add(cellKey);
|
|
930
1019
|
const cellRef = cellKey.substring(sheetKey.length + 1), formula = formulas[cellRef];
|
|
931
1020
|
formula && result.push([ cellRef, formula ]);
|
|
@@ -1003,6 +1092,8 @@ class FormulaEngine {
|
|
|
1003
1092
|
}
|
|
1004
1093
|
updateDependencies(cellKey, formula) {
|
|
1005
1094
|
var _a;
|
|
1095
|
+
if (!this.dependencies) return;
|
|
1096
|
+
if (!this.dependents) return;
|
|
1006
1097
|
const oldDeps = this.dependencies.get(cellKey) || new Set;
|
|
1007
1098
|
for (const dep of oldDeps) {
|
|
1008
1099
|
const depDependents = this.dependents.get(dep) || new Set;
|
|
@@ -1024,10 +1115,27 @@ class FormulaEngine {
|
|
|
1024
1115
|
return [ ...new Set(references) ];
|
|
1025
1116
|
}
|
|
1026
1117
|
extractReferencesFromExpression(expr, references, currentSheet = "Sheet1") {
|
|
1118
|
+
const repeatedSheetRangePattern = new RegExp("([A-Za-z0-9_\\s一-龥]+)[!!]([A-Z]+[0-9]+)\\s*:\\s*([A-Za-z0-9_\\s一-龥]+)[!!]([A-Z]+[0-9]+)", "g");
|
|
1119
|
+
let repeatedMatch;
|
|
1120
|
+
for (;null !== (repeatedMatch = repeatedSheetRangePattern.exec(expr)); ) {
|
|
1121
|
+
const sheet1 = repeatedMatch[1], startCell = repeatedMatch[2], sheet2 = repeatedMatch[3], endCell = repeatedMatch[4];
|
|
1122
|
+
if (sheet1.toLowerCase() === sheet2.toLowerCase()) {
|
|
1123
|
+
const expandedRefs = this.expandRangeToCells(sheet1, startCell, endCell);
|
|
1124
|
+
references.push(...expandedRefs);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const chineseExclamationPattern = /([A-Za-z0-9_\s一-龥]+)!([A-Z]+[0-9]+)(?::([A-Z]+[0-9]+))?/g;
|
|
1128
|
+
let match;
|
|
1129
|
+
for (;null !== (match = chineseExclamationPattern.exec(expr)); ) {
|
|
1130
|
+
const sheetName = match[1], startCell = match[2], endCell = match[3];
|
|
1131
|
+
if (endCell) {
|
|
1132
|
+
const expandedRefs = this.expandRangeToCells(sheetName, startCell, endCell);
|
|
1133
|
+
references.push(...expandedRefs);
|
|
1134
|
+
} else references.push(`${sheetName}!${startCell}`);
|
|
1135
|
+
}
|
|
1027
1136
|
let cleanExpr = expr.replace(/"[^"]*"/g, "");
|
|
1028
1137
|
cleanExpr = cleanExpr.replace(/'[^']*'/g, "");
|
|
1029
1138
|
const cellRefPattern = /(?:([A-Za-z0-9_]+)!)?([A-Z]+[0-9]+)(?::([A-Z]+[0-9]+))?/g;
|
|
1030
|
-
let match;
|
|
1031
1139
|
for (;null !== (match = cellRefPattern.exec(cleanExpr)); ) {
|
|
1032
1140
|
const sheetName = match[1] || currentSheet, startCell = match[2], endCell = match[3];
|
|
1033
1141
|
if (endCell) {
|
|
@@ -1483,5 +1591,4 @@ class FormulaError {
|
|
|
1483
1591
|
constructor(message, type = "VALUE") {
|
|
1484
1592
|
this.message = message, this.type = type;
|
|
1485
1593
|
}
|
|
1486
|
-
}
|
|
1487
|
-
//# sourceMappingURL=formula-engine.js.map
|
|
1594
|
+
}
|