@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.
Files changed (45) hide show
  1. package/dist/browser/excelts.iife.js +1057 -201
  2. package/dist/browser/excelts.iife.js.map +1 -1
  3. package/dist/browser/excelts.iife.min.js +63 -33
  4. package/dist/cjs/doc/column.js +7 -3
  5. package/dist/cjs/doc/pivot-table.js +149 -61
  6. package/dist/cjs/doc/workbook.js +3 -1
  7. package/dist/cjs/doc/worksheet.js +0 -2
  8. package/dist/cjs/stream/xlsx/worksheet-writer.js +1 -1
  9. package/dist/cjs/utils/unzip/zip-parser.js +2 -5
  10. package/dist/cjs/xlsx/xform/book/workbook-xform.js +3 -0
  11. package/dist/cjs/xlsx/xform/core/content-types-xform.js +19 -14
  12. package/dist/cjs/xlsx/xform/pivot-table/cache-field-xform.js +135 -0
  13. package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +7 -4
  14. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
  15. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
  16. package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
  17. package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -0
  18. package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +14 -3
  19. package/dist/cjs/xlsx/xlsx.js +261 -38
  20. package/dist/esm/doc/column.js +7 -3
  21. package/dist/esm/doc/pivot-table.js +150 -62
  22. package/dist/esm/doc/workbook.js +3 -1
  23. package/dist/esm/doc/worksheet.js +0 -2
  24. package/dist/esm/stream/xlsx/worksheet-writer.js +1 -1
  25. package/dist/esm/utils/unzip/zip-parser.js +2 -5
  26. package/dist/esm/xlsx/xform/book/workbook-xform.js +3 -0
  27. package/dist/esm/xlsx/xform/core/content-types-xform.js +19 -14
  28. package/dist/esm/xlsx/xform/pivot-table/cache-field-xform.js +132 -0
  29. package/dist/esm/xlsx/xform/pivot-table/cache-field.js +7 -4
  30. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
  31. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
  32. package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
  33. package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -0
  34. package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +14 -3
  35. package/dist/esm/xlsx/xlsx.js +261 -38
  36. package/dist/types/doc/column.d.ts +13 -6
  37. package/dist/types/doc/pivot-table.d.ts +135 -9
  38. package/dist/types/doc/workbook.d.ts +2 -0
  39. package/dist/types/index.d.ts +1 -0
  40. package/dist/types/xlsx/xform/pivot-table/cache-field-xform.d.ts +42 -0
  41. package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +45 -6
  42. package/dist/types/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +52 -5
  43. package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +98 -5
  44. package/dist/types/xlsx/xlsx.d.ts +27 -0
  45. 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
- // TK
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 { rows, columns, values, cacheFields, cacheId } = model;
20
- // Examples
21
- // --------
22
- // rows: [0, 1], // only 2 items possible for now
23
- // columns: [2], // only 1 item possible for now
24
- // values: [4], // only 1 item possible for now
25
- // metric: 'sum', // only 'sum' possible for now
26
- //
27
- // the numbers are indices into `cacheFields`.
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": "{267EE50F-B116-784D-8DC2-BA77DE3F4F4A}",
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: "1",
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.map(columnIndex => `<field x="${columnIndex}" />`).join("\n ")}
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
- <dataField
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
- parseOpen(_node) {
116
- // TK
117
- return false;
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
- // TK
419
+ // No text content in pivot table elements
121
420
  }
122
- parseClose(_name) {
123
- // TK
124
- return false;
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
- // TK
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 = pivotTable.rows.indexOf(fieldIndex) >= 0
492
+ const fieldType = rowSet.has(fieldIndex)
142
493
  ? "row"
143
- : pivotTable.columns.indexOf(fieldIndex) >= 0
494
+ : colSet.has(fieldIndex)
144
495
  ? "column"
145
- : pivotTable.values.indexOf(fieldIndex) >= 0
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
- if ((model.pivotTables || []).length) {
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: "../pivotTables/pivotTable1.xml"
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;