@cj-tech-master/excelts 1.5.0 → 1.6.1-canary.20251220005548.c4c1e36
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 +0 -15
- package/README_zh.md +0 -15
- package/dist/browser/excelts.iife.js +1057 -222
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +63 -33
- package/dist/cjs/doc/column.js +7 -3
- package/dist/cjs/doc/pivot-table.js +149 -61
- package/dist/cjs/doc/workbook.js +3 -1
- package/dist/cjs/doc/worksheet.js +2 -23
- package/dist/cjs/stream/xlsx/worksheet-writer.js +1 -1
- package/dist/cjs/utils/unzip/zip-parser.js +2 -5
- package/dist/cjs/xlsx/xform/book/workbook-xform.js +3 -0
- package/dist/cjs/xlsx/xform/core/content-types-xform.js +19 -14
- package/dist/cjs/xlsx/xform/pivot-table/cache-field-xform.js +135 -0
- package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +7 -4
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
- package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
- package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -0
- package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +14 -3
- package/dist/cjs/xlsx/xlsx.js +261 -38
- package/dist/esm/doc/column.js +7 -3
- package/dist/esm/doc/pivot-table.js +150 -62
- package/dist/esm/doc/workbook.js +3 -1
- package/dist/esm/doc/worksheet.js +2 -23
- package/dist/esm/stream/xlsx/worksheet-writer.js +1 -1
- package/dist/esm/utils/unzip/zip-parser.js +2 -5
- package/dist/esm/xlsx/xform/book/workbook-xform.js +3 -0
- package/dist/esm/xlsx/xform/core/content-types-xform.js +19 -14
- package/dist/esm/xlsx/xform/pivot-table/cache-field-xform.js +132 -0
- package/dist/esm/xlsx/xform/pivot-table/cache-field.js +7 -4
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
- package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
- package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -0
- package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +14 -3
- package/dist/esm/xlsx/xlsx.js +261 -38
- package/dist/types/doc/column.d.ts +13 -6
- package/dist/types/doc/pivot-table.d.ts +135 -9
- package/dist/types/doc/workbook.d.ts +2 -0
- package/dist/types/doc/worksheet.d.ts +6 -27
- package/dist/types/index.d.ts +1 -0
- package/dist/types/xlsx/xform/pivot-table/cache-field-xform.d.ts +42 -0
- package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +45 -6
- package/dist/types/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +52 -5
- package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +98 -5
- package/dist/types/xlsx/xlsx.d.ts +27 -0
- package/package.json +17 -17
|
@@ -1,34 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PivotTableXform = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
4
5
|
const xml_stream_js_1 = require("../../../utils/xml-stream");
|
|
6
|
+
const utils_js_1 = require("../../../utils/utils");
|
|
5
7
|
const base_xform_js_1 = require("../base-xform");
|
|
6
8
|
class PivotTableXform extends base_xform_js_1.BaseXform {
|
|
7
9
|
constructor() {
|
|
8
10
|
super();
|
|
9
11
|
this.map = {};
|
|
12
|
+
this.model = null;
|
|
13
|
+
this.inPivotFields = false;
|
|
14
|
+
this.inRowFields = false;
|
|
15
|
+
this.inColFields = false;
|
|
16
|
+
this.inDataFields = false;
|
|
17
|
+
this.inRowItems = false;
|
|
18
|
+
this.inColItems = false;
|
|
19
|
+
this.inLocation = false;
|
|
20
|
+
this.currentPivotField = null;
|
|
21
|
+
this.inItems = false;
|
|
22
|
+
this.inPivotTableStyleInfo = false;
|
|
10
23
|
}
|
|
11
24
|
prepare(_model) {
|
|
12
|
-
//
|
|
25
|
+
// No preparation needed
|
|
13
26
|
}
|
|
14
27
|
get tag() {
|
|
15
28
|
// http://www.datypic.com/sc/ooxml/e-ssml_pivotTableDefinition.html
|
|
16
29
|
return "pivotTableDefinition";
|
|
17
30
|
}
|
|
31
|
+
reset() {
|
|
32
|
+
this.model = null;
|
|
33
|
+
this.inPivotFields = false;
|
|
34
|
+
this.inRowFields = false;
|
|
35
|
+
this.inColFields = false;
|
|
36
|
+
this.inDataFields = false;
|
|
37
|
+
this.inRowItems = false;
|
|
38
|
+
this.inColItems = false;
|
|
39
|
+
this.inLocation = false;
|
|
40
|
+
this.currentPivotField = null;
|
|
41
|
+
this.inItems = false;
|
|
42
|
+
this.inPivotTableStyleInfo = false;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render pivot table XML.
|
|
46
|
+
* Supports both newly created models and loaded models.
|
|
47
|
+
*/
|
|
18
48
|
render(xmlStream, model) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
49
|
+
const isLoaded = model.isLoaded;
|
|
50
|
+
if (isLoaded) {
|
|
51
|
+
this.renderLoaded(xmlStream, model);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.renderNew(xmlStream, model);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Render newly created pivot table
|
|
59
|
+
*/
|
|
60
|
+
renderNew(xmlStream, model) {
|
|
61
|
+
const { rows, columns, values, cacheFields, cacheId, applyWidthHeightFormats } = model;
|
|
62
|
+
// Generate unique UID for each pivot table to prevent Excel treating them as identical
|
|
63
|
+
const uniqueUid = `{${(0, uuid_1.v4)().toUpperCase()}}`;
|
|
28
64
|
xmlStream.openXml(xml_stream_js_1.XmlStream.StdDocAttributes);
|
|
29
65
|
xmlStream.openNode(this.tag, {
|
|
30
66
|
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
31
|
-
"xr:uid":
|
|
67
|
+
"xr:uid": uniqueUid,
|
|
32
68
|
name: "PivotTable2",
|
|
33
69
|
cacheId,
|
|
34
70
|
applyNumberFormats: "0",
|
|
@@ -36,7 +72,7 @@ class PivotTableXform extends base_xform_js_1.BaseXform {
|
|
|
36
72
|
applyFontFormats: "0",
|
|
37
73
|
applyPatternFormats: "0",
|
|
38
74
|
applyAlignmentFormats: "0",
|
|
39
|
-
applyWidthHeightFormats
|
|
75
|
+
applyWidthHeightFormats,
|
|
40
76
|
dataCaption: "Values",
|
|
41
77
|
updatedVersion: "8",
|
|
42
78
|
minRefreshableVersion: "3",
|
|
@@ -48,14 +84,6 @@ class PivotTableXform extends base_xform_js_1.BaseXform {
|
|
|
48
84
|
compactData: "0",
|
|
49
85
|
multipleFieldFilters: "0"
|
|
50
86
|
});
|
|
51
|
-
// Note: keeping this pretty-printed and verbose for now to ease debugging.
|
|
52
|
-
//
|
|
53
|
-
// location: ref="A3:E15"
|
|
54
|
-
// pivotFields
|
|
55
|
-
// rowFields and rowItems
|
|
56
|
-
// colFields and colItems
|
|
57
|
-
// dataFields
|
|
58
|
-
// pivotTableStyleInfo
|
|
59
87
|
xmlStream.writeXml(`
|
|
60
88
|
<location ref="A3:E15" firstHeaderRow="1" firstDataRow="2" firstDataCol="1" />
|
|
61
89
|
<pivotFields count="${cacheFields.length}">
|
|
@@ -67,19 +95,16 @@ class PivotTableXform extends base_xform_js_1.BaseXform {
|
|
|
67
95
|
<rowItems count="1">
|
|
68
96
|
<i t="grand"><x /></i>
|
|
69
97
|
</rowItems>
|
|
70
|
-
<colFields count="${columns.length}">
|
|
71
|
-
${columns.
|
|
98
|
+
<colFields count="${columns.length === 0 ? 1 : columns.length}">
|
|
99
|
+
${columns.length === 0
|
|
100
|
+
? '<field x="-2" />'
|
|
101
|
+
: columns.map(columnIndex => `<field x="${columnIndex}" />`).join("\n ")}
|
|
72
102
|
</colFields>
|
|
73
103
|
<colItems count="1">
|
|
74
104
|
<i t="grand"><x /></i>
|
|
75
105
|
</colItems>
|
|
76
106
|
<dataFields count="${values.length}">
|
|
77
|
-
|
|
78
|
-
name="Sum of ${cacheFields[values[0]].name}"
|
|
79
|
-
fld="${values[0]}"
|
|
80
|
-
baseField="0"
|
|
81
|
-
baseItem="0"
|
|
82
|
-
/>
|
|
107
|
+
${buildDataFields(cacheFields, values, model.metric)}
|
|
83
108
|
</dataFields>
|
|
84
109
|
<pivotTableStyleInfo
|
|
85
110
|
name="PivotStyleLight16"
|
|
@@ -112,19 +137,324 @@ class PivotTableXform extends base_xform_js_1.BaseXform {
|
|
|
112
137
|
`);
|
|
113
138
|
xmlStream.closeNode();
|
|
114
139
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Render loaded pivot table (preserving original structure)
|
|
142
|
+
*/
|
|
143
|
+
renderLoaded(xmlStream, model) {
|
|
144
|
+
const uniqueUid = model.uid || `{${(0, uuid_1.v4)().toUpperCase()}}`;
|
|
145
|
+
xmlStream.openXml(xml_stream_js_1.XmlStream.StdDocAttributes);
|
|
146
|
+
xmlStream.openNode(this.tag, {
|
|
147
|
+
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
148
|
+
"xr:uid": uniqueUid,
|
|
149
|
+
name: model.name || "PivotTable1",
|
|
150
|
+
cacheId: model.cacheId,
|
|
151
|
+
applyNumberFormats: model.applyNumberFormats || "0",
|
|
152
|
+
applyBorderFormats: model.applyBorderFormats || "0",
|
|
153
|
+
applyFontFormats: model.applyFontFormats || "0",
|
|
154
|
+
applyPatternFormats: model.applyPatternFormats || "0",
|
|
155
|
+
applyAlignmentFormats: model.applyAlignmentFormats || "0",
|
|
156
|
+
applyWidthHeightFormats: model.applyWidthHeightFormats || "0",
|
|
157
|
+
dataCaption: model.dataCaption || "Values",
|
|
158
|
+
updatedVersion: model.updatedVersion || "8",
|
|
159
|
+
minRefreshableVersion: model.minRefreshableVersion || "3",
|
|
160
|
+
useAutoFormatting: model.useAutoFormatting ? "1" : "0",
|
|
161
|
+
itemPrintTitles: model.itemPrintTitles ? "1" : "0",
|
|
162
|
+
createdVersion: model.createdVersion || "8",
|
|
163
|
+
indent: model.indent !== undefined ? String(model.indent) : "0",
|
|
164
|
+
compact: model.compact ? "1" : "0",
|
|
165
|
+
compactData: model.compactData ? "1" : "0",
|
|
166
|
+
multipleFieldFilters: model.multipleFieldFilters ? "1" : "0"
|
|
167
|
+
});
|
|
168
|
+
// Location
|
|
169
|
+
if (model.location) {
|
|
170
|
+
xmlStream.leafNode("location", {
|
|
171
|
+
ref: model.location.ref,
|
|
172
|
+
firstHeaderRow: model.location.firstHeaderRow,
|
|
173
|
+
firstDataRow: model.location.firstDataRow,
|
|
174
|
+
firstDataCol: model.location.firstDataCol
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Pivot fields
|
|
178
|
+
if (model.pivotFields.length > 0) {
|
|
179
|
+
xmlStream.openNode("pivotFields", { count: model.pivotFields.length });
|
|
180
|
+
for (const pivotField of model.pivotFields) {
|
|
181
|
+
this.renderPivotFieldLoaded(xmlStream, pivotField);
|
|
182
|
+
}
|
|
183
|
+
xmlStream.closeNode();
|
|
184
|
+
}
|
|
185
|
+
// Row fields
|
|
186
|
+
if (model.rowFields.length > 0) {
|
|
187
|
+
xmlStream.openNode("rowFields", { count: model.rowFields.length });
|
|
188
|
+
for (const fieldIndex of model.rowFields) {
|
|
189
|
+
xmlStream.leafNode("field", { x: fieldIndex });
|
|
190
|
+
}
|
|
191
|
+
xmlStream.closeNode();
|
|
192
|
+
}
|
|
193
|
+
// Row items (simplified - just grand total)
|
|
194
|
+
xmlStream.writeXml(`
|
|
195
|
+
<rowItems count="1">
|
|
196
|
+
<i t="grand"><x /></i>
|
|
197
|
+
</rowItems>`);
|
|
198
|
+
// Col fields
|
|
199
|
+
const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
|
|
200
|
+
xmlStream.openNode("colFields", { count: colFieldCount });
|
|
201
|
+
if (model.colFields.length === 0) {
|
|
202
|
+
xmlStream.leafNode("field", { x: -2 });
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
for (const fieldIndex of model.colFields) {
|
|
206
|
+
xmlStream.leafNode("field", { x: fieldIndex });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
xmlStream.closeNode();
|
|
210
|
+
// Col items (simplified - just grand total)
|
|
211
|
+
xmlStream.writeXml(`
|
|
212
|
+
<colItems count="1">
|
|
213
|
+
<i t="grand"><x /></i>
|
|
214
|
+
</colItems>`);
|
|
215
|
+
// Data fields
|
|
216
|
+
if (model.dataFields.length > 0) {
|
|
217
|
+
xmlStream.openNode("dataFields", { count: model.dataFields.length });
|
|
218
|
+
for (const dataField of model.dataFields) {
|
|
219
|
+
const attrs = {
|
|
220
|
+
name: dataField.name,
|
|
221
|
+
fld: dataField.fld,
|
|
222
|
+
baseField: dataField.baseField ?? 0,
|
|
223
|
+
baseItem: dataField.baseItem ?? 0
|
|
224
|
+
};
|
|
225
|
+
if (dataField.subtotal && dataField.subtotal !== "sum") {
|
|
226
|
+
attrs.subtotal = dataField.subtotal;
|
|
227
|
+
}
|
|
228
|
+
xmlStream.leafNode("dataField", attrs);
|
|
229
|
+
}
|
|
230
|
+
xmlStream.closeNode();
|
|
231
|
+
}
|
|
232
|
+
// Style info
|
|
233
|
+
xmlStream.leafNode("pivotTableStyleInfo", {
|
|
234
|
+
name: model.styleName || "PivotStyleLight16",
|
|
235
|
+
showRowHeaders: "1",
|
|
236
|
+
showColHeaders: "1",
|
|
237
|
+
showRowStripes: "0",
|
|
238
|
+
showColStripes: "0",
|
|
239
|
+
showLastColumn: "1"
|
|
240
|
+
});
|
|
241
|
+
// Extensions
|
|
242
|
+
xmlStream.writeXml(`
|
|
243
|
+
<extLst>
|
|
244
|
+
<ext
|
|
245
|
+
uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}"
|
|
246
|
+
xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
|
|
247
|
+
>
|
|
248
|
+
<x14:pivotTableDefinition
|
|
249
|
+
hideValuesRow="1"
|
|
250
|
+
xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"
|
|
251
|
+
/>
|
|
252
|
+
</ext>
|
|
253
|
+
<ext
|
|
254
|
+
uri="{747A6164-185A-40DC-8AA5-F01512510D54}"
|
|
255
|
+
xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"
|
|
256
|
+
>
|
|
257
|
+
<xpdl:pivotTableDefinition16
|
|
258
|
+
EnabledSubtotalsDefault="0"
|
|
259
|
+
SubtotalsOnTopDefault="0"
|
|
260
|
+
/>
|
|
261
|
+
</ext>
|
|
262
|
+
</extLst>
|
|
263
|
+
`);
|
|
264
|
+
xmlStream.closeNode();
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Render a loaded pivot field
|
|
268
|
+
*/
|
|
269
|
+
renderPivotFieldLoaded(xmlStream, field) {
|
|
270
|
+
const attrs = {
|
|
271
|
+
compact: field.compact ? "1" : "0",
|
|
272
|
+
outline: field.outline ? "1" : "0",
|
|
273
|
+
showAll: field.showAll ? "1" : "0",
|
|
274
|
+
defaultSubtotal: field.defaultSubtotal ? "1" : "0"
|
|
275
|
+
};
|
|
276
|
+
if (field.axis) {
|
|
277
|
+
attrs.axis = field.axis;
|
|
278
|
+
}
|
|
279
|
+
if (field.dataField) {
|
|
280
|
+
attrs.dataField = "1";
|
|
281
|
+
}
|
|
282
|
+
if (field.items && field.items.length > 0) {
|
|
283
|
+
xmlStream.openNode("pivotField", attrs);
|
|
284
|
+
xmlStream.openNode("items", { count: field.items.length + 1 });
|
|
285
|
+
for (const itemIndex of field.items) {
|
|
286
|
+
xmlStream.leafNode("item", { x: itemIndex });
|
|
287
|
+
}
|
|
288
|
+
// Grand total item
|
|
289
|
+
xmlStream.writeXml('<item t="default" />');
|
|
290
|
+
xmlStream.closeNode(); // items
|
|
291
|
+
xmlStream.closeNode(); // pivotField
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
xmlStream.leafNode("pivotField", attrs);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
parseOpen(node) {
|
|
298
|
+
const { name, attributes } = node;
|
|
299
|
+
switch (name) {
|
|
300
|
+
case this.tag:
|
|
301
|
+
// pivotTableDefinition root element
|
|
302
|
+
this.reset();
|
|
303
|
+
this.model = {
|
|
304
|
+
name: attributes.name,
|
|
305
|
+
cacheId: parseInt(attributes.cacheId || "0", 10),
|
|
306
|
+
uid: attributes["xr:uid"],
|
|
307
|
+
pivotFields: [],
|
|
308
|
+
rowFields: [],
|
|
309
|
+
colFields: [],
|
|
310
|
+
dataFields: [],
|
|
311
|
+
applyNumberFormats: attributes.applyNumberFormats,
|
|
312
|
+
applyBorderFormats: attributes.applyBorderFormats,
|
|
313
|
+
applyFontFormats: attributes.applyFontFormats,
|
|
314
|
+
applyPatternFormats: attributes.applyPatternFormats,
|
|
315
|
+
applyAlignmentFormats: attributes.applyAlignmentFormats,
|
|
316
|
+
applyWidthHeightFormats: attributes.applyWidthHeightFormats,
|
|
317
|
+
dataCaption: attributes.dataCaption,
|
|
318
|
+
updatedVersion: attributes.updatedVersion,
|
|
319
|
+
minRefreshableVersion: attributes.minRefreshableVersion,
|
|
320
|
+
createdVersion: attributes.createdVersion,
|
|
321
|
+
useAutoFormatting: attributes.useAutoFormatting === "1",
|
|
322
|
+
itemPrintTitles: attributes.itemPrintTitles === "1",
|
|
323
|
+
indent: attributes.indent ? parseInt(attributes.indent, 10) : 0,
|
|
324
|
+
compact: attributes.compact === "1",
|
|
325
|
+
compactData: attributes.compactData === "1",
|
|
326
|
+
multipleFieldFilters: attributes.multipleFieldFilters === "1",
|
|
327
|
+
isLoaded: true
|
|
328
|
+
};
|
|
329
|
+
break;
|
|
330
|
+
case "location":
|
|
331
|
+
if (this.model) {
|
|
332
|
+
this.model.location = {
|
|
333
|
+
ref: attributes.ref,
|
|
334
|
+
firstHeaderRow: attributes.firstHeaderRow
|
|
335
|
+
? parseInt(attributes.firstHeaderRow, 10)
|
|
336
|
+
: undefined,
|
|
337
|
+
firstDataRow: attributes.firstDataRow
|
|
338
|
+
? parseInt(attributes.firstDataRow, 10)
|
|
339
|
+
: undefined,
|
|
340
|
+
firstDataCol: attributes.firstDataCol
|
|
341
|
+
? parseInt(attributes.firstDataCol, 10)
|
|
342
|
+
: undefined
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
case "pivotFields":
|
|
347
|
+
this.inPivotFields = true;
|
|
348
|
+
break;
|
|
349
|
+
case "pivotField":
|
|
350
|
+
if (this.inPivotFields) {
|
|
351
|
+
this.currentPivotField = {
|
|
352
|
+
axis: attributes.axis,
|
|
353
|
+
dataField: attributes.dataField === "1",
|
|
354
|
+
items: [],
|
|
355
|
+
compact: attributes.compact === "1",
|
|
356
|
+
outline: attributes.outline === "1",
|
|
357
|
+
showAll: attributes.showAll === "1",
|
|
358
|
+
defaultSubtotal: attributes.defaultSubtotal === "1"
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
case "items":
|
|
363
|
+
if (this.currentPivotField) {
|
|
364
|
+
this.inItems = true;
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
case "item":
|
|
368
|
+
if (this.inItems && this.currentPivotField && attributes.x !== undefined) {
|
|
369
|
+
this.currentPivotField.items.push(parseInt(attributes.x, 10));
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
case "rowFields":
|
|
373
|
+
this.inRowFields = true;
|
|
374
|
+
break;
|
|
375
|
+
case "colFields":
|
|
376
|
+
this.inColFields = true;
|
|
377
|
+
break;
|
|
378
|
+
case "dataFields":
|
|
379
|
+
this.inDataFields = true;
|
|
380
|
+
break;
|
|
381
|
+
case "rowItems":
|
|
382
|
+
this.inRowItems = true;
|
|
383
|
+
break;
|
|
384
|
+
case "colItems":
|
|
385
|
+
this.inColItems = true;
|
|
386
|
+
break;
|
|
387
|
+
case "field":
|
|
388
|
+
// Handle field element (used in rowFields, colFields)
|
|
389
|
+
if (this.model) {
|
|
390
|
+
const fieldIndex = parseInt(attributes.x || "0", 10);
|
|
391
|
+
if (this.inRowFields) {
|
|
392
|
+
this.model.rowFields.push(fieldIndex);
|
|
393
|
+
}
|
|
394
|
+
else if (this.inColFields) {
|
|
395
|
+
this.model.colFields.push(fieldIndex);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
case "dataField":
|
|
400
|
+
if (this.inDataFields && this.model) {
|
|
401
|
+
this.model.dataFields.push({
|
|
402
|
+
name: (0, utils_js_1.xmlDecode)(attributes.name || ""),
|
|
403
|
+
fld: parseInt(attributes.fld || "0", 10),
|
|
404
|
+
baseField: attributes.baseField ? parseInt(attributes.baseField, 10) : 0,
|
|
405
|
+
baseItem: attributes.baseItem ? parseInt(attributes.baseItem, 10) : 0,
|
|
406
|
+
subtotal: attributes.subtotal
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
case "pivotTableStyleInfo":
|
|
411
|
+
if (this.model) {
|
|
412
|
+
this.model.styleName = attributes.name;
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
118
417
|
}
|
|
119
418
|
parseText(_text) {
|
|
120
|
-
//
|
|
419
|
+
// No text content in pivot table elements
|
|
121
420
|
}
|
|
122
|
-
parseClose(
|
|
123
|
-
|
|
124
|
-
|
|
421
|
+
parseClose(name) {
|
|
422
|
+
switch (name) {
|
|
423
|
+
case this.tag:
|
|
424
|
+
// End of pivotTableDefinition
|
|
425
|
+
return false;
|
|
426
|
+
case "pivotFields":
|
|
427
|
+
this.inPivotFields = false;
|
|
428
|
+
break;
|
|
429
|
+
case "pivotField":
|
|
430
|
+
if (this.currentPivotField && this.model) {
|
|
431
|
+
this.model.pivotFields.push(this.currentPivotField);
|
|
432
|
+
this.currentPivotField = null;
|
|
433
|
+
}
|
|
434
|
+
break;
|
|
435
|
+
case "items":
|
|
436
|
+
this.inItems = false;
|
|
437
|
+
break;
|
|
438
|
+
case "rowFields":
|
|
439
|
+
this.inRowFields = false;
|
|
440
|
+
break;
|
|
441
|
+
case "colFields":
|
|
442
|
+
this.inColFields = false;
|
|
443
|
+
break;
|
|
444
|
+
case "dataFields":
|
|
445
|
+
this.inDataFields = false;
|
|
446
|
+
break;
|
|
447
|
+
case "rowItems":
|
|
448
|
+
this.inRowItems = false;
|
|
449
|
+
break;
|
|
450
|
+
case "colItems":
|
|
451
|
+
this.inColItems = false;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
return true;
|
|
125
455
|
}
|
|
126
456
|
reconcile(_model, _options) {
|
|
127
|
-
//
|
|
457
|
+
// No reconciliation needed
|
|
128
458
|
}
|
|
129
459
|
}
|
|
130
460
|
exports.PivotTableXform = PivotTableXform;
|
|
@@ -135,14 +465,35 @@ PivotTableXform.PIVOT_TABLE_ATTRIBUTES = {
|
|
|
135
465
|
"xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
|
|
136
466
|
};
|
|
137
467
|
// Helpers
|
|
468
|
+
/**
|
|
469
|
+
* Build dataField XML elements for all values in the pivot table.
|
|
470
|
+
* Supports multiple values when columns is empty.
|
|
471
|
+
*/
|
|
472
|
+
function buildDataFields(cacheFields, values, metric) {
|
|
473
|
+
const metricName = metric === "count" ? "Count" : "Sum";
|
|
474
|
+
// For 'count' metric, Excel requires subtotal="count" attribute
|
|
475
|
+
const subtotalAttr = metric === "count" ? ' subtotal="count"' : "";
|
|
476
|
+
return values
|
|
477
|
+
.map(valueIndex => `<dataField
|
|
478
|
+
name="${metricName} of ${(0, utils_js_1.xmlEncode)(cacheFields[valueIndex].name)}"
|
|
479
|
+
fld="${valueIndex}"
|
|
480
|
+
baseField="0"
|
|
481
|
+
baseItem="0"${subtotalAttr}
|
|
482
|
+
/>`)
|
|
483
|
+
.join("");
|
|
484
|
+
}
|
|
138
485
|
function renderPivotFields(pivotTable) {
|
|
486
|
+
// Pre-compute field type lookup for O(1) access
|
|
487
|
+
const rowSet = new Set(pivotTable.rows);
|
|
488
|
+
const colSet = new Set(pivotTable.columns);
|
|
489
|
+
const valueSet = new Set(pivotTable.values);
|
|
139
490
|
return pivotTable.cacheFields
|
|
140
491
|
.map((cacheField, fieldIndex) => {
|
|
141
|
-
const fieldType =
|
|
492
|
+
const fieldType = rowSet.has(fieldIndex)
|
|
142
493
|
? "row"
|
|
143
|
-
:
|
|
494
|
+
: colSet.has(fieldIndex)
|
|
144
495
|
? "column"
|
|
145
|
-
:
|
|
496
|
+
: valueSet.has(fieldIndex)
|
|
146
497
|
? "value"
|
|
147
498
|
: null;
|
|
148
499
|
return renderPivotField(fieldType, cacheField.sharedItems);
|
|
@@ -343,6 +343,12 @@ class CellXform extends base_xform_js_1.BaseXform {
|
|
|
343
343
|
model.type = enums_js_1.Enums.ValueType.Error;
|
|
344
344
|
model.value = { error: model.value };
|
|
345
345
|
break;
|
|
346
|
+
case "d":
|
|
347
|
+
// Strict OpenXML format stores dates as ISO strings with t="d"
|
|
348
|
+
// See: https://www.loc.gov/preservation/digital/formats/fdd/fdd000401.shtml
|
|
349
|
+
model.type = enums_js_1.Enums.ValueType.Date;
|
|
350
|
+
model.value = new Date(model.value);
|
|
351
|
+
break;
|
|
346
352
|
default:
|
|
347
353
|
model.type = enums_js_1.Enums.ValueType.Number;
|
|
348
354
|
model.value = parseFloat(model.value);
|
|
@@ -268,13 +268,13 @@ class WorkSheetXform extends base_xform_js_1.BaseXform {
|
|
|
268
268
|
});
|
|
269
269
|
});
|
|
270
270
|
// prepare pivot tables
|
|
271
|
-
|
|
271
|
+
(model.pivotTables || []).forEach((pivotTable) => {
|
|
272
272
|
rels.push({
|
|
273
273
|
Id: nextRid(rels),
|
|
274
274
|
Type: rel_type_js_1.RelType.PivotTable,
|
|
275
|
-
Target:
|
|
275
|
+
Target: `../pivotTables/pivotTable${pivotTable.tableNumber}.xml`
|
|
276
276
|
});
|
|
277
|
-
}
|
|
277
|
+
});
|
|
278
278
|
// prepare ext items
|
|
279
279
|
this.map.extLst.prepare(model, options);
|
|
280
280
|
}
|
|
@@ -489,6 +489,17 @@ class WorkSheetXform extends base_xform_js_1.BaseXform {
|
|
|
489
489
|
const rel = rels[tablePart.rId];
|
|
490
490
|
return options.tables[rel.Target];
|
|
491
491
|
});
|
|
492
|
+
// Link pivot tables from relationships to worksheet
|
|
493
|
+
// This is needed so that when writing, the worksheet knows which pivot tables it contains
|
|
494
|
+
model.pivotTables = [];
|
|
495
|
+
(model.relationships || []).forEach(rel => {
|
|
496
|
+
if (rel.Type === rel_type_js_1.RelType.PivotTable && options.pivotTables) {
|
|
497
|
+
const pivotTable = options.pivotTables[rel.Target];
|
|
498
|
+
if (pivotTable) {
|
|
499
|
+
model.pivotTables.push(pivotTable);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
});
|
|
492
503
|
delete model.relationships;
|
|
493
504
|
delete model.hyperlinks;
|
|
494
505
|
delete model.comments;
|