@a3s-lab/office 0.12.0 → 0.13.1
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 +112 -7
- package/dist/0~1403.js +20 -12
- package/dist/0~2805.js +241 -241
- package/dist/0~4980.js +1 -1
- package/dist/0~5050.js +1 -20
- package/dist/0~5093.js +547 -547
- package/dist/0~6090.js +63 -4
- package/dist/0~7048.js +31 -29
- package/dist/0~document-editor.js +694 -90
- package/dist/0~pdf-viewer.js +41 -10
- package/dist/0~presentation-editor.js +8 -6
- package/dist/0~spreadsheet-editor.js +11892 -9682
- package/dist/0~work-document-import-worker-client.js +237 -0
- package/dist/0~work-docx-export.js +1 -1
- package/dist/0~work-docx-import.js +1 -1
- package/dist/0~work-docx-large-document-import.js +559 -0
- package/dist/0~work-office-diagnostics.js +46 -32
- package/dist/4104.js +5070 -3112
- package/dist/432.js +51 -7
- package/dist/4476.js +201 -7
- package/dist/5184.js +24 -2
- package/dist/{8928.js → 6282.js} +2338 -93
- package/dist/8715.js +895 -888
- package/dist/9356.js +3 -1
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/features/work/editors/document-editor-support.d.ts +1 -9
- package/dist/internal/features/work/editors/document-external-content.d.ts +1 -1
- package/dist/internal/features/work/editors/office-menu-keyboard.d.ts +1 -0
- package/dist/internal/features/work/editors/presentation-slide-canvas.d.ts +3 -1
- package/dist/internal/features/work/editors/presentation-slide-thumbnail.d.ts +3 -2
- package/dist/internal/features/work/editors/presentation-thumbnail-rail.d.ts +2 -1
- package/dist/internal/features/work/editors/presentation-workspace.d.ts +2 -1
- package/dist/internal/features/work/editors/spreadsheet-border-ribbon.d.ts +5 -0
- package/dist/internal/features/work/editors/spreadsheet-calculation-projection.d.ts +10 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-border-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-border-per-cell.d.ts +5 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-border.d.ts +28 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-fill-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-fill.d.ts +12 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-range.d.ts +14 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-style-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-style-ribbon.d.ts +9 -0
- package/dist/internal/features/work/editors/spreadsheet-cell-style.d.ts +41 -0
- package/dist/internal/features/work/editors/spreadsheet-command-catalog.d.ts +293 -0
- package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +10 -4
- package/dist/internal/features/work/editors/spreadsheet-editor-ribbon.d.ts +3 -1
- package/dist/internal/features/work/editors/spreadsheet-editor-support.d.ts +8 -2
- package/dist/internal/features/work/editors/spreadsheet-home-format-ribbon.d.ts +10 -0
- package/dist/internal/features/work/editors/spreadsheet-in-place-workbook-sync.d.ts +10 -0
- package/dist/internal/features/work/editors/spreadsheet-number-format.d.ts +15 -3
- package/dist/internal/features/work/editors/spreadsheet-operation-projection.d.ts +14 -0
- package/dist/internal/features/work/editors/spreadsheet-structure-selection.d.ts +7 -0
- package/dist/internal/features/work/editors/use-document-pagination.d.ts +5 -0
- package/dist/internal/features/work/editors/use-presentation-design-commands.d.ts +2 -1
- package/dist/internal/features/work/editors/use-spreadsheet-workbook-sync.d.ts +2 -1
- package/dist/internal/features/work/spreadsheet-history-value.d.ts +1 -0
- package/dist/internal/features/work/spreadsheet-sparse.d.ts +6 -1
- package/dist/internal/features/work/work-document-chunk-geometry.d.ts +28 -0
- package/dist/internal/features/work/work-document-chunk-node.d.ts +23 -0
- package/dist/internal/features/work/work-document-extensions.d.ts +1 -0
- package/dist/internal/features/work/work-document-html-fingerprint.d.ts +15 -0
- package/dist/internal/features/work/work-document-import-worker-client.d.ts +13 -0
- package/dist/internal/features/work/work-document-import-worker-protocol.d.ts +36 -0
- package/dist/internal/features/work/work-document-import.worker.d.ts +1 -0
- package/dist/internal/features/work/work-document-incremental-layout.d.ts +1 -1
- package/dist/internal/features/work/work-document-integrity-index.d.ts +14 -0
- package/dist/internal/features/work/work-document-lazy-block.d.ts +3 -0
- package/dist/internal/features/work/work-document-lazy-html.d.ts +21 -0
- package/dist/internal/features/work/work-document-lazy-model.d.ts +29 -0
- package/dist/internal/features/work/work-document-lazy-pagination-measurement.d.ts +3 -0
- package/dist/internal/features/work/work-document-lazy-preview.d.ts +21 -0
- package/dist/internal/features/work/work-document-lazy-update.d.ts +14 -0
- package/dist/internal/features/work/work-document-model.d.ts +18 -2
- package/dist/internal/features/work/work-document-page-surface-registry.d.ts +16 -0
- package/dist/internal/features/work/work-document-pagination-dom.d.ts +1 -0
- package/dist/internal/features/work/work-document-pagination-types.d.ts +1 -0
- package/dist/internal/features/work/work-document-pagination.d.ts +1 -1
- package/dist/internal/features/work/work-document-text-statistics.d.ts +25 -0
- package/dist/internal/features/work/work-document-windowing.d.ts +23 -0
- package/dist/internal/features/work/work-docx-large-document-import.d.ts +35 -0
- package/dist/internal/features/work/work-docx-large-document-parser.d.ts +29 -0
- package/dist/internal/features/work/work-file-import.d.ts +5 -0
- package/dist/internal/features/work/work-file-io.d.ts +3 -3
- package/dist/internal/features/work/work-file-kind.d.ts +5 -0
- package/dist/internal/features/work/work-office-diagnostics.d.ts +10 -1
- package/dist/internal/features/work/work-ooxml-package.d.ts +8 -0
- package/dist/internal/features/work/work-presentation-layouts.d.ts +7 -1
- package/dist/internal/features/work/work-repository.d.ts +1 -0
- package/dist/internal/features/work/work-spreadsheet-compatibility-summary.d.ts +10 -0
- package/dist/internal/features/work/work-spreadsheet-file-export.d.ts +2 -0
- package/dist/internal/features/work/work-spreadsheet-file-import.d.ts +3 -0
- package/dist/internal/features/work/work-spreadsheet-import-diagnostics.d.ts +2 -0
- package/dist/internal/features/work/work-spreadsheet-import-worker-client.d.ts +6 -0
- package/dist/internal/features/work/work-spreadsheet-import-worker-protocol.d.ts +32 -0
- package/dist/internal/features/work/work-spreadsheet-import.worker.d.ts +1 -0
- package/dist/internal/features/work/work-spreadsheet-matrix-profile.d.ts +64 -0
- package/dist/internal/features/work/work-spreadsheet-package-scan-worker-client.d.ts +6 -0
- package/dist/internal/features/work/work-spreadsheet-package-scan-worker-protocol.d.ts +49 -0
- package/dist/internal/features/work/work-spreadsheet-package-scan.worker.d.ts +1 -0
- package/dist/internal/features/work/work-xlsx-cell-borders.d.ts +25 -0
- package/dist/internal/features/work/work-xlsx-cell-style-writer.d.ts +21 -0
- package/dist/internal/features/work/work-xlsx-cell-style-xml.d.ts +17 -0
- package/dist/internal/features/work/work-xlsx-cell-styles.d.ts +13 -0
- package/dist/internal/features/work/work-xlsx-colors.d.ts +6 -0
- package/dist/internal/features/work/work-xlsx-formula-diagnostics.d.ts +3 -1
- package/dist/internal/features/work/work-xlsx-formulas.d.ts +2 -1
- package/dist/internal/features/work/work-xlsx-interop.d.ts +8 -5
- package/dist/internal/features/work/work-xlsx-pivot-diagnostics.d.ts +2 -1
- package/dist/internal/features/work/work-xlsx-plain-fast-path.d.ts +54 -0
- package/dist/internal/features/work/work-xlsx-style-gate.d.ts +1 -0
- package/dist/internal/features/work/work-xlsx-worksheet-scan.d.ts +13 -0
- package/dist/internal/features/work/work-xlsx-worksheet.d.ts +12 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/office-kernel.worker.js +31 -29
- package/dist/react.d.ts +7 -1
- package/dist/styles.css +557 -3
- package/dist/work-document-import.worker.js +558 -0
- package/dist/work-spreadsheet-import.worker.js +24745 -0
- package/dist/work-spreadsheet-import.worker.js.LICENSE.txt +3 -0
- package/dist/work-spreadsheet-package-scan.worker.js +4161 -0
- package/dist/work-spreadsheet-package-scan.worker.js.LICENSE.txt +11 -0
- package/docs/latest/en/browser-editor-architecture.md +351 -15
- package/package.json +23 -3
package/dist/8715.js
CHANGED
|
@@ -25,6 +25,13 @@ function sparseMatrixColumnCount(matrix) {
|
|
|
25
25
|
for (const [, row] of sparseArrayEntries(matrix))maximum = Math.max(maximum, row.length);
|
|
26
26
|
return maximum;
|
|
27
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
|
+
}
|
|
28
35
|
function cloneSparseMatrix(source) {
|
|
29
36
|
const clone = [];
|
|
30
37
|
if (!source) return clone;
|
|
@@ -37,204 +44,284 @@ function cloneSparseMatrix(source) {
|
|
|
37
44
|
}
|
|
38
45
|
return clone;
|
|
39
46
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
if (!Number.isFinite(size)) return 50;
|
|
43
|
-
return Math.min(90, Math.max(10, Math.round(size)));
|
|
44
|
-
}
|
|
45
|
-
function normalizeWorkSpreadsheetRadarStyle(value) {
|
|
46
|
-
return 'standard' === value || 'filled' === value || 'marker' === value ? value : 'marker';
|
|
47
|
+
function spreadsheetPivotCellValue(cell) {
|
|
48
|
+
return cell?.v ?? cell?.m ?? null;
|
|
47
49
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
+
function normalizeSpreadsheetPivotFilterValue(value) {
|
|
51
|
+
if (null == value || '' === value) return null;
|
|
52
|
+
if (value instanceof Date) return value.toISOString();
|
|
53
|
+
if ('number' == typeof value) return Number.isFinite(value) ? value : String(value);
|
|
54
|
+
if ('boolean' == typeof value) return value;
|
|
55
|
+
return String(value);
|
|
50
56
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return Math.min(300, Math.max(0, Math.round(scale)));
|
|
57
|
+
function spreadsheetPivotFilterValueKey(value) {
|
|
58
|
+
if (null === value) return 'blank:';
|
|
59
|
+
return `${typeof value}:${String(value)}`;
|
|
55
60
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
61
|
+
function displaySpreadsheetPivotValue(value) {
|
|
62
|
+
if (null == value || '' === value) return '(空白)';
|
|
63
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
64
|
+
if ('boolean' == typeof value) return value ? 'TRUE' : 'FALSE';
|
|
65
|
+
return String(value);
|
|
58
66
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
67
|
+
function finiteSpreadsheetPivotNumber(value) {
|
|
68
|
+
if ('number' == typeof value) return Number.isFinite(value) ? value : null;
|
|
69
|
+
if (value instanceof Date) return value.getTime();
|
|
70
|
+
return null;
|
|
61
71
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
72
|
+
function spreadsheetPivotFilterItemsFromSource(sourceSheet, sourceBounds, fieldIndex) {
|
|
73
|
+
if (fieldIndex < 0 || fieldIndex > sourceBounds.endColumn - sourceBounds.startColumn) return [];
|
|
74
|
+
const items = new Map();
|
|
75
|
+
for(let row = sourceBounds.startRow + 1; row <= sourceBounds.endRow; row += 1){
|
|
76
|
+
const value = normalizeSpreadsheetPivotFilterValue(spreadsheetPivotCellValue(sourceSheet.data?.[row]?.[sourceBounds.startColumn + fieldIndex]));
|
|
77
|
+
const item = {
|
|
78
|
+
value,
|
|
79
|
+
label: displaySpreadsheetPivotValue(value)
|
|
80
|
+
};
|
|
81
|
+
items.set(spreadsheetPivotFilterValueKey(value), item);
|
|
82
|
+
}
|
|
83
|
+
return Array.from(items.values()).sort((left, right)=>left.label.localeCompare(right.label, 'zh-CN', {
|
|
84
|
+
numeric: true,
|
|
85
|
+
sensitivity: 'base'
|
|
86
|
+
}));
|
|
64
87
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
88
|
+
function buildSpreadsheetPivotOutput(sourceSheet, sourceBounds, fields, pivot) {
|
|
89
|
+
const rowCombinations = new Map();
|
|
90
|
+
const columnCombinations = new Map();
|
|
91
|
+
const cells = new Map();
|
|
92
|
+
const rowTotals = new Map();
|
|
93
|
+
const columnTotals = new Map();
|
|
94
|
+
const totals = pivot.values.map(()=>accumulator());
|
|
95
|
+
const singleColumn = combination([]);
|
|
96
|
+
if (!pivot.columnFields.length) columnCombinations.set(singleColumn.key, singleColumn);
|
|
97
|
+
for(let row = sourceBounds.startRow + 1; row <= sourceBounds.endRow; row += 1){
|
|
98
|
+
const values = fields.map((field)=>spreadsheetPivotCellValue(sourceSheet.data?.[row]?.[sourceBounds.startColumn + field.index]));
|
|
99
|
+
if (values.every((value)=>null == value || '' === value)) continue;
|
|
100
|
+
if (!matchesPivotReportFilters(values, pivot)) continue;
|
|
101
|
+
const rowCombination = combination(pivot.rowFields.map((fieldIndex)=>values[fieldIndex]));
|
|
102
|
+
const columnCombination = pivot.columnFields.length ? combination(pivot.columnFields.map((fieldIndex)=>values[fieldIndex])) : singleColumn;
|
|
103
|
+
rowCombinations.set(rowCombination.key, rowCombination);
|
|
104
|
+
columnCombinations.set(columnCombination.key, columnCombination);
|
|
105
|
+
for (const [valueIndex, valueField] of pivot.values.entries()){
|
|
106
|
+
const value = values[valueField.fieldIndex];
|
|
107
|
+
updateAccumulator(mapAccumulator(cells, cellKey(rowCombination.key, columnCombination.key, valueIndex)), value);
|
|
108
|
+
updateAccumulator(mapAccumulator(rowTotals, cellKey(rowCombination.key, '', valueIndex)), value);
|
|
109
|
+
updateAccumulator(mapAccumulator(columnTotals, cellKey('', columnCombination.key, valueIndex)), value);
|
|
110
|
+
updateAccumulator(totals[valueIndex], value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const sortedRows = Array.from(rowCombinations.values()).sort(compareCombinations);
|
|
114
|
+
const sortedColumns = Array.from(columnCombinations.values()).sort(compareCombinations);
|
|
115
|
+
if (!sortedRows.length) return [];
|
|
116
|
+
const headers = [
|
|
117
|
+
...pivot.rowFields.map((fieldIndex)=>fields[fieldIndex].name),
|
|
118
|
+
...sortedColumns.flatMap((column)=>pivot.values.map((value)=>columnValueCaption(column, pivot.columnFields, fields, value))),
|
|
119
|
+
...pivot.rowGrandTotals && pivot.columnFields.length ? pivot.values.map((value)=>`总计 · ${valueCaption(value, fields)}`) : []
|
|
120
|
+
];
|
|
121
|
+
const output = [
|
|
122
|
+
headers.map((value)=>pivotHeaderCell(value))
|
|
123
|
+
];
|
|
124
|
+
for (const row of sortedRows){
|
|
125
|
+
const result = row.values.map((value)=>pivotDimensionCell(value));
|
|
126
|
+
for (const column of sortedColumns)for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(cells.get(cellKey(row.key, column.key, valueIndex)), value.aggregation), false));
|
|
127
|
+
if (pivot.rowGrandTotals && pivot.columnFields.length) for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(rowTotals.get(cellKey(row.key, '', valueIndex)), value.aggregation), true));
|
|
128
|
+
output.push(result);
|
|
129
|
+
}
|
|
130
|
+
if (pivot.columnGrandTotals) {
|
|
131
|
+
const result = pivot.rowFields.map((_field, index)=>0 === index ? pivotTotalLabelCell() : pivotValueCell(null, true));
|
|
132
|
+
for (const column of sortedColumns)for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(columnTotals.get(cellKey('', column.key, valueIndex)), value.aggregation), true));
|
|
133
|
+
if (pivot.rowGrandTotals && pivot.columnFields.length) for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(totals[valueIndex], value.aggregation), true));
|
|
134
|
+
output.push(result);
|
|
135
|
+
}
|
|
136
|
+
const reportFilters = pivot.reportFilters ?? [];
|
|
137
|
+
if (!reportFilters.length) return output;
|
|
138
|
+
const width = output[0]?.length ?? 0;
|
|
139
|
+
const filterRows = reportFilters.map((filter)=>{
|
|
140
|
+
const row = Array(width).fill(null);
|
|
141
|
+
row[0] = pivotFilterLabelCell(fields[filter.fieldIndex].name);
|
|
142
|
+
row[1] = pivotFilterSelectionCell(void 0 === filter.selectedItem ? '(全部)' : displaySpreadsheetPivotValue(filter.selectedItem));
|
|
143
|
+
return row;
|
|
144
|
+
});
|
|
145
|
+
filterRows.push(Array(width).fill(null));
|
|
146
|
+
return [
|
|
147
|
+
...filterRows,
|
|
148
|
+
...output
|
|
149
|
+
];
|
|
67
150
|
}
|
|
68
|
-
function
|
|
69
|
-
return
|
|
151
|
+
function accumulator() {
|
|
152
|
+
return {
|
|
153
|
+
nonEmptyCount: 0,
|
|
154
|
+
numberCount: 0,
|
|
155
|
+
sum: 0,
|
|
156
|
+
product: 1,
|
|
157
|
+
mean: 0,
|
|
158
|
+
m2: 0
|
|
159
|
+
};
|
|
70
160
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
161
|
+
function mapAccumulator(map, key) {
|
|
162
|
+
const existing = map.get(key);
|
|
163
|
+
if (existing) return existing;
|
|
164
|
+
const next = accumulator();
|
|
165
|
+
map.set(key, next);
|
|
166
|
+
return next;
|
|
75
167
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
168
|
+
function updateAccumulator(state, value) {
|
|
169
|
+
if (null != value && '' !== value) state.nonEmptyCount += 1;
|
|
170
|
+
const number = finiteSpreadsheetPivotNumber(value);
|
|
171
|
+
if (null === number) return;
|
|
172
|
+
state.numberCount += 1;
|
|
173
|
+
state.sum += number;
|
|
174
|
+
state.product *= number;
|
|
175
|
+
state.minimum = void 0 === state.minimum ? number : Math.min(state.minimum, number);
|
|
176
|
+
state.maximum = void 0 === state.maximum ? number : Math.max(state.maximum, number);
|
|
177
|
+
const delta = number - state.mean;
|
|
178
|
+
state.mean += delta / state.numberCount;
|
|
179
|
+
state.m2 += delta * (number - state.mean);
|
|
78
180
|
}
|
|
79
|
-
function
|
|
80
|
-
return '
|
|
181
|
+
function accumulatorValue(state, aggregation) {
|
|
182
|
+
if (!state) return 'sum' === aggregation || 'count' === aggregation || 'counta' === aggregation ? 0 : null;
|
|
183
|
+
if ('count' === aggregation) return state.numberCount;
|
|
184
|
+
if ('counta' === aggregation) return state.nonEmptyCount;
|
|
185
|
+
if ('average' === aggregation) return state.numberCount ? state.sum / state.numberCount : null;
|
|
186
|
+
if ('max' === aggregation) return state.maximum ?? null;
|
|
187
|
+
if ('min' === aggregation) return state.minimum ?? null;
|
|
188
|
+
if ('product' === aggregation) return state.numberCount ? state.product : null;
|
|
189
|
+
if ('stdDev' === aggregation) return state.numberCount > 1 ? Math.sqrt(state.m2 / (state.numberCount - 1)) : null;
|
|
190
|
+
if ('stdDevP' === aggregation) return state.numberCount ? Math.sqrt(state.m2 / state.numberCount) : null;
|
|
191
|
+
if ('var' === aggregation) return state.numberCount > 1 ? state.m2 / (state.numberCount - 1) : null;
|
|
192
|
+
if ('varP' === aggregation) return state.numberCount ? state.m2 / state.numberCount : null;
|
|
193
|
+
return state.sum;
|
|
81
194
|
}
|
|
82
|
-
function
|
|
83
|
-
const direction = workSpreadsheetChartUsesNumericXAxis(chartType) && 'x' === source.direction ? 'x' : 'y';
|
|
84
|
-
const barType = 'plus' === source.barType || 'minus' === source.barType ? source.barType : 'both';
|
|
85
|
-
const valueType = 'percentage' === source.valueType || 'standardDeviation' === source.valueType || 'standardError' === source.valueType || 'custom' === source.valueType || 'fixedValue' === source.valueType ? source.valueType : 'fixedValue';
|
|
86
|
-
const fallbackValue = 'percentage' === valueType ? 5 : 1;
|
|
87
|
-
const numericValue = Number(source.value);
|
|
88
|
-
const value = Number.isFinite(numericValue) ? Math.max(0, numericValue) : fallbackValue;
|
|
89
|
-
const plusValues = source.plusValues?.map(normalizedErrorBarAmount);
|
|
90
|
-
const minusValues = source.minusValues?.map(normalizedErrorBarAmount);
|
|
195
|
+
function combination(values) {
|
|
91
196
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
valueType,
|
|
95
|
-
...'fixedValue' === valueType || 'percentage' === valueType || 'standardDeviation' === valueType ? {
|
|
96
|
-
value
|
|
97
|
-
} : {},
|
|
98
|
-
...false === source.showEndCaps ? {
|
|
99
|
-
showEndCaps: false
|
|
100
|
-
} : {},
|
|
101
|
-
...'custom' === valueType && 'minus' !== barType && plusValues?.length ? {
|
|
102
|
-
plusValues
|
|
103
|
-
} : {},
|
|
104
|
-
...'custom' === valueType && 'minus' !== barType && source.plusReference?.trim() ? {
|
|
105
|
-
plusReference: source.plusReference.trim().replace(/^=/, '')
|
|
106
|
-
} : {},
|
|
107
|
-
...'custom' === valueType && 'plus' !== barType && minusValues?.length ? {
|
|
108
|
-
minusValues
|
|
109
|
-
} : {},
|
|
110
|
-
...'custom' === valueType && 'plus' !== barType && source.minusReference?.trim() ? {
|
|
111
|
-
minusReference: source.minusReference.trim().replace(/^=/, '')
|
|
112
|
-
} : {}
|
|
197
|
+
key: JSON.stringify(values.map(normalizedPivotKeyValue)),
|
|
198
|
+
values
|
|
113
199
|
};
|
|
114
200
|
}
|
|
115
|
-
function
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if ('
|
|
125
|
-
|
|
201
|
+
function normalizedPivotKeyValue(value) {
|
|
202
|
+
if (value instanceof Date) return [
|
|
203
|
+
'date',
|
|
204
|
+
value.toISOString()
|
|
205
|
+
];
|
|
206
|
+
if ('number' == typeof value) return [
|
|
207
|
+
'number',
|
|
208
|
+
Number.isFinite(value) ? value : null
|
|
209
|
+
];
|
|
210
|
+
if ('boolean' == typeof value) return [
|
|
211
|
+
'boolean',
|
|
212
|
+
value
|
|
213
|
+
];
|
|
214
|
+
if (null == value || '' === value) return [
|
|
215
|
+
'blank'
|
|
216
|
+
];
|
|
217
|
+
return [
|
|
218
|
+
'string',
|
|
219
|
+
String(value)
|
|
220
|
+
];
|
|
126
221
|
}
|
|
127
|
-
function
|
|
128
|
-
|
|
222
|
+
function compareCombinations(left, right) {
|
|
223
|
+
const count = Math.max(left.values.length, right.values.length);
|
|
224
|
+
for(let index = 0; index < count; index += 1){
|
|
225
|
+
const difference = displaySpreadsheetPivotValue(left.values[index]).localeCompare(displaySpreadsheetPivotValue(right.values[index]), 'zh-CN', {
|
|
226
|
+
numeric: true,
|
|
227
|
+
sensitivity: 'base'
|
|
228
|
+
});
|
|
229
|
+
if (difference) return difference;
|
|
230
|
+
}
|
|
231
|
+
return 0;
|
|
129
232
|
}
|
|
130
|
-
function
|
|
131
|
-
return
|
|
233
|
+
function cellKey(rowKey, columnKey, valueIndex) {
|
|
234
|
+
return `${rowKey.length}:${rowKey}${columnKey.length}:${columnKey}${valueIndex}`;
|
|
132
235
|
}
|
|
133
|
-
function
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
236
|
+
function columnValueCaption(column, columnFields, fields, value) {
|
|
237
|
+
const dimensions = column.values.map((item, index)=>{
|
|
238
|
+
const label = displaySpreadsheetPivotValue(item);
|
|
239
|
+
return columnFields.length > 1 ? `${fields[columnFields[index]].name}: ${label}` : label;
|
|
240
|
+
});
|
|
241
|
+
return [
|
|
242
|
+
...dimensions,
|
|
243
|
+
valueCaption(value, fields)
|
|
244
|
+
].filter(Boolean).join(' · ');
|
|
245
|
+
}
|
|
246
|
+
function valueCaption(value, fields) {
|
|
247
|
+
return value.caption?.trim() || defaultPivotValueCaption(fields[value.fieldIndex]?.name ?? `字段 ${value.fieldIndex + 1}`, value.aggregation);
|
|
248
|
+
}
|
|
249
|
+
function defaultPivotValueCaption(fieldName, aggregation) {
|
|
250
|
+
return `${fieldName}(${spreadsheetPivotAggregationLabel(aggregation)})`;
|
|
251
|
+
}
|
|
252
|
+
function spreadsheetPivotAggregationLabel(aggregation) {
|
|
253
|
+
if ('count' === aggregation) return '计数';
|
|
254
|
+
if ('counta' === aggregation) return '非空计数';
|
|
255
|
+
if ('average' === aggregation) return '平均值';
|
|
256
|
+
if ('max' === aggregation) return '最大值';
|
|
257
|
+
if ('min' === aggregation) return '最小值';
|
|
258
|
+
if ('product' === aggregation) return '乘积';
|
|
259
|
+
if ('stdDev' === aggregation) return '样本标准差';
|
|
260
|
+
if ('stdDevP' === aggregation) return '总体标准差';
|
|
261
|
+
if ('var' === aggregation) return '样本方差';
|
|
262
|
+
if ('varP' === aggregation) return '总体方差';
|
|
263
|
+
return '求和';
|
|
264
|
+
}
|
|
265
|
+
function pivotHeaderCell(value) {
|
|
266
|
+
return {
|
|
267
|
+
v: value,
|
|
268
|
+
m: value,
|
|
269
|
+
bl: 1,
|
|
270
|
+
bg: '#e7f1ec',
|
|
271
|
+
fc: '#173d2b'
|
|
158
272
|
};
|
|
159
273
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if ('right' === position) return '右侧';
|
|
167
|
-
if ('above' === position) return '上方';
|
|
168
|
-
if ('below' === position) return '下方';
|
|
169
|
-
return '最佳匹配';
|
|
274
|
+
function pivotDimensionCell(value) {
|
|
275
|
+
const display = displaySpreadsheetPivotValue(value);
|
|
276
|
+
return {
|
|
277
|
+
v: null == value || '' === value ? display : value,
|
|
278
|
+
m: display
|
|
279
|
+
};
|
|
170
280
|
}
|
|
171
|
-
function
|
|
172
|
-
|
|
281
|
+
function pivotValueCell(value, total) {
|
|
282
|
+
if (null === value) return total ? {
|
|
283
|
+
v: '',
|
|
284
|
+
m: '',
|
|
285
|
+
bl: 1,
|
|
286
|
+
bg: '#f3f7f5'
|
|
287
|
+
} : null;
|
|
288
|
+
return {
|
|
289
|
+
v: value,
|
|
290
|
+
m: formatPivotNumber(value),
|
|
291
|
+
bl: total ? 1 : void 0,
|
|
292
|
+
bg: total ? '#f3f7f5' : void 0,
|
|
293
|
+
ht: 2
|
|
294
|
+
};
|
|
173
295
|
}
|
|
174
|
-
function
|
|
175
|
-
const type = normalizeWorkSpreadsheetTrendlineType(trendline.type);
|
|
176
|
-
const name = trendline.name?.trim().slice(0, 255);
|
|
177
|
-
const order = normalizedInteger(trendline.order, 2, 6, 2);
|
|
178
|
-
const period = normalizedInteger(trendline.period, 2, 255, 2);
|
|
179
|
-
const forward = normalizedNonNegativeNumber(trendline.forward);
|
|
180
|
-
const backward = normalizedNonNegativeNumber(trendline.backward);
|
|
181
|
-
const intercept = Number.isFinite(trendline.intercept) ? Number(trendline.intercept) : void 0;
|
|
296
|
+
function pivotTotalLabelCell() {
|
|
182
297
|
return {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
...'polynomial' === type ? {
|
|
188
|
-
order
|
|
189
|
-
} : {},
|
|
190
|
-
...'movingAverage' === type ? {
|
|
191
|
-
period
|
|
192
|
-
} : {},
|
|
193
|
-
...forward > 0 ? {
|
|
194
|
-
forward
|
|
195
|
-
} : {},
|
|
196
|
-
...backward > 0 ? {
|
|
197
|
-
backward
|
|
198
|
-
} : {},
|
|
199
|
-
...void 0 !== intercept ? {
|
|
200
|
-
intercept
|
|
201
|
-
} : {},
|
|
202
|
-
...true === trendline.displayEquation ? {
|
|
203
|
-
displayEquation: true
|
|
204
|
-
} : {},
|
|
205
|
-
...true === trendline.displayRSquared ? {
|
|
206
|
-
displayRSquared: true
|
|
207
|
-
} : {}
|
|
298
|
+
v: '总计',
|
|
299
|
+
m: '总计',
|
|
300
|
+
bl: 1,
|
|
301
|
+
bg: '#f3f7f5'
|
|
208
302
|
};
|
|
209
303
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
304
|
+
function pivotFilterLabelCell(value) {
|
|
305
|
+
return {
|
|
306
|
+
v: value,
|
|
307
|
+
m: value,
|
|
308
|
+
bl: 1,
|
|
309
|
+
bg: '#f3f7f5',
|
|
310
|
+
fc: '#173d2b'
|
|
311
|
+
};
|
|
217
312
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
313
|
+
function pivotFilterSelectionCell(value) {
|
|
314
|
+
return {
|
|
315
|
+
v: value,
|
|
316
|
+
m: value,
|
|
317
|
+
bg: '#f3f7f5'
|
|
318
|
+
};
|
|
222
319
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
return Number.isFinite(number) ? Math.max(0, number) : 0;
|
|
320
|
+
function formatPivotNumber(value) {
|
|
321
|
+
return Number.isInteger(value) ? String(value) : String(Number(value.toPrecision(15)));
|
|
226
322
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
if ('line' === type) return '折线图';
|
|
230
|
-
if ('pie' === type) return '饼图';
|
|
231
|
-
if ('doughnut' === type) return '圆环图';
|
|
232
|
-
if ('area' === type) return '面积图';
|
|
233
|
-
if ('radar' === type) return '雷达图';
|
|
234
|
-
if ('scatter' === type) return '散点图';
|
|
235
|
-
if ('bubble' === type) return '气泡图';
|
|
236
|
-
if ('combination' === type) return '组合图';
|
|
237
|
-
return '柱形图';
|
|
323
|
+
function matchesPivotReportFilters(values, pivot) {
|
|
324
|
+
return (pivot.reportFilters ?? []).every((filter)=>void 0 === filter.selectedItem || spreadsheetPivotFilterValueKey(normalizeSpreadsheetPivotFilterValue(values[filter.fieldIndex])) === spreadsheetPivotFilterValueKey(filter.selectedItem));
|
|
238
325
|
}
|
|
239
326
|
const CELL_REFERENCE = /\$?([A-Z]{1,3})\$?([1-9]\d*)/i;
|
|
240
327
|
const CELL_OR_RANGE = new RegExp(`^${CELL_REFERENCE.source}(?::${CELL_REFERENCE.source})?$`, 'i');
|
|
@@ -392,318 +479,39 @@ function encodeColumn(column) {
|
|
|
392
479
|
}
|
|
393
480
|
return label;
|
|
394
481
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if ('number' == typeof value) return Number.isFinite(value) ? value : String(value);
|
|
402
|
-
if ('boolean' == typeof value) return value;
|
|
403
|
-
return String(value);
|
|
404
|
-
}
|
|
405
|
-
function spreadsheetPivotFilterValueKey(value) {
|
|
406
|
-
if (null === value) return 'blank:';
|
|
407
|
-
return `${typeof value}:${String(value)}`;
|
|
408
|
-
}
|
|
409
|
-
function displaySpreadsheetPivotValue(value) {
|
|
410
|
-
if (null == value || '' === value) return '(空白)';
|
|
411
|
-
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
412
|
-
if ('boolean' == typeof value) return value ? 'TRUE' : 'FALSE';
|
|
413
|
-
return String(value);
|
|
414
|
-
}
|
|
415
|
-
function finiteSpreadsheetPivotNumber(value) {
|
|
416
|
-
if ('number' == typeof value) return Number.isFinite(value) ? value : null;
|
|
417
|
-
if (value instanceof Date) return value.getTime();
|
|
418
|
-
return null;
|
|
482
|
+
const MAXIMUM_SOURCE_CELLS = 100000;
|
|
483
|
+
const MAXIMUM_OUTPUT_CELLS = 20000;
|
|
484
|
+
const MAXIMUM_XLSX_ROW = 1048575;
|
|
485
|
+
const MAXIMUM_XLSX_COLUMN = 16383;
|
|
486
|
+
function spreadsheetPivotCount(content) {
|
|
487
|
+
return content.sheets.reduce((count, sheet)=>count + (sheet.pivotTables?.length ?? 0), 0);
|
|
419
488
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
489
|
+
function spreadsheetPivotFields(content, pivot) {
|
|
490
|
+
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
491
|
+
const range = singleRange(pivot.sourceReference);
|
|
492
|
+
if (!sourceSheet || !range) return [];
|
|
493
|
+
const fields = [];
|
|
494
|
+
for(let column = range.startColumn; column <= range.endColumn; column += 1){
|
|
495
|
+
const header = spreadsheetPivotCellValue(sourceSheet.data?.[range.startRow]?.[column]);
|
|
496
|
+
if (null == header || '' === String(header).trim()) return [];
|
|
497
|
+
const name = displaySpreadsheetPivotValue(header).trim();
|
|
498
|
+
let numeric = false;
|
|
499
|
+
for(let row = range.startRow + 1; row <= range.endRow; row += 1){
|
|
500
|
+
const value = spreadsheetPivotCellValue(sourceSheet.data?.[row]?.[column]);
|
|
501
|
+
if (null != value && '' !== value) {
|
|
502
|
+
if (null !== finiteSpreadsheetPivotNumber(value)) {
|
|
503
|
+
numeric = true;
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
fields.push({
|
|
509
|
+
index: column - range.startColumn,
|
|
510
|
+
name,
|
|
511
|
+
numeric
|
|
512
|
+
});
|
|
430
513
|
}
|
|
431
|
-
return
|
|
432
|
-
numeric: true,
|
|
433
|
-
sensitivity: 'base'
|
|
434
|
-
}));
|
|
435
|
-
}
|
|
436
|
-
function buildSpreadsheetPivotOutput(sourceSheet, sourceBounds, fields, pivot) {
|
|
437
|
-
const rowCombinations = new Map();
|
|
438
|
-
const columnCombinations = new Map();
|
|
439
|
-
const cells = new Map();
|
|
440
|
-
const rowTotals = new Map();
|
|
441
|
-
const columnTotals = new Map();
|
|
442
|
-
const totals = pivot.values.map(()=>accumulator());
|
|
443
|
-
const singleColumn = combination([]);
|
|
444
|
-
if (!pivot.columnFields.length) columnCombinations.set(singleColumn.key, singleColumn);
|
|
445
|
-
for(let row = sourceBounds.startRow + 1; row <= sourceBounds.endRow; row += 1){
|
|
446
|
-
const values = fields.map((field)=>spreadsheetPivotCellValue(sourceSheet.data?.[row]?.[sourceBounds.startColumn + field.index]));
|
|
447
|
-
if (values.every((value)=>null == value || '' === value)) continue;
|
|
448
|
-
if (!matchesPivotReportFilters(values, pivot)) continue;
|
|
449
|
-
const rowCombination = combination(pivot.rowFields.map((fieldIndex)=>values[fieldIndex]));
|
|
450
|
-
const columnCombination = pivot.columnFields.length ? combination(pivot.columnFields.map((fieldIndex)=>values[fieldIndex])) : singleColumn;
|
|
451
|
-
rowCombinations.set(rowCombination.key, rowCombination);
|
|
452
|
-
columnCombinations.set(columnCombination.key, columnCombination);
|
|
453
|
-
for (const [valueIndex, valueField] of pivot.values.entries()){
|
|
454
|
-
const value = values[valueField.fieldIndex];
|
|
455
|
-
updateAccumulator(mapAccumulator(cells, cellKey(rowCombination.key, columnCombination.key, valueIndex)), value);
|
|
456
|
-
updateAccumulator(mapAccumulator(rowTotals, cellKey(rowCombination.key, '', valueIndex)), value);
|
|
457
|
-
updateAccumulator(mapAccumulator(columnTotals, cellKey('', columnCombination.key, valueIndex)), value);
|
|
458
|
-
updateAccumulator(totals[valueIndex], value);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
const sortedRows = Array.from(rowCombinations.values()).sort(compareCombinations);
|
|
462
|
-
const sortedColumns = Array.from(columnCombinations.values()).sort(compareCombinations);
|
|
463
|
-
if (!sortedRows.length) return [];
|
|
464
|
-
const headers = [
|
|
465
|
-
...pivot.rowFields.map((fieldIndex)=>fields[fieldIndex].name),
|
|
466
|
-
...sortedColumns.flatMap((column)=>pivot.values.map((value)=>columnValueCaption(column, pivot.columnFields, fields, value))),
|
|
467
|
-
...pivot.rowGrandTotals && pivot.columnFields.length ? pivot.values.map((value)=>`总计 · ${valueCaption(value, fields)}`) : []
|
|
468
|
-
];
|
|
469
|
-
const output = [
|
|
470
|
-
headers.map((value)=>pivotHeaderCell(value))
|
|
471
|
-
];
|
|
472
|
-
for (const row of sortedRows){
|
|
473
|
-
const result = row.values.map((value)=>pivotDimensionCell(value));
|
|
474
|
-
for (const column of sortedColumns)for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(cells.get(cellKey(row.key, column.key, valueIndex)), value.aggregation), false));
|
|
475
|
-
if (pivot.rowGrandTotals && pivot.columnFields.length) for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(rowTotals.get(cellKey(row.key, '', valueIndex)), value.aggregation), true));
|
|
476
|
-
output.push(result);
|
|
477
|
-
}
|
|
478
|
-
if (pivot.columnGrandTotals) {
|
|
479
|
-
const result = pivot.rowFields.map((_field, index)=>0 === index ? pivotTotalLabelCell() : pivotValueCell(null, true));
|
|
480
|
-
for (const column of sortedColumns)for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(columnTotals.get(cellKey('', column.key, valueIndex)), value.aggregation), true));
|
|
481
|
-
if (pivot.rowGrandTotals && pivot.columnFields.length) for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(totals[valueIndex], value.aggregation), true));
|
|
482
|
-
output.push(result);
|
|
483
|
-
}
|
|
484
|
-
const reportFilters = pivot.reportFilters ?? [];
|
|
485
|
-
if (!reportFilters.length) return output;
|
|
486
|
-
const width = output[0]?.length ?? 0;
|
|
487
|
-
const filterRows = reportFilters.map((filter)=>{
|
|
488
|
-
const row = Array(width).fill(null);
|
|
489
|
-
row[0] = pivotFilterLabelCell(fields[filter.fieldIndex].name);
|
|
490
|
-
row[1] = pivotFilterSelectionCell(void 0 === filter.selectedItem ? '(全部)' : displaySpreadsheetPivotValue(filter.selectedItem));
|
|
491
|
-
return row;
|
|
492
|
-
});
|
|
493
|
-
filterRows.push(Array(width).fill(null));
|
|
494
|
-
return [
|
|
495
|
-
...filterRows,
|
|
496
|
-
...output
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
function accumulator() {
|
|
500
|
-
return {
|
|
501
|
-
nonEmptyCount: 0,
|
|
502
|
-
numberCount: 0,
|
|
503
|
-
sum: 0,
|
|
504
|
-
product: 1,
|
|
505
|
-
mean: 0,
|
|
506
|
-
m2: 0
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
function mapAccumulator(map, key) {
|
|
510
|
-
const existing = map.get(key);
|
|
511
|
-
if (existing) return existing;
|
|
512
|
-
const next = accumulator();
|
|
513
|
-
map.set(key, next);
|
|
514
|
-
return next;
|
|
515
|
-
}
|
|
516
|
-
function updateAccumulator(state, value) {
|
|
517
|
-
if (null != value && '' !== value) state.nonEmptyCount += 1;
|
|
518
|
-
const number = finiteSpreadsheetPivotNumber(value);
|
|
519
|
-
if (null === number) return;
|
|
520
|
-
state.numberCount += 1;
|
|
521
|
-
state.sum += number;
|
|
522
|
-
state.product *= number;
|
|
523
|
-
state.minimum = void 0 === state.minimum ? number : Math.min(state.minimum, number);
|
|
524
|
-
state.maximum = void 0 === state.maximum ? number : Math.max(state.maximum, number);
|
|
525
|
-
const delta = number - state.mean;
|
|
526
|
-
state.mean += delta / state.numberCount;
|
|
527
|
-
state.m2 += delta * (number - state.mean);
|
|
528
|
-
}
|
|
529
|
-
function accumulatorValue(state, aggregation) {
|
|
530
|
-
if (!state) return 'sum' === aggregation || 'count' === aggregation || 'counta' === aggregation ? 0 : null;
|
|
531
|
-
if ('count' === aggregation) return state.numberCount;
|
|
532
|
-
if ('counta' === aggregation) return state.nonEmptyCount;
|
|
533
|
-
if ('average' === aggregation) return state.numberCount ? state.sum / state.numberCount : null;
|
|
534
|
-
if ('max' === aggregation) return state.maximum ?? null;
|
|
535
|
-
if ('min' === aggregation) return state.minimum ?? null;
|
|
536
|
-
if ('product' === aggregation) return state.numberCount ? state.product : null;
|
|
537
|
-
if ('stdDev' === aggregation) return state.numberCount > 1 ? Math.sqrt(state.m2 / (state.numberCount - 1)) : null;
|
|
538
|
-
if ('stdDevP' === aggregation) return state.numberCount ? Math.sqrt(state.m2 / state.numberCount) : null;
|
|
539
|
-
if ('var' === aggregation) return state.numberCount > 1 ? state.m2 / (state.numberCount - 1) : null;
|
|
540
|
-
if ('varP' === aggregation) return state.numberCount ? state.m2 / state.numberCount : null;
|
|
541
|
-
return state.sum;
|
|
542
|
-
}
|
|
543
|
-
function combination(values) {
|
|
544
|
-
return {
|
|
545
|
-
key: JSON.stringify(values.map(normalizedPivotKeyValue)),
|
|
546
|
-
values
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
function normalizedPivotKeyValue(value) {
|
|
550
|
-
if (value instanceof Date) return [
|
|
551
|
-
'date',
|
|
552
|
-
value.toISOString()
|
|
553
|
-
];
|
|
554
|
-
if ('number' == typeof value) return [
|
|
555
|
-
'number',
|
|
556
|
-
Number.isFinite(value) ? value : null
|
|
557
|
-
];
|
|
558
|
-
if ('boolean' == typeof value) return [
|
|
559
|
-
'boolean',
|
|
560
|
-
value
|
|
561
|
-
];
|
|
562
|
-
if (null == value || '' === value) return [
|
|
563
|
-
'blank'
|
|
564
|
-
];
|
|
565
|
-
return [
|
|
566
|
-
'string',
|
|
567
|
-
String(value)
|
|
568
|
-
];
|
|
569
|
-
}
|
|
570
|
-
function compareCombinations(left, right) {
|
|
571
|
-
const count = Math.max(left.values.length, right.values.length);
|
|
572
|
-
for(let index = 0; index < count; index += 1){
|
|
573
|
-
const difference = displaySpreadsheetPivotValue(left.values[index]).localeCompare(displaySpreadsheetPivotValue(right.values[index]), 'zh-CN', {
|
|
574
|
-
numeric: true,
|
|
575
|
-
sensitivity: 'base'
|
|
576
|
-
});
|
|
577
|
-
if (difference) return difference;
|
|
578
|
-
}
|
|
579
|
-
return 0;
|
|
580
|
-
}
|
|
581
|
-
function cellKey(rowKey, columnKey, valueIndex) {
|
|
582
|
-
return `${rowKey.length}:${rowKey}${columnKey.length}:${columnKey}${valueIndex}`;
|
|
583
|
-
}
|
|
584
|
-
function columnValueCaption(column, columnFields, fields, value) {
|
|
585
|
-
const dimensions = column.values.map((item, index)=>{
|
|
586
|
-
const label = displaySpreadsheetPivotValue(item);
|
|
587
|
-
return columnFields.length > 1 ? `${fields[columnFields[index]].name}: ${label}` : label;
|
|
588
|
-
});
|
|
589
|
-
return [
|
|
590
|
-
...dimensions,
|
|
591
|
-
valueCaption(value, fields)
|
|
592
|
-
].filter(Boolean).join(' · ');
|
|
593
|
-
}
|
|
594
|
-
function valueCaption(value, fields) {
|
|
595
|
-
return value.caption?.trim() || defaultPivotValueCaption(fields[value.fieldIndex]?.name ?? `字段 ${value.fieldIndex + 1}`, value.aggregation);
|
|
596
|
-
}
|
|
597
|
-
function defaultPivotValueCaption(fieldName, aggregation) {
|
|
598
|
-
return `${fieldName}(${spreadsheetPivotAggregationLabel(aggregation)})`;
|
|
599
|
-
}
|
|
600
|
-
function spreadsheetPivotAggregationLabel(aggregation) {
|
|
601
|
-
if ('count' === aggregation) return '计数';
|
|
602
|
-
if ('counta' === aggregation) return '非空计数';
|
|
603
|
-
if ('average' === aggregation) return '平均值';
|
|
604
|
-
if ('max' === aggregation) return '最大值';
|
|
605
|
-
if ('min' === aggregation) return '最小值';
|
|
606
|
-
if ('product' === aggregation) return '乘积';
|
|
607
|
-
if ('stdDev' === aggregation) return '样本标准差';
|
|
608
|
-
if ('stdDevP' === aggregation) return '总体标准差';
|
|
609
|
-
if ('var' === aggregation) return '样本方差';
|
|
610
|
-
if ('varP' === aggregation) return '总体方差';
|
|
611
|
-
return '求和';
|
|
612
|
-
}
|
|
613
|
-
function pivotHeaderCell(value) {
|
|
614
|
-
return {
|
|
615
|
-
v: value,
|
|
616
|
-
m: value,
|
|
617
|
-
bl: 1,
|
|
618
|
-
bg: '#e7f1ec',
|
|
619
|
-
fc: '#173d2b'
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function pivotDimensionCell(value) {
|
|
623
|
-
const display = displaySpreadsheetPivotValue(value);
|
|
624
|
-
return {
|
|
625
|
-
v: null == value || '' === value ? display : value,
|
|
626
|
-
m: display
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
function pivotValueCell(value, total) {
|
|
630
|
-
if (null === value) return total ? {
|
|
631
|
-
v: '',
|
|
632
|
-
m: '',
|
|
633
|
-
bl: 1,
|
|
634
|
-
bg: '#f3f7f5'
|
|
635
|
-
} : null;
|
|
636
|
-
return {
|
|
637
|
-
v: value,
|
|
638
|
-
m: formatPivotNumber(value),
|
|
639
|
-
bl: total ? 1 : void 0,
|
|
640
|
-
bg: total ? '#f3f7f5' : void 0,
|
|
641
|
-
ht: 2
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
function pivotTotalLabelCell() {
|
|
645
|
-
return {
|
|
646
|
-
v: '总计',
|
|
647
|
-
m: '总计',
|
|
648
|
-
bl: 1,
|
|
649
|
-
bg: '#f3f7f5'
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
function pivotFilterLabelCell(value) {
|
|
653
|
-
return {
|
|
654
|
-
v: value,
|
|
655
|
-
m: value,
|
|
656
|
-
bl: 1,
|
|
657
|
-
bg: '#f3f7f5',
|
|
658
|
-
fc: '#173d2b'
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
function pivotFilterSelectionCell(value) {
|
|
662
|
-
return {
|
|
663
|
-
v: value,
|
|
664
|
-
m: value,
|
|
665
|
-
bg: '#f3f7f5'
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
function formatPivotNumber(value) {
|
|
669
|
-
return Number.isInteger(value) ? String(value) : String(Number(value.toPrecision(15)));
|
|
670
|
-
}
|
|
671
|
-
function matchesPivotReportFilters(values, pivot) {
|
|
672
|
-
return (pivot.reportFilters ?? []).every((filter)=>void 0 === filter.selectedItem || spreadsheetPivotFilterValueKey(normalizeSpreadsheetPivotFilterValue(values[filter.fieldIndex])) === spreadsheetPivotFilterValueKey(filter.selectedItem));
|
|
673
|
-
}
|
|
674
|
-
const MAXIMUM_SOURCE_CELLS = 100000;
|
|
675
|
-
const MAXIMUM_OUTPUT_CELLS = 20000;
|
|
676
|
-
const MAXIMUM_XLSX_ROW = 1048575;
|
|
677
|
-
const MAXIMUM_XLSX_COLUMN = 16383;
|
|
678
|
-
function spreadsheetPivotCount(content) {
|
|
679
|
-
return content.sheets.reduce((count, sheet)=>count + (sheet.pivotTables?.length ?? 0), 0);
|
|
680
|
-
}
|
|
681
|
-
function spreadsheetPivotFields(content, pivot) {
|
|
682
|
-
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
683
|
-
const range = singleRange(pivot.sourceReference);
|
|
684
|
-
if (!sourceSheet || !range) return [];
|
|
685
|
-
const fields = [];
|
|
686
|
-
for(let column = range.startColumn; column <= range.endColumn; column += 1){
|
|
687
|
-
const header = spreadsheetPivotCellValue(sourceSheet.data?.[range.startRow]?.[column]);
|
|
688
|
-
if (null == header || '' === String(header).trim()) return [];
|
|
689
|
-
const name = displaySpreadsheetPivotValue(header).trim();
|
|
690
|
-
let numeric = false;
|
|
691
|
-
for(let row = range.startRow + 1; row <= range.endRow; row += 1){
|
|
692
|
-
const value = spreadsheetPivotCellValue(sourceSheet.data?.[row]?.[column]);
|
|
693
|
-
if (null != value && '' !== value) {
|
|
694
|
-
if (null !== finiteSpreadsheetPivotNumber(value)) {
|
|
695
|
-
numeric = true;
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
fields.push({
|
|
701
|
-
index: column - range.startColumn,
|
|
702
|
-
name,
|
|
703
|
-
numeric
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
return uniqueFieldNames(fields) ? fields : [];
|
|
514
|
+
return uniqueFieldNames(fields) ? fields : [];
|
|
707
515
|
}
|
|
708
516
|
function spreadsheetPivotFilterItems(content, pivot, fieldIndex) {
|
|
709
517
|
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
@@ -1038,64 +846,590 @@ function selectionBounds(selection) {
|
|
|
1038
846
|
endColumn: Math.max(0, Math.max(selection.column[0], selection.column[1]))
|
|
1039
847
|
};
|
|
1040
848
|
}
|
|
1041
|
-
function containsCell(bounds, row, column) {
|
|
1042
|
-
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
1043
|
-
}
|
|
1044
|
-
function rangesOverlap(left, right) {
|
|
1045
|
-
return left.startRow <= right.endRow && left.endRow >= right.startRow && left.startColumn <= right.endColumn && left.endColumn >= right.startColumn;
|
|
1046
|
-
}
|
|
1047
|
-
function formatBounds(bounds) {
|
|
1048
|
-
return formatSpreadsheetCellRanges([
|
|
1049
|
-
{
|
|
1050
|
-
row: [
|
|
1051
|
-
bounds.startRow,
|
|
1052
|
-
bounds.endRow
|
|
1053
|
-
],
|
|
1054
|
-
column: [
|
|
1055
|
-
bounds.startColumn,
|
|
1056
|
-
bounds.endColumn
|
|
1057
|
-
]
|
|
1058
|
-
}
|
|
1059
|
-
]);
|
|
849
|
+
function containsCell(bounds, row, column) {
|
|
850
|
+
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
851
|
+
}
|
|
852
|
+
function rangesOverlap(left, right) {
|
|
853
|
+
return left.startRow <= right.endRow && left.endRow >= right.startRow && left.startColumn <= right.endColumn && left.endColumn >= right.startColumn;
|
|
854
|
+
}
|
|
855
|
+
function formatBounds(bounds) {
|
|
856
|
+
return formatSpreadsheetCellRanges([
|
|
857
|
+
{
|
|
858
|
+
row: [
|
|
859
|
+
bounds.startRow,
|
|
860
|
+
bounds.endRow
|
|
861
|
+
],
|
|
862
|
+
column: [
|
|
863
|
+
bounds.startColumn,
|
|
864
|
+
bounds.endColumn
|
|
865
|
+
]
|
|
866
|
+
}
|
|
867
|
+
]);
|
|
868
|
+
}
|
|
869
|
+
function cloneCellMatrix(source) {
|
|
870
|
+
return (source ?? []).map((row)=>[
|
|
871
|
+
...row
|
|
872
|
+
]);
|
|
873
|
+
}
|
|
874
|
+
function clearRange(data, bounds) {
|
|
875
|
+
if (!bounds) return;
|
|
876
|
+
for(let row = bounds.startRow; row <= bounds.endRow; row += 1)if (data[row]) for(let column = bounds.startColumn; column <= bounds.endColumn; column += 1)data[row][column] = null;
|
|
877
|
+
}
|
|
878
|
+
function writePivotOutput(data, bounds, output) {
|
|
879
|
+
const width = output[0]?.length ?? 0;
|
|
880
|
+
while(data.length <= bounds.endRow)data.push([]);
|
|
881
|
+
for(let row = 0; row < output.length; row += 1){
|
|
882
|
+
const target = data[bounds.startRow + row] ?? [];
|
|
883
|
+
data[bounds.startRow + row] = target;
|
|
884
|
+
while(target.length < bounds.startColumn + width)target.push(null);
|
|
885
|
+
for(let column = 0; column < width; column += 1)target[bounds.startColumn + column] = output[row][column] ?? null;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function nextSheetName(content, base) {
|
|
889
|
+
const existing = new Set(content.sheets.map((sheet)=>sheet.name.toLocaleLowerCase()));
|
|
890
|
+
if (!existing.has(base.toLocaleLowerCase())) return base;
|
|
891
|
+
let index = 2;
|
|
892
|
+
while(existing.has(`${base} ${index}`.toLocaleLowerCase()))index += 1;
|
|
893
|
+
return `${base} ${index}`;
|
|
894
|
+
}
|
|
895
|
+
function nextPivotName(content) {
|
|
896
|
+
const existing = new Set(content.sheets.flatMap((sheet)=>(sheet.pivotTables ?? []).map((pivot)=>pivot.name.toLocaleLowerCase())));
|
|
897
|
+
let index = 1;
|
|
898
|
+
while(existing.has(`pivottable${index}`))index += 1;
|
|
899
|
+
return `PivotTable${index}`;
|
|
900
|
+
}
|
|
901
|
+
function invalid(code, message) {
|
|
902
|
+
return {
|
|
903
|
+
valid: false,
|
|
904
|
+
code,
|
|
905
|
+
message
|
|
906
|
+
};
|
|
907
|
+
}
|
|
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
|
+
const XLSX_EMU_PER_PIXEL = 9525;
|
|
1129
|
+
const DEFAULT_COLUMN_WIDTH = 96;
|
|
1130
|
+
const DEFAULT_ROW_HEIGHT = 24;
|
|
1131
|
+
function readXlsxDrawingAnchor(anchor) {
|
|
1132
|
+
const extent = directChild(anchor, 'ext');
|
|
1133
|
+
const position = directChild(anchor, 'pos');
|
|
1134
|
+
return {
|
|
1135
|
+
from: readMarker(directChild(anchor, 'from')),
|
|
1136
|
+
to: readMarker(directChild(anchor, 'to')),
|
|
1137
|
+
leftEmu: numberAttribute(position, 'x'),
|
|
1138
|
+
topEmu: numberAttribute(position, 'y'),
|
|
1139
|
+
widthEmu: numberAttribute(extent, 'cx'),
|
|
1140
|
+
heightEmu: numberAttribute(extent, 'cy')
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function xlsxDrawingAnchorToBounds(anchor, config) {
|
|
1144
|
+
const left = anchor.from ? markerPosition(anchor.from.column, anchor.from.columnOffsetEmu, (index)=>spreadsheetColumnWidth(config, index)) : emuToPixels(anchor.leftEmu);
|
|
1145
|
+
const top = anchor.from ? markerPosition(anchor.from.row, anchor.from.rowOffsetEmu, (index)=>spreadsheetRowHeight(config, index)) : emuToPixels(anchor.topEmu);
|
|
1146
|
+
const right = anchor.to ? markerPosition(anchor.to.column, anchor.to.columnOffsetEmu, (index)=>spreadsheetColumnWidth(config, index)) : left + emuToPixels(anchor.widthEmu);
|
|
1147
|
+
const bottom = anchor.to ? markerPosition(anchor.to.row, anchor.to.rowOffsetEmu, (index)=>spreadsheetRowHeight(config, index)) : top + emuToPixels(anchor.heightEmu);
|
|
1148
|
+
if (![
|
|
1149
|
+
left,
|
|
1150
|
+
top,
|
|
1151
|
+
right,
|
|
1152
|
+
bottom
|
|
1153
|
+
].every(Number.isFinite)) return null;
|
|
1154
|
+
return {
|
|
1155
|
+
left: roundPosition(Math.max(0, left)),
|
|
1156
|
+
top: roundPosition(Math.max(0, top)),
|
|
1157
|
+
width: roundPosition(Math.max(1, right - left)),
|
|
1158
|
+
height: roundPosition(Math.max(1, bottom - top))
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function xlsxTwoCellAnchorMarkers(bounds, sheet) {
|
|
1162
|
+
const fromColumn = axisMarker(bounds.left, (index)=>spreadsheetColumnWidth(sheet.config, index));
|
|
1163
|
+
const toColumn = axisMarker(bounds.left + bounds.width, (index)=>spreadsheetColumnWidth(sheet.config, index));
|
|
1164
|
+
const fromRow = axisMarker(bounds.top, (index)=>spreadsheetRowHeight(sheet.config, index));
|
|
1165
|
+
const toRow = axisMarker(bounds.top + bounds.height, (index)=>spreadsheetRowHeight(sheet.config, index));
|
|
1166
|
+
return `${markerXml('from', fromColumn, fromRow)}${markerXml('to', toColumn, toRow)}`;
|
|
1167
|
+
}
|
|
1168
|
+
function spreadsheetColumnWidth(config, index) {
|
|
1169
|
+
const key = String(index);
|
|
1170
|
+
if (Object.hasOwn(config?.colhidden ?? {}, key)) return 0;
|
|
1171
|
+
const width = config?.columnlen?.[key];
|
|
1172
|
+
return 'number' == typeof width && width > 0 ? width : DEFAULT_COLUMN_WIDTH;
|
|
1173
|
+
}
|
|
1174
|
+
function spreadsheetRowHeight(config, index) {
|
|
1175
|
+
const key = String(index);
|
|
1176
|
+
if (Object.hasOwn(config?.rowhidden ?? {}, key)) return 0;
|
|
1177
|
+
const height = config?.rowlen?.[key];
|
|
1178
|
+
return 'number' == typeof height && height > 0 ? height : DEFAULT_ROW_HEIGHT;
|
|
1179
|
+
}
|
|
1180
|
+
function readMarker(element) {
|
|
1181
|
+
if (!element) return;
|
|
1182
|
+
return {
|
|
1183
|
+
column: nonNegativeInteger(directChild(element, 'col')?.textContent),
|
|
1184
|
+
row: nonNegativeInteger(directChild(element, 'row')?.textContent),
|
|
1185
|
+
columnOffsetEmu: nonNegativeNumber(directChild(element, 'colOff')?.textContent),
|
|
1186
|
+
rowOffsetEmu: nonNegativeNumber(directChild(element, 'rowOff')?.textContent)
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function numberAttribute(element, name) {
|
|
1190
|
+
if (!element) return;
|
|
1191
|
+
const value = Number(attribute(element, name));
|
|
1192
|
+
return Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1193
|
+
}
|
|
1194
|
+
function nonNegativeInteger(value) {
|
|
1195
|
+
const parsed = Number(value);
|
|
1196
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
1197
|
+
}
|
|
1198
|
+
function nonNegativeNumber(value) {
|
|
1199
|
+
const parsed = Number(value);
|
|
1200
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
1201
|
+
}
|
|
1202
|
+
function markerPosition(index, offsetEmu, size) {
|
|
1203
|
+
let position = 0;
|
|
1204
|
+
for(let current = 0; current < index; current += 1)position += size(current);
|
|
1205
|
+
return position + emuToPixels(offsetEmu);
|
|
1206
|
+
}
|
|
1207
|
+
function emuToPixels(value) {
|
|
1208
|
+
return (value ?? 0) / XLSX_EMU_PER_PIXEL;
|
|
1209
|
+
}
|
|
1210
|
+
function axisMarker(position, size) {
|
|
1211
|
+
let remaining = Math.max(0, Number.isFinite(position) ? position : 0);
|
|
1212
|
+
let index = 0;
|
|
1213
|
+
while(index < 1048576){
|
|
1214
|
+
const currentSize = Math.max(0, size(index));
|
|
1215
|
+
if (currentSize > 0 && remaining < currentSize) break;
|
|
1216
|
+
if (currentSize > 0) remaining -= currentSize;
|
|
1217
|
+
index += 1;
|
|
1218
|
+
}
|
|
1219
|
+
return {
|
|
1220
|
+
index,
|
|
1221
|
+
offsetEmu: Math.round(remaining * XLSX_EMU_PER_PIXEL)
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function markerXml(kind, column, row) {
|
|
1225
|
+
return [
|
|
1226
|
+
`<xdr:${kind}>`,
|
|
1227
|
+
`<xdr:col>${column.index}</xdr:col><xdr:colOff>${column.offsetEmu}</xdr:colOff>`,
|
|
1228
|
+
`<xdr:row>${row.index}</xdr:row><xdr:rowOff>${row.offsetEmu}</xdr:rowOff>`,
|
|
1229
|
+
`</xdr:${kind}>`
|
|
1230
|
+
].join('');
|
|
1231
|
+
}
|
|
1232
|
+
function roundPosition(value) {
|
|
1233
|
+
return Math.round(1000 * value) / 1000;
|
|
1234
|
+
}
|
|
1235
|
+
function normalizeWorkSpreadsheetDoughnutHoleSize(value) {
|
|
1236
|
+
const size = Number(value);
|
|
1237
|
+
if (!Number.isFinite(size)) return 50;
|
|
1238
|
+
return Math.min(90, Math.max(10, Math.round(size)));
|
|
1239
|
+
}
|
|
1240
|
+
function normalizeWorkSpreadsheetRadarStyle(value) {
|
|
1241
|
+
return 'standard' === value || 'filled' === value || 'marker' === value ? value : 'marker';
|
|
1242
|
+
}
|
|
1243
|
+
function normalizeWorkSpreadsheetScatterStyle(value) {
|
|
1244
|
+
return 'line' === value || 'lineMarker' === value || 'smooth' === value || 'smoothMarker' === value || 'marker' === value ? value : 'marker';
|
|
1245
|
+
}
|
|
1246
|
+
function normalizeWorkSpreadsheetBubbleScale(value) {
|
|
1247
|
+
const scale = Number(value);
|
|
1248
|
+
if (!Number.isFinite(scale)) return 100;
|
|
1249
|
+
return Math.min(300, Math.max(0, Math.round(scale)));
|
|
1250
|
+
}
|
|
1251
|
+
function normalizeWorkSpreadsheetBubbleSizeRepresents(value) {
|
|
1252
|
+
return 'width' === value || 'w' === value ? 'width' : 'area';
|
|
1253
|
+
}
|
|
1254
|
+
function workSpreadsheetChartUsesNumericXAxis(type) {
|
|
1255
|
+
return 'scatter' === type || 'bubble' === type;
|
|
1256
|
+
}
|
|
1257
|
+
function workSpreadsheetChartSupportsAxes(type) {
|
|
1258
|
+
return 'pie' !== type && 'doughnut' !== type;
|
|
1259
|
+
}
|
|
1260
|
+
function normalizeWorkSpreadsheetCombinationSeriesType(value) {
|
|
1261
|
+
return 'line' === value || 'area' === value || 'column' === value ? value : 'column';
|
|
1262
|
+
}
|
|
1263
|
+
function normalizeWorkSpreadsheetChartAxisGroup(value) {
|
|
1264
|
+
return 'secondary' === value ? 'secondary' : 'primary';
|
|
1265
|
+
}
|
|
1266
|
+
function workSpreadsheetCombinationSeriesTypeLabel(type) {
|
|
1267
|
+
if ('line' === type) return '折线图';
|
|
1268
|
+
if ('area' === type) return '面积图';
|
|
1269
|
+
return '柱形图';
|
|
1270
|
+
}
|
|
1271
|
+
function workSpreadsheetChartSupportsTrendlines(type) {
|
|
1272
|
+
return 'pie' !== type && 'doughnut' !== type && 'radar' !== type;
|
|
1273
|
+
}
|
|
1274
|
+
function workSpreadsheetChartSupportsErrorBars(type) {
|
|
1275
|
+
return 'pie' !== type && 'doughnut' !== type && 'radar' !== type;
|
|
1276
|
+
}
|
|
1277
|
+
function normalizeWorkSpreadsheetErrorBars(source, chartType) {
|
|
1278
|
+
const direction = workSpreadsheetChartUsesNumericXAxis(chartType) && 'x' === source.direction ? 'x' : 'y';
|
|
1279
|
+
const barType = 'plus' === source.barType || 'minus' === source.barType ? source.barType : 'both';
|
|
1280
|
+
const valueType = 'percentage' === source.valueType || 'standardDeviation' === source.valueType || 'standardError' === source.valueType || 'custom' === source.valueType || 'fixedValue' === source.valueType ? source.valueType : 'fixedValue';
|
|
1281
|
+
const fallbackValue = 'percentage' === valueType ? 5 : 1;
|
|
1282
|
+
const numericValue = Number(source.value);
|
|
1283
|
+
const value = Number.isFinite(numericValue) ? Math.max(0, numericValue) : fallbackValue;
|
|
1284
|
+
const plusValues = source.plusValues?.map(normalizedErrorBarAmount);
|
|
1285
|
+
const minusValues = source.minusValues?.map(normalizedErrorBarAmount);
|
|
1286
|
+
return {
|
|
1287
|
+
direction,
|
|
1288
|
+
barType,
|
|
1289
|
+
valueType,
|
|
1290
|
+
...'fixedValue' === valueType || 'percentage' === valueType || 'standardDeviation' === valueType ? {
|
|
1291
|
+
value
|
|
1292
|
+
} : {},
|
|
1293
|
+
...false === source.showEndCaps ? {
|
|
1294
|
+
showEndCaps: false
|
|
1295
|
+
} : {},
|
|
1296
|
+
...'custom' === valueType && 'minus' !== barType && plusValues?.length ? {
|
|
1297
|
+
plusValues
|
|
1298
|
+
} : {},
|
|
1299
|
+
...'custom' === valueType && 'minus' !== barType && source.plusReference?.trim() ? {
|
|
1300
|
+
plusReference: source.plusReference.trim().replace(/^=/, '')
|
|
1301
|
+
} : {},
|
|
1302
|
+
...'custom' === valueType && 'plus' !== barType && minusValues?.length ? {
|
|
1303
|
+
minusValues
|
|
1304
|
+
} : {},
|
|
1305
|
+
...'custom' === valueType && 'plus' !== barType && source.minusReference?.trim() ? {
|
|
1306
|
+
minusReference: source.minusReference.trim().replace(/^=/, '')
|
|
1307
|
+
} : {}
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function workSpreadsheetErrorBarValueTypeLabel(type) {
|
|
1311
|
+
if ('percentage' === type) return '百分比';
|
|
1312
|
+
if ('standardDeviation' === type) return '标准差';
|
|
1313
|
+
if ('standardError' === type) return '标准误差';
|
|
1314
|
+
if ('custom' === type) return '自定义';
|
|
1315
|
+
return '固定值';
|
|
1316
|
+
}
|
|
1317
|
+
function workSpreadsheetErrorBarTypeLabel(type) {
|
|
1318
|
+
if ('plus' === type) return '正向';
|
|
1319
|
+
if ('minus' === type) return '负向';
|
|
1320
|
+
return '双向';
|
|
1321
|
+
}
|
|
1322
|
+
function normalizedErrorBarAmount(value) {
|
|
1323
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
1324
|
+
}
|
|
1325
|
+
function normalizeWorkSpreadsheetDataLabelPosition(value) {
|
|
1326
|
+
return 'center' === value || 'insideBase' === value || 'insideEnd' === value || 'outsideEnd' === value || 'left' === value || 'right' === value || 'above' === value || 'below' === value || 'bestFit' === value ? value : 'bestFit';
|
|
1327
|
+
}
|
|
1328
|
+
function normalizeWorkSpreadsheetDataLabels(source, chartType) {
|
|
1329
|
+
const position = source.position ? normalizeWorkSpreadsheetDataLabelPosition(source.position) : void 0;
|
|
1330
|
+
const separator = 'string' == typeof source.separator ? source.separator.slice(0, 64) : void 0;
|
|
1331
|
+
return {
|
|
1332
|
+
...true === source.showValue ? {
|
|
1333
|
+
showValue: true
|
|
1334
|
+
} : {},
|
|
1335
|
+
...true === source.showCategoryName ? {
|
|
1336
|
+
showCategoryName: true
|
|
1337
|
+
} : {},
|
|
1338
|
+
...true === source.showSeriesName ? {
|
|
1339
|
+
showSeriesName: true
|
|
1340
|
+
} : {},
|
|
1341
|
+
...true === source.showPercentage && ('pie' === chartType || 'doughnut' === chartType) ? {
|
|
1342
|
+
showPercentage: true
|
|
1343
|
+
} : {},
|
|
1344
|
+
...true === source.showBubbleSize && 'bubble' === chartType ? {
|
|
1345
|
+
showBubbleSize: true
|
|
1346
|
+
} : {},
|
|
1347
|
+
...void 0 !== separator ? {
|
|
1348
|
+
separator
|
|
1349
|
+
} : {},
|
|
1350
|
+
...position ? {
|
|
1351
|
+
position
|
|
1352
|
+
} : {}
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
function workSpreadsheetDataLabelPositionLabel(position) {
|
|
1356
|
+
if ('center' === position) return '居中';
|
|
1357
|
+
if ('insideBase' === position) return '内侧基部';
|
|
1358
|
+
if ('insideEnd' === position) return '内侧末端';
|
|
1359
|
+
if ('outsideEnd' === position) return '外侧末端';
|
|
1360
|
+
if ('left' === position) return '左侧';
|
|
1361
|
+
if ('right' === position) return '右侧';
|
|
1362
|
+
if ('above' === position) return '上方';
|
|
1363
|
+
if ('below' === position) return '下方';
|
|
1364
|
+
return '最佳匹配';
|
|
1060
1365
|
}
|
|
1061
|
-
function
|
|
1062
|
-
return
|
|
1063
|
-
...row
|
|
1064
|
-
]);
|
|
1366
|
+
function normalizeWorkSpreadsheetTrendlineType(value) {
|
|
1367
|
+
return 'exponential' === value || 'logarithmic' === value || 'polynomial' === value || 'power' === value || 'movingAverage' === value || 'linear' === value ? value : 'linear';
|
|
1065
1368
|
}
|
|
1066
|
-
function
|
|
1067
|
-
|
|
1068
|
-
|
|
1369
|
+
function normalizeWorkSpreadsheetTrendline(trendline) {
|
|
1370
|
+
const type = normalizeWorkSpreadsheetTrendlineType(trendline.type);
|
|
1371
|
+
const name = trendline.name?.trim().slice(0, 255);
|
|
1372
|
+
const order = normalizedInteger(trendline.order, 2, 6, 2);
|
|
1373
|
+
const period = normalizedInteger(trendline.period, 2, 255, 2);
|
|
1374
|
+
const forward = normalizedNonNegativeNumber(trendline.forward);
|
|
1375
|
+
const backward = normalizedNonNegativeNumber(trendline.backward);
|
|
1376
|
+
const intercept = Number.isFinite(trendline.intercept) ? Number(trendline.intercept) : void 0;
|
|
1377
|
+
return {
|
|
1378
|
+
type,
|
|
1379
|
+
...name ? {
|
|
1380
|
+
name
|
|
1381
|
+
} : {},
|
|
1382
|
+
...'polynomial' === type ? {
|
|
1383
|
+
order
|
|
1384
|
+
} : {},
|
|
1385
|
+
...'movingAverage' === type ? {
|
|
1386
|
+
period
|
|
1387
|
+
} : {},
|
|
1388
|
+
...forward > 0 ? {
|
|
1389
|
+
forward
|
|
1390
|
+
} : {},
|
|
1391
|
+
...backward > 0 ? {
|
|
1392
|
+
backward
|
|
1393
|
+
} : {},
|
|
1394
|
+
...void 0 !== intercept ? {
|
|
1395
|
+
intercept
|
|
1396
|
+
} : {},
|
|
1397
|
+
...true === trendline.displayEquation ? {
|
|
1398
|
+
displayEquation: true
|
|
1399
|
+
} : {},
|
|
1400
|
+
...true === trendline.displayRSquared ? {
|
|
1401
|
+
displayRSquared: true
|
|
1402
|
+
} : {}
|
|
1403
|
+
};
|
|
1069
1404
|
}
|
|
1070
|
-
function
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
for(let column = 0; column < width; column += 1)target[bounds.startColumn + column] = output[row][column] ?? null;
|
|
1078
|
-
}
|
|
1405
|
+
function workSpreadsheetTrendlineTypeLabel(type) {
|
|
1406
|
+
if ('exponential' === type) return '指数';
|
|
1407
|
+
if ('logarithmic' === type) return '对数';
|
|
1408
|
+
if ('polynomial' === type) return '多项式';
|
|
1409
|
+
if ('power' === type) return '幂';
|
|
1410
|
+
if ('movingAverage' === type) return '移动平均';
|
|
1411
|
+
return '线性';
|
|
1079
1412
|
}
|
|
1080
|
-
function
|
|
1081
|
-
const
|
|
1082
|
-
if (!
|
|
1083
|
-
|
|
1084
|
-
while(existing.has(`${base} ${index}`.toLocaleLowerCase()))index += 1;
|
|
1085
|
-
return `${base} ${index}`;
|
|
1413
|
+
function normalizedInteger(value, minimum, maximum, fallback) {
|
|
1414
|
+
const number = Number(value);
|
|
1415
|
+
if (!Number.isFinite(number)) return fallback;
|
|
1416
|
+
return Math.min(maximum, Math.max(minimum, Math.round(number)));
|
|
1086
1417
|
}
|
|
1087
|
-
function
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
while(existing.has(`pivottable${index}`))index += 1;
|
|
1091
|
-
return `PivotTable${index}`;
|
|
1418
|
+
function normalizedNonNegativeNumber(value) {
|
|
1419
|
+
const number = Number(value);
|
|
1420
|
+
return Number.isFinite(number) ? Math.max(0, number) : 0;
|
|
1092
1421
|
}
|
|
1093
|
-
function
|
|
1094
|
-
return
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1422
|
+
function workSpreadsheetChartTypeLabel(type) {
|
|
1423
|
+
if ('bar' === type) return '条形图';
|
|
1424
|
+
if ('line' === type) return '折线图';
|
|
1425
|
+
if ('pie' === type) return '饼图';
|
|
1426
|
+
if ('doughnut' === type) return '圆环图';
|
|
1427
|
+
if ('area' === type) return '面积图';
|
|
1428
|
+
if ('radar' === type) return '雷达图';
|
|
1429
|
+
if ('scatter' === type) return '散点图';
|
|
1430
|
+
if ('bubble' === type) return '气泡图';
|
|
1431
|
+
if ('combination' === type) return '组合图';
|
|
1432
|
+
return '柱形图';
|
|
1099
1433
|
}
|
|
1100
1434
|
const WORK_SPREADSHEET_CHART_COLORS = [
|
|
1101
1435
|
'#4f6bed',
|
|
@@ -3121,142 +3455,35 @@ function horizontalBarChartSvg(chart, plot, layout, values) {
|
|
|
3121
3455
|
value: point.rawValue
|
|
3122
3456
|
})}`;
|
|
3123
3457
|
}).join('');
|
|
3124
|
-
const errorBars = layout.stacked ? '' : spreadsheetSeriesErrorBarsSvg(chart, seriesIndex, (_direction, pointIndex, value)=>[
|
|
3125
|
-
valuePosition(value),
|
|
3126
|
-
seriesY(pointIndex) + renderedHeight / 2
|
|
3127
|
-
]);
|
|
3128
|
-
return `${seriesBars}${errorBars}`;
|
|
3129
|
-
}).join('');
|
|
3130
|
-
const trendlines = layout.stacked ? '' : chart.series.map((series, seriesIndex)=>spreadsheetSeriesTrendlinesSvg(series, seriesIndex, categoryTrendlineXValues(series), (category, value)=>[
|
|
3131
|
-
valuePosition(value),
|
|
3132
|
-
plot.y + (spreadsheetChartCategoryVisualIndex(category - 1, categoryCount, categoryAxis) + 0.5) / categoryCount * plot.height
|
|
3133
|
-
])).join('');
|
|
3134
|
-
return `<g ${spreadsheetChartAxisScaleAttributes(axis, scale, chart.type, 'bottom')}>${grid}<line x1="${roundChartNumber(zero)}" y1="${plot.y}" x2="${roundChartNumber(zero)}" y2="${roundChartNumber(plot.y + plot.height)}" stroke="#aeb6c4" stroke-width="1.2"/></g><g ${spreadsheetChartAxisDisplayAttributes(categoryAxis, chart.type, 'left')}>${labels}</g>${bars}${trendlines}`;
|
|
3135
|
-
}
|
|
3136
|
-
function categoryLabelsSvg(chart, plot, axisY) {
|
|
3137
|
-
const categoryCount = Math.max(1, chart.categories.length, ...chart.series.map((series)=>series.values.length));
|
|
3138
|
-
const axis = chart.axes?.bottom;
|
|
3139
|
-
const labels = Array.from({
|
|
3140
|
-
length: categoryCount
|
|
3141
|
-
}, (_, index)=>{
|
|
3142
|
-
const visible = spreadsheetChartCategoryLabelVisible(index, categoryCount, axis);
|
|
3143
|
-
const label = chart.categories[index] ?? String(index + 1);
|
|
3144
|
-
const visualIndex = spreadsheetChartCategoryVisualIndex(index, categoryCount, axis);
|
|
3145
|
-
const x = plot.x + (visualIndex + 0.5) / categoryCount * plot.width;
|
|
3146
|
-
const labelLayout = spreadsheetChartAxisLabelLayout(axis, chart.type, 'bottom', plot, x, axisY);
|
|
3147
|
-
return `${spreadsheetChartMajorTickSvg(axis, 'bottom', index, x, axisY)}${visible && labelLayout ? `<text data-axis-category-label="${index}" x="${roundChartNumber(labelLayout.x)}" y="${roundChartNumber(labelLayout.y)}" fill="#536078" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="10" text-anchor="${labelLayout.textAnchor}">${escapeChartXml(truncateChartText(label, 13))}</text>` : ''}`;
|
|
3148
|
-
}).join('');
|
|
3149
|
-
return `<g ${spreadsheetChartAxisDisplayAttributes(axis, chart.type, 'bottom')}>${labels}</g>`;
|
|
3150
|
-
}
|
|
3151
|
-
function emptyChartSvg(plot, message) {
|
|
3152
|
-
return `<text x="${roundChartNumber(plot.x + plot.width / 2)}" y="${roundChartNumber(plot.y + plot.height / 2)}" fill="#8791a3" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" text-anchor="middle">${escapeChartXml(message)}</text>`;
|
|
3153
|
-
}
|
|
3154
|
-
const XLSX_EMU_PER_PIXEL = 9525;
|
|
3155
|
-
const DEFAULT_COLUMN_WIDTH = 96;
|
|
3156
|
-
const DEFAULT_ROW_HEIGHT = 24;
|
|
3157
|
-
function readXlsxDrawingAnchor(anchor) {
|
|
3158
|
-
const extent = directChild(anchor, 'ext');
|
|
3159
|
-
const position = directChild(anchor, 'pos');
|
|
3160
|
-
return {
|
|
3161
|
-
from: readMarker(directChild(anchor, 'from')),
|
|
3162
|
-
to: readMarker(directChild(anchor, 'to')),
|
|
3163
|
-
leftEmu: numberAttribute(position, 'x'),
|
|
3164
|
-
topEmu: numberAttribute(position, 'y'),
|
|
3165
|
-
widthEmu: numberAttribute(extent, 'cx'),
|
|
3166
|
-
heightEmu: numberAttribute(extent, 'cy')
|
|
3167
|
-
};
|
|
3168
|
-
}
|
|
3169
|
-
function xlsxDrawingAnchorToBounds(anchor, config) {
|
|
3170
|
-
const left = anchor.from ? markerPosition(anchor.from.column, anchor.from.columnOffsetEmu, (index)=>spreadsheetColumnWidth(config, index)) : emuToPixels(anchor.leftEmu);
|
|
3171
|
-
const top = anchor.from ? markerPosition(anchor.from.row, anchor.from.rowOffsetEmu, (index)=>spreadsheetRowHeight(config, index)) : emuToPixels(anchor.topEmu);
|
|
3172
|
-
const right = anchor.to ? markerPosition(anchor.to.column, anchor.to.columnOffsetEmu, (index)=>spreadsheetColumnWidth(config, index)) : left + emuToPixels(anchor.widthEmu);
|
|
3173
|
-
const bottom = anchor.to ? markerPosition(anchor.to.row, anchor.to.rowOffsetEmu, (index)=>spreadsheetRowHeight(config, index)) : top + emuToPixels(anchor.heightEmu);
|
|
3174
|
-
if (![
|
|
3175
|
-
left,
|
|
3176
|
-
top,
|
|
3177
|
-
right,
|
|
3178
|
-
bottom
|
|
3179
|
-
].every(Number.isFinite)) return null;
|
|
3180
|
-
return {
|
|
3181
|
-
left: roundPosition(Math.max(0, left)),
|
|
3182
|
-
top: roundPosition(Math.max(0, top)),
|
|
3183
|
-
width: roundPosition(Math.max(1, right - left)),
|
|
3184
|
-
height: roundPosition(Math.max(1, bottom - top))
|
|
3185
|
-
};
|
|
3186
|
-
}
|
|
3187
|
-
function xlsxTwoCellAnchorMarkers(bounds, sheet) {
|
|
3188
|
-
const fromColumn = axisMarker(bounds.left, (index)=>spreadsheetColumnWidth(sheet.config, index));
|
|
3189
|
-
const toColumn = axisMarker(bounds.left + bounds.width, (index)=>spreadsheetColumnWidth(sheet.config, index));
|
|
3190
|
-
const fromRow = axisMarker(bounds.top, (index)=>spreadsheetRowHeight(sheet.config, index));
|
|
3191
|
-
const toRow = axisMarker(bounds.top + bounds.height, (index)=>spreadsheetRowHeight(sheet.config, index));
|
|
3192
|
-
return `${markerXml('from', fromColumn, fromRow)}${markerXml('to', toColumn, toRow)}`;
|
|
3193
|
-
}
|
|
3194
|
-
function spreadsheetColumnWidth(config, index) {
|
|
3195
|
-
const key = String(index);
|
|
3196
|
-
if (Object.hasOwn(config?.colhidden ?? {}, key)) return 0;
|
|
3197
|
-
const width = config?.columnlen?.[key];
|
|
3198
|
-
return 'number' == typeof width && width > 0 ? width : DEFAULT_COLUMN_WIDTH;
|
|
3199
|
-
}
|
|
3200
|
-
function spreadsheetRowHeight(config, index) {
|
|
3201
|
-
const key = String(index);
|
|
3202
|
-
if (Object.hasOwn(config?.rowhidden ?? {}, key)) return 0;
|
|
3203
|
-
const height = config?.rowlen?.[key];
|
|
3204
|
-
return 'number' == typeof height && height > 0 ? height : DEFAULT_ROW_HEIGHT;
|
|
3205
|
-
}
|
|
3206
|
-
function readMarker(element) {
|
|
3207
|
-
if (!element) return;
|
|
3208
|
-
return {
|
|
3209
|
-
column: nonNegativeInteger(directChild(element, 'col')?.textContent),
|
|
3210
|
-
row: nonNegativeInteger(directChild(element, 'row')?.textContent),
|
|
3211
|
-
columnOffsetEmu: nonNegativeNumber(directChild(element, 'colOff')?.textContent),
|
|
3212
|
-
rowOffsetEmu: nonNegativeNumber(directChild(element, 'rowOff')?.textContent)
|
|
3213
|
-
};
|
|
3214
|
-
}
|
|
3215
|
-
function numberAttribute(element, name) {
|
|
3216
|
-
if (!element) return;
|
|
3217
|
-
const value = Number(attribute(element, name));
|
|
3218
|
-
return Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
3219
|
-
}
|
|
3220
|
-
function nonNegativeInteger(value) {
|
|
3221
|
-
const parsed = Number(value);
|
|
3222
|
-
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
3223
|
-
}
|
|
3224
|
-
function nonNegativeNumber(value) {
|
|
3225
|
-
const parsed = Number(value);
|
|
3226
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
3227
|
-
}
|
|
3228
|
-
function markerPosition(index, offsetEmu, size) {
|
|
3229
|
-
let position = 0;
|
|
3230
|
-
for(let current = 0; current < index; current += 1)position += size(current);
|
|
3231
|
-
return position + emuToPixels(offsetEmu);
|
|
3232
|
-
}
|
|
3233
|
-
function emuToPixels(value) {
|
|
3234
|
-
return (value ?? 0) / XLSX_EMU_PER_PIXEL;
|
|
3235
|
-
}
|
|
3236
|
-
function axisMarker(position, size) {
|
|
3237
|
-
let remaining = Math.max(0, Number.isFinite(position) ? position : 0);
|
|
3238
|
-
let index = 0;
|
|
3239
|
-
while(index < 1048576){
|
|
3240
|
-
const currentSize = Math.max(0, size(index));
|
|
3241
|
-
if (currentSize > 0 && remaining < currentSize) break;
|
|
3242
|
-
if (currentSize > 0) remaining -= currentSize;
|
|
3243
|
-
index += 1;
|
|
3244
|
-
}
|
|
3245
|
-
return {
|
|
3246
|
-
index,
|
|
3247
|
-
offsetEmu: Math.round(remaining * XLSX_EMU_PER_PIXEL)
|
|
3248
|
-
};
|
|
3458
|
+
const errorBars = layout.stacked ? '' : spreadsheetSeriesErrorBarsSvg(chart, seriesIndex, (_direction, pointIndex, value)=>[
|
|
3459
|
+
valuePosition(value),
|
|
3460
|
+
seriesY(pointIndex) + renderedHeight / 2
|
|
3461
|
+
]);
|
|
3462
|
+
return `${seriesBars}${errorBars}`;
|
|
3463
|
+
}).join('');
|
|
3464
|
+
const trendlines = layout.stacked ? '' : chart.series.map((series, seriesIndex)=>spreadsheetSeriesTrendlinesSvg(series, seriesIndex, categoryTrendlineXValues(series), (category, value)=>[
|
|
3465
|
+
valuePosition(value),
|
|
3466
|
+
plot.y + (spreadsheetChartCategoryVisualIndex(category - 1, categoryCount, categoryAxis) + 0.5) / categoryCount * plot.height
|
|
3467
|
+
])).join('');
|
|
3468
|
+
return `<g ${spreadsheetChartAxisScaleAttributes(axis, scale, chart.type, 'bottom')}>${grid}<line x1="${roundChartNumber(zero)}" y1="${plot.y}" x2="${roundChartNumber(zero)}" y2="${roundChartNumber(plot.y + plot.height)}" stroke="#aeb6c4" stroke-width="1.2"/></g><g ${spreadsheetChartAxisDisplayAttributes(categoryAxis, chart.type, 'left')}>${labels}</g>${bars}${trendlines}`;
|
|
3249
3469
|
}
|
|
3250
|
-
function
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3470
|
+
function categoryLabelsSvg(chart, plot, axisY) {
|
|
3471
|
+
const categoryCount = Math.max(1, chart.categories.length, ...chart.series.map((series)=>series.values.length));
|
|
3472
|
+
const axis = chart.axes?.bottom;
|
|
3473
|
+
const labels = Array.from({
|
|
3474
|
+
length: categoryCount
|
|
3475
|
+
}, (_, index)=>{
|
|
3476
|
+
const visible = spreadsheetChartCategoryLabelVisible(index, categoryCount, axis);
|
|
3477
|
+
const label = chart.categories[index] ?? String(index + 1);
|
|
3478
|
+
const visualIndex = spreadsheetChartCategoryVisualIndex(index, categoryCount, axis);
|
|
3479
|
+
const x = plot.x + (visualIndex + 0.5) / categoryCount * plot.width;
|
|
3480
|
+
const labelLayout = spreadsheetChartAxisLabelLayout(axis, chart.type, 'bottom', plot, x, axisY);
|
|
3481
|
+
return `${spreadsheetChartMajorTickSvg(axis, 'bottom', index, x, axisY)}${visible && labelLayout ? `<text data-axis-category-label="${index}" x="${roundChartNumber(labelLayout.x)}" y="${roundChartNumber(labelLayout.y)}" fill="#536078" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="10" text-anchor="${labelLayout.textAnchor}">${escapeChartXml(truncateChartText(label, 13))}</text>` : ''}`;
|
|
3482
|
+
}).join('');
|
|
3483
|
+
return `<g ${spreadsheetChartAxisDisplayAttributes(axis, chart.type, 'bottom')}>${labels}</g>`;
|
|
3257
3484
|
}
|
|
3258
|
-
function
|
|
3259
|
-
return
|
|
3485
|
+
function emptyChartSvg(plot, message) {
|
|
3486
|
+
return `<text x="${roundChartNumber(plot.x + plot.width / 2)}" y="${roundChartNumber(plot.y + plot.height / 2)}" fill="#8791a3" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" text-anchor="middle">${escapeChartXml(message)}</text>`;
|
|
3260
3487
|
}
|
|
3261
3488
|
const CHART_PREVIEW_IMAGE_PREFIX = 'work-chart-preview-';
|
|
3262
3489
|
function spreadsheetChartCount(content) {
|
|
@@ -3640,224 +3867,4 @@ function isNumericValue(value) {
|
|
|
3640
3867
|
function finiteDimension(value, fallback, minimum = 0) {
|
|
3641
3868
|
return Number.isFinite(value) ? Math.max(minimum, value) : fallback;
|
|
3642
3869
|
}
|
|
3643
|
-
|
|
3644
|
-
mode: 'automatic',
|
|
3645
|
-
fullCalculationOnLoad: false,
|
|
3646
|
-
forceFullCalculation: false,
|
|
3647
|
-
iterativeCalculation: false,
|
|
3648
|
-
maximumIterations: 100,
|
|
3649
|
-
maximumChange: 0.001,
|
|
3650
|
-
fullPrecision: true
|
|
3651
|
-
};
|
|
3652
|
-
const VOLATILE_FUNCTIONS = new Set([
|
|
3653
|
-
'CELL',
|
|
3654
|
-
'INFO',
|
|
3655
|
-
'INDIRECT',
|
|
3656
|
-
'NOW',
|
|
3657
|
-
'OFFSET',
|
|
3658
|
-
'RAND',
|
|
3659
|
-
'RANDBETWEEN',
|
|
3660
|
-
'TODAY'
|
|
3661
|
-
]);
|
|
3662
|
-
const FUTURE_FUNCTIONS = new Set([
|
|
3663
|
-
'ARRAYTOTEXT',
|
|
3664
|
-
'BYCOL',
|
|
3665
|
-
'BYROW',
|
|
3666
|
-
'CHOOSECOLS',
|
|
3667
|
-
'CHOOSEROWS',
|
|
3668
|
-
'DROP',
|
|
3669
|
-
'EXPAND',
|
|
3670
|
-
'FIELDVALUE',
|
|
3671
|
-
'FILTER',
|
|
3672
|
-
'HSTACK',
|
|
3673
|
-
'IMAGE',
|
|
3674
|
-
'ISOMITTED',
|
|
3675
|
-
'LAMBDA',
|
|
3676
|
-
'LET',
|
|
3677
|
-
'MAKEARRAY',
|
|
3678
|
-
'MAP',
|
|
3679
|
-
'RANDARRAY',
|
|
3680
|
-
'REDUCE',
|
|
3681
|
-
'SCAN',
|
|
3682
|
-
'SEQUENCE',
|
|
3683
|
-
'SORT',
|
|
3684
|
-
'SORTBY',
|
|
3685
|
-
'STOCKHISTORY',
|
|
3686
|
-
'TAKE',
|
|
3687
|
-
'TEXTAFTER',
|
|
3688
|
-
'TEXTBEFORE',
|
|
3689
|
-
'TEXTSPLIT',
|
|
3690
|
-
'TOCOL',
|
|
3691
|
-
'TOROW',
|
|
3692
|
-
'UNIQUE',
|
|
3693
|
-
'VALUETOTEXT',
|
|
3694
|
-
'VSTACK',
|
|
3695
|
-
'WRAPCOLS',
|
|
3696
|
-
'WRAPROWS',
|
|
3697
|
-
'XLOOKUP',
|
|
3698
|
-
'XMATCH'
|
|
3699
|
-
]);
|
|
3700
|
-
const FUTURE_FUNCTION_PREFIXES = new Map([
|
|
3701
|
-
[
|
|
3702
|
-
'FILTER',
|
|
3703
|
-
'_xlfn._xlws.'
|
|
3704
|
-
],
|
|
3705
|
-
[
|
|
3706
|
-
'SORT',
|
|
3707
|
-
'_xlfn._xlws.'
|
|
3708
|
-
],
|
|
3709
|
-
[
|
|
3710
|
-
'SORTBY',
|
|
3711
|
-
'_xlfn._xlws.'
|
|
3712
|
-
],
|
|
3713
|
-
[
|
|
3714
|
-
'UNIQUE',
|
|
3715
|
-
'_xlfn.'
|
|
3716
|
-
],
|
|
3717
|
-
...Array.from(FUTURE_FUNCTIONS).filter((name)=>![
|
|
3718
|
-
'FILTER',
|
|
3719
|
-
'SORT',
|
|
3720
|
-
'SORTBY',
|
|
3721
|
-
'UNIQUE'
|
|
3722
|
-
].includes(name)).map((name)=>[
|
|
3723
|
-
name,
|
|
3724
|
-
'_xlfn.'
|
|
3725
|
-
])
|
|
3726
|
-
]);
|
|
3727
|
-
function effectiveSpreadsheetCalculationSettings(settings) {
|
|
3728
|
-
if (!settings) return {
|
|
3729
|
-
...DEFAULT_SPREADSHEET_CALCULATION_SETTINGS
|
|
3730
|
-
};
|
|
3731
|
-
return {
|
|
3732
|
-
mode: settings.mode,
|
|
3733
|
-
fullCalculationOnLoad: Boolean(settings.fullCalculationOnLoad),
|
|
3734
|
-
forceFullCalculation: Boolean(settings.forceFullCalculation),
|
|
3735
|
-
iterativeCalculation: Boolean(settings.iterativeCalculation),
|
|
3736
|
-
maximumIterations: clampedInteger(settings.maximumIterations, 1, 10000, 100),
|
|
3737
|
-
maximumChange: positiveNumber(settings.maximumChange, 0.001),
|
|
3738
|
-
fullPrecision: false !== settings.fullPrecision
|
|
3739
|
-
};
|
|
3740
|
-
}
|
|
3741
|
-
function editableSpreadsheetFormula(source) {
|
|
3742
|
-
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, ''));
|
|
3743
|
-
}
|
|
3744
|
-
function spreadsheetFormulaForXlsx(currentFormula, sourceFormula) {
|
|
3745
|
-
const current = currentFormula.trim().replace(/^=/, '');
|
|
3746
|
-
const source = sourceFormula?.trim().replace(/^=/, '');
|
|
3747
|
-
if (source && comparableFormula(source) === comparableFormula(current)) return source;
|
|
3748
|
-
return futureFunctionPrefixes(current, source);
|
|
3749
|
-
}
|
|
3750
|
-
function spreadsheetFormulaRangeForCell(sheet, row, column) {
|
|
3751
|
-
const address = spreadsheetCellAddress(row, column);
|
|
3752
|
-
return (sheet.formulaMetadata?.ranges ?? []).find((range)=>range.anchor.toUpperCase().replaceAll('$', '') === address);
|
|
3753
|
-
}
|
|
3754
|
-
function spreadsheetFormulaRangesForSelection(sheet, selection) {
|
|
3755
|
-
return (sheet.formulaMetadata?.ranges ?? []).filter((range)=>work_spreadsheet_formulas_rangesOverlap(selection, parseSpreadsheetFormulaRange(range.reference)));
|
|
3756
|
-
}
|
|
3757
|
-
function spreadsheetFormulaRangeConflict(sheet, range) {
|
|
3758
|
-
const bounds = parseSpreadsheetFormulaRange(range.reference);
|
|
3759
|
-
if (!bounds) return '引用范围无效';
|
|
3760
|
-
const anchor = parseSpreadsheetCellAddress(range.anchor);
|
|
3761
|
-
if (!anchor || !work_spreadsheet_formulas_containsCell(bounds, anchor.row, anchor.column)) return '锚点不在引用范围内';
|
|
3762
|
-
const anchorCell = sheet.data?.[anchor.row]?.[anchor.column];
|
|
3763
|
-
if (!anchorCell) return '锚点单元格不存在';
|
|
3764
|
-
const overlaps = (sheet.formulaMetadata?.ranges ?? []).filter((candidate)=>candidate !== range && work_spreadsheet_formulas_rangesOverlap(bounds, parseSpreadsheetFormulaRange(candidate.reference)));
|
|
3765
|
-
if (overlaps.length) return '与其他公式范围重叠';
|
|
3766
|
-
for (const [row, cells] of sparseArrayEntries(sheet.data))if (!(row < bounds.startRow) && !(row > bounds.endRow)) {
|
|
3767
|
-
for (const [column, cell] of sparseArrayEntries(cells))if (!(column < bounds.startColumn) && !(column > bounds.endColumn)) {
|
|
3768
|
-
if (row !== anchor.row || column !== anchor.column) {
|
|
3769
|
-
if (cell?.f) return `${spreadsheetCellAddress(row, column)} 包含独立公式`;
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
if (Object.values(sheet.config?.merge ?? {}).some((merge)=>work_spreadsheet_formulas_rangesOverlap(bounds, {
|
|
3774
|
-
startRow: merge.r,
|
|
3775
|
-
endRow: merge.r + merge.rs - 1,
|
|
3776
|
-
startColumn: merge.c,
|
|
3777
|
-
endColumn: merge.c + merge.cs - 1
|
|
3778
|
-
}))) return '范围内包含合并单元格';
|
|
3779
|
-
if ('data-table' !== range.type && !anchorCell.f) return '锚点公式已被删除';
|
|
3780
|
-
return null;
|
|
3781
|
-
}
|
|
3782
|
-
function spreadsheetFormulaFunctions(formula) {
|
|
3783
|
-
const withoutStrings = formula.replace(/"(?:[^"]|"")*"/g, '""');
|
|
3784
|
-
const functions = new Set();
|
|
3785
|
-
for (const match of withoutStrings.matchAll(/(?:_xlfn\.)?(?:_xlws\.)?([A-Z][A-Z0-9_.]*)\s*\(/gi))if (match[1]) functions.add(match[1].toUpperCase());
|
|
3786
|
-
return Array.from(functions);
|
|
3787
|
-
}
|
|
3788
|
-
function formulaHasExternalReference(formula) {
|
|
3789
|
-
return /\[[^\]]+\][^!]*!/i.test(formula.replace(/"(?:[^"]|"")*"/g, '""'));
|
|
3790
|
-
}
|
|
3791
|
-
function formulaHasStructuredReference(formula) {
|
|
3792
|
-
const withoutExternalReferences = formula.replace(/"(?:[^"]|"")*"/g, '""').replace(/\[[^\]]+\][^!]*!/gi, '');
|
|
3793
|
-
return /\[(?:@|#)[^\]]+\]/i.test(withoutExternalReferences) || /\b[A-Z_\\][A-Z0-9_.]*\s*\[[^\]]+\]/i.test(withoutExternalReferences);
|
|
3794
|
-
}
|
|
3795
|
-
function volatileSpreadsheetFormulaFunctions(formula) {
|
|
3796
|
-
return spreadsheetFormulaFunctions(formula).filter((name)=>VOLATILE_FUNCTIONS.has(name));
|
|
3797
|
-
}
|
|
3798
|
-
function spreadsheetCellAddress(row, column) {
|
|
3799
|
-
let value = column + 1;
|
|
3800
|
-
let label = '';
|
|
3801
|
-
while(value > 0){
|
|
3802
|
-
value -= 1;
|
|
3803
|
-
label = String.fromCharCode(65 + value % 26) + label;
|
|
3804
|
-
value = Math.floor(value / 26);
|
|
3805
|
-
}
|
|
3806
|
-
return `${label}${row + 1}`;
|
|
3807
|
-
}
|
|
3808
|
-
function parseSpreadsheetFormulaRange(reference) {
|
|
3809
|
-
const normalized = reference.trim().replaceAll('$', '').split('!').at(-1) ?? '';
|
|
3810
|
-
const [startText, endText = startText] = normalized.split(':');
|
|
3811
|
-
const start = parseSpreadsheetCellAddress(startText);
|
|
3812
|
-
const end = parseSpreadsheetCellAddress(endText);
|
|
3813
|
-
if (!start || !end) return null;
|
|
3814
|
-
return {
|
|
3815
|
-
startRow: Math.min(start.row, end.row),
|
|
3816
|
-
endRow: Math.max(start.row, end.row),
|
|
3817
|
-
startColumn: Math.min(start.column, end.column),
|
|
3818
|
-
endColumn: Math.max(start.column, end.column)
|
|
3819
|
-
};
|
|
3820
|
-
}
|
|
3821
|
-
function parseSpreadsheetCellAddress(address) {
|
|
3822
|
-
const match = /^([A-Z]{1,3})([1-9]\d*)$/i.exec(address.trim().replaceAll('$', ''));
|
|
3823
|
-
if (!match) return null;
|
|
3824
|
-
let column = 0;
|
|
3825
|
-
for (const character of match[1].toUpperCase())column = 26 * column + character.charCodeAt(0) - 64;
|
|
3826
|
-
const row = Number(match[2]) - 1;
|
|
3827
|
-
if (row > 1048575 || column > 16384) return null;
|
|
3828
|
-
return {
|
|
3829
|
-
row,
|
|
3830
|
-
column: column - 1
|
|
3831
|
-
};
|
|
3832
|
-
}
|
|
3833
|
-
function work_spreadsheet_formulas_containsCell(bounds, row, column) {
|
|
3834
|
-
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
3835
|
-
}
|
|
3836
|
-
function work_spreadsheet_formulas_rangesOverlap(first, second) {
|
|
3837
|
-
if (!second) return false;
|
|
3838
|
-
return first.startRow <= second.endRow && first.endRow >= second.startRow && first.startColumn <= second.endColumn && first.endColumn >= second.startColumn;
|
|
3839
|
-
}
|
|
3840
|
-
function comparableFormula(formula) {
|
|
3841
|
-
return editableSpreadsheetFormula(formula).trim();
|
|
3842
|
-
}
|
|
3843
|
-
function futureFunctionPrefixes(formula, sourceFormula) {
|
|
3844
|
-
const sourcePrefixes = new Map();
|
|
3845
|
-
const sourceFunctions = (sourceFormula ?? '').replace(/"(?:[^"]|"")*"/g, '""');
|
|
3846
|
-
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]);
|
|
3847
|
-
return transformFormulaOutsideStrings(formula, (segment)=>segment.replace(/(?<![A-Z0-9_.])([A-Z][A-Z0-9_.]*)\s*(?=\()/gi, (match, functionName)=>{
|
|
3848
|
-
const normalized = functionName.toUpperCase();
|
|
3849
|
-
const prefix = sourcePrefixes.get(normalized) ?? FUTURE_FUNCTION_PREFIXES.get(normalized);
|
|
3850
|
-
return prefix ? `${prefix}${match}` : match;
|
|
3851
|
-
}));
|
|
3852
|
-
}
|
|
3853
|
-
function clampedInteger(value, minimum, maximum, fallback) {
|
|
3854
|
-
if (!Number.isFinite(value)) return fallback;
|
|
3855
|
-
return Math.min(maximum, Math.max(minimum, Math.round(value)));
|
|
3856
|
-
}
|
|
3857
|
-
function positiveNumber(value, fallback) {
|
|
3858
|
-
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
3859
|
-
}
|
|
3860
|
-
function transformFormulaOutsideStrings(formula, transform) {
|
|
3861
|
-
return formula.split(/("(?:[^"]|"")*")/).map((segment, index)=>index % 2 ? segment : transform(segment)).join('');
|
|
3862
|
-
}
|
|
3863
|
-
export { cloneSparseMatrix, createSpreadsheetChartFromSelection, createSpreadsheetPivotFromSelection, defaultPivotValueCaption, defaultWorkSpreadsheetChartSeriesStyle, deleteSpreadsheetPivotTable, editableSpreadsheetFormula, effectiveSpreadsheetCalculationSettings, errorBarSourceValues, fitSpreadsheetTrendline, formatSpreadsheetCellRanges, formatSpreadsheetChartAxisNumber, formulaHasExternalReference, formulaHasStructuredReference, isValidSpreadsheetDefinedName, normalizeSpreadsheetPrintArea, normalizeSpreadsheetPrintTitleColumns, normalizeSpreadsheetPrintTitleRows, normalizeWorkSpreadsheetBubbleScale, normalizeWorkSpreadsheetBubbleSizeRepresents, normalizeWorkSpreadsheetChartAxes, normalizeWorkSpreadsheetChartAxisGroup, normalizeWorkSpreadsheetChartColor, normalizeWorkSpreadsheetChartGapWidth, normalizeWorkSpreadsheetChartGrouping, normalizeWorkSpreadsheetChartLayout, normalizeWorkSpreadsheetChartLegendOverlay, normalizeWorkSpreadsheetChartLegendPosition, normalizeWorkSpreadsheetChartOverlap, normalizeWorkSpreadsheetChartSeriesStyle, normalizeWorkSpreadsheetChartSmoothLines, normalizeWorkSpreadsheetCombinationSeriesType, normalizeWorkSpreadsheetDataLabelPosition, normalizeWorkSpreadsheetDataLabels, normalizeWorkSpreadsheetDoughnutHoleSize, normalizeWorkSpreadsheetErrorBars, normalizeWorkSpreadsheetRadarStyle, normalizeWorkSpreadsheetScatterStyle, normalizeWorkSpreadsheetTrendline, normalizeWorkSpreadsheetTrendlineType, parseSpreadsheetCellRanges, parseSpreadsheetChartReference, parseSpreadsheetPrintTitles, qualifySpreadsheetRange, readXlsxDrawingAnchor, reconcileSpreadsheetChartPreviews, reconcileSpreadsheetPivots, refreshSpreadsheetPivotTables, resolveSpreadsheetChart, roundChartNumber, sparseArrayEntries, sparseArrayIndexes, sparseMatrixColumnCount, spreadsheetCellAddress, spreadsheetChartAxisGridlinesVisible, spreadsheetChartAxisLabelLayout, spreadsheetChartAxisScale, spreadsheetChartAxisValueRatio, spreadsheetChartBarGeometry, spreadsheetChartCategoryLabelVisible, spreadsheetChartCategoryVisualIndex, spreadsheetChartCount, spreadsheetChartSeriesFillStyle, spreadsheetChartSeriesLayout, spreadsheetChartSeriesLegendColor, spreadsheetChartSeriesLineStyle, spreadsheetChartSeriesStyleContext, spreadsheetErrorBarAmounts, spreadsheetFormulaForXlsx, spreadsheetFormulaFunctions, spreadsheetFormulaRangeConflict, spreadsheetFormulaRangeForCell, spreadsheetFormulaRangesForSelection, spreadsheetPivotAggregationLabel, spreadsheetPivotCount, spreadsheetPivotFields, spreadsheetPivotFilterItems, spreadsheetPivotFilterValueKey, spreadsheetPivotIntersects, spreadsheetPivotOutputContains, spreadsheetPivotValidation, spreadsheetSheetsWithChartPreviews, stripSpreadsheetSheetQualifier, volatileSpreadsheetFormulaFunctions, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartAxisIsCategoryAxis, workSpreadsheetChartAxisIsValueAxis, workSpreadsheetChartAxisLabelPositionLabel, workSpreadsheetChartAxisPositionLabel, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, workSpreadsheetChartAxisTickMarkLabel, workSpreadsheetChartGroupingIsStacked, workSpreadsheetChartGroupingLabel, workSpreadsheetChartLegendPositionLabel, workSpreadsheetChartSupportsAxes, workSpreadsheetChartSupportsBarSpacing, workSpreadsheetChartSupportsErrorBars, workSpreadsheetChartSupportsGrouping, workSpreadsheetChartSupportsSeriesAnalysis, workSpreadsheetChartSupportsSmoothLines, workSpreadsheetChartSupportsTrendlines, workSpreadsheetChartTypeLabel, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetCombinationSeriesTypeLabel, workSpreadsheetDataLabelPositionLabel, workSpreadsheetErrorBarTypeLabel, workSpreadsheetErrorBarValueTypeLabel, workSpreadsheetTrendlineTypeLabel, xlsxDrawingAnchorToBounds, xlsxTwoCellAnchorMarkers };
|
|
3870
|
+
export { cloneSparseMatrix, createSpreadsheetChartFromSelection, createSpreadsheetPivotFromSelection, defaultPivotValueCaption, defaultWorkSpreadsheetChartSeriesStyle, deleteSpreadsheetPivotTable, editableSpreadsheetFormula, effectiveSpreadsheetCalculationSettings, errorBarSourceValues, fitSpreadsheetTrendline, formatSpreadsheetCellRanges, formatSpreadsheetChartAxisNumber, formulaHasExternalReference, formulaHasStructuredReference, isValidSpreadsheetDefinedName, normalizeSpreadsheetPrintArea, normalizeSpreadsheetPrintTitleColumns, normalizeSpreadsheetPrintTitleRows, normalizeWorkSpreadsheetBubbleScale, normalizeWorkSpreadsheetBubbleSizeRepresents, normalizeWorkSpreadsheetChartAxes, normalizeWorkSpreadsheetChartAxisGroup, normalizeWorkSpreadsheetChartColor, normalizeWorkSpreadsheetChartGapWidth, normalizeWorkSpreadsheetChartGrouping, normalizeWorkSpreadsheetChartLayout, normalizeWorkSpreadsheetChartLegendOverlay, normalizeWorkSpreadsheetChartLegendPosition, normalizeWorkSpreadsheetChartOverlap, normalizeWorkSpreadsheetChartSeriesStyle, normalizeWorkSpreadsheetChartSmoothLines, normalizeWorkSpreadsheetCombinationSeriesType, normalizeWorkSpreadsheetDataLabelPosition, normalizeWorkSpreadsheetDataLabels, normalizeWorkSpreadsheetDoughnutHoleSize, normalizeWorkSpreadsheetErrorBars, normalizeWorkSpreadsheetRadarStyle, normalizeWorkSpreadsheetScatterStyle, normalizeWorkSpreadsheetTrendline, normalizeWorkSpreadsheetTrendlineType, parseSpreadsheetCellRanges, parseSpreadsheetChartReference, parseSpreadsheetPrintTitles, qualifySpreadsheetRange, readXlsxDrawingAnchor, reconcileSpreadsheetChartPreviews, reconcileSpreadsheetPivots, refreshSpreadsheetPivotTables, resolveSpreadsheetChart, roundChartNumber, sparseArrayEntries, sparseArrayIndexes, sparseMatrixColumnCount, spreadsheetCellAddress, spreadsheetChartAxisGridlinesVisible, spreadsheetChartAxisLabelLayout, spreadsheetChartAxisScale, spreadsheetChartAxisValueRatio, spreadsheetChartBarGeometry, spreadsheetChartCategoryLabelVisible, spreadsheetChartCategoryVisualIndex, spreadsheetChartCount, spreadsheetChartSeriesFillStyle, spreadsheetChartSeriesLayout, spreadsheetChartSeriesLegendColor, spreadsheetChartSeriesLineStyle, spreadsheetChartSeriesStyleContext, spreadsheetErrorBarAmounts, spreadsheetFormulaForXlsx, spreadsheetFormulaFunctions, spreadsheetFormulaRangeConflict, spreadsheetFormulaRangeForCell, spreadsheetFormulaRangesForSelection, spreadsheetGridSize, spreadsheetPivotAggregationLabel, spreadsheetPivotCount, spreadsheetPivotFields, spreadsheetPivotFilterItems, spreadsheetPivotFilterValueKey, spreadsheetPivotIntersects, spreadsheetPivotOutputContains, spreadsheetPivotValidation, spreadsheetSheetsWithChartPreviews, stripSpreadsheetSheetQualifier, volatileSpreadsheetFormulaFunctions, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartAxisIsCategoryAxis, workSpreadsheetChartAxisIsValueAxis, workSpreadsheetChartAxisLabelPositionLabel, workSpreadsheetChartAxisPositionLabel, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, workSpreadsheetChartAxisTickMarkLabel, workSpreadsheetChartGroupingIsStacked, workSpreadsheetChartGroupingLabel, workSpreadsheetChartLegendPositionLabel, workSpreadsheetChartSupportsAxes, workSpreadsheetChartSupportsBarSpacing, workSpreadsheetChartSupportsErrorBars, workSpreadsheetChartSupportsGrouping, workSpreadsheetChartSupportsSeriesAnalysis, workSpreadsheetChartSupportsSmoothLines, workSpreadsheetChartSupportsTrendlines, workSpreadsheetChartTypeLabel, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetCombinationSeriesTypeLabel, workSpreadsheetDataLabelPositionLabel, workSpreadsheetErrorBarTypeLabel, workSpreadsheetErrorBarValueTypeLabel, workSpreadsheetTrendlineTypeLabel, xlsxDrawingAnchorToBounds, xlsxTwoCellAnchorMarkers };
|