@a3s-lab/office 0.34.0 → 0.36.0
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/README.md +44 -25
- package/dist/0~7048.js +497 -2
- package/dist/0~presentation-editor.js +19 -5
- package/dist/0~spreadsheet-editor.js +298 -32
- package/dist/0~work-office-diagnostics.js +1 -1
- package/dist/3266.js +189 -10
- package/dist/4121.js +257 -0
- package/dist/8715.js +299 -299
- package/dist/core.js +14 -4
- package/dist/internal/features/work/editors/presentation-editor-focus.d.ts +2 -0
- package/dist/internal/features/work/editors/presentation-text-editor.d.ts +7 -0
- package/dist/internal/features/work/editors/spreadsheet-calculation-model.d.ts +1 -1
- package/dist/internal/features/work/editors/spreadsheet-calculation-projection.d.ts +5 -2
- package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +10 -0
- package/dist/internal/features/work/editors/spreadsheet-command-selection.d.ts +22 -1
- package/dist/internal/features/work/editors/spreadsheet-table-calculated-columns.d.ts +42 -0
- package/dist/internal/features/work/editors/use-presentation-selection.d.ts +1 -1
- package/dist/internal/features/work/work-types.d.ts +5 -0
- package/dist/internal/kernel/office-kernel-protocol.d.ts +1 -1
- package/dist/internal/kernel/office-kernel-spreadsheet-protocol.d.ts +18 -0
- package/dist/internal/kernel/office-kernel-spreadsheet-structured-reference.d.ts +48 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/office-kernel.worker.js +505 -2
- package/docs/latest/en/browser-editor-architecture.md +21 -8
- package/package.json +16 -13
package/dist/8715.js
CHANGED
|
@@ -1,4 +1,269 @@
|
|
|
1
1
|
import { createOfficeId as createWorkId, directChild, attribute } from "./4121.js";
|
|
2
|
+
function sparseArrayIndexes(values) {
|
|
3
|
+
if (!values) return [];
|
|
4
|
+
return Object.keys(values).flatMap((key)=>{
|
|
5
|
+
const index = Number(key);
|
|
6
|
+
return Number.isSafeInteger(index) && index >= 0 && index < values.length ? [
|
|
7
|
+
index
|
|
8
|
+
] : [];
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function sparseArrayEntries(values) {
|
|
12
|
+
if (!values) return [];
|
|
13
|
+
return sparseArrayIndexes(values).flatMap((index)=>{
|
|
14
|
+
const value = values[index];
|
|
15
|
+
return void 0 === value ? [] : [
|
|
16
|
+
[
|
|
17
|
+
index,
|
|
18
|
+
value
|
|
19
|
+
]
|
|
20
|
+
];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function sparseMatrixColumnCount(matrix) {
|
|
24
|
+
let maximum = 0;
|
|
25
|
+
for (const [, row] of sparseArrayEntries(matrix))maximum = Math.max(maximum, row.length);
|
|
26
|
+
return maximum;
|
|
27
|
+
}
|
|
28
|
+
function spreadsheetGridSize(sheet) {
|
|
29
|
+
if (!sheet) return null;
|
|
30
|
+
return {
|
|
31
|
+
rowCount: Math.max(sheet.row ?? 0, sheet.data?.length ?? 0),
|
|
32
|
+
columnCount: Math.max(sheet.column ?? 0, sparseMatrixColumnCount(sheet.data))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function cloneSparseMatrix(source) {
|
|
36
|
+
const clone = [];
|
|
37
|
+
if (!source) return clone;
|
|
38
|
+
clone.length = source.length;
|
|
39
|
+
for (const [rowIndex, sourceRow] of sparseArrayEntries(source)){
|
|
40
|
+
const row = [];
|
|
41
|
+
row.length = sourceRow.length;
|
|
42
|
+
for (const columnIndex of sparseArrayIndexes(sourceRow))row[columnIndex] = sourceRow[columnIndex];
|
|
43
|
+
clone[rowIndex] = row;
|
|
44
|
+
}
|
|
45
|
+
return clone;
|
|
46
|
+
}
|
|
47
|
+
const DEFAULT_SPREADSHEET_CALCULATION_SETTINGS = {
|
|
48
|
+
mode: 'automatic',
|
|
49
|
+
fullCalculationOnLoad: false,
|
|
50
|
+
forceFullCalculation: false,
|
|
51
|
+
iterativeCalculation: false,
|
|
52
|
+
maximumIterations: 100,
|
|
53
|
+
maximumChange: 0.001,
|
|
54
|
+
fullPrecision: true
|
|
55
|
+
};
|
|
56
|
+
const VOLATILE_FUNCTIONS = new Set([
|
|
57
|
+
'CELL',
|
|
58
|
+
'INFO',
|
|
59
|
+
'INDIRECT',
|
|
60
|
+
'NOW',
|
|
61
|
+
'OFFSET',
|
|
62
|
+
'RAND',
|
|
63
|
+
'RANDBETWEEN',
|
|
64
|
+
'TODAY'
|
|
65
|
+
]);
|
|
66
|
+
const FUTURE_FUNCTIONS = new Set([
|
|
67
|
+
'ARRAYTOTEXT',
|
|
68
|
+
'BYCOL',
|
|
69
|
+
'BYROW',
|
|
70
|
+
'CHOOSECOLS',
|
|
71
|
+
'CHOOSEROWS',
|
|
72
|
+
'DROP',
|
|
73
|
+
'EXPAND',
|
|
74
|
+
'FIELDVALUE',
|
|
75
|
+
'FILTER',
|
|
76
|
+
'HSTACK',
|
|
77
|
+
'IMAGE',
|
|
78
|
+
'ISOMITTED',
|
|
79
|
+
'LAMBDA',
|
|
80
|
+
'LET',
|
|
81
|
+
'MAKEARRAY',
|
|
82
|
+
'MAP',
|
|
83
|
+
'RANDARRAY',
|
|
84
|
+
'REDUCE',
|
|
85
|
+
'SCAN',
|
|
86
|
+
'SEQUENCE',
|
|
87
|
+
'SORT',
|
|
88
|
+
'SORTBY',
|
|
89
|
+
'STOCKHISTORY',
|
|
90
|
+
'TAKE',
|
|
91
|
+
'TEXTAFTER',
|
|
92
|
+
'TEXTBEFORE',
|
|
93
|
+
'TEXTSPLIT',
|
|
94
|
+
'TOCOL',
|
|
95
|
+
'TOROW',
|
|
96
|
+
'UNIQUE',
|
|
97
|
+
'VALUETOTEXT',
|
|
98
|
+
'VSTACK',
|
|
99
|
+
'WRAPCOLS',
|
|
100
|
+
'WRAPROWS',
|
|
101
|
+
'XLOOKUP',
|
|
102
|
+
'XMATCH'
|
|
103
|
+
]);
|
|
104
|
+
const FUTURE_FUNCTION_PREFIXES = new Map([
|
|
105
|
+
[
|
|
106
|
+
'FILTER',
|
|
107
|
+
'_xlfn._xlws.'
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
'SORT',
|
|
111
|
+
'_xlfn._xlws.'
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
'SORTBY',
|
|
115
|
+
'_xlfn._xlws.'
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
'UNIQUE',
|
|
119
|
+
'_xlfn.'
|
|
120
|
+
],
|
|
121
|
+
...Array.from(FUTURE_FUNCTIONS).filter((name)=>![
|
|
122
|
+
'FILTER',
|
|
123
|
+
'SORT',
|
|
124
|
+
'SORTBY',
|
|
125
|
+
'UNIQUE'
|
|
126
|
+
].includes(name)).map((name)=>[
|
|
127
|
+
name,
|
|
128
|
+
'_xlfn.'
|
|
129
|
+
])
|
|
130
|
+
]);
|
|
131
|
+
function effectiveSpreadsheetCalculationSettings(settings) {
|
|
132
|
+
if (!settings) return {
|
|
133
|
+
...DEFAULT_SPREADSHEET_CALCULATION_SETTINGS
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
mode: settings.mode,
|
|
137
|
+
fullCalculationOnLoad: Boolean(settings.fullCalculationOnLoad),
|
|
138
|
+
forceFullCalculation: Boolean(settings.forceFullCalculation),
|
|
139
|
+
iterativeCalculation: Boolean(settings.iterativeCalculation),
|
|
140
|
+
maximumIterations: clampedInteger(settings.maximumIterations, 1, 10000, 100),
|
|
141
|
+
maximumChange: positiveNumber(settings.maximumChange, 0.001),
|
|
142
|
+
fullPrecision: false !== settings.fullPrecision
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function editableSpreadsheetFormula(source) {
|
|
146
|
+
return transformFormulaOutsideStrings(source, (segment)=>segment.replace(/_xlfn\._xlws\.(?=[A-Z][A-Z0-9_.]*\s*\()/gi, '').replace(/_xlfn\.(?=[A-Z][A-Z0-9_.]*\s*\()/gi, '').replace(/_xlws\.(?=[A-Z][A-Z0-9_.]*\s*\()/gi, ''));
|
|
147
|
+
}
|
|
148
|
+
function spreadsheetFormulaForXlsx(currentFormula, sourceFormula) {
|
|
149
|
+
const current = currentFormula.trim().replace(/^=/, '');
|
|
150
|
+
const source = sourceFormula?.trim().replace(/^=/, '');
|
|
151
|
+
if (source && comparableFormula(source) === comparableFormula(current)) return source;
|
|
152
|
+
return futureFunctionPrefixes(current, source);
|
|
153
|
+
}
|
|
154
|
+
function spreadsheetFormulaRangeForCell(sheet, row, column) {
|
|
155
|
+
const address = spreadsheetCellAddress(row, column);
|
|
156
|
+
return (sheet.formulaMetadata?.ranges ?? []).find((range)=>range.anchor.toUpperCase().replaceAll('$', '') === address);
|
|
157
|
+
}
|
|
158
|
+
function spreadsheetFormulaRangesForSelection(sheet, selection) {
|
|
159
|
+
return (sheet.formulaMetadata?.ranges ?? []).filter((range)=>rangesOverlap(selection, parseSpreadsheetFormulaRange(range.reference)));
|
|
160
|
+
}
|
|
161
|
+
function spreadsheetFormulaRangeConflict(sheet, range) {
|
|
162
|
+
const bounds = parseSpreadsheetFormulaRange(range.reference);
|
|
163
|
+
if (!bounds) return '引用范围无效';
|
|
164
|
+
const anchor = parseSpreadsheetCellAddress(range.anchor);
|
|
165
|
+
if (!anchor || !containsCell(bounds, anchor.row, anchor.column)) return '锚点不在引用范围内';
|
|
166
|
+
const anchorCell = sheet.data?.[anchor.row]?.[anchor.column];
|
|
167
|
+
if (!anchorCell) return '锚点单元格不存在';
|
|
168
|
+
const overlaps = (sheet.formulaMetadata?.ranges ?? []).filter((candidate)=>candidate !== range && rangesOverlap(bounds, parseSpreadsheetFormulaRange(candidate.reference)));
|
|
169
|
+
if (overlaps.length) return '与其他公式范围重叠';
|
|
170
|
+
for (const [row, cells] of sparseArrayEntries(sheet.data))if (!(row < bounds.startRow) && !(row > bounds.endRow)) {
|
|
171
|
+
for (const [column, cell] of sparseArrayEntries(cells))if (!(column < bounds.startColumn) && !(column > bounds.endColumn)) {
|
|
172
|
+
if (row !== anchor.row || column !== anchor.column) {
|
|
173
|
+
if (cell?.f) return `${spreadsheetCellAddress(row, column)} 包含独立公式`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (Object.values(sheet.config?.merge ?? {}).some((merge)=>rangesOverlap(bounds, {
|
|
178
|
+
startRow: merge.r,
|
|
179
|
+
endRow: merge.r + merge.rs - 1,
|
|
180
|
+
startColumn: merge.c,
|
|
181
|
+
endColumn: merge.c + merge.cs - 1
|
|
182
|
+
}))) return '范围内包含合并单元格';
|
|
183
|
+
if ('data-table' !== range.type && !anchorCell.f) return '锚点公式已被删除';
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function spreadsheetFormulaFunctions(formula) {
|
|
187
|
+
const withoutStrings = formula.replace(/"(?:[^"]|"")*"/g, '""');
|
|
188
|
+
const functions = new Set();
|
|
189
|
+
for (const match of withoutStrings.matchAll(/(?:_xlfn\.)?(?:_xlws\.)?([A-Z][A-Z0-9_.]*)\s*\(/gi))if (match[1]) functions.add(match[1].toUpperCase());
|
|
190
|
+
return Array.from(functions);
|
|
191
|
+
}
|
|
192
|
+
function formulaHasExternalReference(formula) {
|
|
193
|
+
return /\[[^\]]+\][^!]*!/i.test(formula.replace(/"(?:[^"]|"")*"/g, '""'));
|
|
194
|
+
}
|
|
195
|
+
function formulaHasStructuredReference(formula) {
|
|
196
|
+
const withoutExternalReferences = formula.replace(/"(?:[^"]|"")*"/g, '""').replace(/\[[^\]]+\][^!]*!/gi, '');
|
|
197
|
+
return /\[(?:@|#)[^\]]+\]/i.test(withoutExternalReferences) || /\b[A-Z_\\][A-Z0-9_.]*\s*\[[^\]]+\]/i.test(withoutExternalReferences);
|
|
198
|
+
}
|
|
199
|
+
function volatileSpreadsheetFormulaFunctions(formula) {
|
|
200
|
+
return spreadsheetFormulaFunctions(formula).filter((name)=>VOLATILE_FUNCTIONS.has(name));
|
|
201
|
+
}
|
|
202
|
+
function spreadsheetCellAddress(row, column) {
|
|
203
|
+
let value = column + 1;
|
|
204
|
+
let label = '';
|
|
205
|
+
while(value > 0){
|
|
206
|
+
value -= 1;
|
|
207
|
+
label = String.fromCharCode(65 + value % 26) + label;
|
|
208
|
+
value = Math.floor(value / 26);
|
|
209
|
+
}
|
|
210
|
+
return `${label}${row + 1}`;
|
|
211
|
+
}
|
|
212
|
+
function parseSpreadsheetFormulaRange(reference) {
|
|
213
|
+
const normalized = reference.trim().replaceAll('$', '').split('!').at(-1) ?? '';
|
|
214
|
+
const [startText, endText = startText] = normalized.split(':');
|
|
215
|
+
const start = parseSpreadsheetCellAddress(startText);
|
|
216
|
+
const end = parseSpreadsheetCellAddress(endText);
|
|
217
|
+
if (!start || !end) return null;
|
|
218
|
+
return {
|
|
219
|
+
startRow: Math.min(start.row, end.row),
|
|
220
|
+
endRow: Math.max(start.row, end.row),
|
|
221
|
+
startColumn: Math.min(start.column, end.column),
|
|
222
|
+
endColumn: Math.max(start.column, end.column)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function parseSpreadsheetCellAddress(address) {
|
|
226
|
+
const match = /^([A-Z]{1,3})([1-9]\d*)$/i.exec(address.trim().replaceAll('$', ''));
|
|
227
|
+
if (!match) return null;
|
|
228
|
+
let column = 0;
|
|
229
|
+
for (const character of match[1].toUpperCase())column = 26 * column + character.charCodeAt(0) - 64;
|
|
230
|
+
const row = Number(match[2]) - 1;
|
|
231
|
+
if (row > 1048575 || column > 16384) return null;
|
|
232
|
+
return {
|
|
233
|
+
row,
|
|
234
|
+
column: column - 1
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function containsCell(bounds, row, column) {
|
|
238
|
+
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
239
|
+
}
|
|
240
|
+
function rangesOverlap(first, second) {
|
|
241
|
+
if (!second) return false;
|
|
242
|
+
return first.startRow <= second.endRow && first.endRow >= second.startRow && first.startColumn <= second.endColumn && first.endColumn >= second.startColumn;
|
|
243
|
+
}
|
|
244
|
+
function comparableFormula(formula) {
|
|
245
|
+
return editableSpreadsheetFormula(formula).trim();
|
|
246
|
+
}
|
|
247
|
+
function futureFunctionPrefixes(formula, sourceFormula) {
|
|
248
|
+
const sourcePrefixes = new Map();
|
|
249
|
+
const sourceFunctions = (sourceFormula ?? '').replace(/"(?:[^"]|"")*"/g, '""');
|
|
250
|
+
for (const match of sourceFunctions.matchAll(/(_xlfn\.(?:_xlws\.)?)([A-Z][A-Z0-9_.]*)\s*(?=\()/gi))if (match[1] && match[2]) sourcePrefixes.set(match[2].toUpperCase(), match[1]);
|
|
251
|
+
return transformFormulaOutsideStrings(formula, (segment)=>segment.replace(/(?<![A-Z0-9_.])([A-Z][A-Z0-9_.]*)\s*(?=\()/gi, (match, functionName)=>{
|
|
252
|
+
const normalized = functionName.toUpperCase();
|
|
253
|
+
const prefix = sourcePrefixes.get(normalized) ?? FUTURE_FUNCTION_PREFIXES.get(normalized);
|
|
254
|
+
return prefix ? `${prefix}${match}` : match;
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
function clampedInteger(value, minimum, maximum, fallback) {
|
|
258
|
+
if (!Number.isFinite(value)) return fallback;
|
|
259
|
+
return Math.min(maximum, Math.max(minimum, Math.round(value)));
|
|
260
|
+
}
|
|
261
|
+
function positiveNumber(value, fallback) {
|
|
262
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
263
|
+
}
|
|
264
|
+
function transformFormulaOutsideStrings(formula, transform) {
|
|
265
|
+
return formula.split(/("(?:[^"]|"")*")/).map((segment, index)=>index % 2 ? segment : transform(segment)).join('');
|
|
266
|
+
}
|
|
2
267
|
const CELL_REFERENCE = /\$?([A-Z]{1,3})\$?([1-9]\d*)/i;
|
|
3
268
|
const CELL_OR_RANGE = new RegExp(`^${CELL_REFERENCE.source}(?::${CELL_REFERENCE.source})?$`, 'i');
|
|
4
269
|
const COLUMN_RANGE = /^\$?([A-Z]{1,3}):\$?([A-Z]{1,3})$/i;
|
|
@@ -126,79 +391,34 @@ function splitRangeList(value) {
|
|
|
126
391
|
if (current.trim()) parts.push(current.trim());
|
|
127
392
|
return parts;
|
|
128
393
|
}
|
|
129
|
-
function unqualifiedReference(value) {
|
|
130
|
-
const reference = value.trim().replace(/^=/, '');
|
|
131
|
-
const separator = reference.lastIndexOf('!');
|
|
132
|
-
return separator >= 0 ? reference.slice(separator + 1) : reference;
|
|
133
|
-
}
|
|
134
|
-
function decodeCell(column, row) {
|
|
135
|
-
return {
|
|
136
|
-
row: Math.max(0, Number(row) - 1),
|
|
137
|
-
column: decodeColumn(column)
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function decodeColumn(value) {
|
|
141
|
-
let result = 0;
|
|
142
|
-
for (const character of value.toUpperCase())result = 26 * result + character.charCodeAt(0) - 64;
|
|
143
|
-
return Math.max(0, result - 1);
|
|
144
|
-
}
|
|
145
|
-
function encodeCell(row, column) {
|
|
146
|
-
return `${encodeColumn(column)}${Math.max(0, row) + 1}`;
|
|
147
|
-
}
|
|
148
|
-
function encodeColumn(column) {
|
|
149
|
-
let value = Math.max(0, column) + 1;
|
|
150
|
-
let label = '';
|
|
151
|
-
while(value > 0){
|
|
152
|
-
value -= 1;
|
|
153
|
-
label = String.fromCharCode(65 + value % 26) + label;
|
|
154
|
-
value = Math.floor(value / 26);
|
|
155
|
-
}
|
|
156
|
-
return label;
|
|
157
|
-
}
|
|
158
|
-
function sparseArrayIndexes(values) {
|
|
159
|
-
if (!values) return [];
|
|
160
|
-
return Object.keys(values).flatMap((key)=>{
|
|
161
|
-
const index = Number(key);
|
|
162
|
-
return Number.isSafeInteger(index) && index >= 0 && index < values.length ? [
|
|
163
|
-
index
|
|
164
|
-
] : [];
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
function sparseArrayEntries(values) {
|
|
168
|
-
if (!values) return [];
|
|
169
|
-
return sparseArrayIndexes(values).flatMap((index)=>{
|
|
170
|
-
const value = values[index];
|
|
171
|
-
return void 0 === value ? [] : [
|
|
172
|
-
[
|
|
173
|
-
index,
|
|
174
|
-
value
|
|
175
|
-
]
|
|
176
|
-
];
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
function sparseMatrixColumnCount(matrix) {
|
|
180
|
-
let maximum = 0;
|
|
181
|
-
for (const [, row] of sparseArrayEntries(matrix))maximum = Math.max(maximum, row.length);
|
|
182
|
-
return maximum;
|
|
394
|
+
function unqualifiedReference(value) {
|
|
395
|
+
const reference = value.trim().replace(/^=/, '');
|
|
396
|
+
const separator = reference.lastIndexOf('!');
|
|
397
|
+
return separator >= 0 ? reference.slice(separator + 1) : reference;
|
|
183
398
|
}
|
|
184
|
-
function
|
|
185
|
-
if (!sheet) return null;
|
|
399
|
+
function decodeCell(column, row) {
|
|
186
400
|
return {
|
|
187
|
-
|
|
188
|
-
|
|
401
|
+
row: Math.max(0, Number(row) - 1),
|
|
402
|
+
column: decodeColumn(column)
|
|
189
403
|
};
|
|
190
404
|
}
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
405
|
+
function decodeColumn(value) {
|
|
406
|
+
let result = 0;
|
|
407
|
+
for (const character of value.toUpperCase())result = 26 * result + character.charCodeAt(0) - 64;
|
|
408
|
+
return Math.max(0, result - 1);
|
|
409
|
+
}
|
|
410
|
+
function encodeCell(row, column) {
|
|
411
|
+
return `${encodeColumn(column)}${Math.max(0, row) + 1}`;
|
|
412
|
+
}
|
|
413
|
+
function encodeColumn(column) {
|
|
414
|
+
let value = Math.max(0, column) + 1;
|
|
415
|
+
let label = '';
|
|
416
|
+
while(value > 0){
|
|
417
|
+
value -= 1;
|
|
418
|
+
label = String.fromCharCode(65 + value % 26) + label;
|
|
419
|
+
value = Math.floor(value / 26);
|
|
200
420
|
}
|
|
201
|
-
return
|
|
421
|
+
return label;
|
|
202
422
|
}
|
|
203
423
|
function spreadsheetPivotCellValue(cell) {
|
|
204
424
|
return cell?.v ?? cell?.m ?? null;
|
|
@@ -723,13 +943,13 @@ function spreadsheetPivotValidation(content, ownerSheetId, pivot) {
|
|
|
723
943
|
function spreadsheetPivotOutputContains(sheet, row, column) {
|
|
724
944
|
return (sheet.pivotTables ?? []).some((pivot)=>{
|
|
725
945
|
const bounds = singleRange(pivot.outputReference ?? '');
|
|
726
|
-
return Boolean(bounds &&
|
|
946
|
+
return Boolean(bounds && work_spreadsheet_pivots_containsCell(bounds, row, column));
|
|
727
947
|
});
|
|
728
948
|
}
|
|
729
949
|
function spreadsheetPivotIntersects(sheet, range) {
|
|
730
950
|
return (sheet.pivotTables ?? []).filter((pivot)=>{
|
|
731
951
|
const bounds = singleRange(pivot.outputReference ?? '');
|
|
732
|
-
return Boolean(bounds &&
|
|
952
|
+
return Boolean(bounds && work_spreadsheet_pivots_rangesOverlap(bounds, range));
|
|
733
953
|
});
|
|
734
954
|
}
|
|
735
955
|
function resolvePivot(content, ownerSheetId, pivot) {
|
|
@@ -751,14 +971,14 @@ function resolvePivot(content, ownerSheetId, pivot) {
|
|
|
751
971
|
const outputReference = formatBounds(outputBounds);
|
|
752
972
|
if (output.length * output[0].length > MAXIMUM_OUTPUT_CELLS) return null;
|
|
753
973
|
if (outputBounds.endRow > MAXIMUM_XLSX_ROW || outputBounds.endColumn > MAXIMUM_XLSX_COLUMN) return null;
|
|
754
|
-
if (ownerSheet.id === sourceSheet.id &&
|
|
974
|
+
if (ownerSheet.id === sourceSheet.id && work_spreadsheet_pivots_rangesOverlap(sourceBounds, outputBounds)) return null;
|
|
755
975
|
const oldOutput = singleRange(pivot.outputReference ?? '');
|
|
756
976
|
for (const other of ownerSheet.pivotTables ?? []){
|
|
757
977
|
if (other.id === pivot.id) continue;
|
|
758
978
|
const otherOutput = singleRange(other.outputReference ?? '');
|
|
759
|
-
if (otherOutput &&
|
|
979
|
+
if (otherOutput && work_spreadsheet_pivots_rangesOverlap(otherOutput, outputBounds)) return null;
|
|
760
980
|
}
|
|
761
|
-
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput &&
|
|
981
|
+
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput && work_spreadsheet_pivots_containsCell(oldOutput, row, column))) {
|
|
762
982
|
if (ownerSheet.data?.[row]?.[column]) return null;
|
|
763
983
|
}
|
|
764
984
|
return {
|
|
@@ -805,8 +1025,8 @@ function pivotFailure(content, ownerSheetId, pivot) {
|
|
|
805
1025
|
endColumn: anchor.startColumn + output[0].length - 1
|
|
806
1026
|
};
|
|
807
1027
|
if (outputBounds.endRow > MAXIMUM_XLSX_ROW || outputBounds.endColumn > MAXIMUM_XLSX_COLUMN) return invalid('pivot.output-out-of-bounds', '透视表结果超出 XLSX 工作表边界。');
|
|
808
|
-
if (ownerSheet.id === sourceSheet.id &&
|
|
809
|
-
if (Object.values(ownerSheet.config?.merge ?? {}).some((merge)=>
|
|
1028
|
+
if (ownerSheet.id === sourceSheet.id && work_spreadsheet_pivots_rangesOverlap(sourceBounds, outputBounds)) return invalid('pivot.output-overlaps-source', '透视表输出区域与源数据重叠,请改用其他位置或工作表。');
|
|
1029
|
+
if (Object.values(ownerSheet.config?.merge ?? {}).some((merge)=>work_spreadsheet_pivots_rangesOverlap(outputBounds, {
|
|
810
1030
|
startRow: merge.r,
|
|
811
1031
|
endRow: merge.r + merge.rs - 1,
|
|
812
1032
|
startColumn: merge.c,
|
|
@@ -816,9 +1036,9 @@ function pivotFailure(content, ownerSheetId, pivot) {
|
|
|
816
1036
|
for (const other of ownerSheet.pivotTables ?? []){
|
|
817
1037
|
if (other.id === pivot.id) continue;
|
|
818
1038
|
const otherOutput = singleRange(other.outputReference ?? '');
|
|
819
|
-
if (otherOutput &&
|
|
1039
|
+
if (otherOutput && work_spreadsheet_pivots_rangesOverlap(otherOutput, outputBounds)) return invalid('pivot.output-overlaps-pivot', '透视表输出区域与另一个透视表重叠。');
|
|
820
1040
|
}
|
|
821
|
-
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput &&
|
|
1041
|
+
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput && work_spreadsheet_pivots_containsCell(oldOutput, row, column))) {
|
|
822
1042
|
if (ownerSheet.data?.[row]?.[column]) return invalid('pivot.output-not-empty', '透视表输出区域包含现有内容,请选择空白位置。');
|
|
823
1043
|
}
|
|
824
1044
|
return null;
|
|
@@ -846,10 +1066,10 @@ function selectionBounds(selection) {
|
|
|
846
1066
|
endColumn: Math.max(0, Math.max(selection.column[0], selection.column[1]))
|
|
847
1067
|
};
|
|
848
1068
|
}
|
|
849
|
-
function
|
|
1069
|
+
function work_spreadsheet_pivots_containsCell(bounds, row, column) {
|
|
850
1070
|
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
851
1071
|
}
|
|
852
|
-
function
|
|
1072
|
+
function work_spreadsheet_pivots_rangesOverlap(left, right) {
|
|
853
1073
|
return left.startRow <= right.endRow && left.endRow >= right.startRow && left.startColumn <= right.endColumn && left.endColumn >= right.startColumn;
|
|
854
1074
|
}
|
|
855
1075
|
function formatBounds(bounds) {
|
|
@@ -905,226 +1125,6 @@ function invalid(code, message) {
|
|
|
905
1125
|
message
|
|
906
1126
|
};
|
|
907
1127
|
}
|
|
908
|
-
const DEFAULT_SPREADSHEET_CALCULATION_SETTINGS = {
|
|
909
|
-
mode: 'automatic',
|
|
910
|
-
fullCalculationOnLoad: false,
|
|
911
|
-
forceFullCalculation: false,
|
|
912
|
-
iterativeCalculation: false,
|
|
913
|
-
maximumIterations: 100,
|
|
914
|
-
maximumChange: 0.001,
|
|
915
|
-
fullPrecision: true
|
|
916
|
-
};
|
|
917
|
-
const VOLATILE_FUNCTIONS = new Set([
|
|
918
|
-
'CELL',
|
|
919
|
-
'INFO',
|
|
920
|
-
'INDIRECT',
|
|
921
|
-
'NOW',
|
|
922
|
-
'OFFSET',
|
|
923
|
-
'RAND',
|
|
924
|
-
'RANDBETWEEN',
|
|
925
|
-
'TODAY'
|
|
926
|
-
]);
|
|
927
|
-
const FUTURE_FUNCTIONS = new Set([
|
|
928
|
-
'ARRAYTOTEXT',
|
|
929
|
-
'BYCOL',
|
|
930
|
-
'BYROW',
|
|
931
|
-
'CHOOSECOLS',
|
|
932
|
-
'CHOOSEROWS',
|
|
933
|
-
'DROP',
|
|
934
|
-
'EXPAND',
|
|
935
|
-
'FIELDVALUE',
|
|
936
|
-
'FILTER',
|
|
937
|
-
'HSTACK',
|
|
938
|
-
'IMAGE',
|
|
939
|
-
'ISOMITTED',
|
|
940
|
-
'LAMBDA',
|
|
941
|
-
'LET',
|
|
942
|
-
'MAKEARRAY',
|
|
943
|
-
'MAP',
|
|
944
|
-
'RANDARRAY',
|
|
945
|
-
'REDUCE',
|
|
946
|
-
'SCAN',
|
|
947
|
-
'SEQUENCE',
|
|
948
|
-
'SORT',
|
|
949
|
-
'SORTBY',
|
|
950
|
-
'STOCKHISTORY',
|
|
951
|
-
'TAKE',
|
|
952
|
-
'TEXTAFTER',
|
|
953
|
-
'TEXTBEFORE',
|
|
954
|
-
'TEXTSPLIT',
|
|
955
|
-
'TOCOL',
|
|
956
|
-
'TOROW',
|
|
957
|
-
'UNIQUE',
|
|
958
|
-
'VALUETOTEXT',
|
|
959
|
-
'VSTACK',
|
|
960
|
-
'WRAPCOLS',
|
|
961
|
-
'WRAPROWS',
|
|
962
|
-
'XLOOKUP',
|
|
963
|
-
'XMATCH'
|
|
964
|
-
]);
|
|
965
|
-
const FUTURE_FUNCTION_PREFIXES = new Map([
|
|
966
|
-
[
|
|
967
|
-
'FILTER',
|
|
968
|
-
'_xlfn._xlws.'
|
|
969
|
-
],
|
|
970
|
-
[
|
|
971
|
-
'SORT',
|
|
972
|
-
'_xlfn._xlws.'
|
|
973
|
-
],
|
|
974
|
-
[
|
|
975
|
-
'SORTBY',
|
|
976
|
-
'_xlfn._xlws.'
|
|
977
|
-
],
|
|
978
|
-
[
|
|
979
|
-
'UNIQUE',
|
|
980
|
-
'_xlfn.'
|
|
981
|
-
],
|
|
982
|
-
...Array.from(FUTURE_FUNCTIONS).filter((name)=>![
|
|
983
|
-
'FILTER',
|
|
984
|
-
'SORT',
|
|
985
|
-
'SORTBY',
|
|
986
|
-
'UNIQUE'
|
|
987
|
-
].includes(name)).map((name)=>[
|
|
988
|
-
name,
|
|
989
|
-
'_xlfn.'
|
|
990
|
-
])
|
|
991
|
-
]);
|
|
992
|
-
function effectiveSpreadsheetCalculationSettings(settings) {
|
|
993
|
-
if (!settings) return {
|
|
994
|
-
...DEFAULT_SPREADSHEET_CALCULATION_SETTINGS
|
|
995
|
-
};
|
|
996
|
-
return {
|
|
997
|
-
mode: settings.mode,
|
|
998
|
-
fullCalculationOnLoad: Boolean(settings.fullCalculationOnLoad),
|
|
999
|
-
forceFullCalculation: Boolean(settings.forceFullCalculation),
|
|
1000
|
-
iterativeCalculation: Boolean(settings.iterativeCalculation),
|
|
1001
|
-
maximumIterations: clampedInteger(settings.maximumIterations, 1, 10000, 100),
|
|
1002
|
-
maximumChange: positiveNumber(settings.maximumChange, 0.001),
|
|
1003
|
-
fullPrecision: false !== settings.fullPrecision
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
function editableSpreadsheetFormula(source) {
|
|
1007
|
-
return transformFormulaOutsideStrings(source, (segment)=>segment.replace(/_xlfn\._xlws\.(?=[A-Z][A-Z0-9_.]*\s*\()/gi, '').replace(/_xlfn\.(?=[A-Z][A-Z0-9_.]*\s*\()/gi, '').replace(/_xlws\.(?=[A-Z][A-Z0-9_.]*\s*\()/gi, ''));
|
|
1008
|
-
}
|
|
1009
|
-
function spreadsheetFormulaForXlsx(currentFormula, sourceFormula) {
|
|
1010
|
-
const current = currentFormula.trim().replace(/^=/, '');
|
|
1011
|
-
const source = sourceFormula?.trim().replace(/^=/, '');
|
|
1012
|
-
if (source && comparableFormula(source) === comparableFormula(current)) return source;
|
|
1013
|
-
return futureFunctionPrefixes(current, source);
|
|
1014
|
-
}
|
|
1015
|
-
function spreadsheetFormulaRangeForCell(sheet, row, column) {
|
|
1016
|
-
const address = spreadsheetCellAddress(row, column);
|
|
1017
|
-
return (sheet.formulaMetadata?.ranges ?? []).find((range)=>range.anchor.toUpperCase().replaceAll('$', '') === address);
|
|
1018
|
-
}
|
|
1019
|
-
function spreadsheetFormulaRangesForSelection(sheet, selection) {
|
|
1020
|
-
return (sheet.formulaMetadata?.ranges ?? []).filter((range)=>work_spreadsheet_formulas_rangesOverlap(selection, parseSpreadsheetFormulaRange(range.reference)));
|
|
1021
|
-
}
|
|
1022
|
-
function spreadsheetFormulaRangeConflict(sheet, range) {
|
|
1023
|
-
const bounds = parseSpreadsheetFormulaRange(range.reference);
|
|
1024
|
-
if (!bounds) return '引用范围无效';
|
|
1025
|
-
const anchor = parseSpreadsheetCellAddress(range.anchor);
|
|
1026
|
-
if (!anchor || !work_spreadsheet_formulas_containsCell(bounds, anchor.row, anchor.column)) return '锚点不在引用范围内';
|
|
1027
|
-
const anchorCell = sheet.data?.[anchor.row]?.[anchor.column];
|
|
1028
|
-
if (!anchorCell) return '锚点单元格不存在';
|
|
1029
|
-
const overlaps = (sheet.formulaMetadata?.ranges ?? []).filter((candidate)=>candidate !== range && work_spreadsheet_formulas_rangesOverlap(bounds, parseSpreadsheetFormulaRange(candidate.reference)));
|
|
1030
|
-
if (overlaps.length) return '与其他公式范围重叠';
|
|
1031
|
-
for (const [row, cells] of sparseArrayEntries(sheet.data))if (!(row < bounds.startRow) && !(row > bounds.endRow)) {
|
|
1032
|
-
for (const [column, cell] of sparseArrayEntries(cells))if (!(column < bounds.startColumn) && !(column > bounds.endColumn)) {
|
|
1033
|
-
if (row !== anchor.row || column !== anchor.column) {
|
|
1034
|
-
if (cell?.f) return `${spreadsheetCellAddress(row, column)} 包含独立公式`;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (Object.values(sheet.config?.merge ?? {}).some((merge)=>work_spreadsheet_formulas_rangesOverlap(bounds, {
|
|
1039
|
-
startRow: merge.r,
|
|
1040
|
-
endRow: merge.r + merge.rs - 1,
|
|
1041
|
-
startColumn: merge.c,
|
|
1042
|
-
endColumn: merge.c + merge.cs - 1
|
|
1043
|
-
}))) return '范围内包含合并单元格';
|
|
1044
|
-
if ('data-table' !== range.type && !anchorCell.f) return '锚点公式已被删除';
|
|
1045
|
-
return null;
|
|
1046
|
-
}
|
|
1047
|
-
function spreadsheetFormulaFunctions(formula) {
|
|
1048
|
-
const withoutStrings = formula.replace(/"(?:[^"]|"")*"/g, '""');
|
|
1049
|
-
const functions = new Set();
|
|
1050
|
-
for (const match of withoutStrings.matchAll(/(?:_xlfn\.)?(?:_xlws\.)?([A-Z][A-Z0-9_.]*)\s*\(/gi))if (match[1]) functions.add(match[1].toUpperCase());
|
|
1051
|
-
return Array.from(functions);
|
|
1052
|
-
}
|
|
1053
|
-
function formulaHasExternalReference(formula) {
|
|
1054
|
-
return /\[[^\]]+\][^!]*!/i.test(formula.replace(/"(?:[^"]|"")*"/g, '""'));
|
|
1055
|
-
}
|
|
1056
|
-
function formulaHasStructuredReference(formula) {
|
|
1057
|
-
const withoutExternalReferences = formula.replace(/"(?:[^"]|"")*"/g, '""').replace(/\[[^\]]+\][^!]*!/gi, '');
|
|
1058
|
-
return /\[(?:@|#)[^\]]+\]/i.test(withoutExternalReferences) || /\b[A-Z_\\][A-Z0-9_.]*\s*\[[^\]]+\]/i.test(withoutExternalReferences);
|
|
1059
|
-
}
|
|
1060
|
-
function volatileSpreadsheetFormulaFunctions(formula) {
|
|
1061
|
-
return spreadsheetFormulaFunctions(formula).filter((name)=>VOLATILE_FUNCTIONS.has(name));
|
|
1062
|
-
}
|
|
1063
|
-
function spreadsheetCellAddress(row, column) {
|
|
1064
|
-
let value = column + 1;
|
|
1065
|
-
let label = '';
|
|
1066
|
-
while(value > 0){
|
|
1067
|
-
value -= 1;
|
|
1068
|
-
label = String.fromCharCode(65 + value % 26) + label;
|
|
1069
|
-
value = Math.floor(value / 26);
|
|
1070
|
-
}
|
|
1071
|
-
return `${label}${row + 1}`;
|
|
1072
|
-
}
|
|
1073
|
-
function parseSpreadsheetFormulaRange(reference) {
|
|
1074
|
-
const normalized = reference.trim().replaceAll('$', '').split('!').at(-1) ?? '';
|
|
1075
|
-
const [startText, endText = startText] = normalized.split(':');
|
|
1076
|
-
const start = parseSpreadsheetCellAddress(startText);
|
|
1077
|
-
const end = parseSpreadsheetCellAddress(endText);
|
|
1078
|
-
if (!start || !end) return null;
|
|
1079
|
-
return {
|
|
1080
|
-
startRow: Math.min(start.row, end.row),
|
|
1081
|
-
endRow: Math.max(start.row, end.row),
|
|
1082
|
-
startColumn: Math.min(start.column, end.column),
|
|
1083
|
-
endColumn: Math.max(start.column, end.column)
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
function parseSpreadsheetCellAddress(address) {
|
|
1087
|
-
const match = /^([A-Z]{1,3})([1-9]\d*)$/i.exec(address.trim().replaceAll('$', ''));
|
|
1088
|
-
if (!match) return null;
|
|
1089
|
-
let column = 0;
|
|
1090
|
-
for (const character of match[1].toUpperCase())column = 26 * column + character.charCodeAt(0) - 64;
|
|
1091
|
-
const row = Number(match[2]) - 1;
|
|
1092
|
-
if (row > 1048575 || column > 16384) return null;
|
|
1093
|
-
return {
|
|
1094
|
-
row,
|
|
1095
|
-
column: column - 1
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
function work_spreadsheet_formulas_containsCell(bounds, row, column) {
|
|
1099
|
-
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
1100
|
-
}
|
|
1101
|
-
function work_spreadsheet_formulas_rangesOverlap(first, second) {
|
|
1102
|
-
if (!second) return false;
|
|
1103
|
-
return first.startRow <= second.endRow && first.endRow >= second.startRow && first.startColumn <= second.endColumn && first.endColumn >= second.startColumn;
|
|
1104
|
-
}
|
|
1105
|
-
function comparableFormula(formula) {
|
|
1106
|
-
return editableSpreadsheetFormula(formula).trim();
|
|
1107
|
-
}
|
|
1108
|
-
function futureFunctionPrefixes(formula, sourceFormula) {
|
|
1109
|
-
const sourcePrefixes = new Map();
|
|
1110
|
-
const sourceFunctions = (sourceFormula ?? '').replace(/"(?:[^"]|"")*"/g, '""');
|
|
1111
|
-
for (const match of sourceFunctions.matchAll(/(_xlfn\.(?:_xlws\.)?)([A-Z][A-Z0-9_.]*)\s*(?=\()/gi))if (match[1] && match[2]) sourcePrefixes.set(match[2].toUpperCase(), match[1]);
|
|
1112
|
-
return transformFormulaOutsideStrings(formula, (segment)=>segment.replace(/(?<![A-Z0-9_.])([A-Z][A-Z0-9_.]*)\s*(?=\()/gi, (match, functionName)=>{
|
|
1113
|
-
const normalized = functionName.toUpperCase();
|
|
1114
|
-
const prefix = sourcePrefixes.get(normalized) ?? FUTURE_FUNCTION_PREFIXES.get(normalized);
|
|
1115
|
-
return prefix ? `${prefix}${match}` : match;
|
|
1116
|
-
}));
|
|
1117
|
-
}
|
|
1118
|
-
function clampedInteger(value, minimum, maximum, fallback) {
|
|
1119
|
-
if (!Number.isFinite(value)) return fallback;
|
|
1120
|
-
return Math.min(maximum, Math.max(minimum, Math.round(value)));
|
|
1121
|
-
}
|
|
1122
|
-
function positiveNumber(value, fallback) {
|
|
1123
|
-
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1124
|
-
}
|
|
1125
|
-
function transformFormulaOutsideStrings(formula, transform) {
|
|
1126
|
-
return formula.split(/("(?:[^"]|"")*")/).map((segment, index)=>index % 2 ? segment : transform(segment)).join('');
|
|
1127
|
-
}
|
|
1128
1128
|
const XLSX_EMU_PER_PIXEL = 9525;
|
|
1129
1129
|
const DEFAULT_COLUMN_WIDTH = 96;
|
|
1130
1130
|
const DEFAULT_ROW_HEIGHT = 24;
|