@cj-tech-master/excelts 4.2.3-canary.20260115111903.b80904d → 4.2.3-canary.20260122075539.cc11b20

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 (48) 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/utils/parse-sax.d.ts +0 -3
  4. package/dist/browser/modules/excel/utils/parse-sax.js +13 -32
  5. package/dist/browser/modules/excel/utils/passthrough-manager.d.ts +77 -0
  6. package/dist/browser/modules/excel/utils/passthrough-manager.js +129 -0
  7. package/dist/browser/modules/excel/workbook.d.ts +8 -0
  8. package/dist/browser/modules/excel/workbook.js +9 -1
  9. package/dist/browser/modules/excel/worksheet.d.ts +4 -0
  10. package/dist/browser/modules/excel/worksheet.js +4 -1
  11. package/dist/browser/modules/excel/xlsx/xform/base-xform.js +68 -1
  12. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  13. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
  14. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
  15. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
  16. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
  17. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +213 -131
  18. package/dist/cjs/modules/csv/csv.browser.js +3 -3
  19. package/dist/cjs/modules/excel/utils/parse-sax.js +13 -32
  20. package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
  21. package/dist/cjs/modules/excel/workbook.js +9 -1
  22. package/dist/cjs/modules/excel/worksheet.js +4 -1
  23. package/dist/cjs/modules/excel/xlsx/xform/base-xform.js +68 -1
  24. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  25. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
  26. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
  27. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +213 -131
  28. package/dist/esm/modules/csv/csv.browser.js +3 -3
  29. package/dist/esm/modules/excel/utils/parse-sax.js +13 -32
  30. package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
  31. package/dist/esm/modules/excel/workbook.js +9 -1
  32. package/dist/esm/modules/excel/worksheet.js +4 -1
  33. package/dist/esm/modules/excel/xlsx/xform/base-xform.js +68 -1
  34. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  35. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
  36. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
  37. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +213 -131
  38. package/dist/iife/excelts.iife.js +571 -267
  39. package/dist/iife/excelts.iife.js.map +1 -1
  40. package/dist/iife/excelts.iife.min.js +25 -52
  41. package/dist/types/modules/csv/csv-core.d.ts +0 -9
  42. package/dist/types/modules/excel/utils/parse-sax.d.ts +0 -3
  43. package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
  44. package/dist/types/modules/excel/workbook.d.ts +8 -0
  45. package/dist/types/modules/excel/worksheet.d.ts +4 -0
  46. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
  47. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
  48. package/package.json +2 -2
@@ -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,12 +233,19 @@ 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>`);
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();
243
+ }
244
+ else {
245
+ xmlStream.writeXml('<rowItems count="1"><i t="grand"><x/></i></rowItems>');
246
+ }
211
247
  // Col fields
248
+ // Excel commonly emits a synthetic field x=-2 when there are no column fields.
212
249
  const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
213
250
  xmlStream.openNode("colFields", { count: colFieldCount });
214
251
  if (model.colFields.length === 0) {
@@ -220,25 +257,52 @@ class PivotTableXform extends BaseXform {
220
257
  }
221
258
  }
222
259
  xmlStream.closeNode();
223
- // Col items (simplified - just grand total)
224
- xmlStream.writeXml(`
225
- <colItems count="1">
226
- <i t="grand"><x /></i>
227
- </colItems>`);
260
+ // Col items - use parsed items if available
261
+ if (model.colItems && model.colItems.length > 0) {
262
+ xmlStream.openNode("colItems", { count: model.colItems.length });
263
+ for (const item of model.colItems) {
264
+ this.renderRowColItem(xmlStream, item);
265
+ }
266
+ xmlStream.closeNode();
267
+ }
268
+ else {
269
+ xmlStream.writeXml('<colItems count="1"><i t="grand"><x/></i></colItems>');
270
+ }
228
271
  // Data fields
229
272
  if (model.dataFields.length > 0) {
230
273
  xmlStream.openNode("dataFields", { count: model.dataFields.length });
231
274
  for (const dataField of model.dataFields) {
232
- const attrs = {
275
+ const dfAttrs = {
233
276
  name: dataField.name,
234
277
  fld: dataField.fld,
235
278
  baseField: dataField.baseField ?? 0,
236
279
  baseItem: dataField.baseItem ?? 0
237
280
  };
238
281
  if (dataField.subtotal && dataField.subtotal !== "sum") {
239
- attrs.subtotal = dataField.subtotal;
282
+ dfAttrs.subtotal = dataField.subtotal;
283
+ }
284
+ xmlStream.leafNode("dataField", dfAttrs);
285
+ }
286
+ xmlStream.closeNode();
287
+ }
288
+ // Chart formats (for pivot charts) - preserve original pivotArea XML
289
+ if (model.chartFormats && model.chartFormats.length > 0) {
290
+ xmlStream.openNode("chartFormats", { count: model.chartFormats.length });
291
+ for (const cf of model.chartFormats) {
292
+ xmlStream.openNode("chartFormat", {
293
+ chart: cf.chart,
294
+ format: cf.format,
295
+ series: cf.series ? "1" : undefined
296
+ });
297
+ // Use preserved pivotArea XML or fallback to default
298
+ if (cf.pivotAreaXml) {
299
+ xmlStream.writeXml(cf.pivotAreaXml);
300
+ }
301
+ else {
302
+ // Fallback for newly created chart formats (shouldn't happen for loaded models)
303
+ 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>`);
240
304
  }
241
- xmlStream.leafNode("dataField", attrs);
305
+ xmlStream.closeNode();
242
306
  }
243
307
  xmlStream.closeNode();
244
308
  }
@@ -252,46 +316,48 @@ class PivotTableXform extends BaseXform {
252
316
  showLastColumn: "1"
253
317
  });
254
318
  // 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
- `);
319
+ 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
320
  xmlStream.closeNode();
278
321
  }
322
+ /**
323
+ * Render a row or column item element
324
+ */
325
+ renderRowColItem(xmlStream, item) {
326
+ const attrs = {};
327
+ if (item.t) {
328
+ attrs.t = item.t;
329
+ }
330
+ if (item.x && item.x.length > 0) {
331
+ xmlStream.openNode("i", attrs);
332
+ for (const x of item.x) {
333
+ if (x.v && x.v !== 0) {
334
+ xmlStream.leafNode("x", { v: x.v });
335
+ }
336
+ else {
337
+ xmlStream.leafNode("x");
338
+ }
339
+ }
340
+ xmlStream.closeNode();
341
+ }
342
+ else {
343
+ // Empty item (like <i/> in colItems)
344
+ xmlStream.leafNode("i", attrs);
345
+ }
346
+ }
279
347
  /**
280
348
  * Render a loaded pivot field
281
349
  */
282
350
  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
- };
351
+ const attrs = {};
352
+ // Only add attributes that were present in the original
289
353
  if (field.axis) {
290
354
  attrs.axis = field.axis;
291
355
  }
292
356
  if (field.dataField) {
293
357
  attrs.dataField = "1";
294
358
  }
359
+ // showAll is typically always present
360
+ attrs.showAll = field.showAll ? "1" : "0";
295
361
  if (field.items && field.items.length > 0) {
296
362
  xmlStream.openNode("pivotField", attrs);
297
363
  xmlStream.openNode("items", { count: field.items.length + 1 });
@@ -299,7 +365,7 @@ class PivotTableXform extends BaseXform {
299
365
  xmlStream.leafNode("item", { x: itemIndex });
300
366
  }
301
367
  // Grand total item
302
- xmlStream.writeXml('<item t="default" />');
368
+ xmlStream.writeXml('<item t="default"/>');
303
369
  xmlStream.closeNode(); // items
304
370
  xmlStream.closeNode(); // pivotField
305
371
  }
@@ -337,6 +403,12 @@ class PivotTableXform extends BaseXform {
337
403
  compact: attributes.compact === "1",
338
404
  compactData: attributes.compactData === "1",
339
405
  multipleFieldFilters: attributes.multipleFieldFilters === "1",
406
+ outline: attributes.outline === "1",
407
+ outlineData: attributes.outlineData === "1",
408
+ chartFormat: attributes.chartFormat ? parseInt(attributes.chartFormat, 10) : undefined,
409
+ rowItems: [],
410
+ colItems: [],
411
+ chartFormats: [],
340
412
  isLoaded: true
341
413
  };
342
414
  break;
@@ -357,10 +429,10 @@ class PivotTableXform extends BaseXform {
357
429
  }
358
430
  break;
359
431
  case "pivotFields":
360
- this.inPivotFields = true;
432
+ this.state.inPivotFields = true;
361
433
  break;
362
434
  case "pivotField":
363
- if (this.inPivotFields) {
435
+ if (this.state.inPivotFields) {
364
436
  this.currentPivotField = {
365
437
  axis: attributes.axis,
366
438
  dataField: attributes.dataField === "1",
@@ -374,43 +446,101 @@ class PivotTableXform extends BaseXform {
374
446
  break;
375
447
  case "items":
376
448
  if (this.currentPivotField) {
377
- this.inItems = true;
449
+ this.state.inItems = true;
378
450
  }
379
451
  break;
380
452
  case "item":
381
- if (this.inItems && this.currentPivotField && attributes.x !== undefined) {
453
+ if (this.state.inItems && this.currentPivotField && attributes.x !== undefined) {
382
454
  this.currentPivotField.items.push(parseInt(attributes.x, 10));
383
455
  }
384
456
  break;
385
457
  case "rowFields":
386
- this.inRowFields = true;
458
+ this.state.inRowFields = true;
387
459
  break;
388
460
  case "colFields":
389
- this.inColFields = true;
461
+ this.state.inColFields = true;
390
462
  break;
391
463
  case "dataFields":
392
- this.inDataFields = true;
464
+ this.state.inDataFields = true;
393
465
  break;
394
466
  case "rowItems":
395
- this.inRowItems = true;
467
+ this.state.inRowItems = true;
396
468
  break;
397
469
  case "colItems":
398
- this.inColItems = true;
470
+ this.state.inColItems = true;
471
+ break;
472
+ case "i":
473
+ // Handle row/col item element
474
+ if (this.state.inRowItems && this.model) {
475
+ this.currentRowItem = { t: attributes.t, x: [] };
476
+ }
477
+ else if (this.state.inColItems && this.model) {
478
+ this.currentColItem = { t: attributes.t, x: [] };
479
+ }
480
+ break;
481
+ case "x":
482
+ // Handle x element inside row/col items or pivotArea
483
+ if (this.state.inPivotArea) {
484
+ // Collect x element for pivotArea XML
485
+ const xAttrs = Object.entries(attributes)
486
+ .map(([k, v]) => `${k}="${v}"`)
487
+ .join(" ");
488
+ this.pivotAreaXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
489
+ }
490
+ else if (this.currentRowItem) {
491
+ this.currentRowItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
492
+ }
493
+ else if (this.currentColItem) {
494
+ this.currentColItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
495
+ }
496
+ break;
497
+ case "chartFormats":
498
+ this.state.inChartFormats = true;
499
+ break;
500
+ case "chartFormat":
501
+ if (this.state.inChartFormats && this.model) {
502
+ this.currentChartFormat = {
503
+ chart: attributes.chart ? parseInt(attributes.chart, 10) : 0,
504
+ format: attributes.format ? parseInt(attributes.format, 10) : 0,
505
+ series: attributes.series === "1"
506
+ };
507
+ }
508
+ break;
509
+ case "pivotArea":
510
+ // Start collecting pivotArea XML for chartFormat
511
+ if (this.currentChartFormat) {
512
+ this.state.inPivotArea = true;
513
+ const attrsStr = Object.entries(attributes)
514
+ .map(([k, v]) => `${k}="${v}"`)
515
+ .join(" ");
516
+ this.pivotAreaXmlBuffer = [attrsStr ? `<pivotArea ${attrsStr}>` : "<pivotArea>"];
517
+ }
518
+ break;
519
+ case "references":
520
+ case "reference":
521
+ // Collect nested elements in pivotArea
522
+ if (this.state.inPivotArea) {
523
+ this.pivotAreaDepth++;
524
+ const attrsStr = Object.entries(attributes)
525
+ .map(([k, v]) => `${k}="${v}"`)
526
+ .join(" ");
527
+ this.pivotAreaXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
528
+ }
399
529
  break;
400
530
  case "field":
401
531
  // Handle field element (used in rowFields, colFields)
402
532
  if (this.model) {
403
533
  const fieldIndex = parseInt(attributes.x || "0", 10);
404
- if (this.inRowFields) {
534
+ if (this.state.inRowFields) {
405
535
  this.model.rowFields.push(fieldIndex);
406
536
  }
407
- else if (this.inColFields) {
537
+ else if (this.state.inColFields) {
408
538
  this.model.colFields.push(fieldIndex);
409
539
  }
410
540
  }
411
541
  break;
412
542
  case "dataField":
413
- if (this.inDataFields && this.model) {
543
+ if (this.state.inDataFields && this.model) {
414
544
  this.model.dataFields.push({
415
545
  name: xmlDecode(attributes.name || ""),
416
546
  fld: parseInt(attributes.fld || "0", 10),
@@ -432,12 +562,32 @@ class PivotTableXform extends BaseXform {
432
562
  // No text content in pivot table elements
433
563
  }
434
564
  parseClose(name) {
565
+ // Handle pivotArea nested elements - close tags
566
+ if (this.state.inPivotArea) {
567
+ if (name === "pivotArea") {
568
+ this.pivotAreaXmlBuffer.push("</pivotArea>");
569
+ if (this.currentChartFormat) {
570
+ this.currentChartFormat.pivotAreaXml = this.pivotAreaXmlBuffer.join("");
571
+ }
572
+ this.state.inPivotArea = false;
573
+ this.pivotAreaXmlBuffer = [];
574
+ this.pivotAreaDepth = 0;
575
+ return true;
576
+ }
577
+ else if (name === "references" || name === "reference") {
578
+ this.pivotAreaXmlBuffer.push(`</${name}>`);
579
+ this.pivotAreaDepth--;
580
+ return true;
581
+ }
582
+ // x elements are self-closing, no need to handle close
583
+ return true;
584
+ }
435
585
  switch (name) {
436
586
  case this.tag:
437
587
  // End of pivotTableDefinition
438
588
  return false;
439
589
  case "pivotFields":
440
- this.inPivotFields = false;
590
+ this.state.inPivotFields = false;
441
591
  break;
442
592
  case "pivotField":
443
593
  if (this.currentPivotField && this.model) {
@@ -446,22 +596,42 @@ class PivotTableXform extends BaseXform {
446
596
  }
447
597
  break;
448
598
  case "items":
449
- this.inItems = false;
599
+ this.state.inItems = false;
450
600
  break;
451
601
  case "rowFields":
452
- this.inRowFields = false;
602
+ this.state.inRowFields = false;
453
603
  break;
454
604
  case "colFields":
455
- this.inColFields = false;
605
+ this.state.inColFields = false;
456
606
  break;
457
607
  case "dataFields":
458
- this.inDataFields = false;
608
+ this.state.inDataFields = false;
459
609
  break;
460
610
  case "rowItems":
461
- this.inRowItems = false;
611
+ this.state.inRowItems = false;
462
612
  break;
463
613
  case "colItems":
464
- this.inColItems = false;
614
+ this.state.inColItems = false;
615
+ break;
616
+ case "i":
617
+ // Finish row/col item
618
+ if (this.currentRowItem && this.model) {
619
+ this.model.rowItems.push(this.currentRowItem);
620
+ this.currentRowItem = null;
621
+ }
622
+ else if (this.currentColItem && this.model) {
623
+ this.model.colItems.push(this.currentColItem);
624
+ this.currentColItem = null;
625
+ }
626
+ break;
627
+ case "chartFormats":
628
+ this.state.inChartFormats = false;
629
+ break;
630
+ case "chartFormat":
631
+ if (this.currentChartFormat && this.model) {
632
+ this.model.chartFormats.push(this.currentChartFormat);
633
+ this.currentChartFormat = null;
634
+ }
465
635
  break;
466
636
  }
467
637
  return true;
@@ -188,6 +188,23 @@ class WorkSheetXform extends BaseXform {
188
188
  vmlDrawing: `vmlDrawing${model.id}`
189
189
  });
190
190
  }
191
+ // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content
192
+ // This preserves drawings through round-trip even if they don't contain images
193
+ // Note: Always assign a new rId since rels are rebuilt during write
194
+ if (model.drawing && model.drawing.anchors) {
195
+ // This is a loaded drawing that needs to be added to relationships
196
+ const drawing = model.drawing;
197
+ drawing.rId = nextRid(rels);
198
+ if (!drawing.name) {
199
+ drawing.name = `drawing${++options.drawingsCount}`;
200
+ }
201
+ options.drawings.push(drawing);
202
+ rels.push({
203
+ Id: drawing.rId,
204
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
205
+ Target: drawingRelTargetFromWorksheet(drawing.name)
206
+ });
207
+ }
191
208
  const drawingRelsHash = [];
192
209
  let bookImage;
193
210
  model.media.forEach(medium => {
@@ -599,17 +616,27 @@ class WorkSheetXform extends BaseXform {
599
616
  if (match) {
600
617
  const drawingName = match[1];
601
618
  const drawing = options.drawings[drawingName];
602
- drawing.anchors.forEach(anchor => {
603
- if (anchor.medium) {
604
- const image = {
605
- type: "image",
606
- imageId: anchor.medium.index,
607
- range: anchor.range,
608
- hyperlinks: anchor.picture.hyperlinks
609
- };
610
- model.media.push(image);
611
- }
612
- });
619
+ if (drawing) {
620
+ // Preserve the drawing object for round-trip (charts, etc.)
621
+ // This includes the name, anchors, and rels
622
+ model.drawing = {
623
+ ...drawing,
624
+ name: drawingName,
625
+ rels: options.drawingRels?.[drawingName] || drawing.rels || []
626
+ };
627
+ // Also extract images to model.media for backward compatibility
628
+ drawing.anchors.forEach(anchor => {
629
+ if (anchor.medium) {
630
+ const image = {
631
+ type: "image",
632
+ imageId: anchor.medium.index,
633
+ range: anchor.range,
634
+ hyperlinks: anchor.picture.hyperlinks
635
+ };
636
+ model.media.push(image);
637
+ }
638
+ });
639
+ }
613
640
  }
614
641
  }
615
642
  const backgroundRel = model.background && rels[model.background.rId];
@@ -144,6 +144,30 @@ declare class XLSX {
144
144
  * This is the foundation for TRUE streaming reads on platforms that have a
145
145
  * streaming ZIP parser (e.g. Node.js `modules/archive` Parse).
146
146
  */
147
+ /**
148
+ * Create an empty model for parsing XLSX files.
149
+ * Shared by loadFromZipEntries and loadFromFiles.
150
+ */
151
+ private createEmptyModel;
152
+ /**
153
+ * Collect all data from a stream into a single Uint8Array.
154
+ * Reusable helper for passthrough and drawing processing.
155
+ */
156
+ protected collectStreamData(stream: IParseStream): Promise<Uint8Array>;
157
+ /**
158
+ * Check if a drawing has chart references in its relationships
159
+ */
160
+ private drawingHasChartReference;
161
+ /**
162
+ * Check if a drawing rels list references charts.
163
+ * Used to decide whether we need to keep raw drawing XML for passthrough.
164
+ */
165
+ private drawingRelsHasChartReference;
166
+ /**
167
+ * Process a known OOXML entry (workbook, styles, shared strings, etc.)
168
+ * Returns true if handled, false if should be passed to _processDefaultEntry
169
+ */
170
+ protected _processKnownEntry(stream: IParseStream, model: any, entryName: string, options?: XlsxOptions): Promise<boolean>;
147
171
  protected loadFromZipEntries(entries: AsyncIterable<ZipEntryLike>, options?: XlsxOptions): Promise<any>;
148
172
  /**
149
173
  * Write workbook to buffer
@@ -189,8 +213,14 @@ declare class XLSX {
189
213
  loadFromFiles(zipData: Record<string, Uint8Array>, options?: any): Promise<any>;
190
214
  /**
191
215
  * Process default entries (drawings, comments, tables, etc.)
216
+ * @param rawData Optional raw entry data for passthrough preservation (used by loadFromFiles)
192
217
  */
193
- protected _processDefaultEntry(stream: IParseStream, model: any, entryName: string): Promise<boolean>;
218
+ protected _processDefaultEntry(stream: IParseStream, model: any, entryName: string, rawData?: Uint8Array): Promise<boolean>;
219
+ /**
220
+ * Store a passthrough file for preservation during read/write cycles.
221
+ * These files are not parsed but stored as raw bytes to be written back unchanged.
222
+ */
223
+ _processPassthroughEntry(stream: IParseStream, model: any, entryName: string): Promise<void>;
194
224
  addContentTypes(zip: IZipWriter, model: any): Promise<void>;
195
225
  addApp(zip: IZipWriter, model: any): Promise<void>;
196
226
  addCore(zip: IZipWriter, model: any): Promise<void>;
@@ -204,6 +234,11 @@ declare class XLSX {
204
234
  addWorksheets(zip: IZipWriter, model: any): Promise<void>;
205
235
  addDrawings(zip: IZipWriter, model: any): void;
206
236
  addTables(zip: IZipWriter, model: any): void;
237
+ /**
238
+ * Write passthrough files (charts, etc.) that were preserved during read.
239
+ * These files are written back unchanged to preserve unsupported features.
240
+ */
241
+ addPassthrough(zip: IZipWriter, model: any): void;
207
242
  addPivotTables(zip: IZipWriter, model: any): void;
208
243
  _finalize(zip: IZipWriter): Promise<this>;
209
244
  prepareModel(model: any, options: any): void;