@a3s-lab/office 0.31.0 → 0.32.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 +6 -3
- package/dist/0~4808.js +1 -1
- package/dist/0~4980.js +1 -1
- package/dist/0~7240.js +1 -1
- package/dist/0~7614.js +1 -1
- package/dist/0~document-editor.js +1085 -4
- package/dist/0~spreadsheet-editor.js +2 -1
- package/dist/0~work-docx-export.js +1 -1
- package/dist/0~work-docx-import.js +10 -27
- package/dist/0~work-docx-large-document-import.js +1 -1
- package/dist/0~work-office-diagnostics.js +3 -3
- package/dist/0~work-pptx-import.js +1 -1
- package/dist/{1544.js → 3266.js} +17 -1862
- package/dist/4121.js +13 -0
- package/dist/4166.js +3848 -0
- package/dist/{6282.js → 4174.js} +383 -10
- package/dist/9333.js +1849 -0
- package/dist/core.js +5259 -4
- package/dist/index.js +3 -3
- package/dist/internal/features/work/editors/document-compare-dialog.d.ts +14 -0
- package/dist/internal/features/work/editors/document-toolbar.d.ts +3 -1
- package/dist/internal/features/work/editors/use-document-comparison.d.ts +9 -0
- package/dist/internal/features/work/work-document-changes.d.ts +5 -0
- package/dist/internal/features/work/work-document-compare-diff.d.ts +38 -0
- package/dist/internal/features/work/work-document-compare-stability.d.ts +5 -0
- package/dist/internal/features/work/work-document-compare.d.ts +27 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +194 -0
- package/docs/latest/en/browser-editor-architecture.md +23 -0
- package/package.json +6 -3
- package/dist/4104.js +0 -9336
package/dist/4166.js
ADDED
|
@@ -0,0 +1,3848 @@
|
|
|
1
|
+
import jszip from "jszip";
|
|
2
|
+
import { normalizeWorkSpreadsheetCombinationSeriesType, workSpreadsheetChartSupportsGrouping, normalizeWorkSpreadsheetChartColor, readXlsxDrawingAnchor, spreadsheetCellAddress, formatSpreadsheetCellRanges, normalizeWorkSpreadsheetChartSmoothLines, spreadsheetFormulaRangeForCell, sparseArrayEntries, spreadsheetPivotFilterValueKey, normalizeWorkSpreadsheetBubbleScale, normalizeWorkSpreadsheetChartSeriesStyle, normalizeWorkSpreadsheetErrorBars, normalizeWorkSpreadsheetBubbleSizeRepresents, normalizeWorkSpreadsheetChartLegendOverlay, normalizeWorkSpreadsheetChartGapWidth, workSpreadsheetChartSupportsTrendlines, normalizeWorkSpreadsheetChartGrouping, workSpreadsheetChartSupportsBarSpacing, workSpreadsheetChartSupportsSmoothLines, workSpreadsheetChartGroupingIsStacked, xlsxTwoCellAnchorMarkers, xlsxDrawingAnchorToBounds, workSpreadsheetChartSupportsErrorBars, normalizeWorkSpreadsheetChartAxisGroup, normalizeWorkSpreadsheetScatterStyle, workSpreadsheetChartAxisDefaultLabelPosition, spreadsheetPivotValidation, resolveSpreadsheetChart, workSpreadsheetChartAxisIsCategoryAxis, parseSpreadsheetCellRanges, spreadsheetPivotFields, normalizeWorkSpreadsheetDoughnutHoleSize, normalizeWorkSpreadsheetChartAxes, normalizeWorkSpreadsheetChartOverlap, editableSpreadsheetFormula, spreadsheetFormulaRangeConflict, spreadsheetFormulaForXlsx, workSpreadsheetChartUsesNumericXAxis, normalizeWorkSpreadsheetChartLegendPosition, workSpreadsheetChartSupportsAxes, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, effectiveSpreadsheetCalculationSettings, normalizeWorkSpreadsheetTrendline, normalizeWorkSpreadsheetRadarStyle, normalizeWorkSpreadsheetDataLabels } from "./8715.js";
|
|
3
|
+
import { contentTypeForPart, directChildren, bytesToDataUrl, OoxmlPackage as work_ooxml_package_OoxmlPackage, parseXml, firstDescendant, xmlNamespacePrefix, descendants, directChild, xmlContainsAnyElement, attribute, createOfficeId as createWorkId } from "./4121.js";
|
|
4
|
+
import { DEFAULT_PROTECTION_HINT, editableRangeRequiresCredentials, normalizeSpreadsheetPaperSize, normalizeSheetProtectionAuthority, xlsxWorksheetCellEntries } from "./9333.js";
|
|
5
|
+
async function readXlsxFormulaFeaturesFromPackage(archive, worksheetScans) {
|
|
6
|
+
if (!archive.has('xl/workbook.xml')) return {
|
|
7
|
+
calculation: effectiveSpreadsheetCalculationSettings(void 0),
|
|
8
|
+
sheets: new Map(),
|
|
9
|
+
unsupportedCalculationAttributes: []
|
|
10
|
+
};
|
|
11
|
+
const workbook = await archive.xml('xl/workbook.xml');
|
|
12
|
+
const worksheetParts = await readWorksheetParts(archive, workbook);
|
|
13
|
+
const dynamicMetadata = await readDynamicArrayMetadata(archive);
|
|
14
|
+
const sheets = new Map();
|
|
15
|
+
for (const [sheetName, partPath] of worksheetParts){
|
|
16
|
+
if (!archive.has(partPath)) continue;
|
|
17
|
+
const scan = worksheetScans?.[partPath];
|
|
18
|
+
if (scan && !scan.hasFormulaFeatures) {
|
|
19
|
+
sheets.set(sheetName, emptyXlsxFormulaSheetFeatures());
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const source = await archive.text(partPath);
|
|
23
|
+
if (!scan && !xmlContainsAnyElement(source, [
|
|
24
|
+
'f'
|
|
25
|
+
])) {
|
|
26
|
+
sheets.set(sheetName, emptyXlsxFormulaSheetFeatures());
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
sheets.set(sheetName, readWorksheetFormulaFeatures(parseXml(source, partPath), dynamicMetadata));
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
calculation: readCalculationSettings(workbook),
|
|
33
|
+
sheets,
|
|
34
|
+
unsupportedCalculationAttributes: readUnsupportedCalculationAttributes(workbook)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function emptyXlsxFormulaSheetFeatures() {
|
|
38
|
+
return {
|
|
39
|
+
formulas: [],
|
|
40
|
+
ranges: [],
|
|
41
|
+
sourceFormulas: {},
|
|
42
|
+
sharedFormulaGroups: 0,
|
|
43
|
+
sharedFormulaCells: 0,
|
|
44
|
+
unsupportedFormulaAttributes: []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createSpreadsheetFormulaMetadata(worksheet, features) {
|
|
48
|
+
const sourceFormulas = {};
|
|
49
|
+
const rangeByReference = new Map((features?.ranges ?? []).map((range)=>[
|
|
50
|
+
range.reference.toUpperCase().replaceAll('$', ''),
|
|
51
|
+
range
|
|
52
|
+
]));
|
|
53
|
+
for (const { address, cell } of xlsxWorksheetCellEntries(worksheet)){
|
|
54
|
+
const formulaCell = cell;
|
|
55
|
+
if (formulaCell.f) {
|
|
56
|
+
const source = features?.sourceFormulas[address.toUpperCase()] ?? formulaCell.f;
|
|
57
|
+
if (editableSpreadsheetFormula(source) !== source) sourceFormulas[address.toUpperCase()] = source;
|
|
58
|
+
}
|
|
59
|
+
if (!formulaCell.f || !formulaCell.F) continue;
|
|
60
|
+
const reference = formulaCell.F.toUpperCase().replaceAll('$', '');
|
|
61
|
+
const existing = rangeByReference.get(reference);
|
|
62
|
+
if (existing) {
|
|
63
|
+
if (formulaCell.D && 'array' === existing.type) rangeByReference.set(reference, {
|
|
64
|
+
...existing,
|
|
65
|
+
type: 'dynamic-array'
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
rangeByReference.set(reference, {
|
|
70
|
+
type: formulaCell.D ? 'dynamic-array' : 'array',
|
|
71
|
+
anchor: address.toUpperCase(),
|
|
72
|
+
reference: formulaCell.F,
|
|
73
|
+
formula: features?.sourceFormulas[address.toUpperCase()] ?? formulaCell.f
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const ranges = Array.from(rangeByReference.values());
|
|
77
|
+
const metadata = {
|
|
78
|
+
ranges: ranges.length ? ranges : void 0,
|
|
79
|
+
sourceFormulas: Object.keys(sourceFormulas).length ? sourceFormulas : void 0,
|
|
80
|
+
normalizedSharedFormulaGroups: features?.sharedFormulaGroups || void 0,
|
|
81
|
+
normalizedSharedFormulaCells: features?.sharedFormulaCells || void 0
|
|
82
|
+
};
|
|
83
|
+
return Object.values(metadata).some((value)=>void 0 !== value) ? metadata : void 0;
|
|
84
|
+
}
|
|
85
|
+
function createXlsxFormulaCell(cell, row, column, sheet) {
|
|
86
|
+
const address = spreadsheetCellAddress(row, column);
|
|
87
|
+
const range = spreadsheetFormulaRangeForCell(sheet, row, column);
|
|
88
|
+
const sourceFormula = sheet.formulaMetadata?.sourceFormulas?.[address] ?? range?.formula;
|
|
89
|
+
const result = {
|
|
90
|
+
f: spreadsheetFormulaForXlsx(cell.f ?? '', sourceFormula),
|
|
91
|
+
v: cell.ct?.t === 'e' ? xlsxErrorValue(cell.v, cell.m) : cell.v
|
|
92
|
+
};
|
|
93
|
+
if (cell.ct?.t) result.t = cell.ct.t;
|
|
94
|
+
if (range && 'data-table' !== range.type && !spreadsheetFormulaRangeConflict(sheet, range)) {
|
|
95
|
+
result.F = range.reference;
|
|
96
|
+
if ('dynamic-array' === range.type) result.D = true;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
function createXlsxErrorCell(cell) {
|
|
101
|
+
return {
|
|
102
|
+
t: 'e',
|
|
103
|
+
v: xlsxErrorValue(cell.v, cell.m)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function patchXlsxFormulaFeatures(buffer, content) {
|
|
107
|
+
const archive = await work_ooxml_package_OoxmlPackage.load(buffer);
|
|
108
|
+
const zip = await jszip.loadAsync(buffer);
|
|
109
|
+
if (archive.has('xl/workbook.xml')) {
|
|
110
|
+
const entry = zip.file('xl/workbook.xml');
|
|
111
|
+
if (entry) {
|
|
112
|
+
const workbook = parseXml(await entry.async('text'), 'xl/workbook.xml');
|
|
113
|
+
writeCalculationSettings(workbook, content.calculation);
|
|
114
|
+
zip.file('xl/workbook.xml', new XMLSerializer().serializeToString(workbook));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const dataTables = content.sheets.flatMap((sheet)=>(sheet.formulaMetadata?.ranges ?? []).flatMap((range)=>'data-table' !== range.type || spreadsheetFormulaRangeConflict(sheet, range) ? [] : [
|
|
118
|
+
{
|
|
119
|
+
sheet,
|
|
120
|
+
range
|
|
121
|
+
}
|
|
122
|
+
]));
|
|
123
|
+
if (!dataTables.length || !archive.has('xl/workbook.xml')) return zip.generateAsync({
|
|
124
|
+
type: 'arraybuffer',
|
|
125
|
+
compression: 'DEFLATE'
|
|
126
|
+
});
|
|
127
|
+
const workbook = await archive.xml('xl/workbook.xml');
|
|
128
|
+
const worksheetParts = await readWorksheetParts(archive, workbook);
|
|
129
|
+
for (const sheet of content.sheets){
|
|
130
|
+
const ranges = dataTables.filter((item)=>item.sheet === sheet).map((item)=>item.range);
|
|
131
|
+
if (!ranges.length) continue;
|
|
132
|
+
const partPath = worksheetParts.get(sheet.name.slice(0, 31) || '工作表');
|
|
133
|
+
const entry = partPath ? zip.file(partPath) : null;
|
|
134
|
+
if (!partPath || !entry) continue;
|
|
135
|
+
const document = parseXml(await entry.async('text'), partPath);
|
|
136
|
+
for (const range of ranges)writeDataTableFormula(document, range);
|
|
137
|
+
zip.file(partPath, new XMLSerializer().serializeToString(document));
|
|
138
|
+
}
|
|
139
|
+
return zip.generateAsync({
|
|
140
|
+
type: 'arraybuffer',
|
|
141
|
+
compression: 'DEFLATE'
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function readWorksheetFormulaFeatures(document, dynamicMetadataIndexes) {
|
|
145
|
+
const formulas = [];
|
|
146
|
+
const ranges = [];
|
|
147
|
+
const sourceFormulas = {};
|
|
148
|
+
const sharedGroups = new Set();
|
|
149
|
+
const unsupportedFormulaAttributes = new Set();
|
|
150
|
+
let sharedFormulaCells = 0;
|
|
151
|
+
for (const cell of descendants(document, 'c')){
|
|
152
|
+
const formula = directChild(cell, 'f');
|
|
153
|
+
if (!formula) continue;
|
|
154
|
+
const address = (attribute(cell, 'r') ?? '').toUpperCase();
|
|
155
|
+
if (!address) continue;
|
|
156
|
+
const type = attribute(formula, 't') ?? 'normal';
|
|
157
|
+
for (const name of unsupportedFormulaAttributesForElement(formula, type))unsupportedFormulaAttributes.add(name);
|
|
158
|
+
const source = formula.textContent ?? '';
|
|
159
|
+
if (source) sourceFormulas[address] = source;
|
|
160
|
+
const cachedError = 'e' === attribute(cell, 't') ? directChild(cell, 'v')?.textContent ?? '' : void 0;
|
|
161
|
+
formulas.push({
|
|
162
|
+
address,
|
|
163
|
+
formula: source,
|
|
164
|
+
type,
|
|
165
|
+
cachedError
|
|
166
|
+
});
|
|
167
|
+
if ('shared' === type) {
|
|
168
|
+
sharedFormulaCells += 1;
|
|
169
|
+
sharedGroups.add(attribute(formula, 'si') ?? `cell:${address}`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const reference = attribute(formula, 'ref');
|
|
173
|
+
if ('array' === type && reference) {
|
|
174
|
+
const metadataIndex = positiveInteger(attribute(cell, 'cm'));
|
|
175
|
+
ranges.push({
|
|
176
|
+
type: metadataIndex && dynamicMetadataIndexes.has(metadataIndex) ? 'dynamic-array' : 'array',
|
|
177
|
+
anchor: address,
|
|
178
|
+
reference,
|
|
179
|
+
formula: source || void 0
|
|
180
|
+
});
|
|
181
|
+
} else if ('dataTable' === type) ranges.push({
|
|
182
|
+
type: 'data-table',
|
|
183
|
+
anchor: address,
|
|
184
|
+
reference: reference || address,
|
|
185
|
+
formula: source || void 0,
|
|
186
|
+
dataTable: readDataTableOptions(formula)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
formulas,
|
|
191
|
+
ranges,
|
|
192
|
+
sourceFormulas,
|
|
193
|
+
sharedFormulaGroups: sharedGroups.size,
|
|
194
|
+
sharedFormulaCells,
|
|
195
|
+
unsupportedFormulaAttributes: Array.from(unsupportedFormulaAttributes).sort()
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function readCalculationSettings(workbook) {
|
|
199
|
+
const calculation = directChild(workbook.documentElement, 'calcPr');
|
|
200
|
+
return effectiveSpreadsheetCalculationSettings({
|
|
201
|
+
mode: calculationMode(attribute(calculation ?? workbook.documentElement, 'calcMode')),
|
|
202
|
+
fullCalculationOnLoad: booleanAttribute(calculation, 'fullCalcOnLoad', false),
|
|
203
|
+
forceFullCalculation: booleanAttribute(calculation, 'forceFullCalc', false),
|
|
204
|
+
iterativeCalculation: booleanAttribute(calculation, 'iterate', false),
|
|
205
|
+
maximumIterations: numericAttribute(calculation, 'iterateCount', 100),
|
|
206
|
+
maximumChange: numericAttribute(calculation, 'iterateDelta', 0.001),
|
|
207
|
+
fullPrecision: booleanAttribute(calculation, 'fullPrecision', true)
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function readUnsupportedCalculationAttributes(workbook) {
|
|
211
|
+
const calculation = directChild(workbook.documentElement, 'calcPr');
|
|
212
|
+
if (!calculation) return [];
|
|
213
|
+
const supported = new Set([
|
|
214
|
+
'calcId',
|
|
215
|
+
'calcMode',
|
|
216
|
+
'forceFullCalc',
|
|
217
|
+
'fullCalcOnLoad',
|
|
218
|
+
'fullPrecision',
|
|
219
|
+
'iterate',
|
|
220
|
+
'iterateCount',
|
|
221
|
+
'iterateDelta'
|
|
222
|
+
]);
|
|
223
|
+
return Array.from(calculation.attributes).map((item)=>item.localName).filter((name)=>!supported.has(name)).sort();
|
|
224
|
+
}
|
|
225
|
+
function writeCalculationSettings(workbook, source) {
|
|
226
|
+
const settings = effectiveSpreadsheetCalculationSettings(source);
|
|
227
|
+
const root = workbook.documentElement;
|
|
228
|
+
let calculation = directChild(root, 'calcPr');
|
|
229
|
+
if (!calculation) {
|
|
230
|
+
calculation = workbook.createElementNS(root.namespaceURI, 'calcPr');
|
|
231
|
+
root.insertBefore(calculation, directChild(root, 'extLst') ?? null);
|
|
232
|
+
}
|
|
233
|
+
calculation.setAttribute('calcMode', xlsxCalculationMode(settings.mode));
|
|
234
|
+
calculation.setAttribute('fullCalcOnLoad', booleanValue(settings.fullCalculationOnLoad));
|
|
235
|
+
calculation.setAttribute('forceFullCalc', booleanValue(settings.forceFullCalculation));
|
|
236
|
+
calculation.setAttribute('iterate', booleanValue(settings.iterativeCalculation));
|
|
237
|
+
calculation.setAttribute('iterateCount', String(settings.maximumIterations));
|
|
238
|
+
calculation.setAttribute('iterateDelta', String(settings.maximumChange));
|
|
239
|
+
calculation.setAttribute('fullPrecision', booleanValue(settings.fullPrecision));
|
|
240
|
+
}
|
|
241
|
+
async function readWorksheetParts(archive, workbook) {
|
|
242
|
+
const relationships = await archive.relationships('xl/workbook.xml');
|
|
243
|
+
const parts = new Map();
|
|
244
|
+
for (const sheet of firstDescendant(workbook, 'sheets')?.children ?? []){
|
|
245
|
+
if (!(sheet instanceof Element) || 'sheet' !== sheet.localName) continue;
|
|
246
|
+
const name = attribute(sheet, 'name');
|
|
247
|
+
const relationship = relationships.get(attribute(sheet, 'r:id') ?? '');
|
|
248
|
+
if (name && relationship?.type.endsWith('/worksheet')) parts.set(name, relationship.target);
|
|
249
|
+
}
|
|
250
|
+
return parts;
|
|
251
|
+
}
|
|
252
|
+
async function readDynamicArrayMetadata(archive) {
|
|
253
|
+
if (!archive.has('xl/metadata.xml')) return new Set();
|
|
254
|
+
const metadata = await archive.xml('xl/metadata.xml');
|
|
255
|
+
const metadataTypes = directChildren(firstDescendant(metadata, 'metadataTypes') ?? metadata.documentElement);
|
|
256
|
+
const dynamicTypeIndexes = new Set(metadataTypes.flatMap((item, index)=>'metadataType' === item.localName && attribute(item, 'name')?.toUpperCase() === 'XLDAPR' ? [
|
|
257
|
+
index + 1
|
|
258
|
+
] : []));
|
|
259
|
+
if (!dynamicTypeIndexes.size) return new Set();
|
|
260
|
+
const cellMetadata = firstDescendant(metadata, 'cellMetadata');
|
|
261
|
+
if (!cellMetadata) return new Set();
|
|
262
|
+
return new Set(directChildren(cellMetadata, 'bk').flatMap((block, index)=>directChildren(block, 'rc').some((record)=>dynamicTypeIndexes.has(positiveInteger(attribute(record, 't')) ?? -1)) ? [
|
|
263
|
+
index + 1
|
|
264
|
+
] : []));
|
|
265
|
+
}
|
|
266
|
+
function readDataTableOptions(formula) {
|
|
267
|
+
const options = {
|
|
268
|
+
input1Reference: attribute(formula, 'r1') ?? void 0,
|
|
269
|
+
input2Reference: attribute(formula, 'r2') ?? void 0,
|
|
270
|
+
twoDimensional: optionalBooleanAttribute(formula, 'dt2D'),
|
|
271
|
+
rowOriented: optionalBooleanAttribute(formula, 'dtr'),
|
|
272
|
+
input1Deleted: optionalBooleanAttribute(formula, 'del1'),
|
|
273
|
+
input2Deleted: optionalBooleanAttribute(formula, 'del2'),
|
|
274
|
+
calculateOnLoad: optionalBooleanAttribute(formula, 'ca')
|
|
275
|
+
};
|
|
276
|
+
return Object.values(options).some((value)=>void 0 !== value) ? options : void 0;
|
|
277
|
+
}
|
|
278
|
+
function unsupportedFormulaAttributesForElement(formula, type) {
|
|
279
|
+
const common = [
|
|
280
|
+
't'
|
|
281
|
+
];
|
|
282
|
+
const supported = 'shared' === type ? new Set([
|
|
283
|
+
...common,
|
|
284
|
+
'ref',
|
|
285
|
+
'si'
|
|
286
|
+
]) : 'array' === type ? new Set([
|
|
287
|
+
...common,
|
|
288
|
+
'ref'
|
|
289
|
+
]) : 'dataTable' === type ? new Set([
|
|
290
|
+
...common,
|
|
291
|
+
'ca',
|
|
292
|
+
'del1',
|
|
293
|
+
'del2',
|
|
294
|
+
'dt2D',
|
|
295
|
+
'dtr',
|
|
296
|
+
'r1',
|
|
297
|
+
'r2',
|
|
298
|
+
'ref'
|
|
299
|
+
]) : new Set(common);
|
|
300
|
+
return Array.from(formula.attributes).map((item)=>item.localName).filter((name)=>!supported.has(name));
|
|
301
|
+
}
|
|
302
|
+
function writeDataTableFormula(document, range) {
|
|
303
|
+
const anchor = range.anchor.toUpperCase().replaceAll('$', '');
|
|
304
|
+
const cell = descendants(document, 'c').find((candidate)=>(attribute(candidate, 'r') ?? '').toUpperCase().replaceAll('$', '') === anchor);
|
|
305
|
+
if (!cell) return;
|
|
306
|
+
let formula = directChild(cell, 'f');
|
|
307
|
+
if (!formula) {
|
|
308
|
+
formula = document.createElementNS(document.documentElement.namespaceURI, 'f');
|
|
309
|
+
cell.insertBefore(formula, directChild(cell, 'v') ?? cell.firstChild);
|
|
310
|
+
}
|
|
311
|
+
for (const item of Array.from(formula.attributes))formula.removeAttribute(item.name);
|
|
312
|
+
formula.setAttribute('t', 'dataTable');
|
|
313
|
+
formula.setAttribute('ref', range.reference);
|
|
314
|
+
formula.textContent = range.formula ?? '';
|
|
315
|
+
const options = range.dataTable;
|
|
316
|
+
if (!options) return;
|
|
317
|
+
setOptionalAttribute(formula, 'r1', options.input1Reference);
|
|
318
|
+
setOptionalAttribute(formula, 'r2', options.input2Reference);
|
|
319
|
+
setOptionalBooleanAttribute(formula, 'dt2D', options.twoDimensional);
|
|
320
|
+
setOptionalBooleanAttribute(formula, 'dtr', options.rowOriented);
|
|
321
|
+
setOptionalBooleanAttribute(formula, 'del1', options.input1Deleted);
|
|
322
|
+
setOptionalBooleanAttribute(formula, 'del2', options.input2Deleted);
|
|
323
|
+
setOptionalBooleanAttribute(formula, 'ca', options.calculateOnLoad);
|
|
324
|
+
}
|
|
325
|
+
function calculationMode(value) {
|
|
326
|
+
if ('manual' === value) return 'manual';
|
|
327
|
+
if ('autoNoTable' === value) return 'automatic-except-data-tables';
|
|
328
|
+
return 'automatic';
|
|
329
|
+
}
|
|
330
|
+
function xlsxCalculationMode(mode) {
|
|
331
|
+
if ('manual' === mode) return 'manual';
|
|
332
|
+
if ('automatic-except-data-tables' === mode) return 'autoNoTable';
|
|
333
|
+
return 'auto';
|
|
334
|
+
}
|
|
335
|
+
function booleanAttribute(element, name, fallback) {
|
|
336
|
+
const value = element ? attribute(element, name)?.toLowerCase() : null;
|
|
337
|
+
if (null == value) return fallback;
|
|
338
|
+
return '1' === value || 'true' === value;
|
|
339
|
+
}
|
|
340
|
+
function optionalBooleanAttribute(element, name) {
|
|
341
|
+
return null === attribute(element, name) ? void 0 : booleanAttribute(element, name, false);
|
|
342
|
+
}
|
|
343
|
+
function numericAttribute(element, name, fallback) {
|
|
344
|
+
const value = Number(element ? attribute(element, name) : null);
|
|
345
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
346
|
+
}
|
|
347
|
+
function positiveInteger(value) {
|
|
348
|
+
const parsed = Number(value);
|
|
349
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
350
|
+
}
|
|
351
|
+
function booleanValue(value) {
|
|
352
|
+
return value ? '1' : '0';
|
|
353
|
+
}
|
|
354
|
+
function setOptionalAttribute(element, name, value) {
|
|
355
|
+
if (void 0 !== value) element.setAttribute(name, value);
|
|
356
|
+
}
|
|
357
|
+
function setOptionalBooleanAttribute(element, name, value) {
|
|
358
|
+
if (void 0 !== value) element.setAttribute(name, booleanValue(value));
|
|
359
|
+
}
|
|
360
|
+
function xlsxErrorValue(value, formatted) {
|
|
361
|
+
if ('number' == typeof value) return value;
|
|
362
|
+
const codes = {
|
|
363
|
+
'#NULL!': 0,
|
|
364
|
+
'#DIV/0!': 7,
|
|
365
|
+
'#VALUE!': 15,
|
|
366
|
+
'#REF!': 23,
|
|
367
|
+
'#NAME?': 29,
|
|
368
|
+
'#NUM!': 36,
|
|
369
|
+
'#N/A': 42,
|
|
370
|
+
'#GETTING_DATA': 43
|
|
371
|
+
};
|
|
372
|
+
return codes[String(formatted ?? value ?? '').toUpperCase()] ?? value;
|
|
373
|
+
}
|
|
374
|
+
function cachedText(parent) {
|
|
375
|
+
const directValue = directChild(parent, 'v')?.textContent?.trim();
|
|
376
|
+
return directValue || cachedValues(parent)[0] || '';
|
|
377
|
+
}
|
|
378
|
+
function richText(parent) {
|
|
379
|
+
return descendants(parent, 't').map((node)=>node.textContent ?? '').join('');
|
|
380
|
+
}
|
|
381
|
+
function cachedValues(parent) {
|
|
382
|
+
const cache = firstDescendant(parent, 'strCache') ?? firstDescendant(parent, 'numCache') ?? firstDescendant(parent, 'strLit') ?? firstDescendant(parent, 'numLit');
|
|
383
|
+
if (!cache) return [];
|
|
384
|
+
const indexed = descendants(cache, 'pt').map((point, order)=>({
|
|
385
|
+
index: finiteNumber(attribute(point, 'idx'), order),
|
|
386
|
+
value: directChild(point, 'v')?.textContent ?? ''
|
|
387
|
+
})).sort((left, right)=>left.index - right.index);
|
|
388
|
+
if (!indexed.length) return [];
|
|
389
|
+
const values = Array(indexed.at(-1).index + 1).fill('');
|
|
390
|
+
for (const point of indexed)values[point.index] = point.value;
|
|
391
|
+
return values;
|
|
392
|
+
}
|
|
393
|
+
function formulaReference(parent) {
|
|
394
|
+
return firstDescendant(parent, 'f')?.textContent?.trim().replace(/^=/, '') || void 0;
|
|
395
|
+
}
|
|
396
|
+
function stringCacheXml(values) {
|
|
397
|
+
return [
|
|
398
|
+
'<c:strCache>',
|
|
399
|
+
`<c:ptCount val="${values.length}"/>`,
|
|
400
|
+
...values.map((value, index)=>`<c:pt idx="${index}"><c:v>${escapeXml(String(value))}</c:v></c:pt>`),
|
|
401
|
+
'</c:strCache>'
|
|
402
|
+
].join('');
|
|
403
|
+
}
|
|
404
|
+
function numberCacheXml(values) {
|
|
405
|
+
return [
|
|
406
|
+
'<c:numCache><c:formatCode>General</c:formatCode>',
|
|
407
|
+
`<c:ptCount val="${values.length}"/>`,
|
|
408
|
+
...values.map((value, index)=>`<c:pt idx="${index}"><c:v>${finiteNumber(value)}</c:v></c:pt>`),
|
|
409
|
+
'</c:numCache>'
|
|
410
|
+
].join('');
|
|
411
|
+
}
|
|
412
|
+
function stringLiteralXml(values) {
|
|
413
|
+
return [
|
|
414
|
+
'<c:strLit>',
|
|
415
|
+
`<c:ptCount val="${values.length}"/>`,
|
|
416
|
+
...values.map((value, index)=>`<c:pt idx="${index}"><c:v>${escapeXml(String(value))}</c:v></c:pt>`),
|
|
417
|
+
'</c:strLit>'
|
|
418
|
+
].join('');
|
|
419
|
+
}
|
|
420
|
+
function numberLiteralXml(values) {
|
|
421
|
+
return [
|
|
422
|
+
'<c:numLit><c:formatCode>General</c:formatCode>',
|
|
423
|
+
`<c:ptCount val="${values.length}"/>`,
|
|
424
|
+
...values.map((value, index)=>`<c:pt idx="${index}"><c:v>${finiteNumber(value)}</c:v></c:pt>`),
|
|
425
|
+
'</c:numLit>'
|
|
426
|
+
].join('');
|
|
427
|
+
}
|
|
428
|
+
function finiteNumber(value, fallback = 0) {
|
|
429
|
+
const number = Number(value);
|
|
430
|
+
return Number.isFinite(number) ? number : fallback;
|
|
431
|
+
}
|
|
432
|
+
function escapeXml(value) {
|
|
433
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
434
|
+
}
|
|
435
|
+
function parseXlsxChartAxes(document, chartType, hasSecondaryAxes, preserveVisibleDefaultGridlines = false) {
|
|
436
|
+
if (!workSpreadsheetChartSupportsAxes(chartType)) return;
|
|
437
|
+
const plotArea = firstDescendant(document, 'plotArea');
|
|
438
|
+
if (!plotArea) return;
|
|
439
|
+
const axes = {};
|
|
440
|
+
for (const axis of directChildren(plotArea).filter((node)=>'catAx' === node.localName || 'valAx' === node.localName || 'dateAx' === node.localName)){
|
|
441
|
+
const position = attribute(directChild(axis, 'axPos') ?? axis, 'val');
|
|
442
|
+
if ('b' !== position && 'l' !== position && 't' !== position && 'r' !== position) continue;
|
|
443
|
+
const key = 'b' === position ? 'bottom' : 'l' === position ? 'left' : 't' === position ? 'top' : 'right';
|
|
444
|
+
const title = directChild(axis, 'title');
|
|
445
|
+
const text = title ? cachedText(title) || richText(title) || void 0 : void 0;
|
|
446
|
+
const titleReference = title ? formulaReference(title) : void 0;
|
|
447
|
+
const displaySettings = parseXlsxAxisDisplaySettings(axis, chartType, key);
|
|
448
|
+
const valueSettings = 'valAx' === axis.localName ? parseXlsxValueAxisSettings(axis, chartType, key, preserveVisibleDefaultGridlines) : void 0;
|
|
449
|
+
if (text || titleReference || displaySettings || valueSettings) axes[key] = {
|
|
450
|
+
...text ? {
|
|
451
|
+
title: text
|
|
452
|
+
} : {},
|
|
453
|
+
...titleReference ? {
|
|
454
|
+
titleReference
|
|
455
|
+
} : {},
|
|
456
|
+
...displaySettings,
|
|
457
|
+
...valueSettings
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return normalizeWorkSpreadsheetChartAxes(axes, chartType, hasSecondaryAxes);
|
|
461
|
+
}
|
|
462
|
+
function parseXlsxAxisDisplaySettings(axis, chartType, position) {
|
|
463
|
+
const orientation = attribute(directChild(directChild(axis, 'scaling') ?? axis, 'orientation') ?? axis, 'val');
|
|
464
|
+
const rawLabelPosition = attribute(directChild(axis, 'tickLblPos') ?? axis, 'val');
|
|
465
|
+
const labelPosition = 'nextTo' === rawLabelPosition || 'high' === rawLabelPosition || 'low' === rawLabelPosition || 'none' === rawLabelPosition ? rawLabelPosition : void 0;
|
|
466
|
+
const rawTickMark = attribute(directChild(axis, 'majorTickMark') ?? axis, 'val');
|
|
467
|
+
const majorTickMark = 'in' === rawTickMark ? 'inside' : 'out' === rawTickMark ? 'outside' : 'cross' === rawTickMark ? 'cross' : 'none' === rawTickMark ? 'none' : void 0;
|
|
468
|
+
const rawInterval = workSpreadsheetChartAxisIsCategoryAxis(chartType, position) ? finiteAttributeValue(directChild(axis, 'tickLblSkip')) : void 0;
|
|
469
|
+
const labelInterval = Number.isInteger(rawInterval) && Number(rawInterval) >= 1 && Number(rawInterval) <= 31999 ? Number(rawInterval) : void 0;
|
|
470
|
+
const defaultLabelPosition = workSpreadsheetChartAxisDefaultLabelPosition(chartType, position);
|
|
471
|
+
const settings = {
|
|
472
|
+
...'maxMin' === orientation ? {
|
|
473
|
+
reverseOrder: true
|
|
474
|
+
} : {},
|
|
475
|
+
...labelPosition && labelPosition !== defaultLabelPosition ? {
|
|
476
|
+
labelPosition
|
|
477
|
+
} : {},
|
|
478
|
+
...majorTickMark && 'none' !== majorTickMark ? {
|
|
479
|
+
majorTickMark
|
|
480
|
+
} : {},
|
|
481
|
+
...void 0 !== labelInterval ? {
|
|
482
|
+
labelInterval
|
|
483
|
+
} : {}
|
|
484
|
+
};
|
|
485
|
+
return Object.keys(settings).length ? settings : void 0;
|
|
486
|
+
}
|
|
487
|
+
function chartTextTitleXml(title, reference) {
|
|
488
|
+
const text = reference?.trim() ? `<c:strRef><c:f>${escapeXml(reference.replace(/^=/, ''))}</c:f>${stringCacheXml([
|
|
489
|
+
title
|
|
490
|
+
])}</c:strRef>` : [
|
|
491
|
+
'<c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="zh-CN" sz="1400" b="0"/>',
|
|
492
|
+
`<a:t>${escapeXml(title)}</a:t></a:r><a:endParaRPr lang="zh-CN"/></a:p></c:rich>`
|
|
493
|
+
].join('');
|
|
494
|
+
return `<c:title><c:tx>${text}</c:tx><c:layout/><c:overlay val="0"/></c:title>`;
|
|
495
|
+
}
|
|
496
|
+
function chartAxisScalingXml(axis, includeBounds) {
|
|
497
|
+
return [
|
|
498
|
+
`<c:scaling><c:orientation val="${axis?.reverseOrder === true ? 'maxMin' : 'minMax'}"/>`,
|
|
499
|
+
includeBounds && Number.isFinite(axis?.maximum) ? `<c:max val="${Number(axis?.maximum)}"/>` : '',
|
|
500
|
+
includeBounds && Number.isFinite(axis?.minimum) ? `<c:min val="${Number(axis?.minimum)}"/>` : '',
|
|
501
|
+
'</c:scaling>'
|
|
502
|
+
].join('');
|
|
503
|
+
}
|
|
504
|
+
function chartAxisTickLabelPositionXml(axis, chartType, position) {
|
|
505
|
+
const value = axis?.labelPosition ?? workSpreadsheetChartAxisDefaultLabelPosition(chartType, position);
|
|
506
|
+
return `<c:tickLblPos val="${value}"/>`;
|
|
507
|
+
}
|
|
508
|
+
function chartAxisMajorTickMarkXml(axis) {
|
|
509
|
+
const value = axis?.majorTickMark;
|
|
510
|
+
if (!value || 'none' === value) return '';
|
|
511
|
+
const nativeValue = 'inside' === value ? 'in' : 'outside' === value ? 'out' : 'cross';
|
|
512
|
+
return `<c:majorTickMark val="${nativeValue}"/>`;
|
|
513
|
+
}
|
|
514
|
+
function chartCategoryAxisLabelIntervalXml(axis) {
|
|
515
|
+
return Number.isInteger(axis?.labelInterval) && Number(axis?.labelInterval) >= 1 ? `<c:tickLblSkip val="${Number(axis?.labelInterval)}"/>` : '';
|
|
516
|
+
}
|
|
517
|
+
function chartAxesXml(chart, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId) {
|
|
518
|
+
if (workSpreadsheetChartUsesNumericXAxis(chart.type)) return xyChartAxesXml(chart, categoryAxisId, valueAxisId);
|
|
519
|
+
if ('combination' === chart.type) return combinationChartAxesXml(chart, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId);
|
|
520
|
+
const horizontalBars = 'bar' === chart.type;
|
|
521
|
+
const categoryAxis = horizontalBars ? chart.axes?.left : chart.axes?.bottom;
|
|
522
|
+
const categoryAxisPosition = horizontalBars ? 'left' : 'bottom';
|
|
523
|
+
const categoryAxisTitle = chartAxisTitleXml(categoryAxis);
|
|
524
|
+
const valueAxisPosition = horizontalBars ? 'bottom' : 'left';
|
|
525
|
+
const valueAxis = horizontalBars ? chart.axes?.bottom : chart.axes?.left;
|
|
526
|
+
return [
|
|
527
|
+
'<c:catAx>',
|
|
528
|
+
`<c:axId val="${categoryAxisId}"/>`,
|
|
529
|
+
`${chartAxisScalingXml(categoryAxis, false)}<c:delete val="0"/>`,
|
|
530
|
+
`<c:axPos val="${horizontalBars ? 'l' : 'b'}"/>`,
|
|
531
|
+
categoryAxisTitle,
|
|
532
|
+
chartAxisMajorTickMarkXml(categoryAxis),
|
|
533
|
+
chartAxisTickLabelPositionXml(categoryAxis, chart.type, categoryAxisPosition),
|
|
534
|
+
`<c:crossAx val="${valueAxisId}"/><c:crosses val="autoZero"/>`,
|
|
535
|
+
`<c:auto val="1"/><c:lblAlgn val="ctr"/><c:lblOffset val="100"/>${chartCategoryAxisLabelIntervalXml(categoryAxis)}`,
|
|
536
|
+
'</c:catAx>',
|
|
537
|
+
'<c:valAx>',
|
|
538
|
+
`<c:axId val="${valueAxisId}"/>`,
|
|
539
|
+
`${chartAxisScalingXml(valueAxis, true)}<c:delete val="0"/>`,
|
|
540
|
+
`<c:axPos val="${horizontalBars ? 'b' : 'l'}"/>`,
|
|
541
|
+
chartValueAxisGridlinesXml(valueAxis, chart.type, valueAxisPosition),
|
|
542
|
+
chartAxisTitleXml(valueAxis),
|
|
543
|
+
`${chartValueAxisNumberFormatXml(valueAxis)}${chartAxisMajorTickMarkXml(valueAxis)}${chartAxisTickLabelPositionXml(valueAxis, chart.type, valueAxisPosition)}`,
|
|
544
|
+
`<c:crossAx val="${categoryAxisId}"/><c:crosses val="autoZero"/><c:crossBetween val="between"/>`,
|
|
545
|
+
chartValueAxisMajorUnitXml(valueAxis),
|
|
546
|
+
'</c:valAx>'
|
|
547
|
+
].join('');
|
|
548
|
+
}
|
|
549
|
+
function parseXlsxValueAxisSettings(axis, chartType, position, preserveVisibleDefaultGridlines) {
|
|
550
|
+
const scaling = directChild(axis, 'scaling');
|
|
551
|
+
const minimum = finiteAttributeValue(scaling ? directChild(scaling, 'min') : null);
|
|
552
|
+
const maximum = finiteAttributeValue(scaling ? directChild(scaling, 'max') : null);
|
|
553
|
+
const majorUnit = finiteAttributeValue(directChild(axis, 'majorUnit'));
|
|
554
|
+
const showMajorGridlines = 1 === directChildren(axis, 'majorGridlines').length;
|
|
555
|
+
const defaultGridlines = workSpreadsheetChartAxisShowsMajorGridlinesByDefault(chartType, position);
|
|
556
|
+
const numberFormatNode = directChild(axis, 'numFmt');
|
|
557
|
+
const numberFormat = attribute(numberFormatNode ?? axis, 'formatCode')?.trim();
|
|
558
|
+
const numberFormatSourceLinked = numberFormatNode ? readBooleanValue(attribute(numberFormatNode, 'sourceLinked')) : void 0;
|
|
559
|
+
const defaultNumberFormat = (!numberFormat || 'General' === numberFormat) && false !== numberFormatSourceLinked;
|
|
560
|
+
const settings = {
|
|
561
|
+
...void 0 !== minimum ? {
|
|
562
|
+
minimum
|
|
563
|
+
} : {},
|
|
564
|
+
...void 0 !== maximum ? {
|
|
565
|
+
maximum
|
|
566
|
+
} : {},
|
|
567
|
+
...void 0 !== majorUnit && majorUnit > 0 ? {
|
|
568
|
+
majorUnit
|
|
569
|
+
} : {},
|
|
570
|
+
...showMajorGridlines !== defaultGridlines || showMajorGridlines && preserveVisibleDefaultGridlines ? {
|
|
571
|
+
showMajorGridlines
|
|
572
|
+
} : {},
|
|
573
|
+
...!defaultNumberFormat && numberFormat ? {
|
|
574
|
+
numberFormat
|
|
575
|
+
} : {},
|
|
576
|
+
...!defaultNumberFormat && void 0 !== numberFormatSourceLinked ? {
|
|
577
|
+
numberFormatSourceLinked
|
|
578
|
+
} : {}
|
|
579
|
+
};
|
|
580
|
+
return Object.keys(settings).length ? settings : void 0;
|
|
581
|
+
}
|
|
582
|
+
function chartAxisTitleXml(axis) {
|
|
583
|
+
if (!axis) return '';
|
|
584
|
+
const title = axis.title?.trim();
|
|
585
|
+
if (!title && !axis.titleReference?.trim()) return '';
|
|
586
|
+
return chartTextTitleXml(title ?? '', axis.titleReference);
|
|
587
|
+
}
|
|
588
|
+
function chartValueAxisGridlinesXml(axis, chartType, position) {
|
|
589
|
+
const visible = axis?.showMajorGridlines ?? workSpreadsheetChartAxisShowsMajorGridlinesByDefault(chartType, position);
|
|
590
|
+
return visible ? '<c:majorGridlines/>' : '';
|
|
591
|
+
}
|
|
592
|
+
function chartValueAxisNumberFormatXml(axis) {
|
|
593
|
+
const formatCode = axis?.numberFormat?.trim() || 'General';
|
|
594
|
+
const sourceLinked = axis?.numberFormat ? true === axis.numberFormatSourceLinked : axis?.numberFormatSourceLinked !== false;
|
|
595
|
+
return `<c:numFmt formatCode="${escapeXml(formatCode)}" sourceLinked="${sourceLinked ? 1 : 0}"/>`;
|
|
596
|
+
}
|
|
597
|
+
function chartValueAxisMajorUnitXml(axis) {
|
|
598
|
+
return Number.isFinite(axis?.majorUnit) && Number(axis?.majorUnit) > 0 ? `<c:majorUnit val="${Number(axis?.majorUnit)}"/>` : '';
|
|
599
|
+
}
|
|
600
|
+
function combinationChartAxesXml(chart, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId) {
|
|
601
|
+
const primaryCategoryAxis = chart.axes?.bottom;
|
|
602
|
+
const primaryValueAxis = chart.axes?.left;
|
|
603
|
+
const primary = [
|
|
604
|
+
'<c:catAx>',
|
|
605
|
+
`<c:axId val="${categoryAxisId}"/>`,
|
|
606
|
+
`${chartAxisScalingXml(primaryCategoryAxis, false)}<c:delete val="0"/><c:axPos val="b"/>`,
|
|
607
|
+
chartAxisTitleXml(primaryCategoryAxis),
|
|
608
|
+
chartAxisMajorTickMarkXml(primaryCategoryAxis),
|
|
609
|
+
chartAxisTickLabelPositionXml(primaryCategoryAxis, chart.type, 'bottom'),
|
|
610
|
+
`<c:crossAx val="${valueAxisId}"/><c:crosses val="autoZero"/>`,
|
|
611
|
+
`<c:auto val="1"/><c:lblAlgn val="ctr"/><c:lblOffset val="100"/>${chartCategoryAxisLabelIntervalXml(primaryCategoryAxis)}`,
|
|
612
|
+
'</c:catAx>',
|
|
613
|
+
'<c:valAx>',
|
|
614
|
+
`<c:axId val="${valueAxisId}"/>`,
|
|
615
|
+
`${chartAxisScalingXml(primaryValueAxis, true)}<c:delete val="0"/><c:axPos val="l"/>`,
|
|
616
|
+
chartValueAxisGridlinesXml(primaryValueAxis, chart.type, 'left'),
|
|
617
|
+
chartAxisTitleXml(primaryValueAxis),
|
|
618
|
+
`${chartValueAxisNumberFormatXml(primaryValueAxis)}${chartAxisMajorTickMarkXml(primaryValueAxis)}${chartAxisTickLabelPositionXml(primaryValueAxis, chart.type, 'left')}`,
|
|
619
|
+
`<c:crossAx val="${categoryAxisId}"/><c:crosses val="autoZero"/><c:crossBetween val="between"/>`,
|
|
620
|
+
chartValueAxisMajorUnitXml(primaryValueAxis),
|
|
621
|
+
'</c:valAx>'
|
|
622
|
+
].join('');
|
|
623
|
+
const hasSecondary = chart.series.some((series)=>'secondary' === normalizeWorkSpreadsheetChartAxisGroup(series.axisGroup));
|
|
624
|
+
if (!hasSecondary) return primary;
|
|
625
|
+
const secondaryCategoryAxis = chart.axes?.top;
|
|
626
|
+
const secondaryValueAxis = chart.axes?.right;
|
|
627
|
+
const secondaryCategoryLabelPosition = secondaryCategoryAxis?.labelPosition ?? workSpreadsheetChartAxisDefaultLabelPosition(chart.type, 'top');
|
|
628
|
+
const secondary = [
|
|
629
|
+
'<c:catAx>',
|
|
630
|
+
`<c:axId val="${secondaryCategoryAxisId}"/>`,
|
|
631
|
+
`${chartAxisScalingXml(secondaryCategoryAxis, false)}<c:delete val="${'none' === secondaryCategoryLabelPosition ? 1 : 0}"/><c:axPos val="t"/>`,
|
|
632
|
+
chartAxisTitleXml(secondaryCategoryAxis),
|
|
633
|
+
chartAxisMajorTickMarkXml(secondaryCategoryAxis),
|
|
634
|
+
chartAxisTickLabelPositionXml(secondaryCategoryAxis, chart.type, 'top'),
|
|
635
|
+
`<c:crossAx val="${secondaryValueAxisId}"/><c:crosses val="max"/>`,
|
|
636
|
+
`<c:auto val="1"/><c:lblAlgn val="ctr"/><c:lblOffset val="100"/>${chartCategoryAxisLabelIntervalXml(secondaryCategoryAxis)}`,
|
|
637
|
+
'</c:catAx>',
|
|
638
|
+
'<c:valAx>',
|
|
639
|
+
`<c:axId val="${secondaryValueAxisId}"/>`,
|
|
640
|
+
`${chartAxisScalingXml(secondaryValueAxis, true)}<c:delete val="0"/><c:axPos val="r"/>`,
|
|
641
|
+
chartValueAxisGridlinesXml(secondaryValueAxis, chart.type, 'right'),
|
|
642
|
+
chartAxisTitleXml(secondaryValueAxis),
|
|
643
|
+
`${chartValueAxisNumberFormatXml(secondaryValueAxis)}${chartAxisMajorTickMarkXml(secondaryValueAxis)}${chartAxisTickLabelPositionXml(secondaryValueAxis, chart.type, 'right')}`,
|
|
644
|
+
`<c:crossAx val="${secondaryCategoryAxisId}"/><c:crosses val="max"/><c:crossBetween val="between"/>`,
|
|
645
|
+
chartValueAxisMajorUnitXml(secondaryValueAxis),
|
|
646
|
+
'</c:valAx>'
|
|
647
|
+
].join('');
|
|
648
|
+
return `${primary}${secondary}`;
|
|
649
|
+
}
|
|
650
|
+
function xyChartAxesXml(chart, horizontalAxisId, verticalAxisId) {
|
|
651
|
+
const horizontalAxis = chart.axes?.bottom;
|
|
652
|
+
const verticalAxis = chart.axes?.left;
|
|
653
|
+
return [
|
|
654
|
+
'<c:valAx>',
|
|
655
|
+
`<c:axId val="${horizontalAxisId}"/>`,
|
|
656
|
+
`${chartAxisScalingXml(horizontalAxis, true)}<c:delete val="0"/>`,
|
|
657
|
+
'<c:axPos val="b"/>',
|
|
658
|
+
chartValueAxisGridlinesXml(horizontalAxis, chart.type, 'bottom'),
|
|
659
|
+
chartAxisTitleXml(horizontalAxis),
|
|
660
|
+
`${chartValueAxisNumberFormatXml(horizontalAxis)}${chartAxisMajorTickMarkXml(horizontalAxis)}${chartAxisTickLabelPositionXml(horizontalAxis, chart.type, 'bottom')}`,
|
|
661
|
+
`<c:crossAx val="${verticalAxisId}"/><c:crosses val="autoZero"/><c:crossBetween val="midCat"/>`,
|
|
662
|
+
chartValueAxisMajorUnitXml(horizontalAxis),
|
|
663
|
+
'</c:valAx>',
|
|
664
|
+
'<c:valAx>',
|
|
665
|
+
`<c:axId val="${verticalAxisId}"/>`,
|
|
666
|
+
`${chartAxisScalingXml(verticalAxis, true)}<c:delete val="0"/>`,
|
|
667
|
+
'<c:axPos val="l"/>',
|
|
668
|
+
chartValueAxisGridlinesXml(verticalAxis, chart.type, 'left'),
|
|
669
|
+
chartAxisTitleXml(verticalAxis),
|
|
670
|
+
`${chartValueAxisNumberFormatXml(verticalAxis)}${chartAxisMajorTickMarkXml(verticalAxis)}${chartAxisTickLabelPositionXml(verticalAxis, chart.type, 'left')}`,
|
|
671
|
+
`<c:crossAx val="${horizontalAxisId}"/><c:crosses val="autoZero"/><c:crossBetween val="midCat"/>`,
|
|
672
|
+
chartValueAxisMajorUnitXml(verticalAxis),
|
|
673
|
+
'</c:valAx>'
|
|
674
|
+
].join('');
|
|
675
|
+
}
|
|
676
|
+
function finiteAttributeValue(element) {
|
|
677
|
+
const source = element ? attribute(element, 'val') : null;
|
|
678
|
+
const value = source?.trim() ? Number(source) : NaN;
|
|
679
|
+
return Number.isFinite(value) ? value : void 0;
|
|
680
|
+
}
|
|
681
|
+
function readBooleanValue(value) {
|
|
682
|
+
return '1' === value || 'true' === value;
|
|
683
|
+
}
|
|
684
|
+
const LEGEND_POSITIONS = new Set([
|
|
685
|
+
'r',
|
|
686
|
+
'l',
|
|
687
|
+
't',
|
|
688
|
+
'b',
|
|
689
|
+
'tr'
|
|
690
|
+
]);
|
|
691
|
+
const LEGEND_CHILDREN = new Set([
|
|
692
|
+
'legendPos',
|
|
693
|
+
'layout',
|
|
694
|
+
'overlay'
|
|
695
|
+
]);
|
|
696
|
+
const BAR_GROUPINGS = new Set([
|
|
697
|
+
'clustered',
|
|
698
|
+
'standard',
|
|
699
|
+
'stacked',
|
|
700
|
+
'percentStacked'
|
|
701
|
+
]);
|
|
702
|
+
const LINE_AREA_GROUPINGS = new Set([
|
|
703
|
+
'standard',
|
|
704
|
+
'stacked',
|
|
705
|
+
'percentStacked'
|
|
706
|
+
]);
|
|
707
|
+
function parseXlsxChartLegend(document) {
|
|
708
|
+
const legend = firstDescendant(document, 'legend');
|
|
709
|
+
if (!legend) return {
|
|
710
|
+
showLegend: false
|
|
711
|
+
};
|
|
712
|
+
const position = xlsxLegendPosition(attribute(directChild(legend, 'legendPos') ?? legend, 'val'));
|
|
713
|
+
const overlayElement = directChild(legend, 'overlay');
|
|
714
|
+
const overlay = overlayElement ? xlsxBooleanValue(attribute(overlayElement, 'val')) : false;
|
|
715
|
+
return {
|
|
716
|
+
showLegend: true,
|
|
717
|
+
legendPosition: normalizeWorkSpreadsheetChartLegendPosition(position),
|
|
718
|
+
legendOverlay: normalizeWorkSpreadsheetChartLegendOverlay(overlay)
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function xlsxChartLegendXml(chart) {
|
|
722
|
+
if (!chart.showLegend) return '';
|
|
723
|
+
const position = xlsxLegendPositionValue(normalizeWorkSpreadsheetChartLegendPosition(chart.legendPosition));
|
|
724
|
+
const overlay = normalizeWorkSpreadsheetChartLegendOverlay(chart.legendOverlay) ? 1 : 0;
|
|
725
|
+
return `<c:legend><c:legendPos val="${position}"/><c:layout/><c:overlay val="${overlay}"/></c:legend>`;
|
|
726
|
+
}
|
|
727
|
+
function parseXlsxChartPlotLayout(chartNode, chartType) {
|
|
728
|
+
if (!workSpreadsheetChartSupportsGrouping(chartType)) return {};
|
|
729
|
+
const grouping = normalizeWorkSpreadsheetChartGrouping(attribute(directChild(chartNode, 'grouping') ?? chartNode, 'val'), chartType);
|
|
730
|
+
const gapWidth = directChild(chartNode, 'gapWidth');
|
|
731
|
+
const overlap = directChild(chartNode, 'overlap');
|
|
732
|
+
return {
|
|
733
|
+
grouping,
|
|
734
|
+
...workSpreadsheetChartSupportsBarSpacing(chartType) ? {
|
|
735
|
+
gapWidth: normalizeWorkSpreadsheetChartGapWidth(gapWidth ? attribute(gapWidth, 'val') : void 0),
|
|
736
|
+
overlap: normalizeWorkSpreadsheetChartOverlap(overlap ? attribute(overlap, 'val') : void 0, grouping)
|
|
737
|
+
} : {},
|
|
738
|
+
...workSpreadsheetChartSupportsSmoothLines(chartType) ? {
|
|
739
|
+
smoothLines: normalizeWorkSpreadsheetChartSmoothLines(consistentLineSmooth(chartNode))
|
|
740
|
+
} : {}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function xlsxChartGroupingValue(chart) {
|
|
744
|
+
return normalizeWorkSpreadsheetChartGrouping(chart.grouping, chart.type);
|
|
745
|
+
}
|
|
746
|
+
function xlsxChartGapWidthValue(chart) {
|
|
747
|
+
return normalizeWorkSpreadsheetChartGapWidth(chart.gapWidth);
|
|
748
|
+
}
|
|
749
|
+
function xlsxChartOverlapValue(chart) {
|
|
750
|
+
const grouping = xlsxChartGroupingValue(chart);
|
|
751
|
+
return normalizeWorkSpreadsheetChartOverlap(chart.overlap, grouping);
|
|
752
|
+
}
|
|
753
|
+
function xlsxChartSmoothLinesValue(chart) {
|
|
754
|
+
return normalizeWorkSpreadsheetChartSmoothLines(chart.smoothLines);
|
|
755
|
+
}
|
|
756
|
+
function xlsxChartNodeUsesStackedGrouping(chartNode) {
|
|
757
|
+
const grouping = attribute(directChild(chartNode, 'grouping') ?? chartNode, 'val');
|
|
758
|
+
return 'stacked' === grouping || 'percentStacked' === grouping;
|
|
759
|
+
}
|
|
760
|
+
function isSupportedXlsxChartLegend(document) {
|
|
761
|
+
const chart = firstDescendant(document, 'chart');
|
|
762
|
+
if (!chart) return true;
|
|
763
|
+
const legends = directChildren(chart, 'legend');
|
|
764
|
+
if (legends.length > 1) return false;
|
|
765
|
+
const legend = legends[0];
|
|
766
|
+
if (!legend) return true;
|
|
767
|
+
const children = directChildren(legend);
|
|
768
|
+
if (children.some((child)=>!LEGEND_CHILDREN.has(child.localName))) return false;
|
|
769
|
+
const positions = directChildren(legend, 'legendPos');
|
|
770
|
+
if (positions.length > 1 || positions[0] && !LEGEND_POSITIONS.has(attribute(positions[0], 'val') ?? '')) return false;
|
|
771
|
+
const layouts = directChildren(legend, 'layout');
|
|
772
|
+
if (layouts.length > 1 || layouts.some((layout)=>directChildren(layout).length > 0)) return false;
|
|
773
|
+
return validOptionalBooleanElement(legend, 'overlay');
|
|
774
|
+
}
|
|
775
|
+
function isSupportedXlsxChartPlotLayout(chart) {
|
|
776
|
+
const grouping = directChildren(chart, 'grouping');
|
|
777
|
+
if (grouping.length > 1) return false;
|
|
778
|
+
if ('barChart' === chart.localName) {
|
|
779
|
+
if (!grouping.length || !BAR_GROUPINGS.has(attribute(grouping[0], 'val') ?? '')) return false;
|
|
780
|
+
if (!validOptionalIntegerElement(chart, 'gapWidth', 0, 500)) return false;
|
|
781
|
+
if (!validOptionalIntegerElement(chart, 'overlap', -100, 100)) return false;
|
|
782
|
+
} else if ('lineChart' === chart.localName || 'areaChart' === chart.localName) {
|
|
783
|
+
if (!grouping.length || !LINE_AREA_GROUPINGS.has(attribute(grouping[0], 'val') ?? '')) return false;
|
|
784
|
+
if (directChildren(chart, 'gapWidth').length || directChildren(chart, 'overlap').length) return false;
|
|
785
|
+
} else if (grouping.length || directChildren(chart, 'gapWidth').length || directChildren(chart, 'overlap').length) return false;
|
|
786
|
+
if ('lineChart' === chart.localName) {
|
|
787
|
+
if (!hasConsistentLineSmooth(chart)) return false;
|
|
788
|
+
} else if ('scatterChart' !== chart.localName && descendants(chart, 'smooth').some((element)=>xlsxBooleanValue(attribute(element, 'val')))) return false;
|
|
789
|
+
if ('scatterChart' === chart.localName && ![
|
|
790
|
+
'marker',
|
|
791
|
+
'line',
|
|
792
|
+
'lineMarker',
|
|
793
|
+
'smooth',
|
|
794
|
+
'smoothMarker'
|
|
795
|
+
].includes(attribute(firstDescendant(chart, 'scatterStyle') ?? chart, 'val') ?? '')) return false;
|
|
796
|
+
if (descendants(chart, 'bubble3D').some((element)=>xlsxBooleanValue(attribute(element, 'val')))) return false;
|
|
797
|
+
if (descendants(chart, 'explosion').some((element)=>0 !== Number(attribute(element, 'val')))) return false;
|
|
798
|
+
return !descendants(chart, 'firstSliceAng').some((element)=>0 !== Number(attribute(element, 'val')));
|
|
799
|
+
}
|
|
800
|
+
function isSupportedXlsxCombinationPlotNode(chart) {
|
|
801
|
+
if (!isSupportedXlsxChartPlotLayout(chart)) return false;
|
|
802
|
+
const grouping = attribute(directChild(chart, 'grouping') ?? chart, 'val');
|
|
803
|
+
if ('lineChart' === chart.localName || 'areaChart' === chart.localName) return 'standard' === grouping && ('lineChart' !== chart.localName || false === consistentLineSmooth(chart));
|
|
804
|
+
if ('barChart' !== chart.localName || 'clustered' !== grouping) return false;
|
|
805
|
+
const gapWidth = directChild(chart, 'gapWidth');
|
|
806
|
+
const overlap = directChild(chart, 'overlap');
|
|
807
|
+
return 150 === Number(attribute(gapWidth ?? chart, 'val') ?? 150) && 0 === Number(attribute(overlap ?? chart, 'val') ?? 0);
|
|
808
|
+
}
|
|
809
|
+
function xlsxChartGroupingIsStacked(chart) {
|
|
810
|
+
return workSpreadsheetChartGroupingIsStacked(xlsxChartGroupingValue(chart));
|
|
811
|
+
}
|
|
812
|
+
function xlsxLegendPosition(value) {
|
|
813
|
+
if ('l' === value) return 'left';
|
|
814
|
+
if ('t' === value) return 'top';
|
|
815
|
+
if ('b' === value) return 'bottom';
|
|
816
|
+
if ('tr' === value) return 'topRight';
|
|
817
|
+
return 'right';
|
|
818
|
+
}
|
|
819
|
+
function xlsxLegendPositionValue(position) {
|
|
820
|
+
if ('left' === position) return 'l';
|
|
821
|
+
if ('top' === position) return 't';
|
|
822
|
+
if ('bottom' === position) return 'b';
|
|
823
|
+
if ('topRight' === position) return 'tr';
|
|
824
|
+
return 'r';
|
|
825
|
+
}
|
|
826
|
+
function hasConsistentLineSmooth(chart) {
|
|
827
|
+
const chartSmooth = directChildren(chart, 'smooth');
|
|
828
|
+
if (chartSmooth.length > 1 || !chartSmooth.every(validBooleanElement)) return false;
|
|
829
|
+
const series = directChildren(chart, 'ser');
|
|
830
|
+
if (series.some((item)=>directChildren(item, 'smooth').length > 1)) return false;
|
|
831
|
+
const explicit = [
|
|
832
|
+
...chartSmooth,
|
|
833
|
+
...series.flatMap((item)=>directChildren(item, 'smooth'))
|
|
834
|
+
];
|
|
835
|
+
if (!explicit.every(validBooleanElement)) return false;
|
|
836
|
+
if (!explicit.length) return true;
|
|
837
|
+
const values = explicit.map((element)=>xlsxBooleanValue(attribute(element, 'val')));
|
|
838
|
+
return values.every((value)=>value === values[0]);
|
|
839
|
+
}
|
|
840
|
+
function consistentLineSmooth(chart) {
|
|
841
|
+
const explicit = [
|
|
842
|
+
...directChildren(chart, 'smooth'),
|
|
843
|
+
...directChildren(chart, 'ser').flatMap((series)=>directChildren(series, 'smooth'))
|
|
844
|
+
];
|
|
845
|
+
if (!explicit.length || !hasConsistentLineSmooth(chart)) return false;
|
|
846
|
+
return xlsxBooleanValue(attribute(explicit[0], 'val'));
|
|
847
|
+
}
|
|
848
|
+
function validOptionalIntegerElement(parent, localName, minimum, maximum) {
|
|
849
|
+
const elements = directChildren(parent, localName);
|
|
850
|
+
if (elements.length > 1) return false;
|
|
851
|
+
if (!elements.length) return true;
|
|
852
|
+
const value = Number(attribute(elements[0], 'val'));
|
|
853
|
+
return Number.isInteger(value) && value >= minimum && value <= maximum;
|
|
854
|
+
}
|
|
855
|
+
function validOptionalBooleanElement(parent, localName) {
|
|
856
|
+
const elements = directChildren(parent, localName);
|
|
857
|
+
return elements.length <= 1 && elements.every(validBooleanElement);
|
|
858
|
+
}
|
|
859
|
+
function validBooleanElement(element) {
|
|
860
|
+
const value = attribute(element, 'val');
|
|
861
|
+
return null === value || '0' === value || '1' === value || 'false' === value || 'true' === value;
|
|
862
|
+
}
|
|
863
|
+
function xlsxBooleanValue(value) {
|
|
864
|
+
return null === value || '1' === value || 'true' === value;
|
|
865
|
+
}
|
|
866
|
+
const EMUS_PER_POINT = 12700;
|
|
867
|
+
const DASH_VALUES = new Set([
|
|
868
|
+
'solid',
|
|
869
|
+
'dash',
|
|
870
|
+
'dot',
|
|
871
|
+
'dashDot'
|
|
872
|
+
]);
|
|
873
|
+
const MARKER_VALUES = new Set([
|
|
874
|
+
'none',
|
|
875
|
+
'circle',
|
|
876
|
+
'square',
|
|
877
|
+
'diamond',
|
|
878
|
+
'triangle',
|
|
879
|
+
'plus',
|
|
880
|
+
'x',
|
|
881
|
+
'star'
|
|
882
|
+
]);
|
|
883
|
+
const MARKER_CHART_ELEMENTS = new Set([
|
|
884
|
+
'lineChart',
|
|
885
|
+
'radarChart',
|
|
886
|
+
'scatterChart'
|
|
887
|
+
]);
|
|
888
|
+
function parseXlsxChartSeriesStyle(series) {
|
|
889
|
+
const shape = directChild(series, 'spPr');
|
|
890
|
+
const base = shape ? parseShapeProperties(shape) : void 0;
|
|
891
|
+
const markerNode = directChild(series, 'marker');
|
|
892
|
+
const marker = markerNode ? parseMarkerStyle(markerNode) : void 0;
|
|
893
|
+
return normalizeWorkSpreadsheetChartSeriesStyle({
|
|
894
|
+
...base,
|
|
895
|
+
...marker ? {
|
|
896
|
+
marker
|
|
897
|
+
} : {}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function xlsxChartSeriesShapePropertiesXml(style) {
|
|
901
|
+
const normalized = normalizeWorkSpreadsheetChartSeriesStyle(style);
|
|
902
|
+
if (!normalized) return '';
|
|
903
|
+
const fill = normalized.fillColor ? xlsxSolidFillXml(normalized.fillColor, normalized.fillTransparency) : '';
|
|
904
|
+
const line = xlsxLineXml(normalized);
|
|
905
|
+
return fill || line ? `<c:spPr>${fill}${line}</c:spPr>` : '';
|
|
906
|
+
}
|
|
907
|
+
function xlsxChartSeriesMarkerXml(style, defaultVisible, defaultSize = 5) {
|
|
908
|
+
const marker = normalizeWorkSpreadsheetChartSeriesStyle(style)?.marker;
|
|
909
|
+
if (!marker) return defaultVisible ? `<c:marker><c:symbol val="circle"/><c:size val="${defaultSize}"/></c:marker>` : '<c:marker><c:symbol val="none"/></c:marker>';
|
|
910
|
+
const symbol = marker.symbol ?? 'circle';
|
|
911
|
+
if ('none' === symbol) return '<c:marker><c:symbol val="none"/></c:marker>';
|
|
912
|
+
const size = marker.size ?? defaultSize;
|
|
913
|
+
const shape = xlsxMarkerShapePropertiesXml(marker);
|
|
914
|
+
return `<c:marker><c:symbol val="${symbol}"/><c:size val="${size}"/>${shape}</c:marker>`;
|
|
915
|
+
}
|
|
916
|
+
function isSupportedXlsxChartSeriesFormatting(series, chartLocalName) {
|
|
917
|
+
const shapeProperties = directChildren(series, 'spPr');
|
|
918
|
+
if (shapeProperties.length > 1 || shapeProperties.some((shape)=>!isSupportedSeriesShapeProperties(shape))) return false;
|
|
919
|
+
const markers = directChildren(series, 'marker');
|
|
920
|
+
if (markers.length > 1) return false;
|
|
921
|
+
if (!markers.length) return true;
|
|
922
|
+
if (!MARKER_CHART_ELEMENTS.has(chartLocalName)) return false;
|
|
923
|
+
return isSupportedMarker(markers[0]);
|
|
924
|
+
}
|
|
925
|
+
function xlsxChartSeriesFormattingShapeProperties(series) {
|
|
926
|
+
return [
|
|
927
|
+
...directChildren(series, 'spPr'),
|
|
928
|
+
...directChildren(series, 'marker').flatMap((marker)=>directChildren(marker, 'spPr'))
|
|
929
|
+
];
|
|
930
|
+
}
|
|
931
|
+
function parseShapeProperties(shape) {
|
|
932
|
+
const fill = parseSolidFill(directChild(shape, 'solidFill'));
|
|
933
|
+
const line = directChild(shape, 'ln');
|
|
934
|
+
const lineFill = line ? parseSolidFill(directChild(line, 'solidFill')) : void 0;
|
|
935
|
+
const width = line ? xlsxLineWidth(line) : void 0;
|
|
936
|
+
const dash = line ? xlsxLineDash(line) : void 0;
|
|
937
|
+
return normalizeWorkSpreadsheetChartSeriesStyle({
|
|
938
|
+
...fill ? {
|
|
939
|
+
fillColor: fill.color,
|
|
940
|
+
...void 0 !== fill.transparency ? {
|
|
941
|
+
fillTransparency: fill.transparency
|
|
942
|
+
} : {}
|
|
943
|
+
} : {},
|
|
944
|
+
...lineFill ? {
|
|
945
|
+
lineColor: lineFill.color
|
|
946
|
+
} : {},
|
|
947
|
+
...void 0 !== width ? {
|
|
948
|
+
lineWidth: width
|
|
949
|
+
} : {},
|
|
950
|
+
...dash ? {
|
|
951
|
+
lineDash: dash
|
|
952
|
+
} : {}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
function parseMarkerStyle(marker) {
|
|
956
|
+
const symbol = attribute(directChild(marker, 'symbol') ?? marker, 'val');
|
|
957
|
+
const sizeSource = attribute(directChild(marker, 'size') ?? marker, 'val');
|
|
958
|
+
const size = null === sizeSource ? void 0 : Number(sizeSource);
|
|
959
|
+
const shape = directChild(marker, 'spPr');
|
|
960
|
+
const fill = shape ? parseSolidFill(directChild(shape, 'solidFill')) : void 0;
|
|
961
|
+
const line = shape ? directChild(shape, 'ln') : void 0;
|
|
962
|
+
const lineFill = line ? parseSolidFill(directChild(line, 'solidFill')) : void 0;
|
|
963
|
+
const parsed = {
|
|
964
|
+
...MARKER_VALUES.has(symbol) ? {
|
|
965
|
+
symbol: symbol
|
|
966
|
+
} : {},
|
|
967
|
+
...Number.isFinite(size) ? {
|
|
968
|
+
size
|
|
969
|
+
} : {},
|
|
970
|
+
...fill ? {
|
|
971
|
+
fillColor: fill.color
|
|
972
|
+
} : {},
|
|
973
|
+
...lineFill ? {
|
|
974
|
+
lineColor: lineFill.color
|
|
975
|
+
} : {}
|
|
976
|
+
};
|
|
977
|
+
return Object.keys(parsed).length ? parsed : void 0;
|
|
978
|
+
}
|
|
979
|
+
function parseSolidFill(fill) {
|
|
980
|
+
if (!fill) return;
|
|
981
|
+
const color = directChild(fill, 'srgbClr');
|
|
982
|
+
const normalized = normalizeWorkSpreadsheetChartColor(attribute(color ?? fill, 'val'));
|
|
983
|
+
if (!color || !normalized) return;
|
|
984
|
+
const alphaElement = directChild(color, 'alpha');
|
|
985
|
+
if (!alphaElement) return {
|
|
986
|
+
color: normalized
|
|
987
|
+
};
|
|
988
|
+
const alphaSource = attribute(alphaElement, 'val');
|
|
989
|
+
if (null === alphaSource) return {
|
|
990
|
+
color: normalized
|
|
991
|
+
};
|
|
992
|
+
const alpha = Number(alphaSource);
|
|
993
|
+
if (!Number.isFinite(alpha)) return {
|
|
994
|
+
color: normalized
|
|
995
|
+
};
|
|
996
|
+
return {
|
|
997
|
+
color: normalized,
|
|
998
|
+
transparency: Math.round((100 - alpha / 1000) * 100) / 100
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function xlsxLineWidth(line) {
|
|
1002
|
+
const source = attribute(line, 'w');
|
|
1003
|
+
if (null === source) return;
|
|
1004
|
+
const width = Number(source);
|
|
1005
|
+
return Number.isFinite(width) ? Math.round(width / EMUS_PER_POINT * 100) / 100 : void 0;
|
|
1006
|
+
}
|
|
1007
|
+
function xlsxLineDash(line) {
|
|
1008
|
+
const value = attribute(directChild(line, 'prstDash') ?? line, 'val');
|
|
1009
|
+
return DASH_VALUES.has(value) ? value : void 0;
|
|
1010
|
+
}
|
|
1011
|
+
function xlsxSolidFillXml(color, transparency) {
|
|
1012
|
+
const value = color.replace(/^#/, '');
|
|
1013
|
+
if (void 0 === transparency) return `<a:solidFill><a:srgbClr val="${value}"/></a:solidFill>`;
|
|
1014
|
+
return `<a:solidFill><a:srgbClr val="${value}"><a:alpha val="${Math.round((100 - transparency) * 1000)}"/></a:srgbClr></a:solidFill>`;
|
|
1015
|
+
}
|
|
1016
|
+
function xlsxLineXml(style) {
|
|
1017
|
+
if (!style.lineColor && void 0 === style.lineWidth && !style.lineDash) return '';
|
|
1018
|
+
const width = void 0 === style.lineWidth ? '' : ` w="${Math.round(style.lineWidth * EMUS_PER_POINT)}"`;
|
|
1019
|
+
const fill = style.lineColor ? xlsxSolidFillXml(style.lineColor) : '';
|
|
1020
|
+
const dash = style.lineDash ? `<a:prstDash val="${style.lineDash}"/>` : '';
|
|
1021
|
+
return `<a:ln${width}>${fill}${dash}</a:ln>`;
|
|
1022
|
+
}
|
|
1023
|
+
function xlsxMarkerShapePropertiesXml(marker) {
|
|
1024
|
+
const fill = marker.fillColor ? xlsxSolidFillXml(marker.fillColor) : '';
|
|
1025
|
+
const line = marker.lineColor ? `<a:ln>${xlsxSolidFillXml(marker.lineColor)}</a:ln>` : '';
|
|
1026
|
+
return fill || line ? `<c:spPr>${fill}${line}</c:spPr>` : '';
|
|
1027
|
+
}
|
|
1028
|
+
function isSupportedSeriesShapeProperties(shape) {
|
|
1029
|
+
if (!hasOnlyAttributes(shape, [])) return false;
|
|
1030
|
+
const children = directChildren(shape);
|
|
1031
|
+
if (children.some((child)=>'solidFill' !== child.localName && 'ln' !== child.localName)) return false;
|
|
1032
|
+
const fills = directChildren(shape, 'solidFill');
|
|
1033
|
+
const lines = directChildren(shape, 'ln');
|
|
1034
|
+
if (fills.length > 1 || lines.length > 1) return false;
|
|
1035
|
+
if (fills.some((fill)=>!isSupportedSolidFill(fill, true))) return false;
|
|
1036
|
+
return !lines.length || isSupportedSeriesLine(lines[0]);
|
|
1037
|
+
}
|
|
1038
|
+
function isSupportedSeriesLine(line) {
|
|
1039
|
+
if (!hasOnlyAttributes(line, [
|
|
1040
|
+
'w'
|
|
1041
|
+
])) return false;
|
|
1042
|
+
const children = directChildren(line);
|
|
1043
|
+
if (children.some((child)=>'solidFill' !== child.localName && 'prstDash' !== child.localName)) return false;
|
|
1044
|
+
const fills = directChildren(line, 'solidFill');
|
|
1045
|
+
const dashes = directChildren(line, 'prstDash');
|
|
1046
|
+
if (fills.length > 1 || dashes.length > 1 || fills.some((fill)=>!isSupportedSolidFill(fill, false))) return false;
|
|
1047
|
+
const widthSource = attribute(line, 'w');
|
|
1048
|
+
if (null !== widthSource) {
|
|
1049
|
+
const width = Number(widthSource);
|
|
1050
|
+
if (!Number.isInteger(width) || width < 0.25 * EMUS_PER_POINT || width > 20 * EMUS_PER_POINT) return false;
|
|
1051
|
+
}
|
|
1052
|
+
return !dashes.length || DASH_VALUES.has(attribute(dashes[0], 'val'));
|
|
1053
|
+
}
|
|
1054
|
+
function isSupportedMarker(marker) {
|
|
1055
|
+
if (!hasOnlyAttributes(marker, [])) return false;
|
|
1056
|
+
const children = directChildren(marker);
|
|
1057
|
+
if (children.some((child)=>![
|
|
1058
|
+
'symbol',
|
|
1059
|
+
'size',
|
|
1060
|
+
'spPr'
|
|
1061
|
+
].includes(child.localName))) return false;
|
|
1062
|
+
const symbols = directChildren(marker, 'symbol');
|
|
1063
|
+
const sizes = directChildren(marker, 'size');
|
|
1064
|
+
const shapes = directChildren(marker, 'spPr');
|
|
1065
|
+
if (1 !== symbols.length || sizes.length > 1 || shapes.length > 1) return false;
|
|
1066
|
+
const symbol = attribute(symbols[0], 'val');
|
|
1067
|
+
if (!hasOnlyAttributes(symbols[0], [
|
|
1068
|
+
'val'
|
|
1069
|
+
])) return false;
|
|
1070
|
+
if (!MARKER_VALUES.has(symbol)) return false;
|
|
1071
|
+
if (sizes.length) {
|
|
1072
|
+
if (!hasOnlyAttributes(sizes[0], [
|
|
1073
|
+
'val'
|
|
1074
|
+
])) return false;
|
|
1075
|
+
const size = Number(attribute(sizes[0], 'val'));
|
|
1076
|
+
if (!Number.isInteger(size) || size < 2 || size > 72 || 'none' === symbol) return false;
|
|
1077
|
+
}
|
|
1078
|
+
return !shapes.length || isSupportedMarkerShapeProperties(shapes[0]);
|
|
1079
|
+
}
|
|
1080
|
+
function isSupportedMarkerShapeProperties(shape) {
|
|
1081
|
+
if (!hasOnlyAttributes(shape, [])) return false;
|
|
1082
|
+
const children = directChildren(shape);
|
|
1083
|
+
if (children.some((child)=>'solidFill' !== child.localName && 'ln' !== child.localName)) return false;
|
|
1084
|
+
const fills = directChildren(shape, 'solidFill');
|
|
1085
|
+
const lines = directChildren(shape, 'ln');
|
|
1086
|
+
if (fills.length > 1 || lines.length > 1 || fills.some((fill)=>!isSupportedSolidFill(fill, false))) return false;
|
|
1087
|
+
if (!lines.length) return true;
|
|
1088
|
+
const line = lines[0];
|
|
1089
|
+
const lineChildren = directChildren(line);
|
|
1090
|
+
return hasOnlyAttributes(line, []) && lineChildren.length <= 1 && lineChildren.every((child)=>'solidFill' === child.localName && isSupportedSolidFill(child, false));
|
|
1091
|
+
}
|
|
1092
|
+
function isSupportedSolidFill(fill, allowAlpha) {
|
|
1093
|
+
if (!hasOnlyAttributes(fill, [])) return false;
|
|
1094
|
+
const children = directChildren(fill);
|
|
1095
|
+
if (1 !== children.length || 'srgbClr' !== children[0].localName) return false;
|
|
1096
|
+
const color = children[0];
|
|
1097
|
+
if (!hasOnlyAttributes(color, [
|
|
1098
|
+
'val'
|
|
1099
|
+
])) return false;
|
|
1100
|
+
if (!/^[0-9a-f]{6}$/i.test(attribute(color, 'val') ?? '')) return false;
|
|
1101
|
+
const transforms = directChildren(color);
|
|
1102
|
+
if (transforms.some((transform)=>'alpha' !== transform.localName) || transforms.length > (allowAlpha ? 1 : 0)) return false;
|
|
1103
|
+
if (!transforms.length) return true;
|
|
1104
|
+
if (!hasOnlyAttributes(transforms[0], [
|
|
1105
|
+
'val'
|
|
1106
|
+
])) return false;
|
|
1107
|
+
const alpha = Number(attribute(transforms[0], 'val'));
|
|
1108
|
+
return Number.isInteger(alpha) && alpha >= 0 && alpha <= 100000;
|
|
1109
|
+
}
|
|
1110
|
+
function hasOnlyAttributes(element, allowed) {
|
|
1111
|
+
return Array.from(element.attributes).every((item)=>'xmlns' === item.name || 'xmlns' === item.prefix || allowed.includes(item.localName));
|
|
1112
|
+
}
|
|
1113
|
+
const XLSX_CHART_RELATIONSHIP = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart';
|
|
1114
|
+
const XLSX_CHART_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml';
|
|
1115
|
+
const RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
1116
|
+
const CHART_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/chart';
|
|
1117
|
+
const DRAWINGML_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/main';
|
|
1118
|
+
const CHART_GRAPHIC_URI = 'http://schemas.openxmlformats.org/drawingml/2006/chart';
|
|
1119
|
+
const SUPPORTED_CHART_ELEMENTS = new Set([
|
|
1120
|
+
'barChart',
|
|
1121
|
+
'lineChart',
|
|
1122
|
+
'pieChart',
|
|
1123
|
+
'doughnutChart',
|
|
1124
|
+
'areaChart',
|
|
1125
|
+
'radarChart',
|
|
1126
|
+
'scatterChart',
|
|
1127
|
+
'bubbleChart'
|
|
1128
|
+
]);
|
|
1129
|
+
function isSupportedXlsxCombinationChartNodes(chartNodes) {
|
|
1130
|
+
if (chartNodes.length < 2 || !chartNodes.every((node)=>null !== xlsxCombinationSeriesType(node) && isSupportedXlsxCombinationPlotNode(node))) return false;
|
|
1131
|
+
const categoryNodes = chartNodes.flatMap((node)=>directChildren(node, 'ser').map((series)=>directChild(series, 'cat')));
|
|
1132
|
+
if (!categoryNodes.length || categoryNodes.some((category)=>!category)) return false;
|
|
1133
|
+
const signatures = categoryNodes.map((category)=>categorySourceSignature(category));
|
|
1134
|
+
return signatures.every((signature)=>signature === signatures[0]);
|
|
1135
|
+
}
|
|
1136
|
+
function categorySourceSignature(category) {
|
|
1137
|
+
const reference = formulaReference(category);
|
|
1138
|
+
return reference ? `reference:${reference}` : `literal:${JSON.stringify(cachedValues(category))}`;
|
|
1139
|
+
}
|
|
1140
|
+
async function readXlsxWorksheetCharts(archive, worksheetPart, worksheet) {
|
|
1141
|
+
const drawingReference = directChildren(worksheet.documentElement, 'drawing')[0];
|
|
1142
|
+
const relationshipId = drawingReference ? attribute(drawingReference, 'r:id') ?? attribute(drawingReference, 'id') : null;
|
|
1143
|
+
if (!relationshipId) return [];
|
|
1144
|
+
const worksheetRelationships = await archive.relationships(worksheetPart);
|
|
1145
|
+
const drawingRelationship = worksheetRelationships.get(relationshipId);
|
|
1146
|
+
if (!drawingRelationship || 'External' === drawingRelationship.targetMode || !drawingRelationship.type.endsWith('/drawing') || !archive.has(drawingRelationship.target)) return [];
|
|
1147
|
+
const drawing = await archive.xml(drawingRelationship.target);
|
|
1148
|
+
const relationships = await archive.relationships(drawingRelationship.target);
|
|
1149
|
+
const charts = [];
|
|
1150
|
+
for (const [anchorIndex, anchor] of directChildren(drawing.documentElement).entries()){
|
|
1151
|
+
if (![
|
|
1152
|
+
'twoCellAnchor',
|
|
1153
|
+
'oneCellAnchor',
|
|
1154
|
+
'absoluteAnchor'
|
|
1155
|
+
].includes(anchor.localName)) continue;
|
|
1156
|
+
const frame = directChild(anchor, 'graphicFrame');
|
|
1157
|
+
const chartReference = firstDescendant(frame, 'chart');
|
|
1158
|
+
const chartRelationshipId = chartReference ? attribute(chartReference, 'r:id') ?? attribute(chartReference, 'id') : null;
|
|
1159
|
+
const relationship = chartRelationshipId ? relationships.get(chartRelationshipId) : void 0;
|
|
1160
|
+
if (!frame || !relationship || 'External' === relationship.targetMode || !relationship.type.endsWith('/chart') || !archive.has(relationship.target)) continue;
|
|
1161
|
+
const parsed = parseXlsxChart(await archive.xml(relationship.target));
|
|
1162
|
+
if (!parsed) continue;
|
|
1163
|
+
const properties = firstDescendant(frame, 'cNvPr');
|
|
1164
|
+
const sourceId = attribute(properties ?? frame, 'id') ?? String(anchorIndex + 1);
|
|
1165
|
+
charts.push({
|
|
1166
|
+
id: `xlsx-chart-${partNumber(drawingRelationship.target)}-${sourceId}`,
|
|
1167
|
+
name: attribute(properties ?? frame, 'name')?.trim() || `Worksheet chart ${anchorIndex + 1}`,
|
|
1168
|
+
altText: attribute(properties ?? frame, 'descr')?.trim() || void 0,
|
|
1169
|
+
...parsed,
|
|
1170
|
+
...readXlsxDrawingAnchor(anchor)
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
return charts;
|
|
1174
|
+
}
|
|
1175
|
+
function xlsxWorksheetChartsToSheet(charts, config) {
|
|
1176
|
+
return charts.flatMap((chart)=>{
|
|
1177
|
+
const bounds = xlsxDrawingAnchorToBounds(chart, config);
|
|
1178
|
+
if (!bounds) return [];
|
|
1179
|
+
return [
|
|
1180
|
+
{
|
|
1181
|
+
...chart,
|
|
1182
|
+
...bounds
|
|
1183
|
+
}
|
|
1184
|
+
];
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
function xlsxChartGraphicFrameXml(chart, relationshipId, objectId, sheet) {
|
|
1188
|
+
return [
|
|
1189
|
+
'<xdr:twoCellAnchor editAs="oneCell">',
|
|
1190
|
+
xlsxTwoCellAnchorMarkers(chart, sheet),
|
|
1191
|
+
'<xdr:graphicFrame macro="">',
|
|
1192
|
+
'<xdr:nvGraphicFramePr>',
|
|
1193
|
+
`<xdr:cNvPr id="${objectId}" name="${escapeXml(chart.name.trim() || `Chart ${objectId}`)}"${chart.altText?.trim() ? ` descr="${escapeXml(chart.altText.trim())}"` : ''}/>`,
|
|
1194
|
+
'<xdr:cNvGraphicFramePr/>',
|
|
1195
|
+
'</xdr:nvGraphicFramePr>',
|
|
1196
|
+
'<xdr:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/></xdr:xfrm>',
|
|
1197
|
+
`<a:graphic><a:graphicData uri="${CHART_GRAPHIC_URI}">`,
|
|
1198
|
+
`<c:chart xmlns:c="${CHART_NAMESPACE}" xmlns:r="${RELATIONSHIP_NAMESPACE}" r:id="${relationshipId}"/>`,
|
|
1199
|
+
'</a:graphicData></a:graphic>',
|
|
1200
|
+
'</xdr:graphicFrame>',
|
|
1201
|
+
'<xdr:clientData/>',
|
|
1202
|
+
'</xdr:twoCellAnchor>'
|
|
1203
|
+
].join('');
|
|
1204
|
+
}
|
|
1205
|
+
function xlsxChartPartXml(chart, content, ownerSheet, chartIndex) {
|
|
1206
|
+
const resolved = resolveSpreadsheetChart(content, ownerSheet, chart);
|
|
1207
|
+
const categoryAxisId = 10000000 + 2 * chartIndex;
|
|
1208
|
+
const valueAxisId = categoryAxisId + 1;
|
|
1209
|
+
const secondaryCategoryAxisId = categoryAxisId + 2;
|
|
1210
|
+
const secondaryValueAxisId = categoryAxisId + 3;
|
|
1211
|
+
return [
|
|
1212
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
1213
|
+
`<c:chartSpace xmlns:c="${CHART_NAMESPACE}" xmlns:a="${DRAWINGML_NAMESPACE}" xmlns:r="${RELATIONSHIP_NAMESPACE}">`,
|
|
1214
|
+
'<c:date1904 val="0"/><c:lang val="zh-CN"/><c:roundedCorners val="0"/>',
|
|
1215
|
+
'<c:chart>',
|
|
1216
|
+
chartTitleXml(resolved),
|
|
1217
|
+
`<c:autoTitleDeleted val="${resolved.title?.trim() ? 0 : 1}"/>`,
|
|
1218
|
+
'<c:plotArea><c:layout/>',
|
|
1219
|
+
chartPlotXml(resolved, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId),
|
|
1220
|
+
'pie' === resolved.type || 'doughnut' === resolved.type ? '' : chartAxesXml(resolved, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId),
|
|
1221
|
+
'</c:plotArea>',
|
|
1222
|
+
xlsxChartLegendXml(resolved),
|
|
1223
|
+
'<c:plotVisOnly val="1"/><c:dispBlanksAs val="gap"/><c:showDLblsOverMax val="0"/>',
|
|
1224
|
+
'</c:chart>',
|
|
1225
|
+
'<c:printSettings><c:headerFooter/><c:pageMargins b="0.75" l="0.7" r="0.7" t="0.75" header="0.3" footer="0.3"/><c:pageSetup/></c:printSettings>',
|
|
1226
|
+
'</c:chartSpace>'
|
|
1227
|
+
].join('');
|
|
1228
|
+
}
|
|
1229
|
+
function parseXlsxChart(document) {
|
|
1230
|
+
const plotArea = firstDescendant(document, 'plotArea');
|
|
1231
|
+
if (!plotArea) return null;
|
|
1232
|
+
const chartNodes = directChildren(plotArea).filter((element)=>/Chart$/i.test(element.localName));
|
|
1233
|
+
if (isSupportedXlsxCombinationChartNodes(chartNodes)) return parseXlsxCombinationChart(document, chartNodes);
|
|
1234
|
+
if (1 !== chartNodes.length || !SUPPORTED_CHART_ELEMENTS.has(chartNodes[0].localName)) return null;
|
|
1235
|
+
const chartNode = chartNodes[0];
|
|
1236
|
+
const type = xlsxChartType(chartNode);
|
|
1237
|
+
const seriesNodes = directChildren(chartNode, 'ser');
|
|
1238
|
+
if (!seriesNodes.length) return null;
|
|
1239
|
+
const chartDataLabels = parseXlsxDataLabels(directChild(chartNode, 'dLbls'), type);
|
|
1240
|
+
const parsedSeries = seriesNodes.map((node, index)=>parseXlsxChartSeries(node, index, type, chartDataLabels));
|
|
1241
|
+
const series = xlsxChartNodeUsesStackedGrouping(chartNode) ? parsedSeries.map(withoutStackedChartAnalysis) : parsedSeries;
|
|
1242
|
+
const categoryNode = workSpreadsheetChartUsesNumericXAxis(type) ? null : directChild(seriesNodes[0], 'cat');
|
|
1243
|
+
const title = xlsxChartTitle(document);
|
|
1244
|
+
const axes = parseXlsxChartAxes(document, type, false);
|
|
1245
|
+
const legend = parseXlsxChartLegend(document);
|
|
1246
|
+
const layout = parseXlsxChartPlotLayout(chartNode, type);
|
|
1247
|
+
return {
|
|
1248
|
+
type,
|
|
1249
|
+
title: title ? cachedText(title) || richText(title) || void 0 : void 0,
|
|
1250
|
+
titleReference: title ? formulaReference(title) : void 0,
|
|
1251
|
+
categories: categoryNode ? cachedValues(categoryNode) : [],
|
|
1252
|
+
categoryReference: categoryNode ? formulaReference(categoryNode) : void 0,
|
|
1253
|
+
series,
|
|
1254
|
+
...legend,
|
|
1255
|
+
...layout,
|
|
1256
|
+
...axes ? {
|
|
1257
|
+
axes
|
|
1258
|
+
} : {},
|
|
1259
|
+
...'doughnut' === type ? {
|
|
1260
|
+
doughnutHoleSize: readDoughnutHoleSize(chartNode)
|
|
1261
|
+
} : {},
|
|
1262
|
+
...'radar' === type ? {
|
|
1263
|
+
radarStyle: readRadarStyle(chartNode)
|
|
1264
|
+
} : {},
|
|
1265
|
+
...'scatter' === type ? {
|
|
1266
|
+
scatterStyle: readScatterStyle(chartNode)
|
|
1267
|
+
} : {},
|
|
1268
|
+
...'bubble' === type ? {
|
|
1269
|
+
bubbleScale: readBubbleScale(chartNode),
|
|
1270
|
+
showNegativeBubbles: readShowNegativeBubbles(chartNode),
|
|
1271
|
+
bubbleSizeRepresents: readBubbleSizeRepresents(chartNode)
|
|
1272
|
+
} : {}
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
function parseXlsxCombinationChart(document, chartNodes) {
|
|
1276
|
+
const entries = chartNodes.flatMap((chartNode)=>{
|
|
1277
|
+
const chartType = xlsxCombinationSeriesType(chartNode);
|
|
1278
|
+
if (!chartType) return [];
|
|
1279
|
+
const axisGroup = xlsxCombinationAxisGroup(document, chartNode);
|
|
1280
|
+
const chartDataLabels = parseXlsxDataLabels(directChild(chartNode, 'dLbls'), chartType);
|
|
1281
|
+
return directChildren(chartNode, 'ser').map((seriesNode, sourceIndex)=>({
|
|
1282
|
+
seriesNode,
|
|
1283
|
+
chartType,
|
|
1284
|
+
axisGroup,
|
|
1285
|
+
chartDataLabels,
|
|
1286
|
+
order: finiteNumber(attribute(directChild(seriesNode, 'order') ?? seriesNode, 'val') ?? sourceIndex, sourceIndex)
|
|
1287
|
+
}));
|
|
1288
|
+
}).sort((left, right)=>left.order - right.order);
|
|
1289
|
+
if (!entries.length) return null;
|
|
1290
|
+
const categoryNode = directChild(entries[0].seriesNode, 'cat');
|
|
1291
|
+
const title = xlsxChartTitle(document);
|
|
1292
|
+
const hasSecondaryAxes = entries.some((entry)=>'secondary' === entry.axisGroup);
|
|
1293
|
+
const axes = parseXlsxChartAxes(document, 'combination', hasSecondaryAxes);
|
|
1294
|
+
const legend = parseXlsxChartLegend(document);
|
|
1295
|
+
return {
|
|
1296
|
+
type: 'combination',
|
|
1297
|
+
title: title ? cachedText(title) || richText(title) || void 0 : void 0,
|
|
1298
|
+
titleReference: title ? formulaReference(title) : void 0,
|
|
1299
|
+
categories: categoryNode ? cachedValues(categoryNode) : [],
|
|
1300
|
+
categoryReference: categoryNode ? formulaReference(categoryNode) : void 0,
|
|
1301
|
+
series: entries.map((entry, index)=>({
|
|
1302
|
+
...parseXlsxChartSeries(entry.seriesNode, index, entry.chartType, entry.chartDataLabels),
|
|
1303
|
+
chartType: entry.chartType,
|
|
1304
|
+
axisGroup: entry.axisGroup
|
|
1305
|
+
})),
|
|
1306
|
+
...legend,
|
|
1307
|
+
...axes ? {
|
|
1308
|
+
axes
|
|
1309
|
+
} : {}
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
function withoutStackedChartAnalysis(series) {
|
|
1313
|
+
const editable = {
|
|
1314
|
+
...series
|
|
1315
|
+
};
|
|
1316
|
+
delete editable.errorBars;
|
|
1317
|
+
delete editable.trendlines;
|
|
1318
|
+
return editable;
|
|
1319
|
+
}
|
|
1320
|
+
function xlsxChartTitle(document) {
|
|
1321
|
+
const chart = directChild(document.documentElement, 'chart') ?? firstDescendant(document, 'chart');
|
|
1322
|
+
return chart ? directChild(chart, 'title') : void 0;
|
|
1323
|
+
}
|
|
1324
|
+
function xlsxCombinationSeriesType(node) {
|
|
1325
|
+
if ('lineChart' === node.localName) return 'line';
|
|
1326
|
+
if ('areaChart' === node.localName) return 'area';
|
|
1327
|
+
if ('barChart' === node.localName && 'bar' !== attribute(firstDescendant(node, 'barDir') ?? node, 'val')) return 'column';
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
function xlsxCombinationAxisGroup(document, chartNode) {
|
|
1331
|
+
const axisIds = new Set(directChildren(chartNode, 'axId').map((axis)=>attribute(axis, 'val')).filter((value)=>Boolean(value)));
|
|
1332
|
+
const usesRightAxis = descendants(document, 'valAx').some((axis)=>{
|
|
1333
|
+
const axisId = attribute(firstDescendant(axis, 'axId') ?? axis, 'val');
|
|
1334
|
+
const position = attribute(firstDescendant(axis, 'axPos') ?? axis, 'val');
|
|
1335
|
+
return Boolean(axisId && axisIds.has(axisId) && 'r' === position);
|
|
1336
|
+
});
|
|
1337
|
+
return usesRightAxis ? 'secondary' : 'primary';
|
|
1338
|
+
}
|
|
1339
|
+
function parseXlsxChartSeries(node, index, type, inheritedDataLabels) {
|
|
1340
|
+
const title = directChild(node, 'tx');
|
|
1341
|
+
const values = directChild(node, workSpreadsheetChartUsesNumericXAxis(type) ? 'yVal' : 'val');
|
|
1342
|
+
const xValues = workSpreadsheetChartUsesNumericXAxis(type) ? directChild(node, 'xVal') : null;
|
|
1343
|
+
const bubbleSizes = 'bubble' === type ? directChild(node, 'bubbleSize') : null;
|
|
1344
|
+
const dataLabelsNode = directChild(node, 'dLbls');
|
|
1345
|
+
const dataLabels = dataLabelsNode ? parseXlsxDataLabels(dataLabelsNode, type) : inheritedDataLabels;
|
|
1346
|
+
const errorBars = workSpreadsheetChartSupportsErrorBars(type) ? directChildren(node, 'errBars').flatMap((errorBarNode)=>{
|
|
1347
|
+
const parsed = parseXlsxErrorBars(errorBarNode, type);
|
|
1348
|
+
return parsed ? [
|
|
1349
|
+
parsed
|
|
1350
|
+
] : [];
|
|
1351
|
+
}) : [];
|
|
1352
|
+
const trendlines = workSpreadsheetChartSupportsTrendlines(type) ? directChildren(node, 'trendline').flatMap((trendline)=>{
|
|
1353
|
+
const parsed = parseXlsxTrendline(trendline);
|
|
1354
|
+
return parsed ? [
|
|
1355
|
+
parsed
|
|
1356
|
+
] : [];
|
|
1357
|
+
}) : [];
|
|
1358
|
+
const style = parseXlsxChartSeriesStyle(node);
|
|
1359
|
+
return {
|
|
1360
|
+
name: (title ? cachedText(title) || richText(title) : '') || `Series ${index + 1}`,
|
|
1361
|
+
nameReference: title ? formulaReference(title) : void 0,
|
|
1362
|
+
values: values ? cachedValues(values).map((value)=>finiteNumber(value)) : [],
|
|
1363
|
+
valuesReference: values ? formulaReference(values) : void 0,
|
|
1364
|
+
...xValues ? {
|
|
1365
|
+
xValues: cachedValues(xValues).map((value)=>finiteNumber(value)),
|
|
1366
|
+
xValuesReference: formulaReference(xValues)
|
|
1367
|
+
} : {},
|
|
1368
|
+
...bubbleSizes ? {
|
|
1369
|
+
bubbleSizes: cachedValues(bubbleSizes).map((value)=>finiteNumber(value)),
|
|
1370
|
+
bubbleSizesReference: formulaReference(bubbleSizes)
|
|
1371
|
+
} : {},
|
|
1372
|
+
...dataLabels ? {
|
|
1373
|
+
dataLabels: {
|
|
1374
|
+
...dataLabels
|
|
1375
|
+
}
|
|
1376
|
+
} : {},
|
|
1377
|
+
...errorBars.length ? {
|
|
1378
|
+
errorBars
|
|
1379
|
+
} : {},
|
|
1380
|
+
...trendlines.length ? {
|
|
1381
|
+
trendlines
|
|
1382
|
+
} : {},
|
|
1383
|
+
...style ? {
|
|
1384
|
+
style
|
|
1385
|
+
} : {}
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function parseXlsxDataLabels(node, chartType) {
|
|
1389
|
+
if (!node || xlsxBooleanElement(node, 'delete')) return;
|
|
1390
|
+
const separatorElement = directChild(node, 'separator');
|
|
1391
|
+
const position = xlsxDataLabelPosition(attribute(directChild(node, 'dLblPos') ?? node, 'val'));
|
|
1392
|
+
return normalizeWorkSpreadsheetDataLabels({
|
|
1393
|
+
...xlsxBooleanElement(node, 'showVal') ? {
|
|
1394
|
+
showValue: true
|
|
1395
|
+
} : {},
|
|
1396
|
+
...xlsxBooleanElement(node, 'showCatName') ? {
|
|
1397
|
+
showCategoryName: true
|
|
1398
|
+
} : {},
|
|
1399
|
+
...xlsxBooleanElement(node, 'showSerName') ? {
|
|
1400
|
+
showSeriesName: true
|
|
1401
|
+
} : {},
|
|
1402
|
+
...xlsxBooleanElement(node, 'showPercent') ? {
|
|
1403
|
+
showPercentage: true
|
|
1404
|
+
} : {},
|
|
1405
|
+
...xlsxBooleanElement(node, 'showBubbleSize') ? {
|
|
1406
|
+
showBubbleSize: true
|
|
1407
|
+
} : {},
|
|
1408
|
+
...separatorElement ? {
|
|
1409
|
+
separator: separatorElement.textContent ?? ''
|
|
1410
|
+
} : {},
|
|
1411
|
+
...position ? {
|
|
1412
|
+
position
|
|
1413
|
+
} : {}
|
|
1414
|
+
}, chartType);
|
|
1415
|
+
}
|
|
1416
|
+
function xlsxDataLabelPosition(value) {
|
|
1417
|
+
if ('ctr' === value) return 'center';
|
|
1418
|
+
if ('inBase' === value) return 'insideBase';
|
|
1419
|
+
if ('inEnd' === value) return 'insideEnd';
|
|
1420
|
+
if ('outEnd' === value) return 'outsideEnd';
|
|
1421
|
+
if ('l' === value) return 'left';
|
|
1422
|
+
if ('r' === value) return 'right';
|
|
1423
|
+
if ('t' === value) return 'above';
|
|
1424
|
+
if ('b' === value) return 'below';
|
|
1425
|
+
if ('bestFit' === value) return 'bestFit';
|
|
1426
|
+
}
|
|
1427
|
+
function xlsxBooleanElement(parent, localName) {
|
|
1428
|
+
const element = directChild(parent, localName);
|
|
1429
|
+
if (!element) return false;
|
|
1430
|
+
const value = attribute(element, 'val');
|
|
1431
|
+
return null === value || work_xlsx_charts_readBooleanValue(value);
|
|
1432
|
+
}
|
|
1433
|
+
function parseXlsxErrorBars(node, chartType) {
|
|
1434
|
+
if (!workSpreadsheetChartSupportsErrorBars(chartType)) return null;
|
|
1435
|
+
const directionValue = attribute(directChild(node, 'errDir') ?? node, 'val');
|
|
1436
|
+
const direction = 'x' === directionValue ? 'x' : 'y' === directionValue ? 'y' : null;
|
|
1437
|
+
if (!direction || 'x' === direction && !workSpreadsheetChartUsesNumericXAxis(chartType)) return null;
|
|
1438
|
+
const barTypeValue = attribute(directChild(node, 'errBarType') ?? node, 'val');
|
|
1439
|
+
const barType = 'plus' === barTypeValue || 'minus' === barTypeValue || 'both' === barTypeValue ? barTypeValue : null;
|
|
1440
|
+
const valueType = workSpreadsheetErrorBarValueType(attribute(directChild(node, 'errValType') ?? node, 'val'));
|
|
1441
|
+
if (!barType || !valueType) return null;
|
|
1442
|
+
const value = numericElementValue(node, 'val');
|
|
1443
|
+
const plus = directChild(node, 'plus');
|
|
1444
|
+
const minus = directChild(node, 'minus');
|
|
1445
|
+
return normalizeWorkSpreadsheetErrorBars({
|
|
1446
|
+
direction,
|
|
1447
|
+
barType,
|
|
1448
|
+
valueType,
|
|
1449
|
+
...void 0 !== value ? {
|
|
1450
|
+
value
|
|
1451
|
+
} : {},
|
|
1452
|
+
...xlsxBooleanElement(node, 'noEndCap') ? {
|
|
1453
|
+
showEndCaps: false
|
|
1454
|
+
} : {},
|
|
1455
|
+
...plus ? {
|
|
1456
|
+
plusValues: cachedValues(plus).map((item)=>finiteNumber(item)),
|
|
1457
|
+
plusReference: formulaReference(plus)
|
|
1458
|
+
} : {},
|
|
1459
|
+
...minus ? {
|
|
1460
|
+
minusValues: cachedValues(minus).map((item)=>finiteNumber(item)),
|
|
1461
|
+
minusReference: formulaReference(minus)
|
|
1462
|
+
} : {}
|
|
1463
|
+
}, chartType);
|
|
1464
|
+
}
|
|
1465
|
+
function workSpreadsheetErrorBarValueType(value) {
|
|
1466
|
+
if ('fixedVal' === value) return 'fixedValue';
|
|
1467
|
+
if ('percentage' === value) return 'percentage';
|
|
1468
|
+
if ('stdDev' === value) return 'standardDeviation';
|
|
1469
|
+
if ('stdErr' === value) return 'standardError';
|
|
1470
|
+
if ('cust' === value) return 'custom';
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
function parseXlsxTrendline(node) {
|
|
1474
|
+
const type = xlsxTrendlineType(attribute(firstDescendant(node, 'trendlineType') ?? node, 'val'));
|
|
1475
|
+
if (!type) return null;
|
|
1476
|
+
const name = directChild(node, 'name')?.textContent?.trim();
|
|
1477
|
+
const order = numericElementValue(node, 'order');
|
|
1478
|
+
const period = numericElementValue(node, 'period');
|
|
1479
|
+
const forward = numericElementValue(node, 'forward');
|
|
1480
|
+
const backward = numericElementValue(node, 'backward');
|
|
1481
|
+
const intercept = numericElementValue(node, 'intercept');
|
|
1482
|
+
return normalizeWorkSpreadsheetTrendline({
|
|
1483
|
+
type,
|
|
1484
|
+
...name ? {
|
|
1485
|
+
name
|
|
1486
|
+
} : {},
|
|
1487
|
+
...void 0 !== order ? {
|
|
1488
|
+
order
|
|
1489
|
+
} : {},
|
|
1490
|
+
...void 0 !== period ? {
|
|
1491
|
+
period
|
|
1492
|
+
} : {},
|
|
1493
|
+
...void 0 !== forward ? {
|
|
1494
|
+
forward
|
|
1495
|
+
} : {},
|
|
1496
|
+
...void 0 !== backward ? {
|
|
1497
|
+
backward
|
|
1498
|
+
} : {},
|
|
1499
|
+
...void 0 !== intercept ? {
|
|
1500
|
+
intercept
|
|
1501
|
+
} : {},
|
|
1502
|
+
...xlsxBooleanElement(node, 'dispEq') ? {
|
|
1503
|
+
displayEquation: true
|
|
1504
|
+
} : {},
|
|
1505
|
+
...xlsxBooleanElement(node, 'dispRSq') ? {
|
|
1506
|
+
displayRSquared: true
|
|
1507
|
+
} : {}
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
function xlsxTrendlineType(value) {
|
|
1511
|
+
if ('exp' === value) return 'exponential';
|
|
1512
|
+
if ('log' === value) return 'logarithmic';
|
|
1513
|
+
if ('poly' === value) return 'polynomial';
|
|
1514
|
+
if ('power' === value) return 'power';
|
|
1515
|
+
if ('movingAvg' === value) return 'movingAverage';
|
|
1516
|
+
if ('linear' === value) return 'linear';
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
function numericElementValue(parent, localName) {
|
|
1520
|
+
const element = firstDescendant(parent, localName);
|
|
1521
|
+
if (!element) return;
|
|
1522
|
+
const value = Number(attribute(element, 'val'));
|
|
1523
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1524
|
+
}
|
|
1525
|
+
function xlsxChartType(node) {
|
|
1526
|
+
if ('lineChart' === node.localName) return 'line';
|
|
1527
|
+
if ('pieChart' === node.localName) return 'pie';
|
|
1528
|
+
if ('doughnutChart' === node.localName) return 'doughnut';
|
|
1529
|
+
if ('areaChart' === node.localName) return 'area';
|
|
1530
|
+
if ('radarChart' === node.localName) return 'radar';
|
|
1531
|
+
if ('scatterChart' === node.localName) return 'scatter';
|
|
1532
|
+
if ('bubbleChart' === node.localName) return 'bubble';
|
|
1533
|
+
return 'bar' === attribute(firstDescendant(node, 'barDir') ?? node, 'val') ? 'bar' : 'column';
|
|
1534
|
+
}
|
|
1535
|
+
function readDoughnutHoleSize(node) {
|
|
1536
|
+
return normalizeWorkSpreadsheetDoughnutHoleSize(attribute(firstDescendant(node, 'holeSize') ?? node, 'val'));
|
|
1537
|
+
}
|
|
1538
|
+
function readRadarStyle(node) {
|
|
1539
|
+
return normalizeWorkSpreadsheetRadarStyle(attribute(firstDescendant(node, 'radarStyle') ?? node, 'val'));
|
|
1540
|
+
}
|
|
1541
|
+
function readScatterStyle(node) {
|
|
1542
|
+
return normalizeWorkSpreadsheetScatterStyle(attribute(firstDescendant(node, 'scatterStyle') ?? node, 'val'));
|
|
1543
|
+
}
|
|
1544
|
+
function readBubbleScale(node) {
|
|
1545
|
+
return normalizeWorkSpreadsheetBubbleScale(attribute(firstDescendant(node, 'bubbleScale') ?? node, 'val'));
|
|
1546
|
+
}
|
|
1547
|
+
function readShowNegativeBubbles(node) {
|
|
1548
|
+
return work_xlsx_charts_readBooleanValue(attribute(firstDescendant(node, 'showNegBubbles') ?? node, 'val'));
|
|
1549
|
+
}
|
|
1550
|
+
function readBubbleSizeRepresents(node) {
|
|
1551
|
+
return normalizeWorkSpreadsheetBubbleSizeRepresents(attribute(firstDescendant(node, 'sizeRepresents') ?? node, 'val'));
|
|
1552
|
+
}
|
|
1553
|
+
function chartTitleXml(chart) {
|
|
1554
|
+
const title = chart.title?.trim();
|
|
1555
|
+
if (!title && !chart.titleReference?.trim()) return '';
|
|
1556
|
+
return chartTextTitleXml(title ?? '', chart.titleReference);
|
|
1557
|
+
}
|
|
1558
|
+
function chartPlotXml(chart, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId) {
|
|
1559
|
+
if ('combination' === chart.type) return combinationChartPlotXml(chart, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId);
|
|
1560
|
+
const series = chart.series.map((item, index)=>workSpreadsheetChartUsesNumericXAxis(chart.type) ? xyChartSeriesXml(item, chart, index) : chartSeriesXml(item, chart, index)).join('');
|
|
1561
|
+
if ('pie' === chart.type) return `<c:pieChart><c:varyColors val="1"/>${series}<c:firstSliceAng val="0"/></c:pieChart>`;
|
|
1562
|
+
if ('doughnut' === chart.type) {
|
|
1563
|
+
const holeSize = normalizeWorkSpreadsheetDoughnutHoleSize(chart.doughnutHoleSize);
|
|
1564
|
+
return `<c:doughnutChart><c:varyColors val="1"/>${series}<c:firstSliceAng val="0"/><c:holeSize val="${holeSize}"/></c:doughnutChart>`;
|
|
1565
|
+
}
|
|
1566
|
+
const axes = `<c:axId val="${categoryAxisId}"/><c:axId val="${valueAxisId}"/>`;
|
|
1567
|
+
if ('line' === chart.type) return `<c:lineChart><c:grouping val="${xlsxChartGroupingValue(chart)}"/><c:varyColors val="0"/>${series}<c:marker val="1"/><c:smooth val="${xlsxChartSmoothLinesValue(chart) ? 1 : 0}"/>${axes}</c:lineChart>`;
|
|
1568
|
+
if ('area' === chart.type) return `<c:areaChart><c:grouping val="${xlsxChartGroupingValue(chart)}"/><c:varyColors val="0"/>${series}${axes}</c:areaChart>`;
|
|
1569
|
+
if ('radar' === chart.type) {
|
|
1570
|
+
const style = normalizeWorkSpreadsheetRadarStyle(chart.radarStyle);
|
|
1571
|
+
return `<c:radarChart><c:radarStyle val="${style}"/><c:varyColors val="0"/>${series}${axes}</c:radarChart>`;
|
|
1572
|
+
}
|
|
1573
|
+
if ('scatter' === chart.type) {
|
|
1574
|
+
const style = normalizeWorkSpreadsheetScatterStyle(chart.scatterStyle);
|
|
1575
|
+
return `<c:scatterChart><c:scatterStyle val="${style}"/><c:varyColors val="0"/>${series}${axes}</c:scatterChart>`;
|
|
1576
|
+
}
|
|
1577
|
+
if ('bubble' === chart.type) {
|
|
1578
|
+
const scale = normalizeWorkSpreadsheetBubbleScale(chart.bubbleScale);
|
|
1579
|
+
const showNegative = true === chart.showNegativeBubbles ? 1 : 0;
|
|
1580
|
+
const sizeRepresents = 'width' === normalizeWorkSpreadsheetBubbleSizeRepresents(chart.bubbleSizeRepresents) ? 'w' : 'area';
|
|
1581
|
+
return `<c:bubbleChart><c:varyColors val="0"/>${series}<c:bubble3D val="0"/><c:bubbleScale val="${scale}"/><c:showNegBubbles val="${showNegative}"/><c:sizeRepresents val="${sizeRepresents}"/>${axes}</c:bubbleChart>`;
|
|
1582
|
+
}
|
|
1583
|
+
const direction = 'bar' === chart.type ? 'bar' : 'col';
|
|
1584
|
+
return `<c:barChart><c:barDir val="${direction}"/><c:grouping val="${xlsxChartGroupingValue(chart)}"/><c:varyColors val="0"/>${series}<c:gapWidth val="${xlsxChartGapWidthValue(chart)}"/><c:overlap val="${xlsxChartOverlapValue(chart)}"/>${axes}</c:barChart>`;
|
|
1585
|
+
}
|
|
1586
|
+
function combinationChartPlotXml(chart, categoryAxisId, valueAxisId, secondaryCategoryAxisId, secondaryValueAxisId) {
|
|
1587
|
+
const groups = new Map();
|
|
1588
|
+
chart.series.forEach((item, index)=>{
|
|
1589
|
+
const type = normalizeWorkSpreadsheetCombinationSeriesType(item.chartType);
|
|
1590
|
+
const axisGroup = normalizeWorkSpreadsheetChartAxisGroup(item.axisGroup);
|
|
1591
|
+
const key = `${type}:${axisGroup}`;
|
|
1592
|
+
const group = groups.get(key) ?? {
|
|
1593
|
+
type,
|
|
1594
|
+
axisGroup,
|
|
1595
|
+
series: []
|
|
1596
|
+
};
|
|
1597
|
+
group.series.push({
|
|
1598
|
+
item,
|
|
1599
|
+
index
|
|
1600
|
+
});
|
|
1601
|
+
groups.set(key, group);
|
|
1602
|
+
});
|
|
1603
|
+
return Array.from(groups.values()).map((group)=>{
|
|
1604
|
+
const axes = 'secondary' === group.axisGroup ? `<c:axId val="${secondaryCategoryAxisId}"/><c:axId val="${secondaryValueAxisId}"/>` : `<c:axId val="${categoryAxisId}"/><c:axId val="${valueAxisId}"/>`;
|
|
1605
|
+
const seriesChart = {
|
|
1606
|
+
...chart,
|
|
1607
|
+
type: group.type,
|
|
1608
|
+
grouping: 'column' === group.type ? 'clustered' : 'standard',
|
|
1609
|
+
gapWidth: 'column' === group.type ? 150 : void 0,
|
|
1610
|
+
overlap: 'column' === group.type ? 0 : void 0,
|
|
1611
|
+
smoothLines: false
|
|
1612
|
+
};
|
|
1613
|
+
const series = group.series.map(({ item, index })=>chartSeriesXml(item, seriesChart, index)).join('');
|
|
1614
|
+
if ('line' === group.type) return `<c:lineChart><c:grouping val="standard"/><c:varyColors val="0"/>${series}<c:marker val="1"/><c:smooth val="0"/>${axes}</c:lineChart>`;
|
|
1615
|
+
if ('area' === group.type) return `<c:areaChart><c:grouping val="standard"/><c:varyColors val="0"/>${series}${axes}</c:areaChart>`;
|
|
1616
|
+
return `<c:barChart><c:barDir val="col"/><c:grouping val="clustered"/><c:varyColors val="0"/>${series}<c:gapWidth val="150"/><c:overlap val="0"/>${axes}</c:barChart>`;
|
|
1617
|
+
}).join('');
|
|
1618
|
+
}
|
|
1619
|
+
function chartSeriesXml(series, chart, index) {
|
|
1620
|
+
const title = chartSeriesTitleXml(series, index);
|
|
1621
|
+
const categories = chart.categoryReference?.trim() ? `<c:strRef><c:f>${escapeXml(chart.categoryReference.replace(/^=/, ''))}</c:f>${stringCacheXml(chart.categories)}</c:strRef>` : stringLiteralXml(chart.categories);
|
|
1622
|
+
const values = series.valuesReference?.trim() ? `<c:numRef><c:f>${escapeXml(series.valuesReference.replace(/^=/, ''))}</c:f>${numberCacheXml(series.values)}</c:numRef>` : numberLiteralXml(series.values);
|
|
1623
|
+
const showMarkers = 'line' === chart.type || 'radar' === chart.type && 'marker' === normalizeWorkSpreadsheetRadarStyle(chart.radarStyle);
|
|
1624
|
+
const shapeProperties = xlsxChartSeriesShapePropertiesXml(series.style);
|
|
1625
|
+
const marker = 'line' === chart.type || 'radar' === chart.type ? xlsxChartSeriesMarkerXml(series.style, showMarkers) : '';
|
|
1626
|
+
const dataLabels = chartDataLabelsXml(series, chart.type);
|
|
1627
|
+
const supportsAnalysis = !xlsxChartGroupingIsStacked(chart);
|
|
1628
|
+
const trendlines = supportsAnalysis && workSpreadsheetChartSupportsTrendlines(chart.type) ? chartTrendlinesXml(series) : '';
|
|
1629
|
+
const errorBars = supportsAnalysis ? chartErrorBarsXml(series, chart.type) : '';
|
|
1630
|
+
return [
|
|
1631
|
+
'<c:ser>',
|
|
1632
|
+
`<c:idx val="${index}"/><c:order val="${index}"/>`,
|
|
1633
|
+
`<c:tx>${title}</c:tx>`,
|
|
1634
|
+
shapeProperties,
|
|
1635
|
+
marker,
|
|
1636
|
+
dataLabels,
|
|
1637
|
+
trendlines,
|
|
1638
|
+
errorBars,
|
|
1639
|
+
`<c:cat>${categories}</c:cat><c:val>${values}</c:val>`,
|
|
1640
|
+
'line' === chart.type ? `<c:smooth val="${xlsxChartSmoothLinesValue(chart) ? 1 : 0}"/>` : '',
|
|
1641
|
+
'</c:ser>'
|
|
1642
|
+
].join('');
|
|
1643
|
+
}
|
|
1644
|
+
function xyChartSeriesXml(series, chart, index) {
|
|
1645
|
+
const title = chartSeriesTitleXml(series, index);
|
|
1646
|
+
const xValues = series.xValues?.length ? series.xValues : Array.from({
|
|
1647
|
+
length: series.values.length
|
|
1648
|
+
}, (_, valueIndex)=>valueIndex + 1);
|
|
1649
|
+
const xValuesXml = numberReferenceOrLiteralXml(series.xValuesReference, xValues);
|
|
1650
|
+
const yValuesXml = numberReferenceOrLiteralXml(series.valuesReference, series.values);
|
|
1651
|
+
const dataLabels = chartDataLabelsXml(series, chart.type);
|
|
1652
|
+
const trendlines = chartTrendlinesXml(series);
|
|
1653
|
+
const errorBars = chartErrorBarsXml(series, chart.type);
|
|
1654
|
+
const shapeProperties = xlsxChartSeriesShapePropertiesXml(series.style);
|
|
1655
|
+
if ('bubble' === chart.type) {
|
|
1656
|
+
const bubbleSizesXml = numberReferenceOrLiteralXml(series.bubbleSizesReference, series.bubbleSizes ?? []);
|
|
1657
|
+
return [
|
|
1658
|
+
'<c:ser>',
|
|
1659
|
+
`<c:idx val="${index}"/><c:order val="${index}"/>`,
|
|
1660
|
+
`<c:tx>${title}</c:tx>`,
|
|
1661
|
+
shapeProperties,
|
|
1662
|
+
dataLabels,
|
|
1663
|
+
trendlines,
|
|
1664
|
+
errorBars,
|
|
1665
|
+
`<c:xVal>${xValuesXml}</c:xVal><c:yVal>${yValuesXml}</c:yVal>`,
|
|
1666
|
+
`<c:bubbleSize>${bubbleSizesXml}</c:bubbleSize><c:bubble3D val="0"/>`,
|
|
1667
|
+
'</c:ser>'
|
|
1668
|
+
].join('');
|
|
1669
|
+
}
|
|
1670
|
+
const style = normalizeWorkSpreadsheetScatterStyle(chart.scatterStyle);
|
|
1671
|
+
const showMarkers = 'marker' === style || 'lineMarker' === style || 'smoothMarker' === style;
|
|
1672
|
+
const smooth = 'smooth' === style || 'smoothMarker' === style;
|
|
1673
|
+
return [
|
|
1674
|
+
'<c:ser>',
|
|
1675
|
+
`<c:idx val="${index}"/><c:order val="${index}"/>`,
|
|
1676
|
+
`<c:tx>${title}</c:tx>`,
|
|
1677
|
+
shapeProperties,
|
|
1678
|
+
xlsxChartSeriesMarkerXml(series.style, showMarkers),
|
|
1679
|
+
dataLabels,
|
|
1680
|
+
trendlines,
|
|
1681
|
+
errorBars,
|
|
1682
|
+
`<c:xVal>${xValuesXml}</c:xVal><c:yVal>${yValuesXml}</c:yVal>`,
|
|
1683
|
+
`<c:smooth val="${smooth ? 1 : 0}"/>`,
|
|
1684
|
+
'</c:ser>'
|
|
1685
|
+
].join('');
|
|
1686
|
+
}
|
|
1687
|
+
function chartDataLabelsXml(series, chartType) {
|
|
1688
|
+
if (!series.dataLabels) return '';
|
|
1689
|
+
const labels = normalizeWorkSpreadsheetDataLabels(series.dataLabels, chartType);
|
|
1690
|
+
return [
|
|
1691
|
+
'<c:dLbls>',
|
|
1692
|
+
labels.position ? `<c:dLblPos val="${xlsxDataLabelPositionValue(labels.position)}"/>` : '',
|
|
1693
|
+
'<c:showLegendKey val="0"/>',
|
|
1694
|
+
`<c:showVal val="${labels.showValue ? 1 : 0}"/>`,
|
|
1695
|
+
`<c:showCatName val="${labels.showCategoryName ? 1 : 0}"/>`,
|
|
1696
|
+
`<c:showSerName val="${labels.showSeriesName ? 1 : 0}"/>`,
|
|
1697
|
+
`<c:showPercent val="${labels.showPercentage ? 1 : 0}"/>`,
|
|
1698
|
+
`<c:showBubbleSize val="${labels.showBubbleSize ? 1 : 0}"/>`,
|
|
1699
|
+
void 0 !== labels.separator ? `<c:separator>${escapeXml(labels.separator)}</c:separator>` : '',
|
|
1700
|
+
'<c:showLeaderLines val="0"/>',
|
|
1701
|
+
'</c:dLbls>'
|
|
1702
|
+
].join('');
|
|
1703
|
+
}
|
|
1704
|
+
function xlsxDataLabelPositionValue(position) {
|
|
1705
|
+
if ('center' === position) return 'ctr';
|
|
1706
|
+
if ('insideBase' === position) return 'inBase';
|
|
1707
|
+
if ('insideEnd' === position) return 'inEnd';
|
|
1708
|
+
if ('outsideEnd' === position) return 'outEnd';
|
|
1709
|
+
if ('left' === position) return 'l';
|
|
1710
|
+
if ('right' === position) return 'r';
|
|
1711
|
+
if ('above' === position) return 't';
|
|
1712
|
+
if ('below' === position) return 'b';
|
|
1713
|
+
return 'bestFit';
|
|
1714
|
+
}
|
|
1715
|
+
function chartTrendlinesXml(series) {
|
|
1716
|
+
return (series.trendlines ?? []).map(chartTrendlineXml).join('');
|
|
1717
|
+
}
|
|
1718
|
+
function chartErrorBarsXml(series, chartType) {
|
|
1719
|
+
if (!workSpreadsheetChartSupportsErrorBars(chartType)) return '';
|
|
1720
|
+
const directions = new Set();
|
|
1721
|
+
return (series.errorBars ?? []).flatMap((source)=>{
|
|
1722
|
+
if ('x' === source.direction && !workSpreadsheetChartUsesNumericXAxis(chartType)) return [];
|
|
1723
|
+
const errorBars = normalizeWorkSpreadsheetErrorBars(source, chartType);
|
|
1724
|
+
if (directions.has(errorBars.direction)) return [];
|
|
1725
|
+
directions.add(errorBars.direction);
|
|
1726
|
+
return [
|
|
1727
|
+
chartErrorBarXml(errorBars)
|
|
1728
|
+
];
|
|
1729
|
+
}).join('');
|
|
1730
|
+
}
|
|
1731
|
+
function chartErrorBarXml(errorBars) {
|
|
1732
|
+
const plus = 'custom' === errorBars.valueType && 'minus' !== errorBars.barType ? chartCustomErrorBarSourceXml('plus', errorBars.plusReference, errorBars.plusValues) : '';
|
|
1733
|
+
const minus = 'custom' === errorBars.valueType && 'plus' !== errorBars.barType ? chartCustomErrorBarSourceXml('minus', errorBars.minusReference, errorBars.minusValues) : '';
|
|
1734
|
+
const value = 'fixedValue' === errorBars.valueType || 'percentage' === errorBars.valueType || 'standardDeviation' === errorBars.valueType ? `<c:val val="${finiteNumber(errorBars.value, 'percentage' === errorBars.valueType ? 5 : 1)}"/>` : '';
|
|
1735
|
+
return [
|
|
1736
|
+
'<c:errBars>',
|
|
1737
|
+
`<c:errDir val="${errorBars.direction}"/>`,
|
|
1738
|
+
`<c:errBarType val="${errorBars.barType}"/>`,
|
|
1739
|
+
`<c:errValType val="${xlsxErrorBarValueType(errorBars.valueType)}"/>`,
|
|
1740
|
+
false === errorBars.showEndCaps ? '<c:noEndCap val="1"/>' : '',
|
|
1741
|
+
plus,
|
|
1742
|
+
minus,
|
|
1743
|
+
value,
|
|
1744
|
+
'</c:errBars>'
|
|
1745
|
+
].join('');
|
|
1746
|
+
}
|
|
1747
|
+
function chartCustomErrorBarSourceXml(localName, reference, values) {
|
|
1748
|
+
if (!reference?.trim() && !values?.length) return '';
|
|
1749
|
+
return `<c:${localName}>${numberReferenceOrLiteralXml(reference, values ?? [])}</c:${localName}>`;
|
|
1750
|
+
}
|
|
1751
|
+
function xlsxErrorBarValueType(type) {
|
|
1752
|
+
if ('fixedValue' === type) return 'fixedVal';
|
|
1753
|
+
if ('standardDeviation' === type) return 'stdDev';
|
|
1754
|
+
if ('standardError' === type) return 'stdErr';
|
|
1755
|
+
if ('custom' === type) return 'cust';
|
|
1756
|
+
return 'percentage';
|
|
1757
|
+
}
|
|
1758
|
+
function chartTrendlineXml(source) {
|
|
1759
|
+
const trendline = normalizeWorkSpreadsheetTrendline(source);
|
|
1760
|
+
const type = xlsxTrendlineTypeValue(trendline.type);
|
|
1761
|
+
return [
|
|
1762
|
+
'<c:trendline>',
|
|
1763
|
+
trendline.name ? `<c:name>${escapeXml(trendline.name)}</c:name>` : '',
|
|
1764
|
+
`<c:trendlineType val="${type}"/>`,
|
|
1765
|
+
'polynomial' === trendline.type ? `<c:order val="${trendline.order ?? 2}"/>` : '',
|
|
1766
|
+
'movingAverage' === trendline.type ? `<c:period val="${trendline.period ?? 2}"/>` : '',
|
|
1767
|
+
trendline.forward ? `<c:forward val="${finiteNumber(trendline.forward)}"/>` : '',
|
|
1768
|
+
trendline.backward ? `<c:backward val="${finiteNumber(trendline.backward)}"/>` : '',
|
|
1769
|
+
void 0 !== trendline.intercept ? `<c:intercept val="${finiteNumber(trendline.intercept)}"/>` : '',
|
|
1770
|
+
trendline.displayRSquared ? '<c:dispRSq val="1"/>' : '',
|
|
1771
|
+
trendline.displayEquation ? '<c:dispEq val="1"/>' : '',
|
|
1772
|
+
'</c:trendline>'
|
|
1773
|
+
].join('');
|
|
1774
|
+
}
|
|
1775
|
+
function xlsxTrendlineTypeValue(type) {
|
|
1776
|
+
if ('exponential' === type) return 'exp';
|
|
1777
|
+
if ('logarithmic' === type) return 'log';
|
|
1778
|
+
if ('polynomial' === type) return 'poly';
|
|
1779
|
+
if ('movingAverage' === type) return 'movingAvg';
|
|
1780
|
+
return type;
|
|
1781
|
+
}
|
|
1782
|
+
function chartSeriesTitleXml(series, index) {
|
|
1783
|
+
return series.nameReference?.trim() ? `<c:strRef><c:f>${escapeXml(series.nameReference.replace(/^=/, ''))}</c:f>${stringCacheXml([
|
|
1784
|
+
series.name
|
|
1785
|
+
])}</c:strRef>` : `<c:v>${escapeXml(series.name || `Series ${index + 1}`)}</c:v>`;
|
|
1786
|
+
}
|
|
1787
|
+
function numberReferenceOrLiteralXml(reference, values) {
|
|
1788
|
+
return reference?.trim() ? `<c:numRef><c:f>${escapeXml(reference.replace(/^=/, ''))}</c:f>${numberCacheXml(values)}</c:numRef>` : numberLiteralXml(values);
|
|
1789
|
+
}
|
|
1790
|
+
function work_xlsx_charts_readBooleanValue(value) {
|
|
1791
|
+
return '1' === value || 'true' === value;
|
|
1792
|
+
}
|
|
1793
|
+
function partNumber(path) {
|
|
1794
|
+
return /(\d+)(?:\.xml)?$/i.exec(path)?.[1] ?? '1';
|
|
1795
|
+
}
|
|
1796
|
+
const MAX_XLSX_WORKSHEET_IMAGE_BYTES = 10485760;
|
|
1797
|
+
const DRAWING_RELATIONSHIP = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing';
|
|
1798
|
+
const IMAGE_RELATIONSHIP = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';
|
|
1799
|
+
const work_xlsx_images_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
1800
|
+
const PACKAGE_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/package/2006/relationships';
|
|
1801
|
+
const DRAWING_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing';
|
|
1802
|
+
const work_xlsx_images_DRAWINGML_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/main';
|
|
1803
|
+
const DRAWING_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.drawing+xml';
|
|
1804
|
+
const SUPPORTED_IMAGE_TYPES = new Set([
|
|
1805
|
+
'image/bmp',
|
|
1806
|
+
'image/gif',
|
|
1807
|
+
'image/jpeg',
|
|
1808
|
+
'image/png',
|
|
1809
|
+
'image/svg+xml',
|
|
1810
|
+
'image/webp'
|
|
1811
|
+
]);
|
|
1812
|
+
function isSupportedXlsxWorksheetImageContentType(contentType) {
|
|
1813
|
+
return SUPPORTED_IMAGE_TYPES.has(contentType.toLowerCase());
|
|
1814
|
+
}
|
|
1815
|
+
async function readXlsxWorksheetImages(archive, worksheetPart, worksheet, imageBudget) {
|
|
1816
|
+
const drawingReference = directChildren(worksheet.documentElement, 'drawing')[0];
|
|
1817
|
+
const relationshipId = drawingReference ? attribute(drawingReference, 'r:id') ?? attribute(drawingReference, 'id') : null;
|
|
1818
|
+
if (!relationshipId) return [];
|
|
1819
|
+
const worksheetRelationships = await archive.relationships(worksheetPart);
|
|
1820
|
+
const drawingRelationship = worksheetRelationships.get(relationshipId);
|
|
1821
|
+
if (!drawingRelationship || 'External' === drawingRelationship.targetMode || !drawingRelationship.type.endsWith('/drawing') || !archive.has(drawingRelationship.target)) return [];
|
|
1822
|
+
const drawing = await archive.xml(drawingRelationship.target);
|
|
1823
|
+
const relationships = await archive.relationships(drawingRelationship.target);
|
|
1824
|
+
const images = [];
|
|
1825
|
+
for (const [anchorIndex, anchor] of directChildren(drawing.documentElement).entries()){
|
|
1826
|
+
if (![
|
|
1827
|
+
'twoCellAnchor',
|
|
1828
|
+
'oneCellAnchor',
|
|
1829
|
+
'absoluteAnchor'
|
|
1830
|
+
].includes(anchor.localName)) continue;
|
|
1831
|
+
const picture = directChild(anchor, 'pic');
|
|
1832
|
+
const blip = firstDescendant(picture, 'blip');
|
|
1833
|
+
const imageRelationshipId = blip ? attribute(blip, 'r:embed') ?? attribute(blip, 'embed') : null;
|
|
1834
|
+
const imageRelationship = imageRelationshipId ? relationships.get(imageRelationshipId) : void 0;
|
|
1835
|
+
if (!picture || !imageRelationship || !readableImageRelationship(archive, imageRelationship)) continue;
|
|
1836
|
+
const contentType = contentTypeForPart(imageRelationship.target);
|
|
1837
|
+
if (!isSupportedXlsxWorksheetImageContentType(contentType)) continue;
|
|
1838
|
+
const bytes = await archive.bytes(imageRelationship.target);
|
|
1839
|
+
if (imageBudget.bytes + bytes.byteLength > MAX_XLSX_WORKSHEET_IMAGE_BYTES) continue;
|
|
1840
|
+
imageBudget.bytes += bytes.byteLength;
|
|
1841
|
+
const properties = firstDescendant(picture, 'cNvPr');
|
|
1842
|
+
const sourceId = attribute(properties ?? picture, 'id') ?? String(anchorIndex + 1);
|
|
1843
|
+
const drawingNumber = work_xlsx_images_partNumber(drawingRelationship.target);
|
|
1844
|
+
images.push({
|
|
1845
|
+
id: `xlsx-image-${drawingNumber}-${sourceId}`,
|
|
1846
|
+
name: attribute(properties ?? picture, 'name')?.trim() || `Worksheet image ${anchorIndex + 1}`,
|
|
1847
|
+
altText: attribute(properties ?? picture, 'descr')?.trim() || void 0,
|
|
1848
|
+
contentType,
|
|
1849
|
+
dataUrl: bytesToDataUrl(bytes, contentType),
|
|
1850
|
+
...readXlsxDrawingAnchor(anchor)
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
return images;
|
|
1854
|
+
}
|
|
1855
|
+
function xlsxWorksheetImagesToSheet(images, config) {
|
|
1856
|
+
return images.flatMap((image)=>{
|
|
1857
|
+
const bounds = xlsxDrawingAnchorToBounds(image, config);
|
|
1858
|
+
if (!bounds) return [];
|
|
1859
|
+
return [
|
|
1860
|
+
{
|
|
1861
|
+
id: image.id,
|
|
1862
|
+
name: image.name,
|
|
1863
|
+
altText: image.altText,
|
|
1864
|
+
contentType: image.contentType,
|
|
1865
|
+
src: image.dataUrl,
|
|
1866
|
+
...bounds
|
|
1867
|
+
}
|
|
1868
|
+
];
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
async function patchXlsxWorksheetDrawings(buffer, content) {
|
|
1872
|
+
if (!content.sheets.some((sheet)=>sheet.images?.some((image)=>serializableImage(image)) || (sheet.charts?.length ?? 0) > 0)) return buffer;
|
|
1873
|
+
const archive = await work_ooxml_package_OoxmlPackage.load(buffer);
|
|
1874
|
+
const worksheetParts = await exportedWorksheetParts(archive);
|
|
1875
|
+
const zip = await jszip.loadAsync(buffer);
|
|
1876
|
+
const drawingPaths = [];
|
|
1877
|
+
const chartPaths = [];
|
|
1878
|
+
const imageTypes = new Map();
|
|
1879
|
+
let drawingIndex = 1;
|
|
1880
|
+
let mediaIndex = 1;
|
|
1881
|
+
let chartIndex = 1;
|
|
1882
|
+
for (const sheet of content.sheets){
|
|
1883
|
+
const images = (sheet.images ?? []).flatMap((image)=>{
|
|
1884
|
+
const data = serializableImage(image);
|
|
1885
|
+
return data ? [
|
|
1886
|
+
{
|
|
1887
|
+
image,
|
|
1888
|
+
data
|
|
1889
|
+
}
|
|
1890
|
+
] : [];
|
|
1891
|
+
});
|
|
1892
|
+
const charts = sheet.charts ?? [];
|
|
1893
|
+
if (!images.length && !charts.length) continue;
|
|
1894
|
+
const worksheetPart = worksheetParts.get(sheet.name.slice(0, 31) || '工作表');
|
|
1895
|
+
const worksheetEntry = worksheetPart ? zip.file(worksheetPart) : null;
|
|
1896
|
+
if (!worksheetPart || !worksheetEntry) continue;
|
|
1897
|
+
const drawingPath = `xl/drawings/drawing${drawingIndex}.xml`;
|
|
1898
|
+
const drawingRelationshipsPath = `xl/drawings/_rels/drawing${drawingIndex}.xml.rels`;
|
|
1899
|
+
drawingIndex += 1;
|
|
1900
|
+
const media = images.map(({ image, data }, index)=>{
|
|
1901
|
+
const path = `xl/media/image${mediaIndex}.${data.extension}`;
|
|
1902
|
+
mediaIndex += 1;
|
|
1903
|
+
imageTypes.set(data.extension, data.contentType);
|
|
1904
|
+
zip.file(path, data.bytes);
|
|
1905
|
+
return {
|
|
1906
|
+
image,
|
|
1907
|
+
path,
|
|
1908
|
+
relationshipId: `rId${index + 1}`,
|
|
1909
|
+
objectId: index + 1
|
|
1910
|
+
};
|
|
1911
|
+
});
|
|
1912
|
+
const chartParts = charts.map((chart, index)=>{
|
|
1913
|
+
const path = `xl/charts/chart${chartIndex}.xml`;
|
|
1914
|
+
const currentChartIndex = chartIndex;
|
|
1915
|
+
chartIndex += 1;
|
|
1916
|
+
chartPaths.push(path);
|
|
1917
|
+
zip.file(path, xlsxChartPartXml(chart, content, sheet, currentChartIndex));
|
|
1918
|
+
return {
|
|
1919
|
+
chart,
|
|
1920
|
+
path,
|
|
1921
|
+
relationshipId: `rId${media.length + index + 1}`,
|
|
1922
|
+
objectId: media.length + index + 1
|
|
1923
|
+
};
|
|
1924
|
+
});
|
|
1925
|
+
zip.file(drawingPath, drawingXml(media, chartParts, sheet));
|
|
1926
|
+
zip.file(drawingRelationshipsPath, drawingRelationshipsXml(media, chartParts));
|
|
1927
|
+
drawingPaths.push(drawingPath);
|
|
1928
|
+
await bindDrawingToWorksheet(zip, worksheetPart, worksheetEntry, drawingPath);
|
|
1929
|
+
}
|
|
1930
|
+
await updateContentTypes(zip, drawingPaths, chartPaths, imageTypes);
|
|
1931
|
+
return zip.generateAsync({
|
|
1932
|
+
type: 'arraybuffer',
|
|
1933
|
+
compression: 'DEFLATE'
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
function readableImageRelationship(archive, relationship) {
|
|
1937
|
+
return 'External' !== relationship.targetMode && relationship.type.endsWith('/image') && archive.has(relationship.target);
|
|
1938
|
+
}
|
|
1939
|
+
function serializableImage(image) {
|
|
1940
|
+
const match = /^data:([^;,]+);base64,([a-z0-9+/=\s]+)$/i.exec(image.src);
|
|
1941
|
+
if (!match) return null;
|
|
1942
|
+
const contentType = image.contentType?.toLowerCase() || match[1].toLowerCase();
|
|
1943
|
+
const extension = imageExtension(contentType);
|
|
1944
|
+
if (!extension || !isSupportedXlsxWorksheetImageContentType(contentType)) return null;
|
|
1945
|
+
try {
|
|
1946
|
+
const binary = atob(match[2].replace(/\s+/g, ''));
|
|
1947
|
+
return {
|
|
1948
|
+
contentType,
|
|
1949
|
+
extension,
|
|
1950
|
+
bytes: Uint8Array.from(binary, (character)=>character.charCodeAt(0))
|
|
1951
|
+
};
|
|
1952
|
+
} catch {
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function imageExtension(contentType) {
|
|
1957
|
+
const extensions = {
|
|
1958
|
+
'image/bmp': 'bmp',
|
|
1959
|
+
'image/gif': 'gif',
|
|
1960
|
+
'image/jpeg': 'jpeg',
|
|
1961
|
+
'image/png': 'png',
|
|
1962
|
+
'image/svg+xml': 'svg',
|
|
1963
|
+
'image/webp': 'webp'
|
|
1964
|
+
};
|
|
1965
|
+
return extensions[contentType] ?? null;
|
|
1966
|
+
}
|
|
1967
|
+
async function exportedWorksheetParts(archive) {
|
|
1968
|
+
if (!archive.has('xl/workbook.xml')) return new Map();
|
|
1969
|
+
const workbook = await archive.xml('xl/workbook.xml');
|
|
1970
|
+
const relationships = await archive.relationships('xl/workbook.xml');
|
|
1971
|
+
const parts = new Map();
|
|
1972
|
+
for (const sheet of directChildren(firstDescendant(workbook, 'sheets') ?? workbook.documentElement, 'sheet')){
|
|
1973
|
+
const name = attribute(sheet, 'name');
|
|
1974
|
+
const relationship = relationships.get(attribute(sheet, 'r:id') ?? attribute(sheet, 'id') ?? '');
|
|
1975
|
+
if (name && relationship?.type.endsWith('/worksheet')) parts.set(name, relationship.target);
|
|
1976
|
+
}
|
|
1977
|
+
return parts;
|
|
1978
|
+
}
|
|
1979
|
+
function drawingXml(media, charts, sheet) {
|
|
1980
|
+
return [
|
|
1981
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
1982
|
+
`<xdr:wsDr xmlns:xdr="${DRAWING_NAMESPACE}" xmlns:a="${work_xlsx_images_DRAWINGML_NAMESPACE}" xmlns:r="${work_xlsx_images_RELATIONSHIP_NAMESPACE}">`,
|
|
1983
|
+
...media.map(({ image, relationshipId, objectId })=>[
|
|
1984
|
+
'<xdr:twoCellAnchor editAs="oneCell">',
|
|
1985
|
+
xlsxTwoCellAnchorMarkers(image, sheet),
|
|
1986
|
+
'<xdr:pic><xdr:nvPicPr>',
|
|
1987
|
+
`<xdr:cNvPr id="${objectId}" name="${work_xlsx_images_escapeXml(image.name?.trim() || `Image ${objectId}`)}"${image.altText?.trim() ? ` descr="${work_xlsx_images_escapeXml(image.altText.trim())}"` : ''}/>`,
|
|
1988
|
+
'<xdr:cNvPicPr><a:picLocks noChangeAspect="1"/></xdr:cNvPicPr></xdr:nvPicPr>',
|
|
1989
|
+
`<xdr:blipFill><a:blip r:embed="${relationshipId}"/><a:stretch><a:fillRect/></a:stretch></xdr:blipFill>`,
|
|
1990
|
+
'<xdr:spPr><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></xdr:spPr>',
|
|
1991
|
+
'</xdr:pic><xdr:clientData/></xdr:twoCellAnchor>'
|
|
1992
|
+
].join('')),
|
|
1993
|
+
...charts.map(({ chart, relationshipId, objectId })=>xlsxChartGraphicFrameXml(chart, relationshipId, objectId, sheet)),
|
|
1994
|
+
'</xdr:wsDr>'
|
|
1995
|
+
].join('');
|
|
1996
|
+
}
|
|
1997
|
+
function drawingRelationshipsXml(media, charts) {
|
|
1998
|
+
return [
|
|
1999
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
2000
|
+
`<Relationships xmlns="${PACKAGE_RELATIONSHIP_NAMESPACE}">`,
|
|
2001
|
+
...media.map(({ path, relationshipId })=>`<Relationship Id="${relationshipId}" Type="${IMAGE_RELATIONSHIP}" Target="../media/${path.split('/').at(-1)}"/>`),
|
|
2002
|
+
...charts.map(({ path, relationshipId })=>`<Relationship Id="${relationshipId}" Type="${XLSX_CHART_RELATIONSHIP}" Target="../charts/${path.split('/').at(-1)}"/>`),
|
|
2003
|
+
'</Relationships>'
|
|
2004
|
+
].join('');
|
|
2005
|
+
}
|
|
2006
|
+
async function bindDrawingToWorksheet(zip, worksheetPart, worksheetEntry, drawingPath) {
|
|
2007
|
+
const worksheet = parseXml(await worksheetEntry.async('text'), worksheetPart);
|
|
2008
|
+
for (const existing of directChildren(worksheet.documentElement, 'drawing'))existing.remove();
|
|
2009
|
+
const relationshipPath = relationshipsPartPath(worksheetPart);
|
|
2010
|
+
const relationshipEntry = zip.file(relationshipPath);
|
|
2011
|
+
const relationships = relationshipEntry ? parseXml(await relationshipEntry.async('text'), relationshipPath) : parseXml(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${PACKAGE_RELATIONSHIP_NAMESPACE}"/>`, relationshipPath);
|
|
2012
|
+
for (const existing of directChildren(relationships.documentElement, 'Relationship'))if (attribute(existing, 'Type')?.endsWith('/drawing')) existing.remove();
|
|
2013
|
+
const relationshipId = nextRelationshipId(relationships);
|
|
2014
|
+
const relationship = relationships.createElementNS(PACKAGE_RELATIONSHIP_NAMESPACE, 'Relationship');
|
|
2015
|
+
relationship.setAttribute('Id', relationshipId);
|
|
2016
|
+
relationship.setAttribute('Type', DRAWING_RELATIONSHIP);
|
|
2017
|
+
relationship.setAttribute('Target', relativePartTarget(worksheetPart, drawingPath));
|
|
2018
|
+
relationships.documentElement.append(relationship);
|
|
2019
|
+
const drawing = worksheet.createElementNS(worksheet.documentElement.namespaceURI, 'drawing');
|
|
2020
|
+
drawing.setAttributeNS(work_xlsx_images_RELATIONSHIP_NAMESPACE, 'r:id', relationshipId);
|
|
2021
|
+
const insertionPoint = directChildren(worksheet.documentElement).find((element)=>[
|
|
2022
|
+
'legacyDrawing',
|
|
2023
|
+
'legacyDrawingHF',
|
|
2024
|
+
'picture',
|
|
2025
|
+
'oleObjects',
|
|
2026
|
+
'controls',
|
|
2027
|
+
'webPublishItems',
|
|
2028
|
+
'tableParts',
|
|
2029
|
+
'extLst'
|
|
2030
|
+
].includes(element.localName));
|
|
2031
|
+
worksheet.documentElement.insertBefore(drawing, insertionPoint ?? null);
|
|
2032
|
+
zip.file(worksheetPart, new XMLSerializer().serializeToString(worksheet));
|
|
2033
|
+
zip.file(relationshipPath, new XMLSerializer().serializeToString(relationships));
|
|
2034
|
+
}
|
|
2035
|
+
function nextRelationshipId(document) {
|
|
2036
|
+
const ids = new Set(directChildren(document.documentElement, 'Relationship').map((relationship)=>attribute(relationship, 'Id')).filter((id)=>Boolean(id)));
|
|
2037
|
+
let index = 1;
|
|
2038
|
+
while(ids.has(`rId${index}`))index += 1;
|
|
2039
|
+
return `rId${index}`;
|
|
2040
|
+
}
|
|
2041
|
+
async function updateContentTypes(zip, drawingPaths, chartPaths, imageTypes) {
|
|
2042
|
+
const entry = zip.file('[Content_Types].xml');
|
|
2043
|
+
if (!entry) return;
|
|
2044
|
+
const document = parseXml(await entry.async('text'), '[Content_Types].xml');
|
|
2045
|
+
const namespace = document.documentElement.namespaceURI;
|
|
2046
|
+
for (const [extension, contentType] of imageTypes){
|
|
2047
|
+
const existing = directChildren(document.documentElement, 'Default').find((element)=>attribute(element, 'Extension')?.toLowerCase() === extension);
|
|
2048
|
+
if (existing) {
|
|
2049
|
+
existing.setAttribute('ContentType', contentType);
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
const definition = document.createElementNS(namespace, 'Default');
|
|
2053
|
+
definition.setAttribute('Extension', extension);
|
|
2054
|
+
definition.setAttribute('ContentType', contentType);
|
|
2055
|
+
document.documentElement.append(definition);
|
|
2056
|
+
}
|
|
2057
|
+
for (const path of drawingPaths){
|
|
2058
|
+
const partName = `/${path}`;
|
|
2059
|
+
const existing = directChildren(document.documentElement, 'Override').find((element)=>attribute(element, 'PartName') === partName);
|
|
2060
|
+
const override = existing ?? document.createElementNS(namespace, 'Override');
|
|
2061
|
+
override.setAttribute('PartName', partName);
|
|
2062
|
+
override.setAttribute('ContentType', DRAWING_CONTENT_TYPE);
|
|
2063
|
+
if (!existing) document.documentElement.append(override);
|
|
2064
|
+
}
|
|
2065
|
+
for (const path of chartPaths){
|
|
2066
|
+
const partName = `/${path}`;
|
|
2067
|
+
const existing = directChildren(document.documentElement, 'Override').find((element)=>attribute(element, 'PartName') === partName);
|
|
2068
|
+
const override = existing ?? document.createElementNS(namespace, 'Override');
|
|
2069
|
+
override.setAttribute('PartName', partName);
|
|
2070
|
+
override.setAttribute('ContentType', XLSX_CHART_CONTENT_TYPE);
|
|
2071
|
+
if (!existing) document.documentElement.append(override);
|
|
2072
|
+
}
|
|
2073
|
+
zip.file('[Content_Types].xml', new XMLSerializer().serializeToString(document));
|
|
2074
|
+
}
|
|
2075
|
+
function relationshipsPartPath(sourcePart) {
|
|
2076
|
+
const segments = sourcePart.split('/');
|
|
2077
|
+
const name = segments.pop() ?? '';
|
|
2078
|
+
return [
|
|
2079
|
+
...segments,
|
|
2080
|
+
'_rels',
|
|
2081
|
+
`${name}.rels`
|
|
2082
|
+
].join('/');
|
|
2083
|
+
}
|
|
2084
|
+
function relativePartTarget(sourcePart, targetPart) {
|
|
2085
|
+
const source = sourcePart.split('/').slice(0, -1);
|
|
2086
|
+
const target = targetPart.split('/');
|
|
2087
|
+
while(source.length && target.length && source[0] === target[0]){
|
|
2088
|
+
source.shift();
|
|
2089
|
+
target.shift();
|
|
2090
|
+
}
|
|
2091
|
+
return [
|
|
2092
|
+
...source.map(()=>'..'),
|
|
2093
|
+
...target
|
|
2094
|
+
].join('/');
|
|
2095
|
+
}
|
|
2096
|
+
function work_xlsx_images_partNumber(path) {
|
|
2097
|
+
return /(\d+)(?:\.xml)?$/i.exec(path)?.[1] ?? '1';
|
|
2098
|
+
}
|
|
2099
|
+
function work_xlsx_images_escapeXml(value) {
|
|
2100
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
2101
|
+
}
|
|
2102
|
+
const OOXML_TO_FRIENDLY_TOKEN = {
|
|
2103
|
+
P: '{page}',
|
|
2104
|
+
N: '{pages}',
|
|
2105
|
+
A: '{sheet}',
|
|
2106
|
+
F: '{file}',
|
|
2107
|
+
Z: '{path}',
|
|
2108
|
+
D: '{date}',
|
|
2109
|
+
T: '{time}'
|
|
2110
|
+
};
|
|
2111
|
+
const FRIENDLY_TO_OOXML_TOKEN = Object.fromEntries(Object.entries(OOXML_TO_FRIENDLY_TOKEN).map(([ooxml, friendly])=>[
|
|
2112
|
+
friendly,
|
|
2113
|
+
`&${ooxml}`
|
|
2114
|
+
]));
|
|
2115
|
+
const RICH_FORMAT_CODES = new Set([
|
|
2116
|
+
'B',
|
|
2117
|
+
'I',
|
|
2118
|
+
'U',
|
|
2119
|
+
'E',
|
|
2120
|
+
'S',
|
|
2121
|
+
'X',
|
|
2122
|
+
'Y',
|
|
2123
|
+
'O',
|
|
2124
|
+
'H',
|
|
2125
|
+
'+',
|
|
2126
|
+
'-'
|
|
2127
|
+
]);
|
|
2128
|
+
function parseXlsxHeaderFooterSections(source) {
|
|
2129
|
+
if (!source) return;
|
|
2130
|
+
const sections = {
|
|
2131
|
+
left: '',
|
|
2132
|
+
center: '',
|
|
2133
|
+
right: ''
|
|
2134
|
+
};
|
|
2135
|
+
let section = 'center';
|
|
2136
|
+
for(let index = 0; index < source.length; index += 1){
|
|
2137
|
+
if ('&' !== source[index] || index === source.length - 1) {
|
|
2138
|
+
sections[section] += source[index];
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
const code = source[index + 1];
|
|
2142
|
+
if ('&' === code) {
|
|
2143
|
+
sections[section] += '&';
|
|
2144
|
+
index += 1;
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
if ('L' === code || 'C' === code || 'R' === code) {
|
|
2148
|
+
section = 'L' === code ? 'left' : 'C' === code ? 'center' : 'right';
|
|
2149
|
+
index += 1;
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
const friendlyToken = OOXML_TO_FRIENDLY_TOKEN[code];
|
|
2153
|
+
if (friendlyToken) {
|
|
2154
|
+
sections[section] += friendlyToken;
|
|
2155
|
+
index += 1;
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
const formattingEnd = formattingCodeEnd(source, index);
|
|
2159
|
+
if (null !== formattingEnd || 'G' === code) {
|
|
2160
|
+
index = formattingEnd ?? index + 1;
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
sections[section] += `&${code}`;
|
|
2164
|
+
index += 1;
|
|
2165
|
+
}
|
|
2166
|
+
return Object.values(sections).some(Boolean) ? sections : void 0;
|
|
2167
|
+
}
|
|
2168
|
+
function serializeXlsxHeaderFooterSections(sections) {
|
|
2169
|
+
if (!sections) return '';
|
|
2170
|
+
return [
|
|
2171
|
+
[
|
|
2172
|
+
'L',
|
|
2173
|
+
sections.left
|
|
2174
|
+
],
|
|
2175
|
+
[
|
|
2176
|
+
'C',
|
|
2177
|
+
sections.center
|
|
2178
|
+
],
|
|
2179
|
+
[
|
|
2180
|
+
'R',
|
|
2181
|
+
sections.right
|
|
2182
|
+
]
|
|
2183
|
+
].flatMap(([code, template])=>template ? [
|
|
2184
|
+
`&${code}${serializeTemplate(template)}`
|
|
2185
|
+
] : []).join('');
|
|
2186
|
+
}
|
|
2187
|
+
function readXlsxHeaderFooter(element) {
|
|
2188
|
+
if (!element) return {};
|
|
2189
|
+
const settings = {};
|
|
2190
|
+
const header = parseXlsxHeaderFooterSections(directChild(element, 'oddHeader')?.textContent);
|
|
2191
|
+
const footer = parseXlsxHeaderFooterSections(directChild(element, 'oddFooter')?.textContent);
|
|
2192
|
+
if (header) settings.header = header;
|
|
2193
|
+
if (footer) settings.footer = footer;
|
|
2194
|
+
if (null !== attribute(element, 'scaleWithDoc')) settings.scaleWithDocument = work_xlsx_header_footer_booleanAttribute(element, 'scaleWithDoc');
|
|
2195
|
+
if (null !== attribute(element, 'alignWithMargins')) settings.alignWithMargins = work_xlsx_header_footer_booleanAttribute(element, 'alignWithMargins');
|
|
2196
|
+
return settings;
|
|
2197
|
+
}
|
|
2198
|
+
function writeXlsxHeaderFooter(document, pageSetup) {
|
|
2199
|
+
const header = serializeXlsxHeaderFooterSections(pageSetup.header);
|
|
2200
|
+
const footer = serializeXlsxHeaderFooterSections(pageSetup.footer);
|
|
2201
|
+
if (!header && !footer && void 0 === pageSetup.scaleWithDocument && void 0 === pageSetup.alignWithMargins) return null;
|
|
2202
|
+
const element = document.createElementNS(document.documentElement.namespaceURI, 'headerFooter');
|
|
2203
|
+
if (void 0 !== pageSetup.scaleWithDocument) element.setAttribute('scaleWithDoc', pageSetup.scaleWithDocument ? '1' : '0');
|
|
2204
|
+
if (void 0 !== pageSetup.alignWithMargins) element.setAttribute('alignWithMargins', pageSetup.alignWithMargins ? '1' : '0');
|
|
2205
|
+
appendTextChild(document, element, 'oddHeader', header);
|
|
2206
|
+
appendTextChild(document, element, 'oddFooter', footer);
|
|
2207
|
+
return element;
|
|
2208
|
+
}
|
|
2209
|
+
function inspectXlsxHeaderFooterText(source) {
|
|
2210
|
+
const inspection = {
|
|
2211
|
+
hasFormatting: false,
|
|
2212
|
+
hasImage: false
|
|
2213
|
+
};
|
|
2214
|
+
if (!source) return inspection;
|
|
2215
|
+
for(let index = 0; index < source.length - 1; index += 1){
|
|
2216
|
+
if ('&' !== source[index]) continue;
|
|
2217
|
+
const code = source[index + 1];
|
|
2218
|
+
if ('&' === code) {
|
|
2219
|
+
index += 1;
|
|
2220
|
+
continue;
|
|
2221
|
+
}
|
|
2222
|
+
if ('G' === code) inspection.hasImage = true;
|
|
2223
|
+
if (null !== formattingCodeEnd(source, index)) inspection.hasFormatting = true;
|
|
2224
|
+
}
|
|
2225
|
+
return inspection;
|
|
2226
|
+
}
|
|
2227
|
+
function serializeTemplate(template) {
|
|
2228
|
+
const escaped = template.replaceAll('&', '&&');
|
|
2229
|
+
return escaped.replace(/\{(?:page|pages|sheet|file|path|date|time)\}/g, (token)=>FRIENDLY_TO_OOXML_TOKEN[token] ?? token);
|
|
2230
|
+
}
|
|
2231
|
+
function formattingCodeEnd(source, ampersandIndex) {
|
|
2232
|
+
const code = source[ampersandIndex + 1];
|
|
2233
|
+
if (RICH_FORMAT_CODES.has(code)) return ampersandIndex + 1;
|
|
2234
|
+
if (/^\d$/.test(code)) {
|
|
2235
|
+
let end = ampersandIndex + 1;
|
|
2236
|
+
while(end + 1 < source.length && /^\d$/.test(source[end + 1]))end += 1;
|
|
2237
|
+
return end;
|
|
2238
|
+
}
|
|
2239
|
+
if ('"' === code) {
|
|
2240
|
+
const closingQuote = source.indexOf('"', ampersandIndex + 2);
|
|
2241
|
+
return closingQuote < 0 ? source.length - 1 : closingQuote;
|
|
2242
|
+
}
|
|
2243
|
+
if ('K' === code) {
|
|
2244
|
+
let end = ampersandIndex + 1;
|
|
2245
|
+
while(end + 1 < source.length && end < ampersandIndex + 7 && /^[0-9A-Fa-f+-]$/.test(source[end + 1]))end += 1;
|
|
2246
|
+
return end;
|
|
2247
|
+
}
|
|
2248
|
+
return null;
|
|
2249
|
+
}
|
|
2250
|
+
function appendTextChild(document, parent, name, value) {
|
|
2251
|
+
if (!value) return;
|
|
2252
|
+
const child = document.createElementNS(document.documentElement.namespaceURI, name);
|
|
2253
|
+
child.textContent = value;
|
|
2254
|
+
parent.append(child);
|
|
2255
|
+
}
|
|
2256
|
+
function work_xlsx_header_footer_booleanAttribute(element, name) {
|
|
2257
|
+
const value = attribute(element, name)?.toLowerCase();
|
|
2258
|
+
return '1' === value || 'true' === value;
|
|
2259
|
+
}
|
|
2260
|
+
const PAPER_SIZE_CODES = {
|
|
2261
|
+
letter: '1',
|
|
2262
|
+
tabloid: '3',
|
|
2263
|
+
legal: '5',
|
|
2264
|
+
a3: '8',
|
|
2265
|
+
a4: '9',
|
|
2266
|
+
a5: '11'
|
|
2267
|
+
};
|
|
2268
|
+
const PAPER_SIZES_BY_CODE = new Map(Object.entries(PAPER_SIZE_CODES).map(([paperSize, code])=>[
|
|
2269
|
+
code,
|
|
2270
|
+
paperSize
|
|
2271
|
+
]));
|
|
2272
|
+
function readXlsxPageSetup(document) {
|
|
2273
|
+
const root = document.documentElement;
|
|
2274
|
+
const pageSetupElement = directChild(root, 'pageSetup');
|
|
2275
|
+
const marginsElement = directChild(root, 'pageMargins');
|
|
2276
|
+
const printOptions = directChild(root, 'printOptions');
|
|
2277
|
+
const headerFooterElement = directChild(root, 'headerFooter');
|
|
2278
|
+
const sheetProperties = directChild(root, 'sheetPr');
|
|
2279
|
+
const setupProperties = sheetProperties ? directChild(sheetProperties, 'pageSetUpPr') : void 0;
|
|
2280
|
+
if (!pageSetupElement && !marginsElement && !printOptions && !setupProperties && !headerFooterElement) return;
|
|
2281
|
+
const pageSetup = {};
|
|
2282
|
+
const paperSize = pageSetupElement ? attribute(pageSetupElement, 'paperSize') : null;
|
|
2283
|
+
const editablePaperSize = paperSize ? PAPER_SIZES_BY_CODE.get(paperSize) : void 0;
|
|
2284
|
+
if (editablePaperSize) pageSetup.paperSize = editablePaperSize;
|
|
2285
|
+
const orientation = pageSetupElement ? attribute(pageSetupElement, 'orientation')?.toLowerCase() : void 0;
|
|
2286
|
+
if ('portrait' === orientation || 'landscape' === orientation) pageSetup.orientation = orientation;
|
|
2287
|
+
const scale = boundedIntegerAttribute(pageSetupElement, 'scale', 10, 400);
|
|
2288
|
+
if (void 0 !== scale) pageSetup.scale = scale;
|
|
2289
|
+
const fitToWidth = boundedIntegerAttribute(pageSetupElement, 'fitToWidth', 0, 32767);
|
|
2290
|
+
if (void 0 !== fitToWidth) pageSetup.fitToWidth = fitToWidth;
|
|
2291
|
+
const fitToHeight = boundedIntegerAttribute(pageSetupElement, 'fitToHeight', 0, 32767);
|
|
2292
|
+
if (void 0 !== fitToHeight) pageSetup.fitToHeight = fitToHeight;
|
|
2293
|
+
const pageOrder = pageSetupElement ? attribute(pageSetupElement, 'pageOrder') : null;
|
|
2294
|
+
if ('downThenOver' === pageOrder || 'overThenDown' === pageOrder) pageSetup.pageOrder = pageOrder;
|
|
2295
|
+
const pageNumberStart = boundedIntegerAttribute(pageSetupElement, 'firstPageNumber', 1, 32767);
|
|
2296
|
+
const useFirstPageNumber = pageSetupElement ? attribute(pageSetupElement, 'useFirstPageNumber') : null;
|
|
2297
|
+
if (void 0 !== pageNumberStart && (null === useFirstPageNumber || work_xlsx_page_setup_booleanAttribute(pageSetupElement, 'useFirstPageNumber'))) pageSetup.pageNumberStart = pageNumberStart;
|
|
2298
|
+
if (setupProperties && null !== attribute(setupProperties, 'fitToPage')) pageSetup.fitToPage = work_xlsx_page_setup_booleanAttribute(setupProperties, 'fitToPage');
|
|
2299
|
+
if (printOptions && null !== attribute(printOptions, 'horizontalCentered')) pageSetup.horizontalCentered = work_xlsx_page_setup_booleanAttribute(printOptions, 'horizontalCentered');
|
|
2300
|
+
if (printOptions && null !== attribute(printOptions, 'verticalCentered')) pageSetup.verticalCentered = work_xlsx_page_setup_booleanAttribute(printOptions, 'verticalCentered');
|
|
2301
|
+
const margins = readMargins(marginsElement);
|
|
2302
|
+
if (margins) pageSetup.margins = margins;
|
|
2303
|
+
Object.assign(pageSetup, readXlsxHeaderFooter(headerFooterElement));
|
|
2304
|
+
return pageSetup;
|
|
2305
|
+
}
|
|
2306
|
+
function writeXlsxPageSetup(document, pageSetup) {
|
|
2307
|
+
const root = document.documentElement;
|
|
2308
|
+
for (const existing of directChildren(root).filter((element)=>[
|
|
2309
|
+
'printOptions',
|
|
2310
|
+
'pageMargins',
|
|
2311
|
+
'pageSetup',
|
|
2312
|
+
'headerFooter'
|
|
2313
|
+
].includes(element.localName)))existing.remove();
|
|
2314
|
+
writeFitToPage(document, pageSetup?.fitToPage);
|
|
2315
|
+
if (!pageSetup) return;
|
|
2316
|
+
const elements = [];
|
|
2317
|
+
if (void 0 !== pageSetup.horizontalCentered || void 0 !== pageSetup.verticalCentered) {
|
|
2318
|
+
const printOptions = document.createElementNS(root.namespaceURI, 'printOptions');
|
|
2319
|
+
if (void 0 !== pageSetup.horizontalCentered) printOptions.setAttribute('horizontalCentered', pageSetup.horizontalCentered ? '1' : '0');
|
|
2320
|
+
if (void 0 !== pageSetup.verticalCentered) printOptions.setAttribute('verticalCentered', pageSetup.verticalCentered ? '1' : '0');
|
|
2321
|
+
elements.push(printOptions);
|
|
2322
|
+
}
|
|
2323
|
+
if (pageSetup.margins) elements.push(writeMargins(document, pageSetup.margins));
|
|
2324
|
+
const setup = document.createElementNS(root.namespaceURI, 'pageSetup');
|
|
2325
|
+
if (pageSetup.paperSize) setup.setAttribute('paperSize', PAPER_SIZE_CODES[normalizeSpreadsheetPaperSize(pageSetup.paperSize)]);
|
|
2326
|
+
if (pageSetup.orientation) setup.setAttribute('orientation', pageSetup.orientation);
|
|
2327
|
+
if (void 0 !== pageSetup.scale) setup.setAttribute('scale', String(pageSetup.scale));
|
|
2328
|
+
if (void 0 !== pageSetup.fitToWidth) setup.setAttribute('fitToWidth', String(pageSetup.fitToWidth));
|
|
2329
|
+
if (void 0 !== pageSetup.fitToHeight) setup.setAttribute('fitToHeight', String(pageSetup.fitToHeight));
|
|
2330
|
+
if (pageSetup.pageOrder) setup.setAttribute('pageOrder', pageSetup.pageOrder);
|
|
2331
|
+
if (void 0 !== pageSetup.pageNumberStart && Number.isInteger(pageSetup.pageNumberStart) && pageSetup.pageNumberStart >= 1 && pageSetup.pageNumberStart <= 32767) {
|
|
2332
|
+
setup.setAttribute('firstPageNumber', String(pageSetup.pageNumberStart));
|
|
2333
|
+
setup.setAttribute('useFirstPageNumber', '1');
|
|
2334
|
+
}
|
|
2335
|
+
if (setup.attributes.length) elements.push(setup);
|
|
2336
|
+
const headerFooter = writeXlsxHeaderFooter(document, pageSetup);
|
|
2337
|
+
if (headerFooter) elements.push(headerFooter);
|
|
2338
|
+
const anchor = directChildren(root).find((element)=>[
|
|
2339
|
+
'rowBreaks',
|
|
2340
|
+
'colBreaks',
|
|
2341
|
+
'customProperties',
|
|
2342
|
+
'cellWatches',
|
|
2343
|
+
'ignoredErrors',
|
|
2344
|
+
'smartTags',
|
|
2345
|
+
'drawing',
|
|
2346
|
+
'legacyDrawing',
|
|
2347
|
+
'legacyDrawingHF',
|
|
2348
|
+
'picture',
|
|
2349
|
+
'oleObjects',
|
|
2350
|
+
'controls',
|
|
2351
|
+
'webPublishItems',
|
|
2352
|
+
'tableParts',
|
|
2353
|
+
'extLst'
|
|
2354
|
+
].includes(element.localName)) ?? null;
|
|
2355
|
+
for (const element of elements)root.insertBefore(element, anchor);
|
|
2356
|
+
}
|
|
2357
|
+
function isEditableXlsxPaperSizeCode(value) {
|
|
2358
|
+
return PAPER_SIZES_BY_CODE.has(value);
|
|
2359
|
+
}
|
|
2360
|
+
function writeFitToPage(document, fitToPage) {
|
|
2361
|
+
const root = document.documentElement;
|
|
2362
|
+
let sheetProperties = directChild(root, 'sheetPr');
|
|
2363
|
+
let setupProperties = sheetProperties ? directChild(sheetProperties, 'pageSetUpPr') : void 0;
|
|
2364
|
+
if (void 0 !== fitToPage) {
|
|
2365
|
+
if (!sheetProperties) {
|
|
2366
|
+
sheetProperties = document.createElementNS(root.namespaceURI, 'sheetPr');
|
|
2367
|
+
root.insertBefore(sheetProperties, root.firstElementChild);
|
|
2368
|
+
}
|
|
2369
|
+
if (!setupProperties) {
|
|
2370
|
+
setupProperties = document.createElementNS(root.namespaceURI, 'pageSetUpPr');
|
|
2371
|
+
sheetProperties.append(setupProperties);
|
|
2372
|
+
}
|
|
2373
|
+
setupProperties.setAttribute('fitToPage', fitToPage ? '1' : '0');
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
setupProperties?.removeAttribute('fitToPage');
|
|
2377
|
+
if (setupProperties && !setupProperties.attributes.length && !setupProperties.children.length) setupProperties.remove();
|
|
2378
|
+
}
|
|
2379
|
+
function readMargins(element) {
|
|
2380
|
+
if (!element) return null;
|
|
2381
|
+
const values = [
|
|
2382
|
+
'top',
|
|
2383
|
+
'right',
|
|
2384
|
+
'bottom',
|
|
2385
|
+
'left',
|
|
2386
|
+
'header',
|
|
2387
|
+
'footer'
|
|
2388
|
+
].map((name)=>nonnegativeNumberAttribute(element, name));
|
|
2389
|
+
if (values.some((value)=>null === value)) return null;
|
|
2390
|
+
const [top, right, bottom, left, header, footer] = values;
|
|
2391
|
+
return {
|
|
2392
|
+
top: inchesToMillimeters(top),
|
|
2393
|
+
right: inchesToMillimeters(right),
|
|
2394
|
+
bottom: inchesToMillimeters(bottom),
|
|
2395
|
+
left: inchesToMillimeters(left),
|
|
2396
|
+
header: inchesToMillimeters(header),
|
|
2397
|
+
footer: inchesToMillimeters(footer)
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function writeMargins(document, margins) {
|
|
2401
|
+
const element = document.createElementNS(document.documentElement.namespaceURI, 'pageMargins');
|
|
2402
|
+
for (const [name, value] of Object.entries({
|
|
2403
|
+
left: margins.left,
|
|
2404
|
+
right: margins.right,
|
|
2405
|
+
top: margins.top,
|
|
2406
|
+
bottom: margins.bottom,
|
|
2407
|
+
header: margins.header,
|
|
2408
|
+
footer: margins.footer
|
|
2409
|
+
}))element.setAttribute(name, millimetersToInches(value));
|
|
2410
|
+
return element;
|
|
2411
|
+
}
|
|
2412
|
+
function boundedIntegerAttribute(element, name, minimum, maximum) {
|
|
2413
|
+
if (!element) return;
|
|
2414
|
+
const source = attribute(element, name);
|
|
2415
|
+
if (null === source) return;
|
|
2416
|
+
const value = Number(source);
|
|
2417
|
+
return Number.isInteger(value) && value >= minimum && value <= maximum ? value : void 0;
|
|
2418
|
+
}
|
|
2419
|
+
function nonnegativeNumberAttribute(element, name) {
|
|
2420
|
+
const source = attribute(element, name);
|
|
2421
|
+
if (null === source) return null;
|
|
2422
|
+
const value = Number(source);
|
|
2423
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
2424
|
+
}
|
|
2425
|
+
function work_xlsx_page_setup_booleanAttribute(element, name) {
|
|
2426
|
+
const value = element ? attribute(element, name)?.toLowerCase() : void 0;
|
|
2427
|
+
return '1' === value || 'true' === value;
|
|
2428
|
+
}
|
|
2429
|
+
function inchesToMillimeters(value) {
|
|
2430
|
+
return Number((25.4 * value).toFixed(4));
|
|
2431
|
+
}
|
|
2432
|
+
function millimetersToInches(value) {
|
|
2433
|
+
return String(Number((value / 25.4).toFixed(4)));
|
|
2434
|
+
}
|
|
2435
|
+
function readXlsxProtection(worksheet, styles) {
|
|
2436
|
+
const protectedRanges = readProtectedRanges(worksheet);
|
|
2437
|
+
const protection = directChild(worksheet.documentElement, 'sheetProtection');
|
|
2438
|
+
const authority = protection || protectedRanges.length ? {
|
|
2439
|
+
...authorityFromElement(protection),
|
|
2440
|
+
allowRangeList: protectedRanges
|
|
2441
|
+
} : void 0;
|
|
2442
|
+
return {
|
|
2443
|
+
authority,
|
|
2444
|
+
cellProtectionRanges: readCellProtectionRanges(worksheet, styles)
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
function writeXlsxProtection(worksheet, sheet, styles) {
|
|
2448
|
+
const root = worksheet.documentElement;
|
|
2449
|
+
for (const element of directChildren(root, 'sheetProtection'))element.remove();
|
|
2450
|
+
for (const element of directChildren(root, 'protectedRanges'))element.remove();
|
|
2451
|
+
const authority = normalizeSheetProtectionAuthority(sheet.config?.authority);
|
|
2452
|
+
if (1 === authority.sheet) {
|
|
2453
|
+
const element = worksheet.createElementNS(root.namespaceURI, 'sheetProtection');
|
|
2454
|
+
copyAttributes(element, authority.xlsxAttributes);
|
|
2455
|
+
writeProtectionAttributes(element, authority);
|
|
2456
|
+
insertWorksheetFeature(root, element, [
|
|
2457
|
+
'protectedRanges',
|
|
2458
|
+
'scenarios',
|
|
2459
|
+
'autoFilter',
|
|
2460
|
+
'sortState',
|
|
2461
|
+
'dataConsolidate',
|
|
2462
|
+
'customSheetViews',
|
|
2463
|
+
'mergeCells',
|
|
2464
|
+
'phoneticPr',
|
|
2465
|
+
'conditionalFormatting',
|
|
2466
|
+
'dataValidations',
|
|
2467
|
+
'hyperlinks',
|
|
2468
|
+
'printOptions',
|
|
2469
|
+
'pageMargins',
|
|
2470
|
+
'pageSetup',
|
|
2471
|
+
'headerFooter',
|
|
2472
|
+
'drawing',
|
|
2473
|
+
'legacyDrawing',
|
|
2474
|
+
'ignoredErrors',
|
|
2475
|
+
'extLst'
|
|
2476
|
+
]);
|
|
2477
|
+
}
|
|
2478
|
+
writeProtectedRanges(worksheet, [
|
|
2479
|
+
...authority.allowRangeList,
|
|
2480
|
+
...generatedEditableProtectionRanges(authority)
|
|
2481
|
+
]);
|
|
2482
|
+
if (styles) writeCellProtectionStyles(worksheet, sheet, styles);
|
|
2483
|
+
}
|
|
2484
|
+
class XlsxCellProtectionWriter {
|
|
2485
|
+
styles;
|
|
2486
|
+
cellXfs;
|
|
2487
|
+
generated = new Map();
|
|
2488
|
+
changed = false;
|
|
2489
|
+
constructor(styles){
|
|
2490
|
+
this.styles = styles;
|
|
2491
|
+
const root = styles.documentElement;
|
|
2492
|
+
this.cellXfs = directChild(root, 'cellXfs') ?? styles.createElementNS(root.namespaceURI, 'cellXfs');
|
|
2493
|
+
if (!this.cellXfs.parentElement) {
|
|
2494
|
+
const anchor = directChildren(root).find((child)=>[
|
|
2495
|
+
'cellStyles',
|
|
2496
|
+
'dxfs',
|
|
2497
|
+
'tableStyles',
|
|
2498
|
+
'colors',
|
|
2499
|
+
'extLst'
|
|
2500
|
+
].includes(child.localName));
|
|
2501
|
+
root.insertBefore(this.cellXfs, anchor ?? null);
|
|
2502
|
+
}
|
|
2503
|
+
if (!directChildren(this.cellXfs, 'xf').length) {
|
|
2504
|
+
const base = styles.createElementNS(root.namespaceURI, 'xf');
|
|
2505
|
+
base.setAttribute('numFmtId', '0');
|
|
2506
|
+
base.setAttribute('fontId', '0');
|
|
2507
|
+
base.setAttribute('fillId', '0');
|
|
2508
|
+
base.setAttribute('borderId', '0');
|
|
2509
|
+
base.setAttribute('xfId', '0');
|
|
2510
|
+
this.cellXfs.append(base);
|
|
2511
|
+
this.changed = true;
|
|
2512
|
+
}
|
|
2513
|
+
this.updateCount();
|
|
2514
|
+
}
|
|
2515
|
+
styleId(baseStyleId, locked, hidden) {
|
|
2516
|
+
const styles = directChildren(this.cellXfs, 'xf');
|
|
2517
|
+
const baseIndex = Number.isInteger(baseStyleId) && styles[baseStyleId] ? baseStyleId : 0;
|
|
2518
|
+
if (sameProtection(readProtectionStyle(styles[baseIndex]), {
|
|
2519
|
+
locked,
|
|
2520
|
+
hidden
|
|
2521
|
+
})) return baseIndex;
|
|
2522
|
+
const key = `${baseIndex}:${locked ? 1 : 0}:${hidden ? 1 : 0}`;
|
|
2523
|
+
const cached = this.generated.get(key);
|
|
2524
|
+
if (void 0 !== cached) return cached;
|
|
2525
|
+
const clone = styles[baseIndex].cloneNode(true);
|
|
2526
|
+
for (const element of directChildren(clone, 'protection'))element.remove();
|
|
2527
|
+
clone.setAttribute('applyProtection', '1');
|
|
2528
|
+
const protection = this.styles.createElementNS(this.styles.documentElement.namespaceURI, 'protection');
|
|
2529
|
+
protection.setAttribute('locked', locked ? '1' : '0');
|
|
2530
|
+
protection.setAttribute('hidden', hidden ? '1' : '0');
|
|
2531
|
+
clone.insertBefore(protection, directChild(clone, 'extLst') ?? null);
|
|
2532
|
+
const index = styles.length;
|
|
2533
|
+
this.cellXfs.append(clone);
|
|
2534
|
+
this.generated.set(key, index);
|
|
2535
|
+
this.changed = true;
|
|
2536
|
+
this.updateCount();
|
|
2537
|
+
return index;
|
|
2538
|
+
}
|
|
2539
|
+
serialize() {
|
|
2540
|
+
return new XMLSerializer().serializeToString(this.styles);
|
|
2541
|
+
}
|
|
2542
|
+
updateCount() {
|
|
2543
|
+
this.cellXfs.setAttribute('count', String(directChildren(this.cellXfs, 'xf').length));
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
function diagnoseXlsxProtection(worksheet, styles) {
|
|
2547
|
+
const protection = directChild(worksheet.documentElement, 'sheetProtection');
|
|
2548
|
+
const protectedRanges = readProtectedRanges(worksheet);
|
|
2549
|
+
if (!protection && !protectedRanges.length) return [];
|
|
2550
|
+
const diagnostics = [
|
|
2551
|
+
{
|
|
2552
|
+
code: 'xlsx.protection',
|
|
2553
|
+
message: 'Sheet lock state, locked and unlocked cells, selection rules, and passwordless editable ranges are preserved and enforced.',
|
|
2554
|
+
severity: 'info'
|
|
2555
|
+
}
|
|
2556
|
+
];
|
|
2557
|
+
const sourceAttributes = protection ? work_xlsx_protection_attributes(protection) : {};
|
|
2558
|
+
if (hasCredentialAttributes(sourceAttributes)) diagnostics.push({
|
|
2559
|
+
code: 'xlsx.protection.password',
|
|
2560
|
+
message: 'The source password verifier is preserved, but Work cannot authenticate it; locked cells remain protected.',
|
|
2561
|
+
severity: 'warning'
|
|
2562
|
+
});
|
|
2563
|
+
if (protectedRanges.some(editableRangeRequiresCredentials)) diagnostics.push({
|
|
2564
|
+
code: 'xlsx.protection.range-credentials',
|
|
2565
|
+
message: 'Password- or permission-backed editable ranges are preserved but remain locked because Work cannot authenticate their credentials.',
|
|
2566
|
+
severity: 'warning'
|
|
2567
|
+
});
|
|
2568
|
+
if (protection) diagnostics.push({
|
|
2569
|
+
code: 'xlsx.protection.permissions',
|
|
2570
|
+
message: 'Action-specific protection permissions are preserved; Work enforces cell editing and selection but not every Excel row, column, object, filter, or pivot permission.',
|
|
2571
|
+
severity: 'warning'
|
|
2572
|
+
});
|
|
2573
|
+
if (hasHiddenFormula(worksheet, styles)) diagnostics.push({
|
|
2574
|
+
code: 'xlsx.protection.hidden-formulas',
|
|
2575
|
+
message: 'Formula-hidden protection is preserved for XLSX export, but formulas can still appear in the Work formula bar.',
|
|
2576
|
+
severity: 'warning'
|
|
2577
|
+
});
|
|
2578
|
+
return diagnostics;
|
|
2579
|
+
}
|
|
2580
|
+
function authorityFromElement(element) {
|
|
2581
|
+
const defaults = normalizeSheetProtectionAuthority(void 0);
|
|
2582
|
+
if (!element) return defaults;
|
|
2583
|
+
const source = work_xlsx_protection_attributes(element);
|
|
2584
|
+
return {
|
|
2585
|
+
...defaults,
|
|
2586
|
+
sheet: work_xlsx_protection_booleanAttribute(element, 'sheet', false) ? 1 : 0,
|
|
2587
|
+
selectLockedCells: work_xlsx_protection_booleanAttribute(element, 'selectLockedCells', false) ? 0 : 1,
|
|
2588
|
+
selectunLockedCells: work_xlsx_protection_booleanAttribute(element, 'selectUnlockedCells', false) ? 0 : 1,
|
|
2589
|
+
formatCells: work_xlsx_protection_booleanAttribute(element, 'formatCells', true) ? 0 : 1,
|
|
2590
|
+
formatColumns: work_xlsx_protection_booleanAttribute(element, 'formatColumns', true) ? 0 : 1,
|
|
2591
|
+
formatRows: work_xlsx_protection_booleanAttribute(element, 'formatRows', true) ? 0 : 1,
|
|
2592
|
+
insertColumns: work_xlsx_protection_booleanAttribute(element, 'insertColumns', true) ? 0 : 1,
|
|
2593
|
+
insertRows: work_xlsx_protection_booleanAttribute(element, 'insertRows', true) ? 0 : 1,
|
|
2594
|
+
insertHyperlinks: work_xlsx_protection_booleanAttribute(element, 'insertHyperlinks', true) ? 0 : 1,
|
|
2595
|
+
deleteColumns: work_xlsx_protection_booleanAttribute(element, 'deleteColumns', true) ? 0 : 1,
|
|
2596
|
+
deleteRows: work_xlsx_protection_booleanAttribute(element, 'deleteRows', true) ? 0 : 1,
|
|
2597
|
+
sort: work_xlsx_protection_booleanAttribute(element, 'sort', true) ? 0 : 1,
|
|
2598
|
+
filter: work_xlsx_protection_booleanAttribute(element, 'autoFilter', true) ? 0 : 1,
|
|
2599
|
+
usePivotTablereports: work_xlsx_protection_booleanAttribute(element, 'pivotTables', true) ? 0 : 1,
|
|
2600
|
+
editObjects: work_xlsx_protection_booleanAttribute(element, 'objects', false) ? 0 : 1,
|
|
2601
|
+
editScenarios: work_xlsx_protection_booleanAttribute(element, 'scenarios', false) ? 0 : 1,
|
|
2602
|
+
defaultSheetHintText: DEFAULT_PROTECTION_HINT,
|
|
2603
|
+
xlsxAttributes: Object.keys(source).length ? source : void 0
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
function readProtectedRanges(worksheet) {
|
|
2607
|
+
const container = directChild(worksheet.documentElement, 'protectedRanges');
|
|
2608
|
+
if (!container) return [];
|
|
2609
|
+
return directChildren(container, 'protectedRange').flatMap((element, index)=>{
|
|
2610
|
+
const ranges = parseSqref(attribute(element, 'sqref'));
|
|
2611
|
+
if (!ranges.length) return [];
|
|
2612
|
+
return [
|
|
2613
|
+
{
|
|
2614
|
+
name: attribute(element, 'name')?.trim() || `Range ${index + 1}`,
|
|
2615
|
+
sqref: formatSpreadsheetCellRanges(ranges),
|
|
2616
|
+
xlsxAttributes: work_xlsx_protection_attributes(element)
|
|
2617
|
+
}
|
|
2618
|
+
];
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
function readCellProtectionRanges(worksheet, styles) {
|
|
2622
|
+
const xfs = styles ? directChildren(firstDescendant(styles, 'cellXfs') ?? styles.documentElement, 'xf') : [];
|
|
2623
|
+
if (!xfs.length) return [];
|
|
2624
|
+
const styleList = xfs.map(readProtectionStyle);
|
|
2625
|
+
const bounds = worksheetBounds(worksheet);
|
|
2626
|
+
const ranges = [];
|
|
2627
|
+
if (!sameProtection(styleList[0], {
|
|
2628
|
+
locked: true,
|
|
2629
|
+
hidden: false
|
|
2630
|
+
})) ranges.push({
|
|
2631
|
+
range: bounds,
|
|
2632
|
+
...styleList[0]
|
|
2633
|
+
});
|
|
2634
|
+
for (const column of descendants(worksheet, 'col')){
|
|
2635
|
+
const style = indexedStyle(styleList, attribute(column, 'style'));
|
|
2636
|
+
const minimum = work_xlsx_protection_positiveInteger(attribute(column, 'min'));
|
|
2637
|
+
const maximum = work_xlsx_protection_positiveInteger(attribute(column, 'max'));
|
|
2638
|
+
if (style && null !== minimum && null !== maximum) ranges.push({
|
|
2639
|
+
range: {
|
|
2640
|
+
row: [
|
|
2641
|
+
...bounds.row
|
|
2642
|
+
],
|
|
2643
|
+
column: [
|
|
2644
|
+
minimum - 1,
|
|
2645
|
+
maximum - 1
|
|
2646
|
+
]
|
|
2647
|
+
},
|
|
2648
|
+
...style
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
for (const row of descendants(worksheet, 'row')){
|
|
2652
|
+
const style = indexedStyle(styleList, attribute(row, 's'));
|
|
2653
|
+
const rowNumber = work_xlsx_protection_positiveInteger(attribute(row, 'r'));
|
|
2654
|
+
if (style && null !== rowNumber) ranges.push({
|
|
2655
|
+
range: {
|
|
2656
|
+
row: [
|
|
2657
|
+
rowNumber - 1,
|
|
2658
|
+
rowNumber - 1
|
|
2659
|
+
],
|
|
2660
|
+
column: [
|
|
2661
|
+
...bounds.column
|
|
2662
|
+
]
|
|
2663
|
+
},
|
|
2664
|
+
...style
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
for (const cell of descendants(worksheet, 'c')){
|
|
2668
|
+
const style = indexedStyle(styleList, attribute(cell, 's'));
|
|
2669
|
+
const coordinate = parseCellReference(attribute(cell, 'r'));
|
|
2670
|
+
if (style && coordinate) ranges.push({
|
|
2671
|
+
range: {
|
|
2672
|
+
row: [
|
|
2673
|
+
coordinate.row,
|
|
2674
|
+
coordinate.row
|
|
2675
|
+
],
|
|
2676
|
+
column: [
|
|
2677
|
+
coordinate.column,
|
|
2678
|
+
coordinate.column
|
|
2679
|
+
]
|
|
2680
|
+
},
|
|
2681
|
+
...style
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
return ranges;
|
|
2685
|
+
}
|
|
2686
|
+
function writeProtectionAttributes(element, authority) {
|
|
2687
|
+
element.setAttribute('sheet', '1');
|
|
2688
|
+
element.setAttribute('selectLockedCells', authority.selectLockedCells ? '0' : '1');
|
|
2689
|
+
element.setAttribute('selectUnlockedCells', authority.selectunLockedCells ? '0' : '1');
|
|
2690
|
+
const mappings = [
|
|
2691
|
+
[
|
|
2692
|
+
'formatCells',
|
|
2693
|
+
'formatCells'
|
|
2694
|
+
],
|
|
2695
|
+
[
|
|
2696
|
+
'formatColumns',
|
|
2697
|
+
'formatColumns'
|
|
2698
|
+
],
|
|
2699
|
+
[
|
|
2700
|
+
'formatRows',
|
|
2701
|
+
'formatRows'
|
|
2702
|
+
],
|
|
2703
|
+
[
|
|
2704
|
+
'insertColumns',
|
|
2705
|
+
'insertColumns'
|
|
2706
|
+
],
|
|
2707
|
+
[
|
|
2708
|
+
'insertRows',
|
|
2709
|
+
'insertRows'
|
|
2710
|
+
],
|
|
2711
|
+
[
|
|
2712
|
+
'insertHyperlinks',
|
|
2713
|
+
'insertHyperlinks'
|
|
2714
|
+
],
|
|
2715
|
+
[
|
|
2716
|
+
'deleteColumns',
|
|
2717
|
+
'deleteColumns'
|
|
2718
|
+
],
|
|
2719
|
+
[
|
|
2720
|
+
'deleteRows',
|
|
2721
|
+
'deleteRows'
|
|
2722
|
+
],
|
|
2723
|
+
[
|
|
2724
|
+
'sort',
|
|
2725
|
+
'sort'
|
|
2726
|
+
],
|
|
2727
|
+
[
|
|
2728
|
+
'filter',
|
|
2729
|
+
'autoFilter'
|
|
2730
|
+
],
|
|
2731
|
+
[
|
|
2732
|
+
'usePivotTablereports',
|
|
2733
|
+
'pivotTables'
|
|
2734
|
+
],
|
|
2735
|
+
[
|
|
2736
|
+
'editObjects',
|
|
2737
|
+
'objects'
|
|
2738
|
+
],
|
|
2739
|
+
[
|
|
2740
|
+
'editScenarios',
|
|
2741
|
+
'scenarios'
|
|
2742
|
+
]
|
|
2743
|
+
];
|
|
2744
|
+
for (const [key, name] of mappings)element.setAttribute(name, authority[key] ? '0' : '1');
|
|
2745
|
+
}
|
|
2746
|
+
function writeProtectedRanges(document, ranges) {
|
|
2747
|
+
const root = document.documentElement;
|
|
2748
|
+
const container = document.createElementNS(root.namespaceURI, 'protectedRanges');
|
|
2749
|
+
const written = new Set();
|
|
2750
|
+
for (const range of ranges){
|
|
2751
|
+
const parsed = parseSpreadsheetCellRanges(range.sqref);
|
|
2752
|
+
if (!range.name.trim() || !parsed?.length) continue;
|
|
2753
|
+
const reference = formatSpreadsheetCellRanges(parsed).replaceAll(',', ' ');
|
|
2754
|
+
if (written.has(reference)) continue;
|
|
2755
|
+
written.add(reference);
|
|
2756
|
+
const element = document.createElementNS(root.namespaceURI, 'protectedRange');
|
|
2757
|
+
copyAttributes(element, range.xlsxAttributes);
|
|
2758
|
+
element.setAttribute('name', range.name.trim());
|
|
2759
|
+
element.setAttribute('sqref', reference);
|
|
2760
|
+
container.append(element);
|
|
2761
|
+
}
|
|
2762
|
+
if (!container.children.length) return;
|
|
2763
|
+
insertWorksheetFeature(root, container, [
|
|
2764
|
+
'scenarios',
|
|
2765
|
+
'autoFilter',
|
|
2766
|
+
'sortState',
|
|
2767
|
+
'dataConsolidate',
|
|
2768
|
+
'customSheetViews',
|
|
2769
|
+
'mergeCells',
|
|
2770
|
+
'phoneticPr',
|
|
2771
|
+
'conditionalFormatting',
|
|
2772
|
+
'dataValidations',
|
|
2773
|
+
'hyperlinks',
|
|
2774
|
+
'printOptions',
|
|
2775
|
+
'pageMargins',
|
|
2776
|
+
'pageSetup',
|
|
2777
|
+
'headerFooter',
|
|
2778
|
+
'drawing',
|
|
2779
|
+
'legacyDrawing',
|
|
2780
|
+
'ignoredErrors',
|
|
2781
|
+
'extLst'
|
|
2782
|
+
]);
|
|
2783
|
+
}
|
|
2784
|
+
function writeCellProtectionStyles(worksheet, sheet, styles) {
|
|
2785
|
+
const authority = normalizeSheetProtectionAuthority(sheet.config?.authority);
|
|
2786
|
+
for (const [row, values] of sparseArrayEntries(sheet.data))for (const [column, source] of sparseArrayEntries(values)){
|
|
2787
|
+
const cell = source;
|
|
2788
|
+
if (!cell) continue;
|
|
2789
|
+
const compact = compactProtectionAt(authority, row, column);
|
|
2790
|
+
if (void 0 === cell.lo && void 0 === cell.hi && void 0 === compact) continue;
|
|
2791
|
+
const element = ensureCellElement(worksheet, row, column);
|
|
2792
|
+
const baseStyle = nonNegativeInteger(attribute(element, 's')) ?? 0;
|
|
2793
|
+
const styleId = styles.styleId(baseStyle, void 0 === cell.lo ? compact?.locked ?? true : 0 !== cell.lo, void 0 === cell.hi ? compact?.hidden ?? false : 1 === cell.hi);
|
|
2794
|
+
if (styleId) element.setAttribute('s', String(styleId));
|
|
2795
|
+
else element.removeAttribute('s');
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
function generatedEditableProtectionRanges(authority) {
|
|
2799
|
+
let editable = [];
|
|
2800
|
+
for (const item of authority.cellProtectionRanges)if (item.locked) editable = editable.flatMap((range)=>subtractSpreadsheetRange(range, item.range));
|
|
2801
|
+
else editable.push(item.range);
|
|
2802
|
+
return editable.map((range, index)=>({
|
|
2803
|
+
name: `Work editable range ${index + 1}`,
|
|
2804
|
+
sqref: formatSpreadsheetCellRanges([
|
|
2805
|
+
range
|
|
2806
|
+
])
|
|
2807
|
+
}));
|
|
2808
|
+
}
|
|
2809
|
+
function compactProtectionAt(authority, row, column) {
|
|
2810
|
+
for(let index = authority.cellProtectionRanges.length - 1; index >= 0; index -= 1){
|
|
2811
|
+
const item = authority.cellProtectionRanges[index];
|
|
2812
|
+
if (row >= item.range.row[0] && row <= item.range.row[1] && column >= item.range.column[0] && column <= item.range.column[1]) return {
|
|
2813
|
+
locked: item.locked,
|
|
2814
|
+
hidden: item.hidden
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
function subtractSpreadsheetRange(source, removed) {
|
|
2819
|
+
const top = Math.max(source.row[0], removed.row[0]);
|
|
2820
|
+
const bottom = Math.min(source.row[1], removed.row[1]);
|
|
2821
|
+
const left = Math.max(source.column[0], removed.column[0]);
|
|
2822
|
+
const right = Math.min(source.column[1], removed.column[1]);
|
|
2823
|
+
if (top > bottom || left > right) return [
|
|
2824
|
+
source
|
|
2825
|
+
];
|
|
2826
|
+
const remaining = [];
|
|
2827
|
+
if (source.row[0] < top) remaining.push({
|
|
2828
|
+
row: [
|
|
2829
|
+
source.row[0],
|
|
2830
|
+
top - 1
|
|
2831
|
+
],
|
|
2832
|
+
column: [
|
|
2833
|
+
...source.column
|
|
2834
|
+
]
|
|
2835
|
+
});
|
|
2836
|
+
if (bottom < source.row[1]) remaining.push({
|
|
2837
|
+
row: [
|
|
2838
|
+
bottom + 1,
|
|
2839
|
+
source.row[1]
|
|
2840
|
+
],
|
|
2841
|
+
column: [
|
|
2842
|
+
...source.column
|
|
2843
|
+
]
|
|
2844
|
+
});
|
|
2845
|
+
if (source.column[0] < left) remaining.push({
|
|
2846
|
+
row: [
|
|
2847
|
+
top,
|
|
2848
|
+
bottom
|
|
2849
|
+
],
|
|
2850
|
+
column: [
|
|
2851
|
+
source.column[0],
|
|
2852
|
+
left - 1
|
|
2853
|
+
]
|
|
2854
|
+
});
|
|
2855
|
+
if (right < source.column[1]) remaining.push({
|
|
2856
|
+
row: [
|
|
2857
|
+
top,
|
|
2858
|
+
bottom
|
|
2859
|
+
],
|
|
2860
|
+
column: [
|
|
2861
|
+
right + 1,
|
|
2862
|
+
source.column[1]
|
|
2863
|
+
]
|
|
2864
|
+
});
|
|
2865
|
+
return remaining;
|
|
2866
|
+
}
|
|
2867
|
+
function ensureCellElement(document, rowIndex, columnIndex) {
|
|
2868
|
+
const sheetData = directChild(document.documentElement, 'sheetData');
|
|
2869
|
+
if (!sheetData) throw new Error('XLSX worksheet does not contain sheetData');
|
|
2870
|
+
const rowNumber = rowIndex + 1;
|
|
2871
|
+
let row = directChildren(sheetData, 'row').find((element)=>attribute(element, 'r') === String(rowNumber));
|
|
2872
|
+
if (!row) {
|
|
2873
|
+
row = document.createElementNS(document.documentElement.namespaceURI, 'row');
|
|
2874
|
+
row.setAttribute('r', String(rowNumber));
|
|
2875
|
+
const anchor = directChildren(sheetData, 'row').find((element)=>(work_xlsx_protection_positiveInteger(attribute(element, 'r')) ?? Number.MAX_SAFE_INTEGER) > rowNumber);
|
|
2876
|
+
sheetData.insertBefore(row, anchor ?? null);
|
|
2877
|
+
}
|
|
2878
|
+
const reference = encodeCell(rowIndex, columnIndex);
|
|
2879
|
+
let cell = directChildren(row, 'c').find((element)=>attribute(element, 'r') === reference);
|
|
2880
|
+
if (!cell) {
|
|
2881
|
+
cell = document.createElementNS(document.documentElement.namespaceURI, 'c');
|
|
2882
|
+
cell.setAttribute('r', reference);
|
|
2883
|
+
const anchor = directChildren(row, 'c').find((element)=>{
|
|
2884
|
+
const coordinate = parseCellReference(attribute(element, 'r'));
|
|
2885
|
+
return coordinate ? coordinate.column > columnIndex : false;
|
|
2886
|
+
});
|
|
2887
|
+
row.insertBefore(cell, anchor ?? null);
|
|
2888
|
+
expandWorksheetDimension(document, rowIndex, columnIndex);
|
|
2889
|
+
}
|
|
2890
|
+
return cell;
|
|
2891
|
+
}
|
|
2892
|
+
function worksheetBounds(document) {
|
|
2893
|
+
const reference = attribute(directChild(document.documentElement, 'dimension') ?? document.documentElement, 'ref');
|
|
2894
|
+
const parsed = reference ? parseSpreadsheetCellRanges(reference)?.[0] : void 0;
|
|
2895
|
+
if (parsed) return {
|
|
2896
|
+
row: [
|
|
2897
|
+
0,
|
|
2898
|
+
parsed.row[1]
|
|
2899
|
+
],
|
|
2900
|
+
column: [
|
|
2901
|
+
0,
|
|
2902
|
+
parsed.column[1]
|
|
2903
|
+
]
|
|
2904
|
+
};
|
|
2905
|
+
const coordinates = descendants(document, 'c').map((cell)=>parseCellReference(attribute(cell, 'r'))).filter((value)=>Boolean(value));
|
|
2906
|
+
return {
|
|
2907
|
+
row: [
|
|
2908
|
+
0,
|
|
2909
|
+
Math.max(0, ...coordinates.map((value)=>value.row))
|
|
2910
|
+
],
|
|
2911
|
+
column: [
|
|
2912
|
+
0,
|
|
2913
|
+
Math.max(0, ...coordinates.map((value)=>value.column))
|
|
2914
|
+
]
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function expandWorksheetDimension(document, row, column) {
|
|
2918
|
+
const root = document.documentElement;
|
|
2919
|
+
let dimension = directChild(root, 'dimension');
|
|
2920
|
+
const bounds = worksheetBounds(document);
|
|
2921
|
+
bounds.row[1] = Math.max(bounds.row[1], row);
|
|
2922
|
+
bounds.column[1] = Math.max(bounds.column[1], column);
|
|
2923
|
+
if (!dimension) {
|
|
2924
|
+
dimension = document.createElementNS(root.namespaceURI, 'dimension');
|
|
2925
|
+
root.insertBefore(dimension, directChildren(root)[0] ?? null);
|
|
2926
|
+
}
|
|
2927
|
+
dimension.setAttribute('ref', `${encodeCell(bounds.row[0], bounds.column[0])}:${encodeCell(bounds.row[1], bounds.column[1])}`);
|
|
2928
|
+
}
|
|
2929
|
+
function readProtectionStyle(xf) {
|
|
2930
|
+
const protection = directChild(xf, 'protection');
|
|
2931
|
+
if (!protection || false === work_xlsx_protection_booleanAttribute(xf, 'applyProtection', true)) return {
|
|
2932
|
+
locked: true,
|
|
2933
|
+
hidden: false
|
|
2934
|
+
};
|
|
2935
|
+
return {
|
|
2936
|
+
locked: work_xlsx_protection_booleanAttribute(protection, 'locked', true),
|
|
2937
|
+
hidden: work_xlsx_protection_booleanAttribute(protection, 'hidden', false)
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
function hasHiddenFormula(worksheet, styles) {
|
|
2941
|
+
const formulas = descendants(worksheet, 'c').filter((cell)=>Boolean(directChild(cell, 'f')));
|
|
2942
|
+
if (!formulas.length) return false;
|
|
2943
|
+
const ranges = readCellProtectionRanges(worksheet, styles);
|
|
2944
|
+
return formulas.some((cell)=>{
|
|
2945
|
+
const coordinate = parseCellReference(attribute(cell, 'r'));
|
|
2946
|
+
if (!coordinate) return false;
|
|
2947
|
+
let hidden = false;
|
|
2948
|
+
for (const item of ranges)if (coordinate.row >= item.range.row[0] && coordinate.row <= item.range.row[1] && coordinate.column >= item.range.column[0] && coordinate.column <= item.range.column[1]) hidden = item.hidden;
|
|
2949
|
+
return hidden;
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
function insertWorksheetFeature(root, element, anchors) {
|
|
2953
|
+
root.insertBefore(element, directChildren(root).find((child)=>anchors.includes(child.localName)) ?? null);
|
|
2954
|
+
}
|
|
2955
|
+
function parseSqref(value) {
|
|
2956
|
+
return value ? parseSpreadsheetCellRanges(value.trim().replace(/\s+/g, ',')) ?? [] : [];
|
|
2957
|
+
}
|
|
2958
|
+
function indexedStyle(styles, value) {
|
|
2959
|
+
const index = nonNegativeInteger(value);
|
|
2960
|
+
return null === index ? null : styles[index] ?? null;
|
|
2961
|
+
}
|
|
2962
|
+
function parseCellReference(value) {
|
|
2963
|
+
const match = /^\$?([A-Z]{1,3})\$?([1-9]\d*)$/i.exec(value ?? '');
|
|
2964
|
+
if (!match) return null;
|
|
2965
|
+
let column = 0;
|
|
2966
|
+
for (const character of match[1].toUpperCase())column = 26 * column + character.charCodeAt(0) - 64;
|
|
2967
|
+
return {
|
|
2968
|
+
row: Number(match[2]) - 1,
|
|
2969
|
+
column: column - 1
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
function encodeCell(row, column) {
|
|
2973
|
+
let value = column + 1;
|
|
2974
|
+
let label = '';
|
|
2975
|
+
while(value > 0){
|
|
2976
|
+
value -= 1;
|
|
2977
|
+
label = String.fromCharCode(65 + value % 26) + label;
|
|
2978
|
+
value = Math.floor(value / 26);
|
|
2979
|
+
}
|
|
2980
|
+
return `${label}${row + 1}`;
|
|
2981
|
+
}
|
|
2982
|
+
function work_xlsx_protection_attributes(element) {
|
|
2983
|
+
return Object.fromEntries(Array.from(element.attributes).map((item)=>[
|
|
2984
|
+
item.name,
|
|
2985
|
+
item.value
|
|
2986
|
+
]));
|
|
2987
|
+
}
|
|
2988
|
+
function copyAttributes(element, source) {
|
|
2989
|
+
for (const [name, value] of Object.entries(source ?? {}))if (/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(name)) element.setAttribute(name, value);
|
|
2990
|
+
}
|
|
2991
|
+
function hasCredentialAttributes(source) {
|
|
2992
|
+
return Boolean(source.password || source.hashValue || source.saltValue || source.securityDescriptor);
|
|
2993
|
+
}
|
|
2994
|
+
function work_xlsx_protection_booleanAttribute(element, name, fallback) {
|
|
2995
|
+
const value = attribute(element, name)?.toLowerCase();
|
|
2996
|
+
if ('1' === value || 'true' === value || 'on' === value) return true;
|
|
2997
|
+
if ('0' === value || 'false' === value || 'off' === value) return false;
|
|
2998
|
+
return fallback;
|
|
2999
|
+
}
|
|
3000
|
+
function nonNegativeInteger(value) {
|
|
3001
|
+
if (null === value || !/^\d+$/.test(value)) return null;
|
|
3002
|
+
return Number(value);
|
|
3003
|
+
}
|
|
3004
|
+
function work_xlsx_protection_positiveInteger(value) {
|
|
3005
|
+
const parsed = nonNegativeInteger(value);
|
|
3006
|
+
return null !== parsed && parsed > 0 ? parsed : null;
|
|
3007
|
+
}
|
|
3008
|
+
function sameProtection(left, right) {
|
|
3009
|
+
return left.locked === right.locked && left.hidden === right.hidden;
|
|
3010
|
+
}
|
|
3011
|
+
const SPREADSHEET_NAMESPACE = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
|
3012
|
+
const work_xlsx_pivot_xml_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
3013
|
+
function createXlsxPivotXmlParts(content, pivot, cacheId) {
|
|
3014
|
+
const sourceSheet = content.sheets.find((sheet)=>sheet.id === pivot.sourceSheetId);
|
|
3015
|
+
const ranges = parseSpreadsheetCellRanges(pivot.sourceReference);
|
|
3016
|
+
const fields = spreadsheetPivotFields(content, pivot);
|
|
3017
|
+
if (!sourceSheet || ranges?.length !== 1 || !fields.length || !pivot.outputReference) return null;
|
|
3018
|
+
const sourceRange = ranges[0];
|
|
3019
|
+
const dimensionIndexes = new Set([
|
|
3020
|
+
...pivot.rowFields,
|
|
3021
|
+
...pivot.columnFields,
|
|
3022
|
+
...(pivot.reportFilters ?? []).map((filter)=>filter.fieldIndex)
|
|
3023
|
+
]);
|
|
3024
|
+
const cacheFields = fields.map((field)=>{
|
|
3025
|
+
const values = [];
|
|
3026
|
+
for(let row = sourceRange.row[0] + 1; row <= sourceRange.row[1]; row += 1)values.push(pivotSourceValue(sourceSheet.data?.[row]?.[sourceRange.column[0] + field.index]));
|
|
3027
|
+
return {
|
|
3028
|
+
name: field.name,
|
|
3029
|
+
values,
|
|
3030
|
+
sharedItems: dimensionIndexes.has(field.index) ? uniqueSortedPivotValues(values) : void 0
|
|
3031
|
+
};
|
|
3032
|
+
});
|
|
3033
|
+
return {
|
|
3034
|
+
table: pivotTableXml(pivot, cacheFields, cacheId),
|
|
3035
|
+
cacheDefinition: pivotCacheDefinitionXml(sourceSheet, pivot, cacheFields),
|
|
3036
|
+
cacheRecords: pivotCacheRecordsXml(cacheFields)
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
function xlsxPivotAggregation(aggregation) {
|
|
3040
|
+
if ('count' === aggregation) return 'countNums';
|
|
3041
|
+
if ('counta' === aggregation) return 'count';
|
|
3042
|
+
if ('stdDevP' === aggregation) return 'stdDevp';
|
|
3043
|
+
if ('varP' === aggregation) return 'varp';
|
|
3044
|
+
return aggregation;
|
|
3045
|
+
}
|
|
3046
|
+
function workPivotAggregation(value) {
|
|
3047
|
+
if (!value || 'sum' === value) return 'sum';
|
|
3048
|
+
if ('countNums' === value) return 'count';
|
|
3049
|
+
if ('count' === value) return 'counta';
|
|
3050
|
+
if ('average' === value) return 'average';
|
|
3051
|
+
if ('max' === value) return 'max';
|
|
3052
|
+
if ('min' === value) return 'min';
|
|
3053
|
+
if ('product' === value) return 'product';
|
|
3054
|
+
if ('stdDev' === value) return 'stdDev';
|
|
3055
|
+
if ('stdDevp' === value) return 'stdDevP';
|
|
3056
|
+
if ('var' === value) return 'var';
|
|
3057
|
+
if ('varp' === value) return 'varP';
|
|
3058
|
+
return null;
|
|
3059
|
+
}
|
|
3060
|
+
function pivotTableXml(pivot, fields, cacheId) {
|
|
3061
|
+
const dataFieldIndexes = new Set(pivot.values.map((value)=>value.fieldIndex));
|
|
3062
|
+
const reportFilterIndexes = new Set((pivot.reportFilters ?? []).map((filter)=>filter.fieldIndex));
|
|
3063
|
+
const pivotFields = fields.map((field, fieldIndex)=>{
|
|
3064
|
+
const row = pivot.rowFields.includes(fieldIndex);
|
|
3065
|
+
const column = pivot.columnFields.includes(fieldIndex);
|
|
3066
|
+
const page = reportFilterIndexes.has(fieldIndex);
|
|
3067
|
+
const attributes = [
|
|
3068
|
+
row ? 'axis="axisRow"' : '',
|
|
3069
|
+
column ? 'axis="axisCol"' : '',
|
|
3070
|
+
page ? 'axis="axisPage"' : '',
|
|
3071
|
+
dataFieldIndexes.has(fieldIndex) ? 'dataField="1"' : '',
|
|
3072
|
+
'compact="0"',
|
|
3073
|
+
'outline="0"',
|
|
3074
|
+
'showAll="0"',
|
|
3075
|
+
'defaultSubtotal="0"'
|
|
3076
|
+
].filter(Boolean).join(' ');
|
|
3077
|
+
if (!row && !column && !page) return `<pivotField ${attributes}/>`;
|
|
3078
|
+
const items = field.sharedItems ?? [];
|
|
3079
|
+
return [
|
|
3080
|
+
`<pivotField ${attributes}>`,
|
|
3081
|
+
`<items count="${items.length + 1}">`,
|
|
3082
|
+
...items.map((_item, index)=>`<item x="${index}"/>`),
|
|
3083
|
+
'<item t="default"/>',
|
|
3084
|
+
'</items>',
|
|
3085
|
+
'</pivotField>'
|
|
3086
|
+
].join('');
|
|
3087
|
+
}).join('');
|
|
3088
|
+
const rowFields = pivot.rowFields.map((fieldIndex)=>`<field x="${fieldIndex}"/>`).join('');
|
|
3089
|
+
const columnFieldIndexes = [
|
|
3090
|
+
...pivot.columnFields
|
|
3091
|
+
];
|
|
3092
|
+
if (pivot.values.length > 1) columnFieldIndexes.push(-2);
|
|
3093
|
+
const columnFields = columnFieldIndexes.map((fieldIndex)=>`<field x="${fieldIndex}"/>`).join('');
|
|
3094
|
+
const pageFields = (pivot.reportFilters ?? []).map((filter)=>{
|
|
3095
|
+
const items = fields[filter.fieldIndex]?.sharedItems ?? [];
|
|
3096
|
+
const selectedIndex = void 0 === filter.selectedItem ? items.length : items.findIndex((item)=>pivotSourceFilterKey(item) === spreadsheetPivotFilterValueKey(filter.selectedItem));
|
|
3097
|
+
return `<pageField fld="${filter.fieldIndex}" hier="-1" item="${selectedIndex >= 0 ? selectedIndex : items.length}"/>`;
|
|
3098
|
+
}).join('');
|
|
3099
|
+
const dataFields = pivot.values.map((value)=>{
|
|
3100
|
+
const caption = value.caption?.trim() || fields[value.fieldIndex]?.name || `Value ${value.fieldIndex + 1}`;
|
|
3101
|
+
return `<dataField name="${work_xlsx_pivot_xml_escapeXml(caption)}" fld="${value.fieldIndex}" subtotal="${xlsxPivotAggregation(value.aggregation)}" baseField="0" baseItem="0"/>`;
|
|
3102
|
+
}).join('');
|
|
3103
|
+
return [
|
|
3104
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
3105
|
+
`<pivotTableDefinition xmlns="${SPREADSHEET_NAMESPACE}" name="${work_xlsx_pivot_xml_escapeXml(pivot.name)}" cacheId="${cacheId}"`,
|
|
3106
|
+
' applyNumberFormats="0" applyBorderFormats="0" applyFontFormats="0" applyPatternFormats="0"',
|
|
3107
|
+
' applyAlignmentFormats="0" applyWidthHeightFormats="1" dataCaption="值" updatedVersion="8"',
|
|
3108
|
+
' minRefreshableVersion="3" createdVersion="8" useAutoFormatting="1" compact="0" compactData="0"',
|
|
3109
|
+
` rowGrandTotals="${pivot.rowGrandTotals ? 1 : 0}" colGrandTotals="${pivot.columnGrandTotals ? 1 : 0}">`,
|
|
3110
|
+
`<location ref="${work_xlsx_pivot_xml_escapeXml(pivotTableReference(pivot))}" firstHeaderRow="1" firstDataRow="1" firstDataCol="${Math.max(1, pivot.rowFields.length)}"/>`,
|
|
3111
|
+
`<pivotFields count="${fields.length}">${pivotFields}</pivotFields>`,
|
|
3112
|
+
`<rowFields count="${pivot.rowFields.length}">${rowFields}</rowFields>`,
|
|
3113
|
+
'<rowItems count="1"><i t="grand"><x/></i></rowItems>',
|
|
3114
|
+
columnFieldIndexes.length ? `<colFields count="${columnFieldIndexes.length}">${columnFields}</colFields>` : '',
|
|
3115
|
+
'<colItems count="1"><i t="grand"><x/></i></colItems>',
|
|
3116
|
+
pageFields ? `<pageFields count="${pivot.reportFilters.length}">${pageFields}</pageFields>` : '',
|
|
3117
|
+
`<dataFields count="${pivot.values.length}">${dataFields}</dataFields>`,
|
|
3118
|
+
`<pivotTableStyleInfo name="${work_xlsx_pivot_xml_escapeXml(pivot.styleName || 'PivotStyleLight16')}" showRowHeaders="1" showColHeaders="1" showRowStripes="0" showColStripes="0" showLastColumn="1"/>`,
|
|
3119
|
+
'</pivotTableDefinition>'
|
|
3120
|
+
].join('');
|
|
3121
|
+
}
|
|
3122
|
+
function pivotTableReference(pivot) {
|
|
3123
|
+
const ranges = parseSpreadsheetCellRanges(pivot.outputReference ?? '');
|
|
3124
|
+
const reportFilterRows = pivot.reportFilters?.length ? pivot.reportFilters.length + 1 : 0;
|
|
3125
|
+
if (ranges?.length !== 1 || !reportFilterRows) return pivot.outputReference ?? pivot.anchor;
|
|
3126
|
+
const range = ranges[0];
|
|
3127
|
+
const tableStartRow = range.row[0] + reportFilterRows;
|
|
3128
|
+
if (tableStartRow > range.row[1]) return pivot.outputReference ?? pivot.anchor;
|
|
3129
|
+
return formatSpreadsheetCellRanges([
|
|
3130
|
+
{
|
|
3131
|
+
row: [
|
|
3132
|
+
tableStartRow,
|
|
3133
|
+
range.row[1]
|
|
3134
|
+
],
|
|
3135
|
+
column: [
|
|
3136
|
+
range.column[0],
|
|
3137
|
+
range.column[1]
|
|
3138
|
+
]
|
|
3139
|
+
}
|
|
3140
|
+
]);
|
|
3141
|
+
}
|
|
3142
|
+
function pivotCacheDefinitionXml(sourceSheet, pivot, fields) {
|
|
3143
|
+
const recordCount = fields[0]?.values.length ?? 0;
|
|
3144
|
+
return [
|
|
3145
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
3146
|
+
`<pivotCacheDefinition xmlns="${SPREADSHEET_NAMESPACE}" xmlns:r="${work_xlsx_pivot_xml_RELATIONSHIP_NAMESPACE}" r:id="rId1"`,
|
|
3147
|
+
` saveData="1" refreshOnLoad="${pivot.refreshOnLoad ? 1 : 0}" enableRefresh="1"`,
|
|
3148
|
+
` createdVersion="8" refreshedVersion="8" minRefreshableVersion="3" recordCount="${recordCount}">`,
|
|
3149
|
+
'<cacheSource type="worksheet">',
|
|
3150
|
+
`<worksheetSource ref="${work_xlsx_pivot_xml_escapeXml(pivot.sourceReference)}" sheet="${work_xlsx_pivot_xml_escapeXml(sourceSheet.name)}"/>`,
|
|
3151
|
+
'</cacheSource>',
|
|
3152
|
+
`<cacheFields count="${fields.length}">`,
|
|
3153
|
+
...fields.map((field)=>pivotCacheFieldXml(field)),
|
|
3154
|
+
'</cacheFields>',
|
|
3155
|
+
'</pivotCacheDefinition>'
|
|
3156
|
+
].join('');
|
|
3157
|
+
}
|
|
3158
|
+
function pivotCacheFieldXml(field) {
|
|
3159
|
+
const values = field.sharedItems ?? field.values;
|
|
3160
|
+
const statistics = pivotValueStatistics(values);
|
|
3161
|
+
const attributes = [
|
|
3162
|
+
`containsBlank="${statistics.blank ? 1 : 0}"`,
|
|
3163
|
+
`containsString="${statistics.string ? 1 : 0}"`,
|
|
3164
|
+
`containsNumber="${statistics.number ? 1 : 0}"`,
|
|
3165
|
+
`containsInteger="${statistics.integer ? 1 : 0}"`,
|
|
3166
|
+
`containsDate="${statistics.date ? 1 : 0}"`,
|
|
3167
|
+
`containsMixedTypes="${statistics.typeCount > 1 ? 1 : 0}"`,
|
|
3168
|
+
`containsSemiMixedTypes="${statistics.string && statistics.typeCount > 1 ? 1 : 0}"`
|
|
3169
|
+
];
|
|
3170
|
+
if (!field.sharedItems) return `<cacheField name="${work_xlsx_pivot_xml_escapeXml(field.name)}" numFmtId="0"><sharedItems ${attributes.join(' ')}/></cacheField>`;
|
|
3171
|
+
return [
|
|
3172
|
+
`<cacheField name="${work_xlsx_pivot_xml_escapeXml(field.name)}" numFmtId="0">`,
|
|
3173
|
+
`<sharedItems count="${field.sharedItems.length}" ${attributes.join(' ')}>`,
|
|
3174
|
+
...field.sharedItems.map((value)=>pivotCacheValueXml(value)),
|
|
3175
|
+
'</sharedItems>',
|
|
3176
|
+
'</cacheField>'
|
|
3177
|
+
].join('');
|
|
3178
|
+
}
|
|
3179
|
+
function pivotCacheRecordsXml(fields) {
|
|
3180
|
+
const recordCount = fields[0]?.values.length ?? 0;
|
|
3181
|
+
const sharedIndexes = fields.map((field)=>field.sharedItems ? new Map(field.sharedItems.map((value, index)=>[
|
|
3182
|
+
pivotValueKey(value),
|
|
3183
|
+
index
|
|
3184
|
+
])) : void 0);
|
|
3185
|
+
const records = [];
|
|
3186
|
+
for(let row = 0; row < recordCount; row += 1){
|
|
3187
|
+
records.push('<r>');
|
|
3188
|
+
for(let column = 0; column < fields.length; column += 1){
|
|
3189
|
+
const value = fields[column].values[row];
|
|
3190
|
+
const sharedIndex = sharedIndexes[column]?.get(pivotValueKey(value));
|
|
3191
|
+
records.push(void 0 === sharedIndex ? pivotCacheValueXml(value) : `<x v="${sharedIndex}"/>`);
|
|
3192
|
+
}
|
|
3193
|
+
records.push('</r>');
|
|
3194
|
+
}
|
|
3195
|
+
return [
|
|
3196
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
3197
|
+
`<pivotCacheRecords xmlns="${SPREADSHEET_NAMESPACE}" count="${recordCount}">`,
|
|
3198
|
+
...records,
|
|
3199
|
+
'</pivotCacheRecords>'
|
|
3200
|
+
].join('');
|
|
3201
|
+
}
|
|
3202
|
+
function pivotSourceValue(cell) {
|
|
3203
|
+
const value = cell?.v ?? cell?.m ?? null;
|
|
3204
|
+
if (null == value || '' === value) return {
|
|
3205
|
+
value: null,
|
|
3206
|
+
type: 'blank'
|
|
3207
|
+
};
|
|
3208
|
+
if (cell?.ct?.t === 'e') return {
|
|
3209
|
+
value: String(cell.m ?? cell.v ?? '#VALUE!'),
|
|
3210
|
+
type: 'error'
|
|
3211
|
+
};
|
|
3212
|
+
if (value instanceof Date) return {
|
|
3213
|
+
value,
|
|
3214
|
+
type: 'date'
|
|
3215
|
+
};
|
|
3216
|
+
if ('number' == typeof value && Number.isFinite(value)) return {
|
|
3217
|
+
value,
|
|
3218
|
+
type: 'number'
|
|
3219
|
+
};
|
|
3220
|
+
if ('boolean' == typeof value) return {
|
|
3221
|
+
value,
|
|
3222
|
+
type: 'boolean'
|
|
3223
|
+
};
|
|
3224
|
+
return {
|
|
3225
|
+
value: String(value),
|
|
3226
|
+
type: 'string'
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
function uniqueSortedPivotValues(values) {
|
|
3230
|
+
const unique = new Map(values.map((value)=>[
|
|
3231
|
+
pivotValueKey(value),
|
|
3232
|
+
value
|
|
3233
|
+
]));
|
|
3234
|
+
return Array.from(unique.values()).sort((left, right)=>pivotValueDisplay(left).localeCompare(pivotValueDisplay(right), 'zh-CN', {
|
|
3235
|
+
numeric: true,
|
|
3236
|
+
sensitivity: 'base'
|
|
3237
|
+
}));
|
|
3238
|
+
}
|
|
3239
|
+
function pivotCacheValueXml(value) {
|
|
3240
|
+
if ('blank' === value.type) return '<m/>';
|
|
3241
|
+
if ('number' === value.type) return `<n v="${Number(value.value)}"/>`;
|
|
3242
|
+
if ('boolean' === value.type) return `<b v="${value.value ? 1 : 0}"/>`;
|
|
3243
|
+
if ('date' === value.type) {
|
|
3244
|
+
const date = value.value instanceof Date ? value.value.toISOString() : String(value.value);
|
|
3245
|
+
return `<d v="${work_xlsx_pivot_xml_escapeXml(date)}"/>`;
|
|
3246
|
+
}
|
|
3247
|
+
if ('error' === value.type) return `<e v="${work_xlsx_pivot_xml_escapeXml(String(value.value))}"/>`;
|
|
3248
|
+
return `<s v="${work_xlsx_pivot_xml_escapeXml(String(value.value))}"/>`;
|
|
3249
|
+
}
|
|
3250
|
+
function pivotValueKey(value) {
|
|
3251
|
+
const raw = value.value instanceof Date ? value.value.toISOString() : value.value;
|
|
3252
|
+
return `${value.type}:${String(raw ?? '')}`;
|
|
3253
|
+
}
|
|
3254
|
+
function pivotSourceFilterKey(value) {
|
|
3255
|
+
if ('blank' === value.type) return 'blank:';
|
|
3256
|
+
if ('date' === value.type) return `string:${value.value instanceof Date ? value.value.toISOString() : String(value.value)}`;
|
|
3257
|
+
if ('boolean' === value.type) return `boolean:${String(Boolean(value.value))}`;
|
|
3258
|
+
if ('number' === value.type) return `number:${String(Number(value.value))}`;
|
|
3259
|
+
return `string:${String(value.value ?? '')}`;
|
|
3260
|
+
}
|
|
3261
|
+
function pivotValueDisplay(value) {
|
|
3262
|
+
if (value.value instanceof Date) return value.value.toISOString();
|
|
3263
|
+
return String(value.value ?? '');
|
|
3264
|
+
}
|
|
3265
|
+
function pivotValueStatistics(values) {
|
|
3266
|
+
const types = new Set(values.filter((value)=>'blank' !== value.type).map((value)=>value.type));
|
|
3267
|
+
return {
|
|
3268
|
+
blank: values.some((value)=>'blank' === value.type),
|
|
3269
|
+
string: types.has('string') || types.has('error'),
|
|
3270
|
+
number: types.has('number'),
|
|
3271
|
+
integer: types.has('number') && values.filter((value)=>'number' === value.type).every((value)=>Number.isInteger(Number(value.value))),
|
|
3272
|
+
date: types.has('date'),
|
|
3273
|
+
typeCount: types.size
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
function work_xlsx_pivot_xml_escapeXml(value) {
|
|
3277
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
3278
|
+
}
|
|
3279
|
+
const work_xlsx_pivots_PACKAGE_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/package/2006/relationships';
|
|
3280
|
+
const DOCUMENT_RELATIONSHIP_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
3281
|
+
const XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/';
|
|
3282
|
+
const PIVOT_TABLE_RELATIONSHIP = `${DOCUMENT_RELATIONSHIP_NAMESPACE}/pivotTable`;
|
|
3283
|
+
const PIVOT_CACHE_DEFINITION_RELATIONSHIP = `${DOCUMENT_RELATIONSHIP_NAMESPACE}/pivotCacheDefinition`;
|
|
3284
|
+
const PIVOT_CACHE_RECORDS_RELATIONSHIP = `${DOCUMENT_RELATIONSHIP_NAMESPACE}/pivotCacheRecords`;
|
|
3285
|
+
const PIVOT_TABLE_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml';
|
|
3286
|
+
const PIVOT_CACHE_DEFINITION_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml';
|
|
3287
|
+
const PIVOT_CACHE_RECORDS_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml';
|
|
3288
|
+
async function patchXlsxPivotTables(buffer, content) {
|
|
3289
|
+
if (!content.sheets.some((sheet)=>sheet.pivotTables?.length)) return buffer;
|
|
3290
|
+
const archive = await work_ooxml_package_OoxmlPackage.load(buffer);
|
|
3291
|
+
const worksheetParts = await workbookWorksheetParts(archive);
|
|
3292
|
+
const candidates = content.sheets.flatMap((ownerSheet)=>(ownerSheet.pivotTables ?? []).flatMap((pivot)=>{
|
|
3293
|
+
if (!ownerSheet.id || !spreadsheetPivotValidation(content, ownerSheet.id, pivot).valid) return [];
|
|
3294
|
+
const worksheetPart = worksheetParts.get(ownerSheet.name.slice(0, 31) || '工作表');
|
|
3295
|
+
return worksheetPart ? [
|
|
3296
|
+
{
|
|
3297
|
+
ownerSheet,
|
|
3298
|
+
pivot,
|
|
3299
|
+
worksheetPart
|
|
3300
|
+
}
|
|
3301
|
+
] : [];
|
|
3302
|
+
}));
|
|
3303
|
+
if (!candidates.length) return buffer;
|
|
3304
|
+
const zip = await jszip.loadAsync(buffer);
|
|
3305
|
+
const workbookEntry = zip.file('xl/workbook.xml');
|
|
3306
|
+
const workbookRelationshipsEntry = zip.file('xl/_rels/workbook.xml.rels');
|
|
3307
|
+
if (!workbookEntry || !workbookRelationshipsEntry) return buffer;
|
|
3308
|
+
const workbook = parseXml(await workbookEntry.async('text'), 'xl/workbook.xml');
|
|
3309
|
+
const workbookRelationships = parseXml(await workbookRelationshipsEntry.async('text'), 'xl/_rels/workbook.xml.rels');
|
|
3310
|
+
removeRelationships(workbookRelationships, '/pivotCacheDefinition');
|
|
3311
|
+
for (const existing of directChildren(workbook.documentElement, 'pivotCaches'))existing.remove();
|
|
3312
|
+
const pivotCaches = workbook.createElementNS(workbook.documentElement.namespaceURI, 'pivotCaches');
|
|
3313
|
+
ensureRelationshipNamespace(workbook);
|
|
3314
|
+
const worksheetRelationships = new Map();
|
|
3315
|
+
const contentTypeParts = [];
|
|
3316
|
+
let exported = 0;
|
|
3317
|
+
for (const [index, candidate] of candidates.entries()){
|
|
3318
|
+
const partIndex = index + 1;
|
|
3319
|
+
const cacheId = partIndex;
|
|
3320
|
+
const parts = createXlsxPivotXmlParts(content, candidate.pivot, cacheId);
|
|
3321
|
+
if (!parts) continue;
|
|
3322
|
+
const pivotTablePath = `xl/pivotTables/pivotTable${partIndex}.xml`;
|
|
3323
|
+
const pivotCacheDefinitionPath = `xl/pivotCache/pivotCacheDefinition${partIndex}.xml`;
|
|
3324
|
+
const pivotCacheRecordsPath = `xl/pivotCache/pivotCacheRecords${partIndex}.xml`;
|
|
3325
|
+
const workbookRelationshipId = appendRelationship(workbookRelationships, PIVOT_CACHE_DEFINITION_RELATIONSHIP, `pivotCache/pivotCacheDefinition${partIndex}.xml`);
|
|
3326
|
+
const pivotCache = workbook.createElementNS(workbook.documentElement.namespaceURI, 'pivotCache');
|
|
3327
|
+
pivotCache.setAttribute('cacheId', String(cacheId));
|
|
3328
|
+
pivotCache.setAttributeNS(DOCUMENT_RELATIONSHIP_NAMESPACE, 'r:id', workbookRelationshipId);
|
|
3329
|
+
pivotCaches.append(pivotCache);
|
|
3330
|
+
const worksheetRelationshipDocument = worksheetRelationships.get(candidate.worksheetPart) ?? await readRelationshipsDocument(zip, work_xlsx_pivots_relationshipsPartPath(candidate.worksheetPart));
|
|
3331
|
+
if (!worksheetRelationships.has(candidate.worksheetPart)) {
|
|
3332
|
+
removeRelationships(worksheetRelationshipDocument, '/pivotTable');
|
|
3333
|
+
worksheetRelationships.set(candidate.worksheetPart, worksheetRelationshipDocument);
|
|
3334
|
+
}
|
|
3335
|
+
appendRelationship(worksheetRelationshipDocument, PIVOT_TABLE_RELATIONSHIP, `../pivotTables/pivotTable${partIndex}.xml`);
|
|
3336
|
+
zip.file(pivotTablePath, parts.table);
|
|
3337
|
+
zip.file(pivotCacheDefinitionPath, parts.cacheDefinition);
|
|
3338
|
+
zip.file(pivotCacheRecordsPath, parts.cacheRecords);
|
|
3339
|
+
zip.file(`xl/pivotTables/_rels/pivotTable${partIndex}.xml.rels`, relationshipsXml([
|
|
3340
|
+
{
|
|
3341
|
+
id: 'rId1',
|
|
3342
|
+
type: PIVOT_CACHE_DEFINITION_RELATIONSHIP,
|
|
3343
|
+
target: `../pivotCache/pivotCacheDefinition${partIndex}.xml`
|
|
3344
|
+
}
|
|
3345
|
+
]));
|
|
3346
|
+
zip.file(`xl/pivotCache/_rels/pivotCacheDefinition${partIndex}.xml.rels`, relationshipsXml([
|
|
3347
|
+
{
|
|
3348
|
+
id: 'rId1',
|
|
3349
|
+
type: PIVOT_CACHE_RECORDS_RELATIONSHIP,
|
|
3350
|
+
target: `pivotCacheRecords${partIndex}.xml`
|
|
3351
|
+
}
|
|
3352
|
+
]));
|
|
3353
|
+
contentTypeParts.push({
|
|
3354
|
+
path: pivotTablePath,
|
|
3355
|
+
contentType: PIVOT_TABLE_CONTENT_TYPE
|
|
3356
|
+
}, {
|
|
3357
|
+
path: pivotCacheDefinitionPath,
|
|
3358
|
+
contentType: PIVOT_CACHE_DEFINITION_CONTENT_TYPE
|
|
3359
|
+
}, {
|
|
3360
|
+
path: pivotCacheRecordsPath,
|
|
3361
|
+
contentType: PIVOT_CACHE_RECORDS_CONTENT_TYPE
|
|
3362
|
+
});
|
|
3363
|
+
exported += 1;
|
|
3364
|
+
}
|
|
3365
|
+
if (!exported) return buffer;
|
|
3366
|
+
insertWorkbookPivotCaches(workbook, pivotCaches);
|
|
3367
|
+
zip.file('xl/workbook.xml', new XMLSerializer().serializeToString(workbook));
|
|
3368
|
+
zip.file('xl/_rels/workbook.xml.rels', new XMLSerializer().serializeToString(workbookRelationships));
|
|
3369
|
+
for (const [worksheetPart, relationships] of worksheetRelationships)zip.file(work_xlsx_pivots_relationshipsPartPath(worksheetPart), new XMLSerializer().serializeToString(relationships));
|
|
3370
|
+
await work_xlsx_pivots_updateContentTypes(zip, contentTypeParts);
|
|
3371
|
+
return zip.generateAsync({
|
|
3372
|
+
type: 'arraybuffer',
|
|
3373
|
+
compression: 'DEFLATE'
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
async function inspectXlsxPivotTables(archive) {
|
|
3377
|
+
if (!archive.has('xl/workbook.xml')) return {
|
|
3378
|
+
tables: [],
|
|
3379
|
+
unsupported: [],
|
|
3380
|
+
detected: 0
|
|
3381
|
+
};
|
|
3382
|
+
const workbook = await archive.xml('xl/workbook.xml');
|
|
3383
|
+
const workbookRelationships = await archive.relationships('xl/workbook.xml');
|
|
3384
|
+
const cacheById = new Map();
|
|
3385
|
+
for (const cache of directChildren(firstDescendant(workbook, 'pivotCaches') ?? workbook.documentElement, 'pivotCache')){
|
|
3386
|
+
const cacheId = integerAttribute(cache, 'cacheId');
|
|
3387
|
+
const relationship = workbookRelationships.get(attribute(cache, 'r:id') ?? attribute(cache, 'id') ?? '');
|
|
3388
|
+
if (null !== cacheId && relationship && archive.has(relationship.target)) cacheById.set(cacheId, parsePivotCache(await archive.xml(relationship.target)));
|
|
3389
|
+
}
|
|
3390
|
+
const worksheetParts = await workbookWorksheetParts(archive);
|
|
3391
|
+
const tables = [];
|
|
3392
|
+
const unsupported = [];
|
|
3393
|
+
let detected = 0;
|
|
3394
|
+
for (const [destinationSheetName, worksheetPart] of worksheetParts){
|
|
3395
|
+
const relationships = await archive.relationships(worksheetPart);
|
|
3396
|
+
for (const relationship of relationships.values()){
|
|
3397
|
+
if (!relationship.type.endsWith('/pivotTable')) continue;
|
|
3398
|
+
detected += 1;
|
|
3399
|
+
if ('External' === relationship.targetMode || !archive.has(relationship.target)) {
|
|
3400
|
+
unsupported.push({
|
|
3401
|
+
code: 'xlsx.pivots.relationship',
|
|
3402
|
+
message: 'The pivot-table relationship is external or missing.',
|
|
3403
|
+
location: destinationSheetName
|
|
3404
|
+
});
|
|
3405
|
+
continue;
|
|
3406
|
+
}
|
|
3407
|
+
const parsed = parsePivotTable(await archive.xml(relationship.target), destinationSheetName, cacheById);
|
|
3408
|
+
if ('unsupported' in parsed) unsupported.push(parsed.unsupported);
|
|
3409
|
+
else tables.push(parsed.table);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
if (!detected && (archive.paths('xl/pivotTables/').length || archive.paths('xl/pivotCache/').length)) unsupported.push({
|
|
3413
|
+
code: 'xlsx.pivots.orphaned',
|
|
3414
|
+
message: 'Pivot-table package parts are present without a readable worksheet relationship.'
|
|
3415
|
+
});
|
|
3416
|
+
if (archive.paths('xl/slicers/').length || archive.paths('xl/slicerCaches/').length) unsupported.push({
|
|
3417
|
+
code: 'xlsx.pivots.slicers',
|
|
3418
|
+
message: 'Pivot slicers and timelines are not editable in Work.'
|
|
3419
|
+
});
|
|
3420
|
+
return {
|
|
3421
|
+
tables,
|
|
3422
|
+
unsupported,
|
|
3423
|
+
detected: Math.max(detected, tables.length + unsupported.length)
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
function applyImportedXlsxPivotTables(content, imported) {
|
|
3427
|
+
if (!imported?.tables.length) return content;
|
|
3428
|
+
const sheets = content.sheets.map((sheet)=>({
|
|
3429
|
+
...sheet,
|
|
3430
|
+
pivotTables: sheet.pivotTables ? [
|
|
3431
|
+
...sheet.pivotTables
|
|
3432
|
+
] : void 0
|
|
3433
|
+
}));
|
|
3434
|
+
const byName = new Map(sheets.map((sheet)=>[
|
|
3435
|
+
sheet.name,
|
|
3436
|
+
sheet
|
|
3437
|
+
]));
|
|
3438
|
+
for (const table of imported.tables){
|
|
3439
|
+
const ownerSheet = byName.get(table.destinationSheetName);
|
|
3440
|
+
const sourceSheet = byName.get(table.sourceSheetName);
|
|
3441
|
+
if (!ownerSheet || !sourceSheet?.id) continue;
|
|
3442
|
+
const pivot = {
|
|
3443
|
+
id: createWorkId('pivot'),
|
|
3444
|
+
name: table.name,
|
|
3445
|
+
sourceSheetId: sourceSheet.id,
|
|
3446
|
+
sourceReference: table.sourceReference,
|
|
3447
|
+
anchor: table.anchor,
|
|
3448
|
+
rowFields: [
|
|
3449
|
+
...table.rowFields
|
|
3450
|
+
],
|
|
3451
|
+
columnFields: [
|
|
3452
|
+
...table.columnFields
|
|
3453
|
+
],
|
|
3454
|
+
reportFilters: table.reportFilters.map((filter)=>({
|
|
3455
|
+
...filter
|
|
3456
|
+
})),
|
|
3457
|
+
values: table.values.map((value)=>({
|
|
3458
|
+
...value
|
|
3459
|
+
})),
|
|
3460
|
+
rowGrandTotals: table.rowGrandTotals,
|
|
3461
|
+
columnGrandTotals: table.columnGrandTotals,
|
|
3462
|
+
styleName: table.styleName,
|
|
3463
|
+
refreshOnLoad: table.refreshOnLoad,
|
|
3464
|
+
outputReference: table.outputReference
|
|
3465
|
+
};
|
|
3466
|
+
ownerSheet.pivotTables = [
|
|
3467
|
+
...ownerSheet.pivotTables ?? [],
|
|
3468
|
+
pivot
|
|
3469
|
+
];
|
|
3470
|
+
}
|
|
3471
|
+
return {
|
|
3472
|
+
...content,
|
|
3473
|
+
sheets
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
async function workbookWorksheetParts(archive) {
|
|
3477
|
+
if (!archive.has('xl/workbook.xml')) return new Map();
|
|
3478
|
+
const workbook = await archive.xml('xl/workbook.xml');
|
|
3479
|
+
const relationships = await archive.relationships('xl/workbook.xml');
|
|
3480
|
+
const parts = new Map();
|
|
3481
|
+
for (const sheet of directChildren(firstDescendant(workbook, 'sheets') ?? workbook.documentElement, 'sheet')){
|
|
3482
|
+
const name = attribute(sheet, 'name');
|
|
3483
|
+
const relationship = relationships.get(attribute(sheet, 'r:id') ?? attribute(sheet, 'id') ?? '');
|
|
3484
|
+
if (name && relationship?.type.endsWith('/worksheet')) parts.set(name, relationship.target);
|
|
3485
|
+
}
|
|
3486
|
+
return parts;
|
|
3487
|
+
}
|
|
3488
|
+
function parsePivotCache(document) {
|
|
3489
|
+
const root = document.documentElement;
|
|
3490
|
+
const cacheSource = directChild(root, 'cacheSource');
|
|
3491
|
+
const sourceType = attribute(cacheSource ?? root, 'type');
|
|
3492
|
+
const worksheetSource = directChild(cacheSource ?? root, 'worksheetSource');
|
|
3493
|
+
const sourceSheetName = attribute(worksheetSource ?? root, 'sheet')?.trim() || void 0;
|
|
3494
|
+
const sourceReference = attribute(worksheetSource ?? root, 'ref')?.trim() || void 0;
|
|
3495
|
+
const cacheFields = directChildren(directChild(root, 'cacheFields') ?? root, 'cacheField');
|
|
3496
|
+
const fields = cacheFields.map((field, index)=>attribute(field, 'name')?.trim() || `Field ${index + 1}`);
|
|
3497
|
+
const fieldItems = cacheFields.map((field)=>directChildren(directChild(field, 'sharedItems') ?? field).flatMap((item)=>{
|
|
3498
|
+
const value = pivotCacheItemValue(item);
|
|
3499
|
+
return value ? [
|
|
3500
|
+
value.value
|
|
3501
|
+
] : [];
|
|
3502
|
+
}));
|
|
3503
|
+
let unsupported;
|
|
3504
|
+
if ('worksheet' !== sourceType || !sourceSheetName || !sourceReference || attribute(worksheetSource ?? root, 'name')) unsupported = {
|
|
3505
|
+
code: 'external' === sourceType ? 'xlsx.pivots.external-source' : 'xlsx.pivots.source',
|
|
3506
|
+
message: 'Only worksheet pivots with an explicit cell-range source are editable.'
|
|
3507
|
+
};
|
|
3508
|
+
else if (descendants(root, 'fieldGroup').length || directChildren(directChild(root, 'cacheFields') ?? root, 'cacheField').some((field)=>attribute(field, 'formula'))) unsupported = {
|
|
3509
|
+
code: 'xlsx.pivots.grouping',
|
|
3510
|
+
message: 'Grouped or calculated pivot-cache fields remain cached values only.'
|
|
3511
|
+
};
|
|
3512
|
+
else if (descendants(root, 'calculatedItems').length || descendants(root, 'calculatedMembers').length) unsupported = {
|
|
3513
|
+
code: 'xlsx.pivots.calculated-items',
|
|
3514
|
+
message: 'Calculated pivot items and members remain cached values only.'
|
|
3515
|
+
};
|
|
3516
|
+
return {
|
|
3517
|
+
sourceSheetName,
|
|
3518
|
+
sourceReference,
|
|
3519
|
+
fields,
|
|
3520
|
+
fieldItems,
|
|
3521
|
+
refreshOnLoad: work_xlsx_pivots_booleanAttribute(root, 'refreshOnLoad'),
|
|
3522
|
+
unsupported
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
function pivotCacheItemValue(element) {
|
|
3526
|
+
const source = attribute(element, 'v');
|
|
3527
|
+
if ('m' === element.localName) return {
|
|
3528
|
+
value: null
|
|
3529
|
+
};
|
|
3530
|
+
if ('b' === element.localName) return {
|
|
3531
|
+
value: '1' === source || source?.toLowerCase() === 'true'
|
|
3532
|
+
};
|
|
3533
|
+
if ('n' === element.localName) {
|
|
3534
|
+
const value = Number(source);
|
|
3535
|
+
return Number.isFinite(value) ? {
|
|
3536
|
+
value
|
|
3537
|
+
} : null;
|
|
3538
|
+
}
|
|
3539
|
+
if ('s' === element.localName || 'e' === element.localName || 'd' === element.localName) return {
|
|
3540
|
+
value: source ?? ''
|
|
3541
|
+
};
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
function parsePivotReportFilters(root, cache, location) {
|
|
3545
|
+
const pageFields = directChild(root, 'pageFields');
|
|
3546
|
+
if (!pageFields) return {
|
|
3547
|
+
reportFilters: []
|
|
3548
|
+
};
|
|
3549
|
+
const pageWrap = integerAttribute(root, 'pageWrap') ?? 0;
|
|
3550
|
+
if (pageWrap > 0 || work_xlsx_pivots_booleanAttribute(root, 'pageOverThenDown')) return {
|
|
3551
|
+
unsupported: {
|
|
3552
|
+
code: 'xlsx.pivots.report-filter-layout',
|
|
3553
|
+
message: 'Wrapped or horizontal report-filter layouts remain cached values only.',
|
|
3554
|
+
location
|
|
3555
|
+
}
|
|
3556
|
+
};
|
|
3557
|
+
const pivotFields = directChildren(directChild(root, 'pivotFields') ?? root, 'pivotField');
|
|
3558
|
+
const reportFilters = [];
|
|
3559
|
+
for (const pageField of directChildren(pageFields, 'pageField')){
|
|
3560
|
+
const fieldIndex = integerAttribute(pageField, 'fld');
|
|
3561
|
+
const pivotField = null === fieldIndex ? void 0 : pivotFields[fieldIndex];
|
|
3562
|
+
if (null === fieldIndex || !pivotField || !cache.fields[fieldIndex]) return {
|
|
3563
|
+
unsupported: {
|
|
3564
|
+
code: 'xlsx.pivots.report-filter-field',
|
|
3565
|
+
message: 'A report filter refers to a missing pivot-cache field.',
|
|
3566
|
+
location
|
|
3567
|
+
}
|
|
3568
|
+
};
|
|
3569
|
+
const items = directChildren(directChild(pivotField, 'items') ?? pivotField, 'item');
|
|
3570
|
+
if (work_xlsx_pivots_booleanAttribute(pivotField, 'multipleItemSelectionAllowed') || items.some((item)=>work_xlsx_pivots_booleanAttribute(item, 'h'))) return {
|
|
3571
|
+
unsupported: {
|
|
3572
|
+
code: 'xlsx.pivots.report-filter-multi-select',
|
|
3573
|
+
message: 'Multi-selection report filters remain cached values only.',
|
|
3574
|
+
location
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
const itemIndex = integerAttribute(pageField, 'item');
|
|
3578
|
+
if (null === itemIndex) {
|
|
3579
|
+
reportFilters.push({
|
|
3580
|
+
fieldIndex
|
|
3581
|
+
});
|
|
3582
|
+
continue;
|
|
3583
|
+
}
|
|
3584
|
+
const pivotItem = items[itemIndex];
|
|
3585
|
+
if (!pivotItem) return {
|
|
3586
|
+
unsupported: {
|
|
3587
|
+
code: 'xlsx.pivots.report-filter-item',
|
|
3588
|
+
message: 'A report filter refers to a missing pivot item.',
|
|
3589
|
+
location
|
|
3590
|
+
}
|
|
3591
|
+
};
|
|
3592
|
+
if ('default' === attribute(pivotItem, 't')) {
|
|
3593
|
+
reportFilters.push({
|
|
3594
|
+
fieldIndex
|
|
3595
|
+
});
|
|
3596
|
+
continue;
|
|
3597
|
+
}
|
|
3598
|
+
const sharedItemIndex = integerAttribute(pivotItem, 'x');
|
|
3599
|
+
const sharedItems = cache.fieldItems[fieldIndex] ?? [];
|
|
3600
|
+
if (null === sharedItemIndex || sharedItemIndex < 0 || sharedItemIndex >= sharedItems.length) return {
|
|
3601
|
+
unsupported: {
|
|
3602
|
+
code: 'xlsx.pivots.report-filter-item',
|
|
3603
|
+
message: 'A report filter item is missing from the pivot cache.',
|
|
3604
|
+
location
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
reportFilters.push({
|
|
3608
|
+
fieldIndex,
|
|
3609
|
+
selectedItem: sharedItems[sharedItemIndex]
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
return {
|
|
3613
|
+
reportFilters
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
function parsePivotTable(document, destinationSheetName, cacheById) {
|
|
3617
|
+
const root = document.documentElement;
|
|
3618
|
+
const name = attribute(root, 'name')?.trim() || 'PivotTable';
|
|
3619
|
+
const locationLabel = destinationSheetName ? `${destinationSheetName} · ${name}` : name;
|
|
3620
|
+
const cacheId = integerAttribute(root, 'cacheId');
|
|
3621
|
+
const cache = null === cacheId ? void 0 : cacheById.get(cacheId);
|
|
3622
|
+
if (!cache || cache.unsupported || !cache.sourceSheetName || !cache.sourceReference) return {
|
|
3623
|
+
unsupported: {
|
|
3624
|
+
...cache?.unsupported ?? {
|
|
3625
|
+
code: 'xlsx.pivots.cache',
|
|
3626
|
+
message: 'The pivot cache definition is missing or unreadable.'
|
|
3627
|
+
},
|
|
3628
|
+
location: locationLabel
|
|
3629
|
+
}
|
|
3630
|
+
};
|
|
3631
|
+
if (directChildren(root, 'filters').length || work_xlsx_pivots_booleanAttribute(root, 'dataOnRows')) return {
|
|
3632
|
+
unsupported: {
|
|
3633
|
+
code: 'xlsx.pivots.filters-layout',
|
|
3634
|
+
message: 'Label or value filters and values-on-rows layouts remain cached values only.',
|
|
3635
|
+
location: locationLabel
|
|
3636
|
+
}
|
|
3637
|
+
};
|
|
3638
|
+
const parsedReportFilters = parsePivotReportFilters(root, cache, locationLabel);
|
|
3639
|
+
if ('unsupported' in parsedReportFilters) return parsedReportFilters;
|
|
3640
|
+
const reportFilters = parsedReportFilters.reportFilters;
|
|
3641
|
+
const rowFields = readPivotFieldIndexes(directChild(root, 'rowFields'));
|
|
3642
|
+
const columnFields = readPivotFieldIndexes(directChild(root, 'colFields')).filter((index)=>-2 !== index);
|
|
3643
|
+
if (rowFields.includes(-2)) return {
|
|
3644
|
+
unsupported: {
|
|
3645
|
+
code: 'xlsx.pivots.values-on-rows',
|
|
3646
|
+
message: 'Values-on-rows pivot layouts remain cached values only.',
|
|
3647
|
+
location: locationLabel
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
3650
|
+
const values = [];
|
|
3651
|
+
for (const dataField of directChildren(directChild(root, 'dataFields') ?? root, 'dataField')){
|
|
3652
|
+
const fieldIndex = integerAttribute(dataField, 'fld');
|
|
3653
|
+
const aggregation = workPivotAggregation(attribute(dataField, 'subtotal'));
|
|
3654
|
+
if (null === fieldIndex || !aggregation || !cache.fields[fieldIndex]) return {
|
|
3655
|
+
unsupported: {
|
|
3656
|
+
code: 'xlsx.pivots.aggregation',
|
|
3657
|
+
message: 'The pivot uses an unsupported value calculation or field reference.',
|
|
3658
|
+
location: locationLabel
|
|
3659
|
+
}
|
|
3660
|
+
};
|
|
3661
|
+
values.push({
|
|
3662
|
+
fieldIndex,
|
|
3663
|
+
aggregation,
|
|
3664
|
+
caption: attribute(dataField, 'name')?.trim() || void 0
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
const location = directChild(root, 'location');
|
|
3668
|
+
const outputReference = attribute(location ?? root, 'ref')?.trim() ?? '';
|
|
3669
|
+
const outputRange = parseSpreadsheetCellRanges(outputReference);
|
|
3670
|
+
const reportFilterFields = reportFilters.map((filter)=>filter.fieldIndex);
|
|
3671
|
+
if (!rowFields.length || !values.length || outputRange?.length !== 1 || [
|
|
3672
|
+
...rowFields,
|
|
3673
|
+
...columnFields,
|
|
3674
|
+
...reportFilterFields
|
|
3675
|
+
].some((index)=>index < 0 || !cache.fields[index]) || new Set([
|
|
3676
|
+
...rowFields,
|
|
3677
|
+
...columnFields,
|
|
3678
|
+
...reportFilterFields
|
|
3679
|
+
]).size !== rowFields.length + columnFields.length + reportFilterFields.length) return {
|
|
3680
|
+
unsupported: {
|
|
3681
|
+
code: 'xlsx.pivots.layout',
|
|
3682
|
+
message: 'The pivot layout cannot be represented by the editable Work pivot model.',
|
|
3683
|
+
location: locationLabel
|
|
3684
|
+
}
|
|
3685
|
+
};
|
|
3686
|
+
const reportFilterRows = reportFilters.length ? reportFilters.length + 1 : 0;
|
|
3687
|
+
if (outputRange[0].row[0] < reportFilterRows) return {
|
|
3688
|
+
unsupported: {
|
|
3689
|
+
code: 'xlsx.pivots.report-filter-layout',
|
|
3690
|
+
message: 'The report-filter placement cannot be represented by the Work report layout.',
|
|
3691
|
+
location: locationLabel
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
const ownedRange = {
|
|
3695
|
+
row: [
|
|
3696
|
+
outputRange[0].row[0] - reportFilterRows,
|
|
3697
|
+
outputRange[0].row[1]
|
|
3698
|
+
],
|
|
3699
|
+
column: [
|
|
3700
|
+
outputRange[0].column[0],
|
|
3701
|
+
outputRange[0].column[1]
|
|
3702
|
+
]
|
|
3703
|
+
};
|
|
3704
|
+
const anchor = formatSpreadsheetCellRanges([
|
|
3705
|
+
{
|
|
3706
|
+
row: [
|
|
3707
|
+
ownedRange.row[0],
|
|
3708
|
+
ownedRange.row[0]
|
|
3709
|
+
],
|
|
3710
|
+
column: [
|
|
3711
|
+
ownedRange.column[0],
|
|
3712
|
+
ownedRange.column[0]
|
|
3713
|
+
]
|
|
3714
|
+
}
|
|
3715
|
+
]);
|
|
3716
|
+
return {
|
|
3717
|
+
table: {
|
|
3718
|
+
destinationSheetName,
|
|
3719
|
+
sourceSheetName: cache.sourceSheetName,
|
|
3720
|
+
sourceReference: cache.sourceReference,
|
|
3721
|
+
name,
|
|
3722
|
+
anchor,
|
|
3723
|
+
outputReference: formatSpreadsheetCellRanges([
|
|
3724
|
+
ownedRange
|
|
3725
|
+
]),
|
|
3726
|
+
rowFields,
|
|
3727
|
+
columnFields,
|
|
3728
|
+
reportFilters,
|
|
3729
|
+
values,
|
|
3730
|
+
rowGrandTotals: work_xlsx_pivots_booleanAttribute(root, 'rowGrandTotals', true),
|
|
3731
|
+
columnGrandTotals: work_xlsx_pivots_booleanAttribute(root, 'colGrandTotals', true),
|
|
3732
|
+
styleName: attribute(directChild(root, 'pivotTableStyleInfo') ?? root, 'name') || 'PivotStyleLight16',
|
|
3733
|
+
refreshOnLoad: cache.refreshOnLoad
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
function readPivotFieldIndexes(parent) {
|
|
3738
|
+
if (!parent) return [];
|
|
3739
|
+
return directChildren(parent, 'field').flatMap((field)=>{
|
|
3740
|
+
const index = integerAttribute(field, 'x');
|
|
3741
|
+
return null === index ? [] : [
|
|
3742
|
+
index
|
|
3743
|
+
];
|
|
3744
|
+
});
|
|
3745
|
+
}
|
|
3746
|
+
async function readRelationshipsDocument(zip, path) {
|
|
3747
|
+
const entry = zip.file(path);
|
|
3748
|
+
return entry ? parseXml(await entry.async('text'), path) : parseXml(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${work_xlsx_pivots_PACKAGE_RELATIONSHIP_NAMESPACE}"/>`, path);
|
|
3749
|
+
}
|
|
3750
|
+
function appendRelationship(document, type, target) {
|
|
3751
|
+
const id = work_xlsx_pivots_nextRelationshipId(document);
|
|
3752
|
+
const relationship = document.createElementNS(work_xlsx_pivots_PACKAGE_RELATIONSHIP_NAMESPACE, 'Relationship');
|
|
3753
|
+
relationship.setAttribute('Id', id);
|
|
3754
|
+
relationship.setAttribute('Type', type);
|
|
3755
|
+
relationship.setAttribute('Target', target);
|
|
3756
|
+
document.documentElement.append(relationship);
|
|
3757
|
+
return id;
|
|
3758
|
+
}
|
|
3759
|
+
function removeRelationships(document, typeSuffix) {
|
|
3760
|
+
for (const relationship of directChildren(document.documentElement, 'Relationship'))if (attribute(relationship, 'Type')?.endsWith(typeSuffix)) relationship.remove();
|
|
3761
|
+
}
|
|
3762
|
+
function work_xlsx_pivots_nextRelationshipId(document) {
|
|
3763
|
+
const ids = new Set(directChildren(document.documentElement, 'Relationship').map((relationship)=>attribute(relationship, 'Id')).filter((id)=>Boolean(id)));
|
|
3764
|
+
let index = 1;
|
|
3765
|
+
while(ids.has(`rId${index}`))index += 1;
|
|
3766
|
+
return `rId${index}`;
|
|
3767
|
+
}
|
|
3768
|
+
function relationshipsXml(relationships) {
|
|
3769
|
+
return [
|
|
3770
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
|
|
3771
|
+
`<Relationships xmlns="${work_xlsx_pivots_PACKAGE_RELATIONSHIP_NAMESPACE}">`,
|
|
3772
|
+
...relationships.map((relationship)=>`<Relationship Id="${relationship.id}" Type="${relationship.type}" Target="${relationship.target}"/>`),
|
|
3773
|
+
'</Relationships>'
|
|
3774
|
+
].join('');
|
|
3775
|
+
}
|
|
3776
|
+
function insertWorkbookPivotCaches(document, pivotCaches) {
|
|
3777
|
+
const laterElements = new Set([
|
|
3778
|
+
'smartTagPr',
|
|
3779
|
+
'smartTagTypes',
|
|
3780
|
+
'webPublishing',
|
|
3781
|
+
'fileRecoveryPr',
|
|
3782
|
+
'webPublishObjects',
|
|
3783
|
+
'extLst'
|
|
3784
|
+
]);
|
|
3785
|
+
const insertionPoint = directChildren(document.documentElement).find((element)=>laterElements.has(element.localName));
|
|
3786
|
+
document.documentElement.insertBefore(pivotCaches, insertionPoint ?? null);
|
|
3787
|
+
}
|
|
3788
|
+
function ensureRelationshipNamespace(document) {
|
|
3789
|
+
if (xmlNamespacePrefix(document.documentElement, DOCUMENT_RELATIONSHIP_NAMESPACE)) return;
|
|
3790
|
+
document.documentElement.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:r', DOCUMENT_RELATIONSHIP_NAMESPACE);
|
|
3791
|
+
}
|
|
3792
|
+
async function work_xlsx_pivots_updateContentTypes(zip, parts) {
|
|
3793
|
+
const entry = zip.file('[Content_Types].xml');
|
|
3794
|
+
if (!entry) return;
|
|
3795
|
+
const document = parseXml(await entry.async('text'), '[Content_Types].xml');
|
|
3796
|
+
const namespace = document.documentElement.namespaceURI;
|
|
3797
|
+
for (const override of directChildren(document.documentElement, 'Override')){
|
|
3798
|
+
const partName = attribute(override, 'PartName') ?? '';
|
|
3799
|
+
if (partName.startsWith('/xl/pivotTables/') || partName.startsWith('/xl/pivotCache/')) override.remove();
|
|
3800
|
+
}
|
|
3801
|
+
for (const part of parts){
|
|
3802
|
+
const override = document.createElementNS(namespace, 'Override');
|
|
3803
|
+
override.setAttribute('PartName', `/${part.path}`);
|
|
3804
|
+
override.setAttribute('ContentType', part.contentType);
|
|
3805
|
+
document.documentElement.append(override);
|
|
3806
|
+
}
|
|
3807
|
+
zip.file('[Content_Types].xml', new XMLSerializer().serializeToString(document));
|
|
3808
|
+
}
|
|
3809
|
+
function work_xlsx_pivots_relationshipsPartPath(sourcePart) {
|
|
3810
|
+
const segments = sourcePart.split('/');
|
|
3811
|
+
const name = segments.pop() ?? '';
|
|
3812
|
+
return [
|
|
3813
|
+
...segments,
|
|
3814
|
+
'_rels',
|
|
3815
|
+
`${name}.rels`
|
|
3816
|
+
].join('/');
|
|
3817
|
+
}
|
|
3818
|
+
function integerAttribute(element, name) {
|
|
3819
|
+
const source = attribute(element, name);
|
|
3820
|
+
if (null === source || !source.trim()) return null;
|
|
3821
|
+
const value = Number(source);
|
|
3822
|
+
return Number.isInteger(value) ? value : null;
|
|
3823
|
+
}
|
|
3824
|
+
function work_xlsx_pivots_booleanAttribute(element, name, fallback = false) {
|
|
3825
|
+
const value = attribute(element, name);
|
|
3826
|
+
if (null === value) return fallback;
|
|
3827
|
+
return '1' === value || 'true' === value.toLowerCase();
|
|
3828
|
+
}
|
|
3829
|
+
function emptySpreadsheetWorksheetCompatibilitySummary() {
|
|
3830
|
+
return {
|
|
3831
|
+
hasArrayFormulas: false,
|
|
3832
|
+
hasComments: false,
|
|
3833
|
+
hasCommentThreads: false,
|
|
3834
|
+
hasLinks: false,
|
|
3835
|
+
hasRichText: false
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
function updateSpreadsheetWorksheetCompatibilitySummary(summary, cell) {
|
|
3839
|
+
const source = cell;
|
|
3840
|
+
if (source.c?.length) {
|
|
3841
|
+
summary.hasComments = true;
|
|
3842
|
+
if (source.c.length > 1 || source.c.some((comment)=>comment.T)) summary.hasCommentThreads = true;
|
|
3843
|
+
}
|
|
3844
|
+
if (source.l?.Target) summary.hasLinks = true;
|
|
3845
|
+
if (source.F) summary.hasArrayFormulas = true;
|
|
3846
|
+
if (source.r) summary.hasRichText = true;
|
|
3847
|
+
}
|
|
3848
|
+
export { MAX_XLSX_WORKSHEET_IMAGE_BYTES, XlsxCellProtectionWriter, applyImportedXlsxPivotTables, createSpreadsheetFormulaMetadata, createXlsxErrorCell, createXlsxFormulaCell, diagnoseXlsxProtection, emptySpreadsheetWorksheetCompatibilitySummary, inspectXlsxHeaderFooterText, inspectXlsxPivotTables, isEditableXlsxPaperSizeCode, isSupportedXlsxChartLegend, isSupportedXlsxChartPlotLayout, isSupportedXlsxChartSeriesFormatting, isSupportedXlsxCombinationChartNodes, isSupportedXlsxWorksheetImageContentType, parseXlsxChartAxes, parseXlsxChartLegend, parseXlsxChartPlotLayout, parseXlsxChartSeriesStyle, parseXlsxErrorBars, parseXlsxTrendline, patchXlsxFormulaFeatures, patchXlsxPivotTables, patchXlsxWorksheetDrawings, readXlsxFormulaFeaturesFromPackage, readXlsxPageSetup, readXlsxProtection, readXlsxWorksheetCharts, readXlsxWorksheetImages, updateSpreadsheetWorksheetCompatibilitySummary, writeXlsxPageSetup, writeXlsxProtection, xlsxChartNodeUsesStackedGrouping, xlsxChartSeriesFormattingShapeProperties, xlsxChartSeriesMarkerXml, xlsxChartSeriesShapePropertiesXml, xlsxWorksheetChartsToSheet, xlsxWorksheetImagesToSheet };
|