@cj-tech-master/excelts 4.2.3 → 5.0.0-canary.20260123012457.1fdf506

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 (84) hide show
  1. package/dist/browser/modules/csv/csv-core.d.ts +0 -9
  2. package/dist/browser/modules/csv/csv.browser.js +3 -3
  3. package/dist/browser/modules/excel/column.d.ts +5 -0
  4. package/dist/browser/modules/excel/column.js +10 -2
  5. package/dist/browser/modules/excel/row.d.ts +2 -0
  6. package/dist/browser/modules/excel/row.js +3 -1
  7. package/dist/browser/modules/excel/utils/parse-sax.d.ts +0 -3
  8. package/dist/browser/modules/excel/utils/parse-sax.js +13 -32
  9. package/dist/browser/modules/excel/utils/passthrough-manager.d.ts +77 -0
  10. package/dist/browser/modules/excel/utils/passthrough-manager.js +129 -0
  11. package/dist/browser/modules/excel/workbook.d.ts +12 -0
  12. package/dist/browser/modules/excel/workbook.js +12 -1
  13. package/dist/browser/modules/excel/worksheet.d.ts +4 -0
  14. package/dist/browser/modules/excel/worksheet.js +4 -1
  15. package/dist/browser/modules/excel/xlsx/xform/base-xform.js +68 -1
  16. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  17. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +35 -11
  18. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +271 -94
  19. package/dist/browser/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -0
  20. package/dist/browser/modules/excel/xlsx/xform/sheet/row-xform.js +7 -1
  21. package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -0
  22. package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +9 -4
  23. package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +4 -2
  24. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +40 -12
  25. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.d.ts +7 -0
  26. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +26 -6
  27. package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.d.ts +6 -0
  28. package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.js +52 -4
  29. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
  30. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +220 -131
  31. package/dist/browser/modules/stream/streams.browser.js +0 -3
  32. package/dist/cjs/modules/csv/csv.browser.js +3 -3
  33. package/dist/cjs/modules/excel/column.js +10 -2
  34. package/dist/cjs/modules/excel/row.js +3 -1
  35. package/dist/cjs/modules/excel/utils/parse-sax.js +13 -32
  36. package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
  37. package/dist/cjs/modules/excel/workbook.js +12 -1
  38. package/dist/cjs/modules/excel/worksheet.js +4 -1
  39. package/dist/cjs/modules/excel/xlsx/xform/base-xform.js +68 -1
  40. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  41. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +271 -94
  42. package/dist/cjs/modules/excel/xlsx/xform/sheet/row-xform.js +7 -1
  43. package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +9 -4
  44. package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +4 -2
  45. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +40 -12
  46. package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +26 -6
  47. package/dist/cjs/modules/excel/xlsx/xform/style/styles-xform.js +52 -4
  48. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +220 -131
  49. package/dist/cjs/modules/stream/streams.browser.js +0 -3
  50. package/dist/esm/modules/csv/csv.browser.js +3 -3
  51. package/dist/esm/modules/excel/column.js +10 -2
  52. package/dist/esm/modules/excel/row.js +3 -1
  53. package/dist/esm/modules/excel/utils/parse-sax.js +13 -32
  54. package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
  55. package/dist/esm/modules/excel/workbook.js +12 -1
  56. package/dist/esm/modules/excel/worksheet.js +4 -1
  57. package/dist/esm/modules/excel/xlsx/xform/base-xform.js +68 -1
  58. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  59. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +271 -94
  60. package/dist/esm/modules/excel/xlsx/xform/sheet/row-xform.js +7 -1
  61. package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +9 -4
  62. package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +4 -2
  63. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +40 -12
  64. package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +26 -6
  65. package/dist/esm/modules/excel/xlsx/xform/style/styles-xform.js +52 -4
  66. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +220 -131
  67. package/dist/esm/modules/stream/streams.browser.js +0 -3
  68. package/dist/iife/excelts.iife.js +1009 -650
  69. package/dist/iife/excelts.iife.js.map +1 -1
  70. package/dist/iife/excelts.iife.min.js +25 -52
  71. package/dist/types/modules/csv/csv-core.d.ts +0 -9
  72. package/dist/types/modules/excel/column.d.ts +5 -0
  73. package/dist/types/modules/excel/row.d.ts +2 -0
  74. package/dist/types/modules/excel/utils/parse-sax.d.ts +0 -3
  75. package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
  76. package/dist/types/modules/excel/workbook.d.ts +12 -0
  77. package/dist/types/modules/excel/worksheet.d.ts +4 -0
  78. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +35 -11
  79. package/dist/types/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -0
  80. package/dist/types/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -0
  81. package/dist/types/modules/excel/xlsx/xform/style/style-xform.d.ts +7 -0
  82. package/dist/types/modules/excel/xlsx/xform/style/styles-xform.d.ts +6 -0
  83. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
  84. package/package.json +15 -15
@@ -4,18 +4,30 @@ import { BaseXform } from "../base-xform.js";
4
4
  class PivotTableXform extends BaseXform {
5
5
  constructor() {
6
6
  super();
7
+ // Parser state consolidated into object for easier reset
8
+ this.state = {
9
+ inPivotFields: false,
10
+ inRowFields: false,
11
+ inColFields: false,
12
+ inDataFields: false,
13
+ inRowItems: false,
14
+ inColItems: false,
15
+ inLocation: false,
16
+ inItems: false,
17
+ inPivotTableStyleInfo: false,
18
+ inChartFormats: false,
19
+ inPivotArea: false
20
+ };
21
+ // Current parsing context
22
+ this.currentPivotField = null;
23
+ this.currentRowItem = null;
24
+ this.currentColItem = null;
25
+ this.currentChartFormat = null;
26
+ // Buffer for collecting pivotArea XML
27
+ this.pivotAreaXmlBuffer = [];
28
+ this.pivotAreaDepth = 0;
7
29
  this.map = {};
8
30
  this.model = null;
9
- this.inPivotFields = false;
10
- this.inRowFields = false;
11
- this.inColFields = false;
12
- this.inDataFields = false;
13
- this.inRowItems = false;
14
- this.inColItems = false;
15
- this.inLocation = false;
16
- this.currentPivotField = null;
17
- this.inItems = false;
18
- this.inPivotTableStyleInfo = false;
19
31
  }
20
32
  prepare(_model) {
21
33
  // No preparation needed
@@ -26,16 +38,17 @@ class PivotTableXform extends BaseXform {
26
38
  }
27
39
  reset() {
28
40
  this.model = null;
29
- this.inPivotFields = false;
30
- this.inRowFields = false;
31
- this.inColFields = false;
32
- this.inDataFields = false;
33
- this.inRowItems = false;
34
- this.inColItems = false;
35
- this.inLocation = false;
41
+ // Reset all parser state flags using object
42
+ Object.keys(this.state).forEach(key => {
43
+ this.state[key] = false;
44
+ });
45
+ // Reset current context
36
46
  this.currentPivotField = null;
37
- this.inItems = false;
38
- this.inPivotTableStyleInfo = false;
47
+ this.currentRowItem = null;
48
+ this.currentColItem = null;
49
+ this.currentChartFormat = null;
50
+ this.pivotAreaXmlBuffer = [];
51
+ this.pivotAreaDepth = 0;
39
52
  }
40
53
  /**
41
54
  * Render pivot table XML.
@@ -156,8 +169,7 @@ class PivotTableXform extends BaseXform {
156
169
  * Render loaded pivot table (preserving original structure)
157
170
  */
158
171
  renderLoaded(xmlStream, model) {
159
- xmlStream.openXml(XmlStream.StdDocAttributes);
160
- xmlStream.openNode(this.tag, {
172
+ const attrs = {
161
173
  ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
162
174
  name: model.name || "PivotTable1",
163
175
  cacheId: model.cacheId,
@@ -166,7 +178,8 @@ class PivotTableXform extends BaseXform {
166
178
  applyFontFormats: model.applyFontFormats || "0",
167
179
  applyPatternFormats: model.applyPatternFormats || "0",
168
180
  applyAlignmentFormats: model.applyAlignmentFormats || "0",
169
- applyWidthHeightFormats: model.applyWidthHeightFormats || "0",
181
+ // Preserve original value when present; default to Excel's typical "0".
182
+ applyWidthHeightFormats: model.applyWidthHeightFormats ?? "0",
170
183
  dataCaption: model.dataCaption || "Values",
171
184
  updatedVersion: model.updatedVersion || "8",
172
185
  minRefreshableVersion: model.minRefreshableVersion || "3",
@@ -174,10 +187,27 @@ class PivotTableXform extends BaseXform {
174
187
  itemPrintTitles: model.itemPrintTitles ? "1" : "0",
175
188
  createdVersion: model.createdVersion || "8",
176
189
  indent: model.indent !== undefined ? String(model.indent) : "0",
177
- compact: model.compact ? "1" : "0",
178
- compactData: model.compactData ? "1" : "0",
179
190
  multipleFieldFilters: model.multipleFieldFilters ? "1" : "0"
180
- });
191
+ };
192
+ // Add outline attributes if present
193
+ if (model.outline) {
194
+ attrs.outline = "1";
195
+ }
196
+ if (model.outlineData) {
197
+ attrs.outlineData = "1";
198
+ }
199
+ if (model.chartFormat !== undefined) {
200
+ attrs.chartFormat = String(model.chartFormat);
201
+ }
202
+ // Only add compact/compactData if they are true (some files don't have them)
203
+ if (model.compact) {
204
+ attrs.compact = "1";
205
+ }
206
+ if (model.compactData) {
207
+ attrs.compactData = "1";
208
+ }
209
+ xmlStream.openXml(XmlStream.StdDocAttributes);
210
+ xmlStream.openNode(this.tag, attrs);
181
211
  // Location
182
212
  if (model.location) {
183
213
  xmlStream.leafNode("location", {
@@ -203,42 +233,79 @@ class PivotTableXform extends BaseXform {
203
233
  }
204
234
  xmlStream.closeNode();
205
235
  }
206
- // Row items (simplified - just grand total)
207
- xmlStream.writeXml(`
208
- <rowItems count="1">
209
- <i t="grand"><x /></i>
210
- </rowItems>`);
211
- // Col fields
212
- const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
213
- xmlStream.openNode("colFields", { count: colFieldCount });
214
- if (model.colFields.length === 0) {
215
- xmlStream.leafNode("field", { x: -2 });
236
+ // Row items - use parsed items if available; otherwise emit a minimal grand total
237
+ if (model.rowItems && model.rowItems.length > 0) {
238
+ xmlStream.openNode("rowItems", { count: model.rowItems.length });
239
+ for (const item of model.rowItems) {
240
+ this.renderRowColItem(xmlStream, item);
241
+ }
242
+ xmlStream.closeNode();
216
243
  }
217
244
  else {
218
- for (const fieldIndex of model.colFields) {
219
- xmlStream.leafNode("field", { x: fieldIndex });
245
+ xmlStream.writeXml('<rowItems count="1"><i t="grand"><x/></i></rowItems>');
246
+ }
247
+ // Col fields
248
+ // Only render colFields if it was present in the original file or if there are actual column fields
249
+ // Some pivot tables don't have colFields element at all
250
+ if (model.hasColFields || model.colFields.length > 0) {
251
+ const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
252
+ xmlStream.openNode("colFields", { count: colFieldCount });
253
+ if (model.colFields.length === 0) {
254
+ xmlStream.leafNode("field", { x: -2 });
255
+ }
256
+ else {
257
+ for (const fieldIndex of model.colFields) {
258
+ xmlStream.leafNode("field", { x: fieldIndex });
259
+ }
220
260
  }
261
+ xmlStream.closeNode();
262
+ }
263
+ // Col items - use parsed items if available
264
+ if (model.colItems && model.colItems.length > 0) {
265
+ xmlStream.openNode("colItems", { count: model.colItems.length });
266
+ for (const item of model.colItems) {
267
+ this.renderRowColItem(xmlStream, item);
268
+ }
269
+ xmlStream.closeNode();
270
+ }
271
+ else {
272
+ xmlStream.writeXml('<colItems count="1"><i t="grand"><x/></i></colItems>');
221
273
  }
222
- xmlStream.closeNode();
223
- // Col items (simplified - just grand total)
224
- xmlStream.writeXml(`
225
- <colItems count="1">
226
- <i t="grand"><x /></i>
227
- </colItems>`);
228
274
  // Data fields
229
275
  if (model.dataFields.length > 0) {
230
276
  xmlStream.openNode("dataFields", { count: model.dataFields.length });
231
277
  for (const dataField of model.dataFields) {
232
- const attrs = {
278
+ const dfAttrs = {
233
279
  name: dataField.name,
234
280
  fld: dataField.fld,
235
281
  baseField: dataField.baseField ?? 0,
236
282
  baseItem: dataField.baseItem ?? 0
237
283
  };
238
284
  if (dataField.subtotal && dataField.subtotal !== "sum") {
239
- attrs.subtotal = dataField.subtotal;
285
+ dfAttrs.subtotal = dataField.subtotal;
240
286
  }
241
- xmlStream.leafNode("dataField", attrs);
287
+ xmlStream.leafNode("dataField", dfAttrs);
288
+ }
289
+ xmlStream.closeNode();
290
+ }
291
+ // Chart formats (for pivot charts) - preserve original pivotArea XML
292
+ if (model.chartFormats && model.chartFormats.length > 0) {
293
+ xmlStream.openNode("chartFormats", { count: model.chartFormats.length });
294
+ for (const cf of model.chartFormats) {
295
+ xmlStream.openNode("chartFormat", {
296
+ chart: cf.chart,
297
+ format: cf.format,
298
+ series: cf.series ? "1" : undefined
299
+ });
300
+ // Use preserved pivotArea XML or fallback to default
301
+ if (cf.pivotAreaXml) {
302
+ xmlStream.writeXml(cf.pivotAreaXml);
303
+ }
304
+ else {
305
+ // Fallback for newly created chart formats (shouldn't happen for loaded models)
306
+ xmlStream.writeXml(`<pivotArea type="data" outline="0" fieldPosition="0"><references count="1"><reference field="4294967294" count="1" selected="0"><x v="0"/></reference></references></pivotArea>`);
307
+ }
308
+ xmlStream.closeNode();
242
309
  }
243
310
  xmlStream.closeNode();
244
311
  }
@@ -252,46 +319,48 @@ class PivotTableXform extends BaseXform {
252
319
  showLastColumn: "1"
253
320
  });
254
321
  // Extensions
255
- xmlStream.writeXml(`
256
- <extLst>
257
- <ext
258
- uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}"
259
- xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
260
- >
261
- <x14:pivotTableDefinition
262
- hideValuesRow="1"
263
- xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"
264
- />
265
- </ext>
266
- <ext
267
- uri="{747A6164-185A-40DC-8AA5-F01512510D54}"
268
- xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"
269
- >
270
- <xpdl:pivotTableDefinition16
271
- EnabledSubtotalsDefault="0"
272
- SubtotalsOnTopDefault="0"
273
- />
274
- </ext>
275
- </extLst>
276
- `);
322
+ xmlStream.writeXml(`<extLst><ext uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><x14:pivotTableDefinition hideValuesRow="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"/></ext><ext uri="{747A6164-185A-40DC-8AA5-F01512510D54}" xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"><xpdl:pivotTableDefinition16/></ext></extLst>`);
277
323
  xmlStream.closeNode();
278
324
  }
325
+ /**
326
+ * Render a row or column item element
327
+ */
328
+ renderRowColItem(xmlStream, item) {
329
+ const attrs = {};
330
+ if (item.t) {
331
+ attrs.t = item.t;
332
+ }
333
+ if (item.x && item.x.length > 0) {
334
+ xmlStream.openNode("i", attrs);
335
+ for (const x of item.x) {
336
+ if (x.v && x.v !== 0) {
337
+ xmlStream.leafNode("x", { v: x.v });
338
+ }
339
+ else {
340
+ xmlStream.leafNode("x");
341
+ }
342
+ }
343
+ xmlStream.closeNode();
344
+ }
345
+ else {
346
+ // Empty item (like <i/> in colItems)
347
+ xmlStream.leafNode("i", attrs);
348
+ }
349
+ }
279
350
  /**
280
351
  * Render a loaded pivot field
281
352
  */
282
353
  renderPivotFieldLoaded(xmlStream, field) {
283
- const attrs = {
284
- compact: field.compact ? "1" : "0",
285
- outline: field.outline ? "1" : "0",
286
- showAll: field.showAll ? "1" : "0",
287
- defaultSubtotal: field.defaultSubtotal ? "1" : "0"
288
- };
354
+ const attrs = {};
355
+ // Only add attributes that were present in the original
289
356
  if (field.axis) {
290
357
  attrs.axis = field.axis;
291
358
  }
292
359
  if (field.dataField) {
293
360
  attrs.dataField = "1";
294
361
  }
362
+ // showAll is typically always present
363
+ attrs.showAll = field.showAll ? "1" : "0";
295
364
  if (field.items && field.items.length > 0) {
296
365
  xmlStream.openNode("pivotField", attrs);
297
366
  xmlStream.openNode("items", { count: field.items.length + 1 });
@@ -299,7 +368,7 @@ class PivotTableXform extends BaseXform {
299
368
  xmlStream.leafNode("item", { x: itemIndex });
300
369
  }
301
370
  // Grand total item
302
- xmlStream.writeXml('<item t="default" />');
371
+ xmlStream.writeXml('<item t="default"/>');
303
372
  xmlStream.closeNode(); // items
304
373
  xmlStream.closeNode(); // pivotField
305
374
  }
@@ -337,6 +406,12 @@ class PivotTableXform extends BaseXform {
337
406
  compact: attributes.compact === "1",
338
407
  compactData: attributes.compactData === "1",
339
408
  multipleFieldFilters: attributes.multipleFieldFilters === "1",
409
+ outline: attributes.outline === "1",
410
+ outlineData: attributes.outlineData === "1",
411
+ chartFormat: attributes.chartFormat ? parseInt(attributes.chartFormat, 10) : undefined,
412
+ rowItems: [],
413
+ colItems: [],
414
+ chartFormats: [],
340
415
  isLoaded: true
341
416
  };
342
417
  break;
@@ -357,10 +432,10 @@ class PivotTableXform extends BaseXform {
357
432
  }
358
433
  break;
359
434
  case "pivotFields":
360
- this.inPivotFields = true;
435
+ this.state.inPivotFields = true;
361
436
  break;
362
437
  case "pivotField":
363
- if (this.inPivotFields) {
438
+ if (this.state.inPivotFields) {
364
439
  this.currentPivotField = {
365
440
  axis: attributes.axis,
366
441
  dataField: attributes.dataField === "1",
@@ -374,43 +449,105 @@ class PivotTableXform extends BaseXform {
374
449
  break;
375
450
  case "items":
376
451
  if (this.currentPivotField) {
377
- this.inItems = true;
452
+ this.state.inItems = true;
378
453
  }
379
454
  break;
380
455
  case "item":
381
- if (this.inItems && this.currentPivotField && attributes.x !== undefined) {
456
+ if (this.state.inItems && this.currentPivotField && attributes.x !== undefined) {
382
457
  this.currentPivotField.items.push(parseInt(attributes.x, 10));
383
458
  }
384
459
  break;
385
460
  case "rowFields":
386
- this.inRowFields = true;
461
+ this.state.inRowFields = true;
387
462
  break;
388
463
  case "colFields":
389
- this.inColFields = true;
464
+ this.state.inColFields = true;
465
+ // Track that colFields element was present in original file
466
+ if (this.model) {
467
+ this.model.hasColFields = true;
468
+ }
390
469
  break;
391
470
  case "dataFields":
392
- this.inDataFields = true;
471
+ this.state.inDataFields = true;
393
472
  break;
394
473
  case "rowItems":
395
- this.inRowItems = true;
474
+ this.state.inRowItems = true;
396
475
  break;
397
476
  case "colItems":
398
- this.inColItems = true;
477
+ this.state.inColItems = true;
478
+ break;
479
+ case "i":
480
+ // Handle row/col item element
481
+ if (this.state.inRowItems && this.model) {
482
+ this.currentRowItem = { t: attributes.t, x: [] };
483
+ }
484
+ else if (this.state.inColItems && this.model) {
485
+ this.currentColItem = { t: attributes.t, x: [] };
486
+ }
487
+ break;
488
+ case "x":
489
+ // Handle x element inside row/col items or pivotArea
490
+ if (this.state.inPivotArea) {
491
+ // Collect x element for pivotArea XML
492
+ const xAttrs = Object.entries(attributes)
493
+ .map(([k, v]) => `${k}="${v}"`)
494
+ .join(" ");
495
+ this.pivotAreaXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
496
+ }
497
+ else if (this.currentRowItem) {
498
+ this.currentRowItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
499
+ }
500
+ else if (this.currentColItem) {
501
+ this.currentColItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
502
+ }
503
+ break;
504
+ case "chartFormats":
505
+ this.state.inChartFormats = true;
506
+ break;
507
+ case "chartFormat":
508
+ if (this.state.inChartFormats && this.model) {
509
+ this.currentChartFormat = {
510
+ chart: attributes.chart ? parseInt(attributes.chart, 10) : 0,
511
+ format: attributes.format ? parseInt(attributes.format, 10) : 0,
512
+ series: attributes.series === "1"
513
+ };
514
+ }
515
+ break;
516
+ case "pivotArea":
517
+ // Start collecting pivotArea XML for chartFormat
518
+ if (this.currentChartFormat) {
519
+ this.state.inPivotArea = true;
520
+ const attrsStr = Object.entries(attributes)
521
+ .map(([k, v]) => `${k}="${v}"`)
522
+ .join(" ");
523
+ this.pivotAreaXmlBuffer = [attrsStr ? `<pivotArea ${attrsStr}>` : "<pivotArea>"];
524
+ }
525
+ break;
526
+ case "references":
527
+ case "reference":
528
+ // Collect nested elements in pivotArea
529
+ if (this.state.inPivotArea) {
530
+ this.pivotAreaDepth++;
531
+ const attrsStr = Object.entries(attributes)
532
+ .map(([k, v]) => `${k}="${v}"`)
533
+ .join(" ");
534
+ this.pivotAreaXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
535
+ }
399
536
  break;
400
537
  case "field":
401
538
  // Handle field element (used in rowFields, colFields)
402
539
  if (this.model) {
403
540
  const fieldIndex = parseInt(attributes.x || "0", 10);
404
- if (this.inRowFields) {
541
+ if (this.state.inRowFields) {
405
542
  this.model.rowFields.push(fieldIndex);
406
543
  }
407
- else if (this.inColFields) {
544
+ else if (this.state.inColFields) {
408
545
  this.model.colFields.push(fieldIndex);
409
546
  }
410
547
  }
411
548
  break;
412
549
  case "dataField":
413
- if (this.inDataFields && this.model) {
550
+ if (this.state.inDataFields && this.model) {
414
551
  this.model.dataFields.push({
415
552
  name: xmlDecode(attributes.name || ""),
416
553
  fld: parseInt(attributes.fld || "0", 10),
@@ -432,12 +569,32 @@ class PivotTableXform extends BaseXform {
432
569
  // No text content in pivot table elements
433
570
  }
434
571
  parseClose(name) {
572
+ // Handle pivotArea nested elements - close tags
573
+ if (this.state.inPivotArea) {
574
+ if (name === "pivotArea") {
575
+ this.pivotAreaXmlBuffer.push("</pivotArea>");
576
+ if (this.currentChartFormat) {
577
+ this.currentChartFormat.pivotAreaXml = this.pivotAreaXmlBuffer.join("");
578
+ }
579
+ this.state.inPivotArea = false;
580
+ this.pivotAreaXmlBuffer = [];
581
+ this.pivotAreaDepth = 0;
582
+ return true;
583
+ }
584
+ else if (name === "references" || name === "reference") {
585
+ this.pivotAreaXmlBuffer.push(`</${name}>`);
586
+ this.pivotAreaDepth--;
587
+ return true;
588
+ }
589
+ // x elements are self-closing, no need to handle close
590
+ return true;
591
+ }
435
592
  switch (name) {
436
593
  case this.tag:
437
594
  // End of pivotTableDefinition
438
595
  return false;
439
596
  case "pivotFields":
440
- this.inPivotFields = false;
597
+ this.state.inPivotFields = false;
441
598
  break;
442
599
  case "pivotField":
443
600
  if (this.currentPivotField && this.model) {
@@ -446,22 +603,42 @@ class PivotTableXform extends BaseXform {
446
603
  }
447
604
  break;
448
605
  case "items":
449
- this.inItems = false;
606
+ this.state.inItems = false;
450
607
  break;
451
608
  case "rowFields":
452
- this.inRowFields = false;
609
+ this.state.inRowFields = false;
453
610
  break;
454
611
  case "colFields":
455
- this.inColFields = false;
612
+ this.state.inColFields = false;
456
613
  break;
457
614
  case "dataFields":
458
- this.inDataFields = false;
615
+ this.state.inDataFields = false;
459
616
  break;
460
617
  case "rowItems":
461
- this.inRowItems = false;
618
+ this.state.inRowItems = false;
462
619
  break;
463
620
  case "colItems":
464
- this.inColItems = false;
621
+ this.state.inColItems = false;
622
+ break;
623
+ case "i":
624
+ // Finish row/col item
625
+ if (this.currentRowItem && this.model) {
626
+ this.model.rowItems.push(this.currentRowItem);
627
+ this.currentRowItem = null;
628
+ }
629
+ else if (this.currentColItem && this.model) {
630
+ this.model.colItems.push(this.currentColItem);
631
+ this.currentColItem = null;
632
+ }
633
+ break;
634
+ case "chartFormats":
635
+ this.state.inChartFormats = false;
636
+ break;
637
+ case "chartFormat":
638
+ if (this.currentChartFormat && this.model) {
639
+ this.model.chartFormats.push(this.currentChartFormat);
640
+ this.currentChartFormat = null;
641
+ }
465
642
  break;
466
643
  }
467
644
  return true;
@@ -48,7 +48,10 @@ class RowXform extends BaseXform {
48
48
  xmlStream.addAttribute("s", model.styleId);
49
49
  xmlStream.addAttribute("customFormat", "1");
50
50
  }
51
- // Note: dyDescent is MS extension, not output by default (Excel auto-calculates)
51
+ // Output dyDescent if present (MS extension for font descent)
52
+ if (model.dyDescent !== undefined) {
53
+ xmlStream.addAttribute("x14ac:dyDescent", model.dyDescent);
54
+ }
52
55
  if (model.outlineLevel) {
53
56
  xmlStream.addAttribute("outlineLevel", model.outlineLevel);
54
57
  }
@@ -99,6 +102,9 @@ class RowXform extends BaseXform {
99
102
  if (parseBoolean(node.attributes.collapsed)) {
100
103
  model.collapsed = true;
101
104
  }
105
+ if (node.attributes["x14ac:dyDescent"] !== undefined) {
106
+ model.dyDescent = parseFloat(node.attributes["x14ac:dyDescent"]);
107
+ }
102
108
  return true;
103
109
  }
104
110
  this.parser = this.map[node.name];
@@ -11,14 +11,14 @@ class SheetFormatPropertiesXform extends BaseXform {
11
11
  outlineLevelRow: model.outlineLevelRow || undefined,
12
12
  outlineLevelCol: model.outlineLevelCol || undefined,
13
13
  // Only output dyDescent if explicitly set (MS extension, not ECMA-376 standard)
14
- "x14ac:dyDescent": model.dyDescent || undefined
14
+ "x14ac:dyDescent": model.dyDescent !== undefined && model.dyDescent !== 0 ? model.dyDescent : undefined
15
15
  };
16
16
  // Only output defaultColWidth if explicitly set
17
17
  if (model.defaultColWidth) {
18
18
  attributes.defaultColWidth = model.defaultColWidth;
19
19
  }
20
- // default value for 'defaultRowHeight' is 15, this should not be 'custom'
21
- if (!model.defaultRowHeight || model.defaultRowHeight !== 15) {
20
+ // Only output customHeight if it was present in the original file
21
+ if (model.customHeight) {
22
22
  attributes.customHeight = "1";
23
23
  }
24
24
  if (Object.values(attributes).some((value) => value !== undefined)) {
@@ -30,13 +30,18 @@ class SheetFormatPropertiesXform extends BaseXform {
30
30
  if (node.name === "sheetFormatPr") {
31
31
  this.model = {
32
32
  defaultRowHeight: parseFloat(node.attributes.defaultRowHeight || "0"),
33
- dyDescent: parseFloat(node.attributes["x14ac:dyDescent"] || "0"),
33
+ dyDescent: node.attributes["x14ac:dyDescent"] !== undefined
34
+ ? parseFloat(node.attributes["x14ac:dyDescent"])
35
+ : undefined,
34
36
  outlineLevelRow: parseInt(node.attributes.outlineLevelRow || "0", 10),
35
37
  outlineLevelCol: parseInt(node.attributes.outlineLevelCol || "0", 10)
36
38
  };
37
39
  if (node.attributes.defaultColWidth) {
38
40
  this.model.defaultColWidth = parseFloat(node.attributes.defaultColWidth);
39
41
  }
42
+ if (node.attributes.customHeight === "1") {
43
+ this.model.customHeight = true;
44
+ }
40
45
  return true;
41
46
  }
42
47
  return false;
@@ -37,8 +37,8 @@ class SheetViewXform extends BaseXform {
37
37
  add("showRuler", "0", model.showRuler === false);
38
38
  add("showRowColHeaders", "0", model.showRowColHeaders === false);
39
39
  add("showGridLines", "0", model.showGridLines === false);
40
- add("zoomScale", model.zoomScale, model.zoomScale);
41
- add("zoomScaleNormal", model.zoomScaleNormal, model.zoomScaleNormal);
40
+ add("zoomScale", model.zoomScale, model.zoomScale !== undefined && model.zoomScale !== 100);
41
+ add("zoomScaleNormal", model.zoomScaleNormal, model.zoomScaleNormal !== undefined && model.zoomScaleNormal !== 100);
42
42
  add("view", model.style, model.style);
43
43
  let topLeftCell;
44
44
  let xSplit;
@@ -143,6 +143,7 @@ class SheetViewXform extends BaseXform {
143
143
  model = this.model = {
144
144
  workbookViewId: this.sheetView.workbookViewId,
145
145
  rightToLeft: this.sheetView.rightToLeft,
146
+ tabSelected: this.sheetView.tabSelected,
146
147
  state: VIEW_STATES[this.pane.state] || "split", // split is default
147
148
  xSplit: this.pane.xSplit,
148
149
  ySplit: this.pane.ySplit,
@@ -168,6 +169,7 @@ class SheetViewXform extends BaseXform {
168
169
  model = this.model = {
169
170
  workbookViewId: this.sheetView.workbookViewId,
170
171
  rightToLeft: this.sheetView.rightToLeft,
172
+ tabSelected: this.sheetView.tabSelected,
171
173
  state: "normal",
172
174
  showRuler: this.sheetView.showRuler,
173
175
  showRowColHeaders: this.sheetView.showRowColHeaders,