@a3s-lab/office 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -6
- 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~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 +4272 -2753
- 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 +3 -3
- package/dist/0~work-docx-large-document-import.js +559 -0
- package/dist/0~work-office-diagnostics.js +51 -35
- package/dist/0~work-pptx-import.js +2 -2
- package/dist/4104.js +4127 -3037
- package/dist/432.js +51 -7
- package/dist/4476.js +280 -69
- package/dist/5184.js +24 -2
- package/dist/{8928.js → 6282.js} +2338 -93
- package/dist/8715.js +1201 -1156
- package/dist/9356.js +128 -8
- package/dist/core.d.ts +1 -0
- 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/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.d.ts +14 -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-command-catalog.d.ts +159 -0
- package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +8 -4
- package/dist/internal/features/work/editors/spreadsheet-editor-support.d.ts +8 -1
- 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-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-collaboration.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 +10 -0
- 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-file-io.d.ts +2 -1
- 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-import.d.ts +2 -1
- 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-data.d.ts +7 -1
- package/dist/internal/features/work/work-file-import.d.ts +33 -0
- package/dist/internal/features/work/work-file-io.d.ts +5 -4
- package/dist/internal/features/work/work-file-kind.d.ts +5 -0
- package/dist/internal/features/work/work-markdown-file-io.d.ts +2 -1
- package/dist/internal/features/work/work-office-diagnostics.d.ts +12 -2
- package/dist/internal/features/work/work-ooxml-package.d.ts +8 -0
- package/dist/internal/features/work/work-pptx-import.d.ts +1 -1
- package/dist/internal/features/work/work-presentation-file-io.d.ts +2 -1
- 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-spreadsheet-protection.d.ts +2 -0
- package/dist/internal/features/work/work-types.d.ts +22 -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 -17
- 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 +12 -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 +440 -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 +4157 -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 +26 -4
package/dist/8715.js
CHANGED
|
@@ -1,202 +1,327 @@
|
|
|
1
1
|
import { createOfficeId as createWorkId, directChild, attribute } from "./5184.js";
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
function sparseArrayIndexes(values) {
|
|
3
|
+
if (!values) return [];
|
|
4
|
+
return Object.keys(values).flatMap((key)=>{
|
|
5
|
+
const index = Number(key);
|
|
6
|
+
return Number.isSafeInteger(index) && index >= 0 && index < values.length ? [
|
|
7
|
+
index
|
|
8
|
+
] : [];
|
|
9
|
+
});
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
11
|
+
function sparseArrayEntries(values) {
|
|
12
|
+
if (!values) return [];
|
|
13
|
+
return sparseArrayIndexes(values).flatMap((index)=>{
|
|
14
|
+
const value = values[index];
|
|
15
|
+
return void 0 === value ? [] : [
|
|
16
|
+
[
|
|
17
|
+
index,
|
|
18
|
+
value
|
|
19
|
+
]
|
|
20
|
+
];
|
|
21
|
+
});
|
|
12
22
|
}
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
23
|
+
function sparseMatrixColumnCount(matrix) {
|
|
24
|
+
let maximum = 0;
|
|
25
|
+
for (const [, row] of sparseArrayEntries(matrix))maximum = Math.max(maximum, row.length);
|
|
26
|
+
return maximum;
|
|
17
27
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
28
|
+
function spreadsheetGridSize(sheet) {
|
|
29
|
+
if (!sheet) return null;
|
|
30
|
+
return {
|
|
31
|
+
rowCount: Math.max(sheet.row ?? 0, sheet.data?.length ?? 0),
|
|
32
|
+
columnCount: Math.max(sheet.column ?? 0, sparseMatrixColumnCount(sheet.data))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function cloneSparseMatrix(source) {
|
|
36
|
+
const clone = [];
|
|
37
|
+
if (!source) return clone;
|
|
38
|
+
clone.length = source.length;
|
|
39
|
+
for (const [rowIndex, sourceRow] of sparseArrayEntries(source)){
|
|
40
|
+
const row = [];
|
|
41
|
+
row.length = sourceRow.length;
|
|
42
|
+
for (const columnIndex of sparseArrayIndexes(sourceRow))row[columnIndex] = sourceRow[columnIndex];
|
|
43
|
+
clone[rowIndex] = row;
|
|
44
|
+
}
|
|
45
|
+
return clone;
|
|
20
46
|
}
|
|
21
|
-
function
|
|
22
|
-
return
|
|
47
|
+
function spreadsheetPivotCellValue(cell) {
|
|
48
|
+
return cell?.v ?? cell?.m ?? null;
|
|
23
49
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
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);
|
|
26
56
|
}
|
|
27
|
-
function
|
|
28
|
-
|
|
57
|
+
function spreadsheetPivotFilterValueKey(value) {
|
|
58
|
+
if (null === value) return 'blank:';
|
|
59
|
+
return `${typeof value}:${String(value)}`;
|
|
29
60
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
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);
|
|
32
66
|
}
|
|
33
|
-
function
|
|
34
|
-
if ('
|
|
35
|
-
if (
|
|
36
|
-
return
|
|
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;
|
|
37
71
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
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
|
+
}));
|
|
40
87
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
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
|
+
];
|
|
43
150
|
}
|
|
44
|
-
function
|
|
45
|
-
const direction = workSpreadsheetChartUsesNumericXAxis(chartType) && 'x' === source.direction ? 'x' : 'y';
|
|
46
|
-
const barType = 'plus' === source.barType || 'minus' === source.barType ? source.barType : 'both';
|
|
47
|
-
const valueType = 'percentage' === source.valueType || 'standardDeviation' === source.valueType || 'standardError' === source.valueType || 'custom' === source.valueType || 'fixedValue' === source.valueType ? source.valueType : 'fixedValue';
|
|
48
|
-
const fallbackValue = 'percentage' === valueType ? 5 : 1;
|
|
49
|
-
const numericValue = Number(source.value);
|
|
50
|
-
const value = Number.isFinite(numericValue) ? Math.max(0, numericValue) : fallbackValue;
|
|
51
|
-
const plusValues = source.plusValues?.map(normalizedErrorBarAmount);
|
|
52
|
-
const minusValues = source.minusValues?.map(normalizedErrorBarAmount);
|
|
151
|
+
function accumulator() {
|
|
53
152
|
return {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...false === source.showEndCaps ? {
|
|
61
|
-
showEndCaps: false
|
|
62
|
-
} : {},
|
|
63
|
-
...'custom' === valueType && 'minus' !== barType && plusValues?.length ? {
|
|
64
|
-
plusValues
|
|
65
|
-
} : {},
|
|
66
|
-
...'custom' === valueType && 'minus' !== barType && source.plusReference?.trim() ? {
|
|
67
|
-
plusReference: source.plusReference.trim().replace(/^=/, '')
|
|
68
|
-
} : {},
|
|
69
|
-
...'custom' === valueType && 'plus' !== barType && minusValues?.length ? {
|
|
70
|
-
minusValues
|
|
71
|
-
} : {},
|
|
72
|
-
...'custom' === valueType && 'plus' !== barType && source.minusReference?.trim() ? {
|
|
73
|
-
minusReference: source.minusReference.trim().replace(/^=/, '')
|
|
74
|
-
} : {}
|
|
153
|
+
nonEmptyCount: 0,
|
|
154
|
+
numberCount: 0,
|
|
155
|
+
sum: 0,
|
|
156
|
+
product: 1,
|
|
157
|
+
mean: 0,
|
|
158
|
+
m2: 0
|
|
75
159
|
};
|
|
76
160
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
function workSpreadsheetErrorBarTypeLabel(type) {
|
|
85
|
-
if ('plus' === type) return '正向';
|
|
86
|
-
if ('minus' === type) return '负向';
|
|
87
|
-
return '双向';
|
|
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;
|
|
88
167
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
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);
|
|
91
180
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
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;
|
|
94
194
|
}
|
|
95
|
-
function
|
|
96
|
-
const position = source.position ? normalizeWorkSpreadsheetDataLabelPosition(source.position) : void 0;
|
|
97
|
-
const separator = 'string' == typeof source.separator ? source.separator.slice(0, 64) : void 0;
|
|
195
|
+
function combination(values) {
|
|
98
196
|
return {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} : {},
|
|
102
|
-
...true === source.showCategoryName ? {
|
|
103
|
-
showCategoryName: true
|
|
104
|
-
} : {},
|
|
105
|
-
...true === source.showSeriesName ? {
|
|
106
|
-
showSeriesName: true
|
|
107
|
-
} : {},
|
|
108
|
-
...true === source.showPercentage && ('pie' === chartType || 'doughnut' === chartType) ? {
|
|
109
|
-
showPercentage: true
|
|
110
|
-
} : {},
|
|
111
|
-
...true === source.showBubbleSize && 'bubble' === chartType ? {
|
|
112
|
-
showBubbleSize: true
|
|
113
|
-
} : {},
|
|
114
|
-
...void 0 !== separator ? {
|
|
115
|
-
separator
|
|
116
|
-
} : {},
|
|
117
|
-
...position ? {
|
|
118
|
-
position
|
|
119
|
-
} : {}
|
|
197
|
+
key: JSON.stringify(values.map(normalizedPivotKeyValue)),
|
|
198
|
+
values
|
|
120
199
|
};
|
|
121
200
|
}
|
|
122
|
-
function
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if ('
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
];
|
|
132
221
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
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;
|
|
135
232
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
233
|
+
function cellKey(rowKey, columnKey, valueIndex) {
|
|
234
|
+
return `${rowKey.length}:${rowKey}${columnKey.length}:${columnKey}${valueIndex}`;
|
|
235
|
+
}
|
|
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) {
|
|
144
266
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
order
|
|
151
|
-
} : {},
|
|
152
|
-
...'movingAverage' === type ? {
|
|
153
|
-
period
|
|
154
|
-
} : {},
|
|
155
|
-
...forward > 0 ? {
|
|
156
|
-
forward
|
|
157
|
-
} : {},
|
|
158
|
-
...backward > 0 ? {
|
|
159
|
-
backward
|
|
160
|
-
} : {},
|
|
161
|
-
...void 0 !== intercept ? {
|
|
162
|
-
intercept
|
|
163
|
-
} : {},
|
|
164
|
-
...true === trendline.displayEquation ? {
|
|
165
|
-
displayEquation: true
|
|
166
|
-
} : {},
|
|
167
|
-
...true === trendline.displayRSquared ? {
|
|
168
|
-
displayRSquared: true
|
|
169
|
-
} : {}
|
|
267
|
+
v: value,
|
|
268
|
+
m: value,
|
|
269
|
+
bl: 1,
|
|
270
|
+
bg: '#e7f1ec',
|
|
271
|
+
fc: '#173d2b'
|
|
170
272
|
};
|
|
171
273
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return '线性';
|
|
274
|
+
function pivotDimensionCell(value) {
|
|
275
|
+
const display = displaySpreadsheetPivotValue(value);
|
|
276
|
+
return {
|
|
277
|
+
v: null == value || '' === value ? display : value,
|
|
278
|
+
m: display
|
|
279
|
+
};
|
|
179
280
|
}
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
};
|
|
184
295
|
}
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
|
|
296
|
+
function pivotTotalLabelCell() {
|
|
297
|
+
return {
|
|
298
|
+
v: '总计',
|
|
299
|
+
m: '总计',
|
|
300
|
+
bl: 1,
|
|
301
|
+
bg: '#f3f7f5'
|
|
302
|
+
};
|
|
188
303
|
}
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
304
|
+
function pivotFilterLabelCell(value) {
|
|
305
|
+
return {
|
|
306
|
+
v: value,
|
|
307
|
+
m: value,
|
|
308
|
+
bl: 1,
|
|
309
|
+
bg: '#f3f7f5',
|
|
310
|
+
fc: '#173d2b'
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function pivotFilterSelectionCell(value) {
|
|
314
|
+
return {
|
|
315
|
+
v: value,
|
|
316
|
+
m: value,
|
|
317
|
+
bg: '#f3f7f5'
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function formatPivotNumber(value) {
|
|
321
|
+
return Number.isInteger(value) ? String(value) : String(Number(value.toPrecision(15)));
|
|
322
|
+
}
|
|
323
|
+
function matchesPivotReportFilters(values, pivot) {
|
|
324
|
+
return (pivot.reportFilters ?? []).every((filter)=>void 0 === filter.selectedItem || spreadsheetPivotFilterValueKey(normalizeSpreadsheetPivotFilterValue(values[filter.fieldIndex])) === spreadsheetPivotFilterValueKey(filter.selectedItem));
|
|
200
325
|
}
|
|
201
326
|
const CELL_REFERENCE = /\$?([A-Z]{1,3})\$?([1-9]\d*)/i;
|
|
202
327
|
const CELL_OR_RANGE = new RegExp(`^${CELL_REFERENCE.source}(?::${CELL_REFERENCE.source})?$`, 'i');
|
|
@@ -354,342 +479,381 @@ function encodeColumn(column) {
|
|
|
354
479
|
}
|
|
355
480
|
return label;
|
|
356
481
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if ('number' == typeof value) return Number.isFinite(value) ? value : String(value);
|
|
364
|
-
if ('boolean' == typeof value) return value;
|
|
365
|
-
return String(value);
|
|
366
|
-
}
|
|
367
|
-
function spreadsheetPivotFilterValueKey(value) {
|
|
368
|
-
if (null === value) return 'blank:';
|
|
369
|
-
return `${typeof value}:${String(value)}`;
|
|
370
|
-
}
|
|
371
|
-
function displaySpreadsheetPivotValue(value) {
|
|
372
|
-
if (null == value || '' === value) return '(空白)';
|
|
373
|
-
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
374
|
-
if ('boolean' == typeof value) return value ? 'TRUE' : 'FALSE';
|
|
375
|
-
return String(value);
|
|
376
|
-
}
|
|
377
|
-
function finiteSpreadsheetPivotNumber(value) {
|
|
378
|
-
if ('number' == typeof value) return Number.isFinite(value) ? value : null;
|
|
379
|
-
if (value instanceof Date) return value.getTime();
|
|
380
|
-
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);
|
|
381
488
|
}
|
|
382
|
-
function
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
+
});
|
|
392
513
|
}
|
|
393
|
-
return
|
|
394
|
-
numeric: true,
|
|
395
|
-
sensitivity: 'base'
|
|
396
|
-
}));
|
|
514
|
+
return uniqueFieldNames(fields) ? fields : [];
|
|
397
515
|
}
|
|
398
|
-
function
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const columnTotals = new Map();
|
|
404
|
-
const totals = pivot.values.map(()=>accumulator());
|
|
405
|
-
const singleColumn = combination([]);
|
|
406
|
-
if (!pivot.columnFields.length) columnCombinations.set(singleColumn.key, singleColumn);
|
|
407
|
-
for(let row = sourceBounds.startRow + 1; row <= sourceBounds.endRow; row += 1){
|
|
408
|
-
const values = fields.map((field)=>spreadsheetPivotCellValue(sourceSheet.data?.[row]?.[sourceBounds.startColumn + field.index]));
|
|
409
|
-
if (values.every((value)=>null == value || '' === value)) continue;
|
|
410
|
-
if (!matchesPivotReportFilters(values, pivot)) continue;
|
|
411
|
-
const rowCombination = combination(pivot.rowFields.map((fieldIndex)=>values[fieldIndex]));
|
|
412
|
-
const columnCombination = pivot.columnFields.length ? combination(pivot.columnFields.map((fieldIndex)=>values[fieldIndex])) : singleColumn;
|
|
413
|
-
rowCombinations.set(rowCombination.key, rowCombination);
|
|
414
|
-
columnCombinations.set(columnCombination.key, columnCombination);
|
|
415
|
-
for (const [valueIndex, valueField] of pivot.values.entries()){
|
|
416
|
-
const value = values[valueField.fieldIndex];
|
|
417
|
-
updateAccumulator(mapAccumulator(cells, cellKey(rowCombination.key, columnCombination.key, valueIndex)), value);
|
|
418
|
-
updateAccumulator(mapAccumulator(rowTotals, cellKey(rowCombination.key, '', valueIndex)), value);
|
|
419
|
-
updateAccumulator(mapAccumulator(columnTotals, cellKey('', columnCombination.key, valueIndex)), value);
|
|
420
|
-
updateAccumulator(totals[valueIndex], value);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
const sortedRows = Array.from(rowCombinations.values()).sort(compareCombinations);
|
|
424
|
-
const sortedColumns = Array.from(columnCombinations.values()).sort(compareCombinations);
|
|
425
|
-
if (!sortedRows.length) return [];
|
|
426
|
-
const headers = [
|
|
427
|
-
...pivot.rowFields.map((fieldIndex)=>fields[fieldIndex].name),
|
|
428
|
-
...sortedColumns.flatMap((column)=>pivot.values.map((value)=>columnValueCaption(column, pivot.columnFields, fields, value))),
|
|
429
|
-
...pivot.rowGrandTotals && pivot.columnFields.length ? pivot.values.map((value)=>`总计 · ${valueCaption(value, fields)}`) : []
|
|
430
|
-
];
|
|
431
|
-
const output = [
|
|
432
|
-
headers.map((value)=>pivotHeaderCell(value))
|
|
433
|
-
];
|
|
434
|
-
for (const row of sortedRows){
|
|
435
|
-
const result = row.values.map((value)=>pivotDimensionCell(value));
|
|
436
|
-
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));
|
|
437
|
-
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));
|
|
438
|
-
output.push(result);
|
|
439
|
-
}
|
|
440
|
-
if (pivot.columnGrandTotals) {
|
|
441
|
-
const result = pivot.rowFields.map((_field, index)=>0 === index ? pivotTotalLabelCell() : pivotValueCell(null, true));
|
|
442
|
-
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));
|
|
443
|
-
if (pivot.rowGrandTotals && pivot.columnFields.length) for (const [valueIndex, value] of pivot.values.entries())result.push(pivotValueCell(accumulatorValue(totals[valueIndex], value.aggregation), true));
|
|
444
|
-
output.push(result);
|
|
445
|
-
}
|
|
446
|
-
const reportFilters = pivot.reportFilters ?? [];
|
|
447
|
-
if (!reportFilters.length) return output;
|
|
448
|
-
const width = output[0]?.length ?? 0;
|
|
449
|
-
const filterRows = reportFilters.map((filter)=>{
|
|
450
|
-
const row = Array(width).fill(null);
|
|
451
|
-
row[0] = pivotFilterLabelCell(fields[filter.fieldIndex].name);
|
|
452
|
-
row[1] = pivotFilterSelectionCell(void 0 === filter.selectedItem ? '(全部)' : displaySpreadsheetPivotValue(filter.selectedItem));
|
|
453
|
-
return row;
|
|
454
|
-
});
|
|
455
|
-
filterRows.push(Array(width).fill(null));
|
|
456
|
-
return [
|
|
457
|
-
...filterRows,
|
|
458
|
-
...output
|
|
459
|
-
];
|
|
516
|
+
function spreadsheetPivotFilterItems(content, pivot, fieldIndex) {
|
|
517
|
+
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
518
|
+
const range = singleRange(pivot.sourceReference);
|
|
519
|
+
if (!sourceSheet || !range || fieldIndex < 0 || fieldIndex > range.endColumn - range.startColumn) return [];
|
|
520
|
+
return spreadsheetPivotFilterItemsFromSource(sourceSheet, range, fieldIndex);
|
|
460
521
|
}
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
mean: 0,
|
|
468
|
-
m2: 0
|
|
522
|
+
function createSpreadsheetPivotFromSelection(content, sourceSheetId, selection) {
|
|
523
|
+
const sourceSheet = content.sheets.find((sheet)=>sheet.id === sourceSheetId);
|
|
524
|
+
const bounds = selectionBounds(selection);
|
|
525
|
+
if (!sourceSheet || !bounds || bounds.endRow <= bounds.startRow) return {
|
|
526
|
+
content,
|
|
527
|
+
error: '请选择包含标题行和至少一行数据的连续区域。'
|
|
469
528
|
};
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
function updateAccumulator(state, value) {
|
|
479
|
-
if (null != value && '' !== value) state.nonEmptyCount += 1;
|
|
480
|
-
const number = finiteSpreadsheetPivotNumber(value);
|
|
481
|
-
if (null === number) return;
|
|
482
|
-
state.numberCount += 1;
|
|
483
|
-
state.sum += number;
|
|
484
|
-
state.product *= number;
|
|
485
|
-
state.minimum = void 0 === state.minimum ? number : Math.min(state.minimum, number);
|
|
486
|
-
state.maximum = void 0 === state.maximum ? number : Math.max(state.maximum, number);
|
|
487
|
-
const delta = number - state.mean;
|
|
488
|
-
state.mean += delta / state.numberCount;
|
|
489
|
-
state.m2 += delta * (number - state.mean);
|
|
490
|
-
}
|
|
491
|
-
function accumulatorValue(state, aggregation) {
|
|
492
|
-
if (!state) return 'sum' === aggregation || 'count' === aggregation || 'counta' === aggregation ? 0 : null;
|
|
493
|
-
if ('count' === aggregation) return state.numberCount;
|
|
494
|
-
if ('counta' === aggregation) return state.nonEmptyCount;
|
|
495
|
-
if ('average' === aggregation) return state.numberCount ? state.sum / state.numberCount : null;
|
|
496
|
-
if ('max' === aggregation) return state.maximum ?? null;
|
|
497
|
-
if ('min' === aggregation) return state.minimum ?? null;
|
|
498
|
-
if ('product' === aggregation) return state.numberCount ? state.product : null;
|
|
499
|
-
if ('stdDev' === aggregation) return state.numberCount > 1 ? Math.sqrt(state.m2 / (state.numberCount - 1)) : null;
|
|
500
|
-
if ('stdDevP' === aggregation) return state.numberCount ? Math.sqrt(state.m2 / state.numberCount) : null;
|
|
501
|
-
if ('var' === aggregation) return state.numberCount > 1 ? state.m2 / (state.numberCount - 1) : null;
|
|
502
|
-
if ('varP' === aggregation) return state.numberCount ? state.m2 / state.numberCount : null;
|
|
503
|
-
return state.sum;
|
|
504
|
-
}
|
|
505
|
-
function combination(values) {
|
|
506
|
-
return {
|
|
507
|
-
key: JSON.stringify(values.map(normalizedPivotKeyValue)),
|
|
508
|
-
values
|
|
529
|
+
if (spreadsheetPivotIntersects(sourceSheet, {
|
|
530
|
+
startRow: bounds.startRow,
|
|
531
|
+
endRow: bounds.endRow,
|
|
532
|
+
startColumn: bounds.startColumn,
|
|
533
|
+
endColumn: bounds.endColumn
|
|
534
|
+
}).length) return {
|
|
535
|
+
content,
|
|
536
|
+
error: '不能把现有数据透视表的输出区域作为新的透视表源。'
|
|
509
537
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
538
|
+
const sourceReference = formatSpreadsheetCellRanges([
|
|
539
|
+
{
|
|
540
|
+
row: [
|
|
541
|
+
bounds.startRow,
|
|
542
|
+
bounds.endRow
|
|
543
|
+
],
|
|
544
|
+
column: [
|
|
545
|
+
bounds.startColumn,
|
|
546
|
+
bounds.endColumn
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
]);
|
|
550
|
+
const draftPivot = {
|
|
551
|
+
id: createWorkId('pivot'),
|
|
552
|
+
name: nextPivotName(content),
|
|
553
|
+
sourceSheetId,
|
|
554
|
+
sourceReference,
|
|
555
|
+
anchor: 'A1',
|
|
556
|
+
rowFields: [],
|
|
557
|
+
columnFields: [],
|
|
558
|
+
reportFilters: [],
|
|
559
|
+
values: [],
|
|
560
|
+
rowGrandTotals: true,
|
|
561
|
+
columnGrandTotals: true,
|
|
562
|
+
styleName: 'PivotStyleLight16',
|
|
563
|
+
refreshOnLoad: true
|
|
564
|
+
};
|
|
565
|
+
const fields = spreadsheetPivotFields(content, draftPivot);
|
|
566
|
+
if (!fields.length) return {
|
|
567
|
+
content,
|
|
568
|
+
error: '透视表源区域的标题必须非空且不重复。'
|
|
569
|
+
};
|
|
570
|
+
const valueField = fields.find((field)=>field.numeric) ?? fields.at(-1);
|
|
571
|
+
const dimensions = fields.filter((field)=>field.index !== valueField.index);
|
|
572
|
+
const rowField = dimensions[0] ?? fields.find((field)=>field.index !== valueField.index);
|
|
573
|
+
if (!rowField) return {
|
|
574
|
+
content,
|
|
575
|
+
error: '透视表至少需要一个分类字段和一个值字段。'
|
|
576
|
+
};
|
|
577
|
+
const columnField = dimensions.find((field)=>field.index !== rowField.index);
|
|
578
|
+
draftPivot.rowFields = [
|
|
579
|
+
rowField.index
|
|
526
580
|
];
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
581
|
+
draftPivot.columnFields = columnField ? [
|
|
582
|
+
columnField.index
|
|
583
|
+
] : [];
|
|
584
|
+
draftPivot.values = [
|
|
585
|
+
{
|
|
586
|
+
fieldIndex: valueField.index,
|
|
587
|
+
aggregation: valueField.numeric ? 'sum' : 'counta',
|
|
588
|
+
caption: defaultPivotValueCaption(valueField.name, valueField.numeric ? 'sum' : 'counta')
|
|
589
|
+
}
|
|
530
590
|
];
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
function columnValueCaption(column, columnFields, fields, value) {
|
|
547
|
-
const dimensions = column.values.map((item, index)=>{
|
|
548
|
-
const label = displaySpreadsheetPivotValue(item);
|
|
549
|
-
return columnFields.length > 1 ? `${fields[columnFields[index]].name}: ${label}` : label;
|
|
550
|
-
});
|
|
551
|
-
return [
|
|
552
|
-
...dimensions,
|
|
553
|
-
valueCaption(value, fields)
|
|
554
|
-
].filter(Boolean).join(' · ');
|
|
555
|
-
}
|
|
556
|
-
function valueCaption(value, fields) {
|
|
557
|
-
return value.caption?.trim() || defaultPivotValueCaption(fields[value.fieldIndex]?.name ?? `字段 ${value.fieldIndex + 1}`, value.aggregation);
|
|
558
|
-
}
|
|
559
|
-
function defaultPivotValueCaption(fieldName, aggregation) {
|
|
560
|
-
return `${fieldName}(${spreadsheetPivotAggregationLabel(aggregation)})`;
|
|
561
|
-
}
|
|
562
|
-
function spreadsheetPivotAggregationLabel(aggregation) {
|
|
563
|
-
if ('count' === aggregation) return '计数';
|
|
564
|
-
if ('counta' === aggregation) return '非空计数';
|
|
565
|
-
if ('average' === aggregation) return '平均值';
|
|
566
|
-
if ('max' === aggregation) return '最大值';
|
|
567
|
-
if ('min' === aggregation) return '最小值';
|
|
568
|
-
if ('product' === aggregation) return '乘积';
|
|
569
|
-
if ('stdDev' === aggregation) return '样本标准差';
|
|
570
|
-
if ('stdDevP' === aggregation) return '总体标准差';
|
|
571
|
-
if ('var' === aggregation) return '样本方差';
|
|
572
|
-
if ('varP' === aggregation) return '总体方差';
|
|
573
|
-
return '求和';
|
|
574
|
-
}
|
|
575
|
-
function pivotHeaderCell(value) {
|
|
576
|
-
return {
|
|
577
|
-
v: value,
|
|
578
|
-
m: value,
|
|
579
|
-
bl: 1,
|
|
580
|
-
bg: '#e7f1ec',
|
|
581
|
-
fc: '#173d2b'
|
|
591
|
+
const ownerSheetId = createWorkId('sheet');
|
|
592
|
+
const ownerSheet = {
|
|
593
|
+
id: ownerSheetId,
|
|
594
|
+
name: nextSheetName(content, '数据透视表'),
|
|
595
|
+
order: content.sheets.length,
|
|
596
|
+
status: 0,
|
|
597
|
+
row: 40,
|
|
598
|
+
column: 12,
|
|
599
|
+
data: Array.from({
|
|
600
|
+
length: 40
|
|
601
|
+
}, ()=>Array(12).fill(null)),
|
|
602
|
+
config: {},
|
|
603
|
+
pivotTables: [
|
|
604
|
+
draftPivot
|
|
605
|
+
]
|
|
582
606
|
};
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
607
|
+
const withReport = {
|
|
608
|
+
...content,
|
|
609
|
+
sheets: [
|
|
610
|
+
...content.sheets,
|
|
611
|
+
ownerSheet
|
|
612
|
+
]
|
|
613
|
+
};
|
|
614
|
+
const validation = spreadsheetPivotValidation(withReport, ownerSheetId, draftPivot);
|
|
615
|
+
if (!validation.valid) return {
|
|
616
|
+
content,
|
|
617
|
+
error: validation.message
|
|
589
618
|
};
|
|
590
|
-
|
|
591
|
-
function pivotValueCell(value, total) {
|
|
592
|
-
if (null === value) return total ? {
|
|
593
|
-
v: '',
|
|
594
|
-
m: '',
|
|
595
|
-
bl: 1,
|
|
596
|
-
bg: '#f3f7f5'
|
|
597
|
-
} : null;
|
|
619
|
+
const next = refreshSpreadsheetPivotTables(withReport);
|
|
598
620
|
return {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
bg: total ? '#f3f7f5' : void 0,
|
|
603
|
-
ht: 2
|
|
621
|
+
content: next,
|
|
622
|
+
ownerSheetId,
|
|
623
|
+
pivotId: draftPivot.id
|
|
604
624
|
};
|
|
605
625
|
}
|
|
606
|
-
function
|
|
607
|
-
return
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
626
|
+
function refreshSpreadsheetPivotTables(content) {
|
|
627
|
+
if (!spreadsheetPivotCount(content)) return content;
|
|
628
|
+
const sheets = content.sheets.map((sheet)=>({
|
|
629
|
+
...sheet,
|
|
630
|
+
pivotTables: sheet.pivotTables?.map((pivot)=>({
|
|
631
|
+
...pivot,
|
|
632
|
+
rowFields: [
|
|
633
|
+
...pivot.rowFields
|
|
634
|
+
],
|
|
635
|
+
columnFields: [
|
|
636
|
+
...pivot.columnFields
|
|
637
|
+
],
|
|
638
|
+
reportFilters: pivot.reportFilters?.map((filter)=>({
|
|
639
|
+
...filter
|
|
640
|
+
})),
|
|
641
|
+
values: pivot.values.map((value)=>({
|
|
642
|
+
...value
|
|
643
|
+
}))
|
|
644
|
+
}))
|
|
645
|
+
}));
|
|
646
|
+
const next = {
|
|
647
|
+
...content,
|
|
648
|
+
sheets
|
|
612
649
|
};
|
|
650
|
+
for (const ownerSheet of sheets){
|
|
651
|
+
if (!ownerSheet.pivotTables?.length || !ownerSheet.id) continue;
|
|
652
|
+
let data = ownerSheet.data;
|
|
653
|
+
let dataChanged = false;
|
|
654
|
+
for (const pivot of ownerSheet.pivotTables){
|
|
655
|
+
const resolved = resolvePivot(next, ownerSheet.id, pivot);
|
|
656
|
+
if (resolved) {
|
|
657
|
+
if (!dataChanged) {
|
|
658
|
+
data = cloneCellMatrix(ownerSheet.data);
|
|
659
|
+
ownerSheet.data = data;
|
|
660
|
+
dataChanged = true;
|
|
661
|
+
}
|
|
662
|
+
clearRange(data, singleRange(pivot.outputReference ?? ''));
|
|
663
|
+
writePivotOutput(data, resolved.outputBounds, resolved.output);
|
|
664
|
+
pivot.outputReference = resolved.outputReference;
|
|
665
|
+
ownerSheet.row = Math.max(ownerSheet.row ?? 0, resolved.outputBounds.endRow + 1, data.length);
|
|
666
|
+
ownerSheet.column = Math.max(ownerSheet.column ?? 0, resolved.outputBounds.endColumn + 1);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return next;
|
|
613
671
|
}
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
672
|
+
function reconcileSpreadsheetPivots(content, changedSheets) {
|
|
673
|
+
const sourceById = new Map(content.sheets.flatMap((sheet)=>sheet.id ? [
|
|
674
|
+
[
|
|
675
|
+
sheet.id,
|
|
676
|
+
sheet
|
|
677
|
+
]
|
|
678
|
+
] : []));
|
|
679
|
+
const sheets = changedSheets.map((sheet)=>{
|
|
680
|
+
const source = sheet.id ? sourceById.get(sheet.id) : void 0;
|
|
681
|
+
return source?.pivotTables?.length ? {
|
|
682
|
+
...sheet,
|
|
683
|
+
pivotTables: source.pivotTables
|
|
684
|
+
} : sheet;
|
|
685
|
+
});
|
|
686
|
+
return refreshSpreadsheetPivotTables({
|
|
687
|
+
...content,
|
|
688
|
+
sheets
|
|
689
|
+
});
|
|
622
690
|
}
|
|
623
|
-
function
|
|
691
|
+
function deleteSpreadsheetPivotTable(content, ownerSheetId, pivotId) {
|
|
692
|
+
const sheets = content.sheets.map((sheet)=>{
|
|
693
|
+
if (sheet.id !== ownerSheetId) return sheet;
|
|
694
|
+
const pivot = sheet.pivotTables?.find((candidate)=>candidate.id === pivotId);
|
|
695
|
+
if (!pivot) return sheet;
|
|
696
|
+
const data = cloneCellMatrix(sheet.data);
|
|
697
|
+
clearRange(data, singleRange(pivot.outputReference ?? ''));
|
|
698
|
+
const pivotTables = (sheet.pivotTables ?? []).filter((candidate)=>candidate.id !== pivotId);
|
|
699
|
+
return {
|
|
700
|
+
...sheet,
|
|
701
|
+
data,
|
|
702
|
+
pivotTables: pivotTables.length ? pivotTables : void 0
|
|
703
|
+
};
|
|
704
|
+
});
|
|
624
705
|
return {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
bg: '#f3f7f5'
|
|
706
|
+
...content,
|
|
707
|
+
sheets
|
|
628
708
|
};
|
|
629
709
|
}
|
|
630
|
-
function
|
|
631
|
-
|
|
710
|
+
function spreadsheetPivotValidation(content, ownerSheetId, pivot) {
|
|
711
|
+
const failure = pivotFailure(content, ownerSheetId, pivot);
|
|
712
|
+
if (failure) return failure;
|
|
713
|
+
const resolved = resolvePivot(content, ownerSheetId, pivot);
|
|
714
|
+
return resolved ? {
|
|
715
|
+
valid: true,
|
|
716
|
+
outputReference: resolved.outputReference
|
|
717
|
+
} : {
|
|
718
|
+
valid: false,
|
|
719
|
+
code: 'pivot.invalid',
|
|
720
|
+
message: '无法根据当前字段和源数据生成透视表。'
|
|
721
|
+
};
|
|
632
722
|
}
|
|
633
|
-
function
|
|
634
|
-
return (
|
|
723
|
+
function spreadsheetPivotOutputContains(sheet, row, column) {
|
|
724
|
+
return (sheet.pivotTables ?? []).some((pivot)=>{
|
|
725
|
+
const bounds = singleRange(pivot.outputReference ?? '');
|
|
726
|
+
return Boolean(bounds && containsCell(bounds, row, column));
|
|
727
|
+
});
|
|
635
728
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return content.sheets.reduce((count, sheet)=>count + (sheet.pivotTables?.length ?? 0), 0);
|
|
729
|
+
function spreadsheetPivotIntersects(sheet, range) {
|
|
730
|
+
return (sheet.pivotTables ?? []).filter((pivot)=>{
|
|
731
|
+
const bounds = singleRange(pivot.outputReference ?? '');
|
|
732
|
+
return Boolean(bounds && rangesOverlap(bounds, range));
|
|
733
|
+
});
|
|
642
734
|
}
|
|
643
|
-
function
|
|
735
|
+
function resolvePivot(content, ownerSheetId, pivot) {
|
|
736
|
+
const failure = pivotFailure(content, ownerSheetId, pivot);
|
|
737
|
+
if (failure) return null;
|
|
738
|
+
const ownerSheet = content.sheets.find((sheet)=>sheet.id === ownerSheetId);
|
|
644
739
|
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
const fields =
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
numeric
|
|
666
|
-
});
|
|
740
|
+
const sourceBounds = singleRange(pivot.sourceReference);
|
|
741
|
+
const anchor = singleRange(pivot.anchor);
|
|
742
|
+
const fields = spreadsheetPivotFields(content, pivot);
|
|
743
|
+
const output = buildSpreadsheetPivotOutput(sourceSheet, sourceBounds, fields, pivot);
|
|
744
|
+
if (!output.length || !output[0]?.length) return null;
|
|
745
|
+
const outputBounds = {
|
|
746
|
+
startRow: anchor.startRow,
|
|
747
|
+
endRow: anchor.startRow + output.length - 1,
|
|
748
|
+
startColumn: anchor.startColumn,
|
|
749
|
+
endColumn: anchor.startColumn + output[0].length - 1
|
|
750
|
+
};
|
|
751
|
+
const outputReference = formatBounds(outputBounds);
|
|
752
|
+
if (output.length * output[0].length > MAXIMUM_OUTPUT_CELLS) return null;
|
|
753
|
+
if (outputBounds.endRow > MAXIMUM_XLSX_ROW || outputBounds.endColumn > MAXIMUM_XLSX_COLUMN) return null;
|
|
754
|
+
if (ownerSheet.id === sourceSheet.id && rangesOverlap(sourceBounds, outputBounds)) return null;
|
|
755
|
+
const oldOutput = singleRange(pivot.outputReference ?? '');
|
|
756
|
+
for (const other of ownerSheet.pivotTables ?? []){
|
|
757
|
+
if (other.id === pivot.id) continue;
|
|
758
|
+
const otherOutput = singleRange(other.outputReference ?? '');
|
|
759
|
+
if (otherOutput && rangesOverlap(otherOutput, outputBounds)) return null;
|
|
667
760
|
}
|
|
668
|
-
|
|
761
|
+
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput && containsCell(oldOutput, row, column))) {
|
|
762
|
+
if (ownerSheet.data?.[row]?.[column]) return null;
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
ownerSheet,
|
|
766
|
+
sourceSheet,
|
|
767
|
+
sourceBounds,
|
|
768
|
+
outputBounds,
|
|
769
|
+
outputReference,
|
|
770
|
+
fields,
|
|
771
|
+
output
|
|
772
|
+
};
|
|
669
773
|
}
|
|
670
|
-
function
|
|
774
|
+
function pivotFailure(content, ownerSheetId, pivot) {
|
|
775
|
+
const ownerSheet = content.sheets.find((sheet)=>sheet.id === ownerSheetId);
|
|
671
776
|
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
return
|
|
777
|
+
if (!ownerSheet || !sourceSheet) return invalid('pivot.source-sheet-missing', '找不到透视表的源工作表或目标工作表。');
|
|
778
|
+
const sourceBounds = singleRange(pivot.sourceReference);
|
|
779
|
+
if (!sourceBounds || sourceBounds.endRow <= sourceBounds.startRow) return invalid('pivot.source-reference-invalid', '源区域必须是包含标题和数据的连续单元格范围。');
|
|
780
|
+
const sourceCellCount = (sourceBounds.endRow - sourceBounds.startRow + 1) * (sourceBounds.endColumn - sourceBounds.startColumn + 1);
|
|
781
|
+
if (sourceCellCount > MAXIMUM_SOURCE_CELLS) return invalid('pivot.source-too-large', `源区域不能超过 ${MAXIMUM_SOURCE_CELLS.toLocaleString()} 个单元格。`);
|
|
782
|
+
const anchor = singleRange(pivot.anchor);
|
|
783
|
+
if (!anchor || anchor.startRow !== anchor.endRow || anchor.startColumn !== anchor.endColumn) return invalid('pivot.anchor-invalid', '输出位置必须是一个 A1 单元格地址。');
|
|
784
|
+
const fields = spreadsheetPivotFields(content, pivot);
|
|
785
|
+
if (!fields.length) return invalid('pivot.headers-invalid', '源区域的标题必须非空且不重复。');
|
|
786
|
+
const reportFilters = pivot.reportFilters ?? [];
|
|
787
|
+
const reportFilterFields = reportFilters.map((filter)=>filter.fieldIndex);
|
|
788
|
+
if (new Set(reportFilterFields).size !== reportFilterFields.length || reportFilterFields.some((index)=>!fields[index]) || reportFilterFields.some((index)=>pivot.rowFields.includes(index) || pivot.columnFields.includes(index))) return invalid('pivot.filters-invalid', '报表筛选字段必须存在、互不重复,且不能同时作为行或列字段。');
|
|
789
|
+
for (const filter of reportFilters)if (void 0 !== filter.selectedItem && !spreadsheetPivotFilterItems(content, pivot, filter.fieldIndex).some((item)=>spreadsheetPivotFilterValueKey(item.value) === spreadsheetPivotFilterValueKey(filter.selectedItem))) return invalid('pivot.filter-item-invalid', '报表筛选器选择的项目已不在源数据中。');
|
|
790
|
+
const dimensionFields = [
|
|
791
|
+
...pivot.rowFields,
|
|
792
|
+
...pivot.columnFields,
|
|
793
|
+
...reportFilterFields
|
|
794
|
+
];
|
|
795
|
+
if (!pivot.rowFields.length) return invalid('pivot.rows-empty', '至少选择一个行字段。');
|
|
796
|
+
if (!pivot.values.length) return invalid('pivot.values-empty', '至少选择一个值字段。');
|
|
797
|
+
if (new Set(dimensionFields).size !== dimensionFields.length || dimensionFields.some((index)=>!fields[index]) || pivot.values.some((value)=>!fields[value.fieldIndex])) return invalid('pivot.fields-invalid', '透视表包含重复、缺失或超出源区域的字段。');
|
|
798
|
+
const output = buildSpreadsheetPivotOutput(sourceSheet, sourceBounds, fields, pivot);
|
|
799
|
+
if (!output.length || !output[0]?.length) return invalid('pivot.output-empty', '当前源数据不能生成透视表结果。');
|
|
800
|
+
if (output.length * output[0].length > MAXIMUM_OUTPUT_CELLS) return invalid('pivot.output-too-large', `透视表结果不能超过 ${MAXIMUM_OUTPUT_CELLS.toLocaleString()} 个单元格。`);
|
|
801
|
+
const outputBounds = {
|
|
802
|
+
startRow: anchor.startRow,
|
|
803
|
+
endRow: anchor.startRow + output.length - 1,
|
|
804
|
+
startColumn: anchor.startColumn,
|
|
805
|
+
endColumn: anchor.startColumn + output[0].length - 1
|
|
806
|
+
};
|
|
807
|
+
if (outputBounds.endRow > MAXIMUM_XLSX_ROW || outputBounds.endColumn > MAXIMUM_XLSX_COLUMN) return invalid('pivot.output-out-of-bounds', '透视表结果超出 XLSX 工作表边界。');
|
|
808
|
+
if (ownerSheet.id === sourceSheet.id && rangesOverlap(sourceBounds, outputBounds)) return invalid('pivot.output-overlaps-source', '透视表输出区域与源数据重叠,请改用其他位置或工作表。');
|
|
809
|
+
if (Object.values(ownerSheet.config?.merge ?? {}).some((merge)=>rangesOverlap(outputBounds, {
|
|
810
|
+
startRow: merge.r,
|
|
811
|
+
endRow: merge.r + merge.rs - 1,
|
|
812
|
+
startColumn: merge.c,
|
|
813
|
+
endColumn: merge.c + merge.cs - 1
|
|
814
|
+
}))) return invalid('pivot.output-overlaps-merge', '透视表输出区域与合并单元格重叠。');
|
|
815
|
+
const oldOutput = singleRange(pivot.outputReference ?? '');
|
|
816
|
+
for (const other of ownerSheet.pivotTables ?? []){
|
|
817
|
+
if (other.id === pivot.id) continue;
|
|
818
|
+
const otherOutput = singleRange(other.outputReference ?? '');
|
|
819
|
+
if (otherOutput && rangesOverlap(otherOutput, outputBounds)) return invalid('pivot.output-overlaps-pivot', '透视表输出区域与另一个透视表重叠。');
|
|
820
|
+
}
|
|
821
|
+
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput && containsCell(oldOutput, row, column))) {
|
|
822
|
+
if (ownerSheet.data?.[row]?.[column]) return invalid('pivot.output-not-empty', '透视表输出区域包含现有内容,请选择空白位置。');
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
675
825
|
}
|
|
676
|
-
function
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
826
|
+
function uniqueFieldNames(fields) {
|
|
827
|
+
const names = fields.map((field)=>field.name.trim().toLocaleLowerCase());
|
|
828
|
+
return names.every(Boolean) && new Set(names).size === names.length;
|
|
829
|
+
}
|
|
830
|
+
function singleRange(reference) {
|
|
831
|
+
const ranges = parseSpreadsheetCellRanges(reference);
|
|
832
|
+
if (ranges?.length !== 1) return null;
|
|
833
|
+
return {
|
|
834
|
+
startRow: ranges[0].row[0],
|
|
835
|
+
endRow: ranges[0].row[1],
|
|
836
|
+
startColumn: ranges[0].column[0],
|
|
837
|
+
endColumn: ranges[0].column[1]
|
|
682
838
|
};
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
839
|
+
}
|
|
840
|
+
function selectionBounds(selection) {
|
|
841
|
+
if (selection.row.length < 2 || selection.column.length < 2) return null;
|
|
842
|
+
return {
|
|
843
|
+
startRow: Math.max(0, Math.min(selection.row[0], selection.row[1])),
|
|
844
|
+
endRow: Math.max(0, Math.max(selection.row[0], selection.row[1])),
|
|
845
|
+
startColumn: Math.max(0, Math.min(selection.column[0], selection.column[1])),
|
|
846
|
+
endColumn: Math.max(0, Math.max(selection.column[0], selection.column[1]))
|
|
691
847
|
};
|
|
692
|
-
|
|
848
|
+
}
|
|
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([
|
|
693
857
|
{
|
|
694
858
|
row: [
|
|
695
859
|
bounds.startRow,
|
|
@@ -701,363 +865,571 @@ function createSpreadsheetPivotFromSelection(content, sourceSheetId, selection)
|
|
|
701
865
|
]
|
|
702
866
|
}
|
|
703
867
|
]);
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
|
730
906
|
};
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
|
760
995
|
};
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
|
767
1004
|
};
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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)
|
|
772
1084
|
};
|
|
773
|
-
|
|
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;
|
|
774
1093
|
return {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
pivotId: draftPivot.id
|
|
1094
|
+
row,
|
|
1095
|
+
column: column - 1
|
|
778
1096
|
};
|
|
779
1097
|
}
|
|
780
|
-
function
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
}))
|
|
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;
|
|
799
1116
|
}));
|
|
800
|
-
const next = {
|
|
801
|
-
...content,
|
|
802
|
-
sheets
|
|
803
|
-
};
|
|
804
|
-
for (const ownerSheet of sheets){
|
|
805
|
-
if (!ownerSheet.pivotTables?.length || !ownerSheet.id) continue;
|
|
806
|
-
let data = ownerSheet.data;
|
|
807
|
-
let dataChanged = false;
|
|
808
|
-
for (const pivot of ownerSheet.pivotTables){
|
|
809
|
-
const resolved = resolvePivot(next, ownerSheet.id, pivot);
|
|
810
|
-
if (resolved) {
|
|
811
|
-
if (!dataChanged) {
|
|
812
|
-
data = cloneCellMatrix(ownerSheet.data);
|
|
813
|
-
ownerSheet.data = data;
|
|
814
|
-
dataChanged = true;
|
|
815
|
-
}
|
|
816
|
-
clearRange(data, singleRange(pivot.outputReference ?? ''));
|
|
817
|
-
writePivotOutput(data, resolved.outputBounds, resolved.output);
|
|
818
|
-
pivot.outputReference = resolved.outputReference;
|
|
819
|
-
ownerSheet.row = Math.max(ownerSheet.row ?? 0, resolved.outputBounds.endRow + 1, data.length);
|
|
820
|
-
ownerSheet.column = Math.max(ownerSheet.column ?? 0, resolved.outputBounds.endColumn + 1);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
return next;
|
|
825
1117
|
}
|
|
826
|
-
function
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
sheet.id,
|
|
830
|
-
sheet
|
|
831
|
-
]
|
|
832
|
-
] : []));
|
|
833
|
-
const sheets = changedSheets.map((sheet)=>{
|
|
834
|
-
const source = sheet.id ? sourceById.get(sheet.id) : void 0;
|
|
835
|
-
return source?.pivotTables?.length ? {
|
|
836
|
-
...sheet,
|
|
837
|
-
pivotTables: source.pivotTables
|
|
838
|
-
} : sheet;
|
|
839
|
-
});
|
|
840
|
-
return refreshSpreadsheetPivotTables({
|
|
841
|
-
...content,
|
|
842
|
-
sheets
|
|
843
|
-
});
|
|
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)));
|
|
844
1121
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
};
|
|
858
|
-
});
|
|
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');
|
|
859
1134
|
return {
|
|
860
|
-
|
|
861
|
-
|
|
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')
|
|
862
1141
|
};
|
|
863
1142
|
}
|
|
864
|
-
function
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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)
|
|
875
1187
|
};
|
|
876
1188
|
}
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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;
|
|
882
1197
|
}
|
|
883
|
-
function
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return Boolean(bounds && rangesOverlap(bounds, range));
|
|
887
|
-
});
|
|
1198
|
+
function nonNegativeNumber(value) {
|
|
1199
|
+
const parsed = Number(value);
|
|
1200
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
888
1201
|
}
|
|
889
|
-
function
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const outputReference = formatBounds(outputBounds);
|
|
906
|
-
if (output.length * output[0].length > MAXIMUM_OUTPUT_CELLS) return null;
|
|
907
|
-
if (outputBounds.endRow > MAXIMUM_XLSX_ROW || outputBounds.endColumn > MAXIMUM_XLSX_COLUMN) return null;
|
|
908
|
-
if (ownerSheet.id === sourceSheet.id && rangesOverlap(sourceBounds, outputBounds)) return null;
|
|
909
|
-
const oldOutput = singleRange(pivot.outputReference ?? '');
|
|
910
|
-
for (const other of ownerSheet.pivotTables ?? []){
|
|
911
|
-
if (other.id === pivot.id) continue;
|
|
912
|
-
const otherOutput = singleRange(other.outputReference ?? '');
|
|
913
|
-
if (otherOutput && rangesOverlap(otherOutput, outputBounds)) return null;
|
|
914
|
-
}
|
|
915
|
-
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput && containsCell(oldOutput, row, column))) {
|
|
916
|
-
if (ownerSheet.data?.[row]?.[column]) return null;
|
|
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;
|
|
917
1218
|
}
|
|
918
1219
|
return {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
sourceBounds,
|
|
922
|
-
outputBounds,
|
|
923
|
-
outputReference,
|
|
924
|
-
fields,
|
|
925
|
-
output
|
|
1220
|
+
index,
|
|
1221
|
+
offsetEmu: Math.round(remaining * XLSX_EMU_PER_PIXEL)
|
|
926
1222
|
};
|
|
927
1223
|
}
|
|
928
|
-
function
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (sourceCellCount > MAXIMUM_SOURCE_CELLS) return invalid('pivot.source-too-large', `源区域不能超过 ${MAXIMUM_SOURCE_CELLS.toLocaleString()} 个单元格。`);
|
|
936
|
-
const anchor = singleRange(pivot.anchor);
|
|
937
|
-
if (!anchor || anchor.startRow !== anchor.endRow || anchor.startColumn !== anchor.endColumn) return invalid('pivot.anchor-invalid', '输出位置必须是一个 A1 单元格地址。');
|
|
938
|
-
const fields = spreadsheetPivotFields(content, pivot);
|
|
939
|
-
if (!fields.length) return invalid('pivot.headers-invalid', '源区域的标题必须非空且不重复。');
|
|
940
|
-
const reportFilters = pivot.reportFilters ?? [];
|
|
941
|
-
const reportFilterFields = reportFilters.map((filter)=>filter.fieldIndex);
|
|
942
|
-
if (new Set(reportFilterFields).size !== reportFilterFields.length || reportFilterFields.some((index)=>!fields[index]) || reportFilterFields.some((index)=>pivot.rowFields.includes(index) || pivot.columnFields.includes(index))) return invalid('pivot.filters-invalid', '报表筛选字段必须存在、互不重复,且不能同时作为行或列字段。');
|
|
943
|
-
for (const filter of reportFilters)if (void 0 !== filter.selectedItem && !spreadsheetPivotFilterItems(content, pivot, filter.fieldIndex).some((item)=>spreadsheetPivotFilterValueKey(item.value) === spreadsheetPivotFilterValueKey(filter.selectedItem))) return invalid('pivot.filter-item-invalid', '报表筛选器选择的项目已不在源数据中。');
|
|
944
|
-
const dimensionFields = [
|
|
945
|
-
...pivot.rowFields,
|
|
946
|
-
...pivot.columnFields,
|
|
947
|
-
...reportFilterFields
|
|
948
|
-
];
|
|
949
|
-
if (!pivot.rowFields.length) return invalid('pivot.rows-empty', '至少选择一个行字段。');
|
|
950
|
-
if (!pivot.values.length) return invalid('pivot.values-empty', '至少选择一个值字段。');
|
|
951
|
-
if (new Set(dimensionFields).size !== dimensionFields.length || dimensionFields.some((index)=>!fields[index]) || pivot.values.some((value)=>!fields[value.fieldIndex])) return invalid('pivot.fields-invalid', '透视表包含重复、缺失或超出源区域的字段。');
|
|
952
|
-
const output = buildSpreadsheetPivotOutput(sourceSheet, sourceBounds, fields, pivot);
|
|
953
|
-
if (!output.length || !output[0]?.length) return invalid('pivot.output-empty', '当前源数据不能生成透视表结果。');
|
|
954
|
-
if (output.length * output[0].length > MAXIMUM_OUTPUT_CELLS) return invalid('pivot.output-too-large', `透视表结果不能超过 ${MAXIMUM_OUTPUT_CELLS.toLocaleString()} 个单元格。`);
|
|
955
|
-
const outputBounds = {
|
|
956
|
-
startRow: anchor.startRow,
|
|
957
|
-
endRow: anchor.startRow + output.length - 1,
|
|
958
|
-
startColumn: anchor.startColumn,
|
|
959
|
-
endColumn: anchor.startColumn + output[0].length - 1
|
|
960
|
-
};
|
|
961
|
-
if (outputBounds.endRow > MAXIMUM_XLSX_ROW || outputBounds.endColumn > MAXIMUM_XLSX_COLUMN) return invalid('pivot.output-out-of-bounds', '透视表结果超出 XLSX 工作表边界。');
|
|
962
|
-
if (ownerSheet.id === sourceSheet.id && rangesOverlap(sourceBounds, outputBounds)) return invalid('pivot.output-overlaps-source', '透视表输出区域与源数据重叠,请改用其他位置或工作表。');
|
|
963
|
-
if (Object.values(ownerSheet.config?.merge ?? {}).some((merge)=>rangesOverlap(outputBounds, {
|
|
964
|
-
startRow: merge.r,
|
|
965
|
-
endRow: merge.r + merge.rs - 1,
|
|
966
|
-
startColumn: merge.c,
|
|
967
|
-
endColumn: merge.c + merge.cs - 1
|
|
968
|
-
}))) return invalid('pivot.output-overlaps-merge', '透视表输出区域与合并单元格重叠。');
|
|
969
|
-
const oldOutput = singleRange(pivot.outputReference ?? '');
|
|
970
|
-
for (const other of ownerSheet.pivotTables ?? []){
|
|
971
|
-
if (other.id === pivot.id) continue;
|
|
972
|
-
const otherOutput = singleRange(other.outputReference ?? '');
|
|
973
|
-
if (otherOutput && rangesOverlap(otherOutput, outputBounds)) return invalid('pivot.output-overlaps-pivot', '透视表输出区域与另一个透视表重叠。');
|
|
974
|
-
}
|
|
975
|
-
for(let row = outputBounds.startRow; row <= outputBounds.endRow; row += 1)for(let column = outputBounds.startColumn; column <= outputBounds.endColumn; column += 1)if (!(oldOutput && containsCell(oldOutput, row, column))) {
|
|
976
|
-
if (ownerSheet.data?.[row]?.[column]) return invalid('pivot.output-not-empty', '透视表输出区域包含现有内容,请选择空白位置。');
|
|
977
|
-
}
|
|
978
|
-
return null;
|
|
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('');
|
|
979
1231
|
}
|
|
980
|
-
function
|
|
981
|
-
|
|
982
|
-
return names.every(Boolean) && new Set(names).size === names.length;
|
|
1232
|
+
function roundPosition(value) {
|
|
1233
|
+
return Math.round(1000 * value) / 1000;
|
|
983
1234
|
}
|
|
984
|
-
function
|
|
985
|
-
const
|
|
986
|
-
if (
|
|
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);
|
|
987
1286
|
return {
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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
|
+
} : {}
|
|
992
1308
|
};
|
|
993
1309
|
}
|
|
994
|
-
function
|
|
995
|
-
if (
|
|
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;
|
|
996
1331
|
return {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
+
} : {}
|
|
1001
1353
|
};
|
|
1002
1354
|
}
|
|
1003
|
-
function
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
return
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
bounds.startRow,
|
|
1014
|
-
bounds.endRow
|
|
1015
|
-
],
|
|
1016
|
-
column: [
|
|
1017
|
-
bounds.startColumn,
|
|
1018
|
-
bounds.endColumn
|
|
1019
|
-
]
|
|
1020
|
-
}
|
|
1021
|
-
]);
|
|
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 '最佳匹配';
|
|
1022
1365
|
}
|
|
1023
|
-
function
|
|
1024
|
-
return
|
|
1025
|
-
...row
|
|
1026
|
-
]);
|
|
1366
|
+
function normalizeWorkSpreadsheetTrendlineType(value) {
|
|
1367
|
+
return 'exponential' === value || 'logarithmic' === value || 'polynomial' === value || 'power' === value || 'movingAverage' === value || 'linear' === value ? value : 'linear';
|
|
1027
1368
|
}
|
|
1028
|
-
function
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
+
};
|
|
1031
1404
|
}
|
|
1032
|
-
function
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
for(let column = 0; column < width; column += 1)target[bounds.startColumn + column] = output[row][column] ?? null;
|
|
1040
|
-
}
|
|
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 '线性';
|
|
1041
1412
|
}
|
|
1042
|
-
function
|
|
1043
|
-
const
|
|
1044
|
-
if (!
|
|
1045
|
-
|
|
1046
|
-
while(existing.has(`${base} ${index}`.toLocaleLowerCase()))index += 1;
|
|
1047
|
-
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)));
|
|
1048
1417
|
}
|
|
1049
|
-
function
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
while(existing.has(`pivottable${index}`))index += 1;
|
|
1053
|
-
return `PivotTable${index}`;
|
|
1418
|
+
function normalizedNonNegativeNumber(value) {
|
|
1419
|
+
const number = Number(value);
|
|
1420
|
+
return Number.isFinite(number) ? Math.max(0, number) : 0;
|
|
1054
1421
|
}
|
|
1055
|
-
function
|
|
1056
|
-
return
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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 '柱形图';
|
|
1061
1433
|
}
|
|
1062
1434
|
const WORK_SPREADSHEET_CHART_COLORS = [
|
|
1063
1435
|
'#4f6bed',
|
|
@@ -3081,144 +3453,37 @@ function horizontalBarChartSvg(chart, plot, layout, values) {
|
|
|
3081
3453
|
valueX: endX,
|
|
3082
3454
|
baselineX: startX,
|
|
3083
3455
|
value: point.rawValue
|
|
3084
|
-
})}`;
|
|
3085
|
-
}).join('');
|
|
3086
|
-
const errorBars = layout.stacked ? '' : spreadsheetSeriesErrorBarsSvg(chart, seriesIndex, (_direction, pointIndex, value)=>[
|
|
3087
|
-
valuePosition(value),
|
|
3088
|
-
seriesY(pointIndex) + renderedHeight / 2
|
|
3089
|
-
]);
|
|
3090
|
-
return `${seriesBars}${errorBars}`;
|
|
3091
|
-
}).join('');
|
|
3092
|
-
const trendlines = layout.stacked ? '' : chart.series.map((series, seriesIndex)=>spreadsheetSeriesTrendlinesSvg(series, seriesIndex, categoryTrendlineXValues(series), (category, value)=>[
|
|
3093
|
-
valuePosition(value),
|
|
3094
|
-
plot.y + (spreadsheetChartCategoryVisualIndex(category - 1, categoryCount, categoryAxis) + 0.5) / categoryCount * plot.height
|
|
3095
|
-
])).join('');
|
|
3096
|
-
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}`;
|
|
3097
|
-
}
|
|
3098
|
-
function categoryLabelsSvg(chart, plot, axisY) {
|
|
3099
|
-
const categoryCount = Math.max(1, chart.categories.length, ...chart.series.map((series)=>series.values.length));
|
|
3100
|
-
const axis = chart.axes?.bottom;
|
|
3101
|
-
const labels = Array.from({
|
|
3102
|
-
length: categoryCount
|
|
3103
|
-
}, (_, index)=>{
|
|
3104
|
-
const visible = spreadsheetChartCategoryLabelVisible(index, categoryCount, axis);
|
|
3105
|
-
const label = chart.categories[index] ?? String(index + 1);
|
|
3106
|
-
const visualIndex = spreadsheetChartCategoryVisualIndex(index, categoryCount, axis);
|
|
3107
|
-
const x = plot.x + (visualIndex + 0.5) / categoryCount * plot.width;
|
|
3108
|
-
const labelLayout = spreadsheetChartAxisLabelLayout(axis, chart.type, 'bottom', plot, x, axisY);
|
|
3109
|
-
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>` : ''}`;
|
|
3110
|
-
}).join('');
|
|
3111
|
-
return `<g ${spreadsheetChartAxisDisplayAttributes(axis, chart.type, 'bottom')}>${labels}</g>`;
|
|
3112
|
-
}
|
|
3113
|
-
function emptyChartSvg(plot, message) {
|
|
3114
|
-
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>`;
|
|
3115
|
-
}
|
|
3116
|
-
const XLSX_EMU_PER_PIXEL = 9525;
|
|
3117
|
-
const DEFAULT_COLUMN_WIDTH = 96;
|
|
3118
|
-
const DEFAULT_ROW_HEIGHT = 24;
|
|
3119
|
-
function readXlsxDrawingAnchor(anchor) {
|
|
3120
|
-
const extent = directChild(anchor, 'ext');
|
|
3121
|
-
const position = directChild(anchor, 'pos');
|
|
3122
|
-
return {
|
|
3123
|
-
from: readMarker(directChild(anchor, 'from')),
|
|
3124
|
-
to: readMarker(directChild(anchor, 'to')),
|
|
3125
|
-
leftEmu: numberAttribute(position, 'x'),
|
|
3126
|
-
topEmu: numberAttribute(position, 'y'),
|
|
3127
|
-
widthEmu: numberAttribute(extent, 'cx'),
|
|
3128
|
-
heightEmu: numberAttribute(extent, 'cy')
|
|
3129
|
-
};
|
|
3130
|
-
}
|
|
3131
|
-
function xlsxDrawingAnchorToBounds(anchor, config) {
|
|
3132
|
-
const left = anchor.from ? markerPosition(anchor.from.column, anchor.from.columnOffsetEmu, (index)=>spreadsheetColumnWidth(config, index)) : emuToPixels(anchor.leftEmu);
|
|
3133
|
-
const top = anchor.from ? markerPosition(anchor.from.row, anchor.from.rowOffsetEmu, (index)=>spreadsheetRowHeight(config, index)) : emuToPixels(anchor.topEmu);
|
|
3134
|
-
const right = anchor.to ? markerPosition(anchor.to.column, anchor.to.columnOffsetEmu, (index)=>spreadsheetColumnWidth(config, index)) : left + emuToPixels(anchor.widthEmu);
|
|
3135
|
-
const bottom = anchor.to ? markerPosition(anchor.to.row, anchor.to.rowOffsetEmu, (index)=>spreadsheetRowHeight(config, index)) : top + emuToPixels(anchor.heightEmu);
|
|
3136
|
-
if (![
|
|
3137
|
-
left,
|
|
3138
|
-
top,
|
|
3139
|
-
right,
|
|
3140
|
-
bottom
|
|
3141
|
-
].every(Number.isFinite)) return null;
|
|
3142
|
-
return {
|
|
3143
|
-
left: roundPosition(Math.max(0, left)),
|
|
3144
|
-
top: roundPosition(Math.max(0, top)),
|
|
3145
|
-
width: roundPosition(Math.max(1, right - left)),
|
|
3146
|
-
height: roundPosition(Math.max(1, bottom - top))
|
|
3147
|
-
};
|
|
3148
|
-
}
|
|
3149
|
-
function xlsxTwoCellAnchorMarkers(bounds, sheet) {
|
|
3150
|
-
const fromColumn = axisMarker(bounds.left, (index)=>spreadsheetColumnWidth(sheet.config, index));
|
|
3151
|
-
const toColumn = axisMarker(bounds.left + bounds.width, (index)=>spreadsheetColumnWidth(sheet.config, index));
|
|
3152
|
-
const fromRow = axisMarker(bounds.top, (index)=>spreadsheetRowHeight(sheet.config, index));
|
|
3153
|
-
const toRow = axisMarker(bounds.top + bounds.height, (index)=>spreadsheetRowHeight(sheet.config, index));
|
|
3154
|
-
return `${markerXml('from', fromColumn, fromRow)}${markerXml('to', toColumn, toRow)}`;
|
|
3155
|
-
}
|
|
3156
|
-
function spreadsheetColumnWidth(config, index) {
|
|
3157
|
-
const key = String(index);
|
|
3158
|
-
if (Object.hasOwn(config?.colhidden ?? {}, key)) return 0;
|
|
3159
|
-
const width = config?.columnlen?.[key];
|
|
3160
|
-
return 'number' == typeof width && width > 0 ? width : DEFAULT_COLUMN_WIDTH;
|
|
3161
|
-
}
|
|
3162
|
-
function spreadsheetRowHeight(config, index) {
|
|
3163
|
-
const key = String(index);
|
|
3164
|
-
if (Object.hasOwn(config?.rowhidden ?? {}, key)) return 0;
|
|
3165
|
-
const height = config?.rowlen?.[key];
|
|
3166
|
-
return 'number' == typeof height && height > 0 ? height : DEFAULT_ROW_HEIGHT;
|
|
3167
|
-
}
|
|
3168
|
-
function readMarker(element) {
|
|
3169
|
-
if (!element) return;
|
|
3170
|
-
return {
|
|
3171
|
-
column: nonNegativeInteger(directChild(element, 'col')?.textContent),
|
|
3172
|
-
row: nonNegativeInteger(directChild(element, 'row')?.textContent),
|
|
3173
|
-
columnOffsetEmu: nonNegativeNumber(directChild(element, 'colOff')?.textContent),
|
|
3174
|
-
rowOffsetEmu: nonNegativeNumber(directChild(element, 'rowOff')?.textContent)
|
|
3175
|
-
};
|
|
3176
|
-
}
|
|
3177
|
-
function numberAttribute(element, name) {
|
|
3178
|
-
if (!element) return;
|
|
3179
|
-
const value = Number(attribute(element, name));
|
|
3180
|
-
return Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
3181
|
-
}
|
|
3182
|
-
function nonNegativeInteger(value) {
|
|
3183
|
-
const parsed = Number(value);
|
|
3184
|
-
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
3185
|
-
}
|
|
3186
|
-
function nonNegativeNumber(value) {
|
|
3187
|
-
const parsed = Number(value);
|
|
3188
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
3189
|
-
}
|
|
3190
|
-
function markerPosition(index, offsetEmu, size) {
|
|
3191
|
-
let position = 0;
|
|
3192
|
-
for(let current = 0; current < index; current += 1)position += size(current);
|
|
3193
|
-
return position + emuToPixels(offsetEmu);
|
|
3194
|
-
}
|
|
3195
|
-
function emuToPixels(value) {
|
|
3196
|
-
return (value ?? 0) / XLSX_EMU_PER_PIXEL;
|
|
3197
|
-
}
|
|
3198
|
-
function axisMarker(position, size) {
|
|
3199
|
-
let remaining = Math.max(0, Number.isFinite(position) ? position : 0);
|
|
3200
|
-
let index = 0;
|
|
3201
|
-
while(index < 1048576){
|
|
3202
|
-
const currentSize = Math.max(0, size(index));
|
|
3203
|
-
if (currentSize > 0 && remaining < currentSize) break;
|
|
3204
|
-
if (currentSize > 0) remaining -= currentSize;
|
|
3205
|
-
index += 1;
|
|
3206
|
-
}
|
|
3207
|
-
return {
|
|
3208
|
-
index,
|
|
3209
|
-
offsetEmu: Math.round(remaining * XLSX_EMU_PER_PIXEL)
|
|
3210
|
-
};
|
|
3456
|
+
})}`;
|
|
3457
|
+
}).join('');
|
|
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}`;
|
|
3211
3469
|
}
|
|
3212
|
-
function
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
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>`;
|
|
3219
3484
|
}
|
|
3220
|
-
function
|
|
3221
|
-
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>`;
|
|
3222
3487
|
}
|
|
3223
3488
|
const CHART_PREVIEW_IMAGE_PREFIX = 'work-chart-preview-';
|
|
3224
3489
|
function spreadsheetChartCount(content) {
|
|
@@ -3602,224 +3867,4 @@ function isNumericValue(value) {
|
|
|
3602
3867
|
function finiteDimension(value, fallback, minimum = 0) {
|
|
3603
3868
|
return Number.isFinite(value) ? Math.max(minimum, value) : fallback;
|
|
3604
3869
|
}
|
|
3605
|
-
|
|
3606
|
-
mode: 'automatic',
|
|
3607
|
-
fullCalculationOnLoad: false,
|
|
3608
|
-
forceFullCalculation: false,
|
|
3609
|
-
iterativeCalculation: false,
|
|
3610
|
-
maximumIterations: 100,
|
|
3611
|
-
maximumChange: 0.001,
|
|
3612
|
-
fullPrecision: true
|
|
3613
|
-
};
|
|
3614
|
-
const VOLATILE_FUNCTIONS = new Set([
|
|
3615
|
-
'CELL',
|
|
3616
|
-
'INFO',
|
|
3617
|
-
'INDIRECT',
|
|
3618
|
-
'NOW',
|
|
3619
|
-
'OFFSET',
|
|
3620
|
-
'RAND',
|
|
3621
|
-
'RANDBETWEEN',
|
|
3622
|
-
'TODAY'
|
|
3623
|
-
]);
|
|
3624
|
-
const FUTURE_FUNCTIONS = new Set([
|
|
3625
|
-
'ARRAYTOTEXT',
|
|
3626
|
-
'BYCOL',
|
|
3627
|
-
'BYROW',
|
|
3628
|
-
'CHOOSECOLS',
|
|
3629
|
-
'CHOOSEROWS',
|
|
3630
|
-
'DROP',
|
|
3631
|
-
'EXPAND',
|
|
3632
|
-
'FIELDVALUE',
|
|
3633
|
-
'FILTER',
|
|
3634
|
-
'HSTACK',
|
|
3635
|
-
'IMAGE',
|
|
3636
|
-
'ISOMITTED',
|
|
3637
|
-
'LAMBDA',
|
|
3638
|
-
'LET',
|
|
3639
|
-
'MAKEARRAY',
|
|
3640
|
-
'MAP',
|
|
3641
|
-
'RANDARRAY',
|
|
3642
|
-
'REDUCE',
|
|
3643
|
-
'SCAN',
|
|
3644
|
-
'SEQUENCE',
|
|
3645
|
-
'SORT',
|
|
3646
|
-
'SORTBY',
|
|
3647
|
-
'STOCKHISTORY',
|
|
3648
|
-
'TAKE',
|
|
3649
|
-
'TEXTAFTER',
|
|
3650
|
-
'TEXTBEFORE',
|
|
3651
|
-
'TEXTSPLIT',
|
|
3652
|
-
'TOCOL',
|
|
3653
|
-
'TOROW',
|
|
3654
|
-
'UNIQUE',
|
|
3655
|
-
'VALUETOTEXT',
|
|
3656
|
-
'VSTACK',
|
|
3657
|
-
'WRAPCOLS',
|
|
3658
|
-
'WRAPROWS',
|
|
3659
|
-
'XLOOKUP',
|
|
3660
|
-
'XMATCH'
|
|
3661
|
-
]);
|
|
3662
|
-
const FUTURE_FUNCTION_PREFIXES = new Map([
|
|
3663
|
-
[
|
|
3664
|
-
'FILTER',
|
|
3665
|
-
'_xlfn._xlws.'
|
|
3666
|
-
],
|
|
3667
|
-
[
|
|
3668
|
-
'SORT',
|
|
3669
|
-
'_xlfn._xlws.'
|
|
3670
|
-
],
|
|
3671
|
-
[
|
|
3672
|
-
'SORTBY',
|
|
3673
|
-
'_xlfn._xlws.'
|
|
3674
|
-
],
|
|
3675
|
-
[
|
|
3676
|
-
'UNIQUE',
|
|
3677
|
-
'_xlfn.'
|
|
3678
|
-
],
|
|
3679
|
-
...Array.from(FUTURE_FUNCTIONS).filter((name)=>![
|
|
3680
|
-
'FILTER',
|
|
3681
|
-
'SORT',
|
|
3682
|
-
'SORTBY',
|
|
3683
|
-
'UNIQUE'
|
|
3684
|
-
].includes(name)).map((name)=>[
|
|
3685
|
-
name,
|
|
3686
|
-
'_xlfn.'
|
|
3687
|
-
])
|
|
3688
|
-
]);
|
|
3689
|
-
function effectiveSpreadsheetCalculationSettings(settings) {
|
|
3690
|
-
if (!settings) return {
|
|
3691
|
-
...DEFAULT_SPREADSHEET_CALCULATION_SETTINGS
|
|
3692
|
-
};
|
|
3693
|
-
return {
|
|
3694
|
-
mode: settings.mode,
|
|
3695
|
-
fullCalculationOnLoad: Boolean(settings.fullCalculationOnLoad),
|
|
3696
|
-
forceFullCalculation: Boolean(settings.forceFullCalculation),
|
|
3697
|
-
iterativeCalculation: Boolean(settings.iterativeCalculation),
|
|
3698
|
-
maximumIterations: clampedInteger(settings.maximumIterations, 1, 10000, 100),
|
|
3699
|
-
maximumChange: positiveNumber(settings.maximumChange, 0.001),
|
|
3700
|
-
fullPrecision: false !== settings.fullPrecision
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
function editableSpreadsheetFormula(source) {
|
|
3704
|
-
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, ''));
|
|
3705
|
-
}
|
|
3706
|
-
function spreadsheetFormulaForXlsx(currentFormula, sourceFormula) {
|
|
3707
|
-
const current = currentFormula.trim().replace(/^=/, '');
|
|
3708
|
-
const source = sourceFormula?.trim().replace(/^=/, '');
|
|
3709
|
-
if (source && comparableFormula(source) === comparableFormula(current)) return source;
|
|
3710
|
-
return futureFunctionPrefixes(current, source);
|
|
3711
|
-
}
|
|
3712
|
-
function spreadsheetFormulaRangeForCell(sheet, row, column) {
|
|
3713
|
-
const address = spreadsheetCellAddress(row, column);
|
|
3714
|
-
return (sheet.formulaMetadata?.ranges ?? []).find((range)=>range.anchor.toUpperCase().replaceAll('$', '') === address);
|
|
3715
|
-
}
|
|
3716
|
-
function spreadsheetFormulaRangesForSelection(sheet, selection) {
|
|
3717
|
-
return (sheet.formulaMetadata?.ranges ?? []).filter((range)=>work_spreadsheet_formulas_rangesOverlap(selection, parseSpreadsheetFormulaRange(range.reference)));
|
|
3718
|
-
}
|
|
3719
|
-
function spreadsheetFormulaRangeConflict(sheet, range) {
|
|
3720
|
-
const bounds = parseSpreadsheetFormulaRange(range.reference);
|
|
3721
|
-
if (!bounds) return '引用范围无效';
|
|
3722
|
-
const anchor = parseSpreadsheetCellAddress(range.anchor);
|
|
3723
|
-
if (!anchor || !work_spreadsheet_formulas_containsCell(bounds, anchor.row, anchor.column)) return '锚点不在引用范围内';
|
|
3724
|
-
const anchorCell = sheet.data?.[anchor.row]?.[anchor.column];
|
|
3725
|
-
if (!anchorCell) return '锚点单元格不存在';
|
|
3726
|
-
const overlaps = (sheet.formulaMetadata?.ranges ?? []).filter((candidate)=>candidate !== range && work_spreadsheet_formulas_rangesOverlap(bounds, parseSpreadsheetFormulaRange(candidate.reference)));
|
|
3727
|
-
if (overlaps.length) return '与其他公式范围重叠';
|
|
3728
|
-
for (const [row, cells] of (sheet.data ?? []).entries())if (!(row < bounds.startRow) && !(row > bounds.endRow)) {
|
|
3729
|
-
for (const [column, cell] of cells.entries())if (!(column < bounds.startColumn) && !(column > bounds.endColumn)) {
|
|
3730
|
-
if (row !== anchor.row || column !== anchor.column) {
|
|
3731
|
-
if (cell?.f) return `${spreadsheetCellAddress(row, column)} 包含独立公式`;
|
|
3732
|
-
}
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
if (Object.values(sheet.config?.merge ?? {}).some((merge)=>work_spreadsheet_formulas_rangesOverlap(bounds, {
|
|
3736
|
-
startRow: merge.r,
|
|
3737
|
-
endRow: merge.r + merge.rs - 1,
|
|
3738
|
-
startColumn: merge.c,
|
|
3739
|
-
endColumn: merge.c + merge.cs - 1
|
|
3740
|
-
}))) return '范围内包含合并单元格';
|
|
3741
|
-
if ('data-table' !== range.type && !anchorCell.f) return '锚点公式已被删除';
|
|
3742
|
-
return null;
|
|
3743
|
-
}
|
|
3744
|
-
function spreadsheetFormulaFunctions(formula) {
|
|
3745
|
-
const withoutStrings = formula.replace(/"(?:[^"]|"")*"/g, '""');
|
|
3746
|
-
const functions = new Set();
|
|
3747
|
-
for (const match of withoutStrings.matchAll(/(?:_xlfn\.)?(?:_xlws\.)?([A-Z][A-Z0-9_.]*)\s*\(/gi))if (match[1]) functions.add(match[1].toUpperCase());
|
|
3748
|
-
return Array.from(functions);
|
|
3749
|
-
}
|
|
3750
|
-
function formulaHasExternalReference(formula) {
|
|
3751
|
-
return /\[[^\]]+\][^!]*!/i.test(formula.replace(/"(?:[^"]|"")*"/g, '""'));
|
|
3752
|
-
}
|
|
3753
|
-
function formulaHasStructuredReference(formula) {
|
|
3754
|
-
const withoutExternalReferences = formula.replace(/"(?:[^"]|"")*"/g, '""').replace(/\[[^\]]+\][^!]*!/gi, '');
|
|
3755
|
-
return /\[(?:@|#)[^\]]+\]/i.test(withoutExternalReferences) || /\b[A-Z_\\][A-Z0-9_.]*\s*\[[^\]]+\]/i.test(withoutExternalReferences);
|
|
3756
|
-
}
|
|
3757
|
-
function volatileSpreadsheetFormulaFunctions(formula) {
|
|
3758
|
-
return spreadsheetFormulaFunctions(formula).filter((name)=>VOLATILE_FUNCTIONS.has(name));
|
|
3759
|
-
}
|
|
3760
|
-
function spreadsheetCellAddress(row, column) {
|
|
3761
|
-
let value = column + 1;
|
|
3762
|
-
let label = '';
|
|
3763
|
-
while(value > 0){
|
|
3764
|
-
value -= 1;
|
|
3765
|
-
label = String.fromCharCode(65 + value % 26) + label;
|
|
3766
|
-
value = Math.floor(value / 26);
|
|
3767
|
-
}
|
|
3768
|
-
return `${label}${row + 1}`;
|
|
3769
|
-
}
|
|
3770
|
-
function parseSpreadsheetFormulaRange(reference) {
|
|
3771
|
-
const normalized = reference.trim().replaceAll('$', '').split('!').at(-1) ?? '';
|
|
3772
|
-
const [startText, endText = startText] = normalized.split(':');
|
|
3773
|
-
const start = parseSpreadsheetCellAddress(startText);
|
|
3774
|
-
const end = parseSpreadsheetCellAddress(endText);
|
|
3775
|
-
if (!start || !end) return null;
|
|
3776
|
-
return {
|
|
3777
|
-
startRow: Math.min(start.row, end.row),
|
|
3778
|
-
endRow: Math.max(start.row, end.row),
|
|
3779
|
-
startColumn: Math.min(start.column, end.column),
|
|
3780
|
-
endColumn: Math.max(start.column, end.column)
|
|
3781
|
-
};
|
|
3782
|
-
}
|
|
3783
|
-
function parseSpreadsheetCellAddress(address) {
|
|
3784
|
-
const match = /^([A-Z]{1,3})([1-9]\d*)$/i.exec(address.trim().replaceAll('$', ''));
|
|
3785
|
-
if (!match) return null;
|
|
3786
|
-
let column = 0;
|
|
3787
|
-
for (const character of match[1].toUpperCase())column = 26 * column + character.charCodeAt(0) - 64;
|
|
3788
|
-
const row = Number(match[2]) - 1;
|
|
3789
|
-
if (row > 1048575 || column > 16384) return null;
|
|
3790
|
-
return {
|
|
3791
|
-
row,
|
|
3792
|
-
column: column - 1
|
|
3793
|
-
};
|
|
3794
|
-
}
|
|
3795
|
-
function work_spreadsheet_formulas_containsCell(bounds, row, column) {
|
|
3796
|
-
return row >= bounds.startRow && row <= bounds.endRow && column >= bounds.startColumn && column <= bounds.endColumn;
|
|
3797
|
-
}
|
|
3798
|
-
function work_spreadsheet_formulas_rangesOverlap(first, second) {
|
|
3799
|
-
if (!second) return false;
|
|
3800
|
-
return first.startRow <= second.endRow && first.endRow >= second.startRow && first.startColumn <= second.endColumn && first.endColumn >= second.startColumn;
|
|
3801
|
-
}
|
|
3802
|
-
function comparableFormula(formula) {
|
|
3803
|
-
return editableSpreadsheetFormula(formula).trim();
|
|
3804
|
-
}
|
|
3805
|
-
function futureFunctionPrefixes(formula, sourceFormula) {
|
|
3806
|
-
const sourcePrefixes = new Map();
|
|
3807
|
-
const sourceFunctions = (sourceFormula ?? '').replace(/"(?:[^"]|"")*"/g, '""');
|
|
3808
|
-
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]);
|
|
3809
|
-
return transformFormulaOutsideStrings(formula, (segment)=>segment.replace(/(?<![A-Z0-9_.])([A-Z][A-Z0-9_.]*)\s*(?=\()/gi, (match, functionName)=>{
|
|
3810
|
-
const normalized = functionName.toUpperCase();
|
|
3811
|
-
const prefix = sourcePrefixes.get(normalized) ?? FUTURE_FUNCTION_PREFIXES.get(normalized);
|
|
3812
|
-
return prefix ? `${prefix}${match}` : match;
|
|
3813
|
-
}));
|
|
3814
|
-
}
|
|
3815
|
-
function clampedInteger(value, minimum, maximum, fallback) {
|
|
3816
|
-
if (!Number.isFinite(value)) return fallback;
|
|
3817
|
-
return Math.min(maximum, Math.max(minimum, Math.round(value)));
|
|
3818
|
-
}
|
|
3819
|
-
function positiveNumber(value, fallback) {
|
|
3820
|
-
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
3821
|
-
}
|
|
3822
|
-
function transformFormulaOutsideStrings(formula, transform) {
|
|
3823
|
-
return formula.split(/("(?:[^"]|"")*")/).map((segment, index)=>index % 2 ? segment : transform(segment)).join('');
|
|
3824
|
-
}
|
|
3825
|
-
export { 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, 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 };
|