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