@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
@@ -7,18 +7,30 @@ const base_xform_1 = require("../base-xform.js");
7
7
  class PivotTableXform extends base_xform_1.BaseXform {
8
8
  constructor() {
9
9
  super();
10
+ // Parser state consolidated into object for easier reset
11
+ this.state = {
12
+ inPivotFields: false,
13
+ inRowFields: false,
14
+ inColFields: false,
15
+ inDataFields: false,
16
+ inRowItems: false,
17
+ inColItems: false,
18
+ inLocation: false,
19
+ inItems: false,
20
+ inPivotTableStyleInfo: false,
21
+ inChartFormats: false,
22
+ inPivotArea: false
23
+ };
24
+ // Current parsing context
25
+ this.currentPivotField = null;
26
+ this.currentRowItem = null;
27
+ this.currentColItem = null;
28
+ this.currentChartFormat = null;
29
+ // Buffer for collecting pivotArea XML
30
+ this.pivotAreaXmlBuffer = [];
31
+ this.pivotAreaDepth = 0;
10
32
  this.map = {};
11
33
  this.model = null;
12
- this.inPivotFields = false;
13
- this.inRowFields = false;
14
- this.inColFields = false;
15
- this.inDataFields = false;
16
- this.inRowItems = false;
17
- this.inColItems = false;
18
- this.inLocation = false;
19
- this.currentPivotField = null;
20
- this.inItems = false;
21
- this.inPivotTableStyleInfo = false;
22
34
  }
23
35
  prepare(_model) {
24
36
  // No preparation needed
@@ -29,16 +41,17 @@ class PivotTableXform extends base_xform_1.BaseXform {
29
41
  }
30
42
  reset() {
31
43
  this.model = null;
32
- this.inPivotFields = false;
33
- this.inRowFields = false;
34
- this.inColFields = false;
35
- this.inDataFields = false;
36
- this.inRowItems = false;
37
- this.inColItems = false;
38
- this.inLocation = false;
44
+ // Reset all parser state flags using object
45
+ Object.keys(this.state).forEach(key => {
46
+ this.state[key] = false;
47
+ });
48
+ // Reset current context
39
49
  this.currentPivotField = null;
40
- this.inItems = false;
41
- this.inPivotTableStyleInfo = false;
50
+ this.currentRowItem = null;
51
+ this.currentColItem = null;
52
+ this.currentChartFormat = null;
53
+ this.pivotAreaXmlBuffer = [];
54
+ this.pivotAreaDepth = 0;
42
55
  }
43
56
  /**
44
57
  * Render pivot table XML.
@@ -159,8 +172,7 @@ class PivotTableXform extends base_xform_1.BaseXform {
159
172
  * Render loaded pivot table (preserving original structure)
160
173
  */
161
174
  renderLoaded(xmlStream, model) {
162
- xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
163
- xmlStream.openNode(this.tag, {
175
+ const attrs = {
164
176
  ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
165
177
  name: model.name || "PivotTable1",
166
178
  cacheId: model.cacheId,
@@ -169,7 +181,8 @@ class PivotTableXform extends base_xform_1.BaseXform {
169
181
  applyFontFormats: model.applyFontFormats || "0",
170
182
  applyPatternFormats: model.applyPatternFormats || "0",
171
183
  applyAlignmentFormats: model.applyAlignmentFormats || "0",
172
- applyWidthHeightFormats: model.applyWidthHeightFormats || "0",
184
+ // Preserve original value when present; default to Excel's typical "0".
185
+ applyWidthHeightFormats: model.applyWidthHeightFormats ?? "0",
173
186
  dataCaption: model.dataCaption || "Values",
174
187
  updatedVersion: model.updatedVersion || "8",
175
188
  minRefreshableVersion: model.minRefreshableVersion || "3",
@@ -177,10 +190,27 @@ class PivotTableXform extends base_xform_1.BaseXform {
177
190
  itemPrintTitles: model.itemPrintTitles ? "1" : "0",
178
191
  createdVersion: model.createdVersion || "8",
179
192
  indent: model.indent !== undefined ? String(model.indent) : "0",
180
- compact: model.compact ? "1" : "0",
181
- compactData: model.compactData ? "1" : "0",
182
193
  multipleFieldFilters: model.multipleFieldFilters ? "1" : "0"
183
- });
194
+ };
195
+ // Add outline attributes if present
196
+ if (model.outline) {
197
+ attrs.outline = "1";
198
+ }
199
+ if (model.outlineData) {
200
+ attrs.outlineData = "1";
201
+ }
202
+ if (model.chartFormat !== undefined) {
203
+ attrs.chartFormat = String(model.chartFormat);
204
+ }
205
+ // Only add compact/compactData if they are true (some files don't have them)
206
+ if (model.compact) {
207
+ attrs.compact = "1";
208
+ }
209
+ if (model.compactData) {
210
+ attrs.compactData = "1";
211
+ }
212
+ xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
213
+ xmlStream.openNode(this.tag, attrs);
184
214
  // Location
185
215
  if (model.location) {
186
216
  xmlStream.leafNode("location", {
@@ -206,12 +236,19 @@ class PivotTableXform extends base_xform_1.BaseXform {
206
236
  }
207
237
  xmlStream.closeNode();
208
238
  }
209
- // Row items (simplified - just grand total)
210
- xmlStream.writeXml(`
211
- <rowItems count="1">
212
- <i t="grand"><x /></i>
213
- </rowItems>`);
239
+ // Row items - use parsed items if available; otherwise emit a minimal grand total
240
+ if (model.rowItems && model.rowItems.length > 0) {
241
+ xmlStream.openNode("rowItems", { count: model.rowItems.length });
242
+ for (const item of model.rowItems) {
243
+ this.renderRowColItem(xmlStream, item);
244
+ }
245
+ xmlStream.closeNode();
246
+ }
247
+ else {
248
+ xmlStream.writeXml('<rowItems count="1"><i t="grand"><x/></i></rowItems>');
249
+ }
214
250
  // Col fields
251
+ // Excel commonly emits a synthetic field x=-2 when there are no column fields.
215
252
  const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
216
253
  xmlStream.openNode("colFields", { count: colFieldCount });
217
254
  if (model.colFields.length === 0) {
@@ -223,25 +260,52 @@ class PivotTableXform extends base_xform_1.BaseXform {
223
260
  }
224
261
  }
225
262
  xmlStream.closeNode();
226
- // Col items (simplified - just grand total)
227
- xmlStream.writeXml(`
228
- <colItems count="1">
229
- <i t="grand"><x /></i>
230
- </colItems>`);
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>');
273
+ }
231
274
  // Data fields
232
275
  if (model.dataFields.length > 0) {
233
276
  xmlStream.openNode("dataFields", { count: model.dataFields.length });
234
277
  for (const dataField of model.dataFields) {
235
- const attrs = {
278
+ const dfAttrs = {
236
279
  name: dataField.name,
237
280
  fld: dataField.fld,
238
281
  baseField: dataField.baseField ?? 0,
239
282
  baseItem: dataField.baseItem ?? 0
240
283
  };
241
284
  if (dataField.subtotal && dataField.subtotal !== "sum") {
242
- attrs.subtotal = dataField.subtotal;
285
+ dfAttrs.subtotal = dataField.subtotal;
286
+ }
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>`);
243
307
  }
244
- xmlStream.leafNode("dataField", attrs);
308
+ xmlStream.closeNode();
245
309
  }
246
310
  xmlStream.closeNode();
247
311
  }
@@ -255,46 +319,48 @@ class PivotTableXform extends base_xform_1.BaseXform {
255
319
  showLastColumn: "1"
256
320
  });
257
321
  // Extensions
258
- xmlStream.writeXml(`
259
- <extLst>
260
- <ext
261
- uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}"
262
- xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
263
- >
264
- <x14:pivotTableDefinition
265
- hideValuesRow="1"
266
- xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"
267
- />
268
- </ext>
269
- <ext
270
- uri="{747A6164-185A-40DC-8AA5-F01512510D54}"
271
- xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"
272
- >
273
- <xpdl:pivotTableDefinition16
274
- EnabledSubtotalsDefault="0"
275
- SubtotalsOnTopDefault="0"
276
- />
277
- </ext>
278
- </extLst>
279
- `);
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>`);
280
323
  xmlStream.closeNode();
281
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
+ }
282
350
  /**
283
351
  * Render a loaded pivot field
284
352
  */
285
353
  renderPivotFieldLoaded(xmlStream, field) {
286
- const attrs = {
287
- compact: field.compact ? "1" : "0",
288
- outline: field.outline ? "1" : "0",
289
- showAll: field.showAll ? "1" : "0",
290
- defaultSubtotal: field.defaultSubtotal ? "1" : "0"
291
- };
354
+ const attrs = {};
355
+ // Only add attributes that were present in the original
292
356
  if (field.axis) {
293
357
  attrs.axis = field.axis;
294
358
  }
295
359
  if (field.dataField) {
296
360
  attrs.dataField = "1";
297
361
  }
362
+ // showAll is typically always present
363
+ attrs.showAll = field.showAll ? "1" : "0";
298
364
  if (field.items && field.items.length > 0) {
299
365
  xmlStream.openNode("pivotField", attrs);
300
366
  xmlStream.openNode("items", { count: field.items.length + 1 });
@@ -302,7 +368,7 @@ class PivotTableXform extends base_xform_1.BaseXform {
302
368
  xmlStream.leafNode("item", { x: itemIndex });
303
369
  }
304
370
  // Grand total item
305
- xmlStream.writeXml('<item t="default" />');
371
+ xmlStream.writeXml('<item t="default"/>');
306
372
  xmlStream.closeNode(); // items
307
373
  xmlStream.closeNode(); // pivotField
308
374
  }
@@ -340,6 +406,12 @@ class PivotTableXform extends base_xform_1.BaseXform {
340
406
  compact: attributes.compact === "1",
341
407
  compactData: attributes.compactData === "1",
342
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: [],
343
415
  isLoaded: true
344
416
  };
345
417
  break;
@@ -360,10 +432,10 @@ class PivotTableXform extends base_xform_1.BaseXform {
360
432
  }
361
433
  break;
362
434
  case "pivotFields":
363
- this.inPivotFields = true;
435
+ this.state.inPivotFields = true;
364
436
  break;
365
437
  case "pivotField":
366
- if (this.inPivotFields) {
438
+ if (this.state.inPivotFields) {
367
439
  this.currentPivotField = {
368
440
  axis: attributes.axis,
369
441
  dataField: attributes.dataField === "1",
@@ -377,43 +449,101 @@ class PivotTableXform extends base_xform_1.BaseXform {
377
449
  break;
378
450
  case "items":
379
451
  if (this.currentPivotField) {
380
- this.inItems = true;
452
+ this.state.inItems = true;
381
453
  }
382
454
  break;
383
455
  case "item":
384
- if (this.inItems && this.currentPivotField && attributes.x !== undefined) {
456
+ if (this.state.inItems && this.currentPivotField && attributes.x !== undefined) {
385
457
  this.currentPivotField.items.push(parseInt(attributes.x, 10));
386
458
  }
387
459
  break;
388
460
  case "rowFields":
389
- this.inRowFields = true;
461
+ this.state.inRowFields = true;
390
462
  break;
391
463
  case "colFields":
392
- this.inColFields = true;
464
+ this.state.inColFields = true;
393
465
  break;
394
466
  case "dataFields":
395
- this.inDataFields = true;
467
+ this.state.inDataFields = true;
396
468
  break;
397
469
  case "rowItems":
398
- this.inRowItems = true;
470
+ this.state.inRowItems = true;
399
471
  break;
400
472
  case "colItems":
401
- this.inColItems = true;
473
+ this.state.inColItems = true;
474
+ break;
475
+ case "i":
476
+ // Handle row/col item element
477
+ if (this.state.inRowItems && this.model) {
478
+ this.currentRowItem = { t: attributes.t, x: [] };
479
+ }
480
+ else if (this.state.inColItems && this.model) {
481
+ this.currentColItem = { t: attributes.t, x: [] };
482
+ }
483
+ break;
484
+ case "x":
485
+ // Handle x element inside row/col items or pivotArea
486
+ if (this.state.inPivotArea) {
487
+ // Collect x element for pivotArea XML
488
+ const xAttrs = Object.entries(attributes)
489
+ .map(([k, v]) => `${k}="${v}"`)
490
+ .join(" ");
491
+ this.pivotAreaXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
492
+ }
493
+ else if (this.currentRowItem) {
494
+ this.currentRowItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
495
+ }
496
+ else if (this.currentColItem) {
497
+ this.currentColItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
498
+ }
499
+ break;
500
+ case "chartFormats":
501
+ this.state.inChartFormats = true;
502
+ break;
503
+ case "chartFormat":
504
+ if (this.state.inChartFormats && this.model) {
505
+ this.currentChartFormat = {
506
+ chart: attributes.chart ? parseInt(attributes.chart, 10) : 0,
507
+ format: attributes.format ? parseInt(attributes.format, 10) : 0,
508
+ series: attributes.series === "1"
509
+ };
510
+ }
511
+ break;
512
+ case "pivotArea":
513
+ // Start collecting pivotArea XML for chartFormat
514
+ if (this.currentChartFormat) {
515
+ this.state.inPivotArea = true;
516
+ const attrsStr = Object.entries(attributes)
517
+ .map(([k, v]) => `${k}="${v}"`)
518
+ .join(" ");
519
+ this.pivotAreaXmlBuffer = [attrsStr ? `<pivotArea ${attrsStr}>` : "<pivotArea>"];
520
+ }
521
+ break;
522
+ case "references":
523
+ case "reference":
524
+ // Collect nested elements in pivotArea
525
+ if (this.state.inPivotArea) {
526
+ this.pivotAreaDepth++;
527
+ const attrsStr = Object.entries(attributes)
528
+ .map(([k, v]) => `${k}="${v}"`)
529
+ .join(" ");
530
+ this.pivotAreaXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
531
+ }
402
532
  break;
403
533
  case "field":
404
534
  // Handle field element (used in rowFields, colFields)
405
535
  if (this.model) {
406
536
  const fieldIndex = parseInt(attributes.x || "0", 10);
407
- if (this.inRowFields) {
537
+ if (this.state.inRowFields) {
408
538
  this.model.rowFields.push(fieldIndex);
409
539
  }
410
- else if (this.inColFields) {
540
+ else if (this.state.inColFields) {
411
541
  this.model.colFields.push(fieldIndex);
412
542
  }
413
543
  }
414
544
  break;
415
545
  case "dataField":
416
- if (this.inDataFields && this.model) {
546
+ if (this.state.inDataFields && this.model) {
417
547
  this.model.dataFields.push({
418
548
  name: (0, utils_1.xmlDecode)(attributes.name || ""),
419
549
  fld: parseInt(attributes.fld || "0", 10),
@@ -435,12 +565,32 @@ class PivotTableXform extends base_xform_1.BaseXform {
435
565
  // No text content in pivot table elements
436
566
  }
437
567
  parseClose(name) {
568
+ // Handle pivotArea nested elements - close tags
569
+ if (this.state.inPivotArea) {
570
+ if (name === "pivotArea") {
571
+ this.pivotAreaXmlBuffer.push("</pivotArea>");
572
+ if (this.currentChartFormat) {
573
+ this.currentChartFormat.pivotAreaXml = this.pivotAreaXmlBuffer.join("");
574
+ }
575
+ this.state.inPivotArea = false;
576
+ this.pivotAreaXmlBuffer = [];
577
+ this.pivotAreaDepth = 0;
578
+ return true;
579
+ }
580
+ else if (name === "references" || name === "reference") {
581
+ this.pivotAreaXmlBuffer.push(`</${name}>`);
582
+ this.pivotAreaDepth--;
583
+ return true;
584
+ }
585
+ // x elements are self-closing, no need to handle close
586
+ return true;
587
+ }
438
588
  switch (name) {
439
589
  case this.tag:
440
590
  // End of pivotTableDefinition
441
591
  return false;
442
592
  case "pivotFields":
443
- this.inPivotFields = false;
593
+ this.state.inPivotFields = false;
444
594
  break;
445
595
  case "pivotField":
446
596
  if (this.currentPivotField && this.model) {
@@ -449,22 +599,42 @@ class PivotTableXform extends base_xform_1.BaseXform {
449
599
  }
450
600
  break;
451
601
  case "items":
452
- this.inItems = false;
602
+ this.state.inItems = false;
453
603
  break;
454
604
  case "rowFields":
455
- this.inRowFields = false;
605
+ this.state.inRowFields = false;
456
606
  break;
457
607
  case "colFields":
458
- this.inColFields = false;
608
+ this.state.inColFields = false;
459
609
  break;
460
610
  case "dataFields":
461
- this.inDataFields = false;
611
+ this.state.inDataFields = false;
462
612
  break;
463
613
  case "rowItems":
464
- this.inRowItems = false;
614
+ this.state.inRowItems = false;
465
615
  break;
466
616
  case "colItems":
467
- this.inColItems = false;
617
+ this.state.inColItems = false;
618
+ break;
619
+ case "i":
620
+ // Finish row/col item
621
+ if (this.currentRowItem && this.model) {
622
+ this.model.rowItems.push(this.currentRowItem);
623
+ this.currentRowItem = null;
624
+ }
625
+ else if (this.currentColItem && this.model) {
626
+ this.model.colItems.push(this.currentColItem);
627
+ this.currentColItem = null;
628
+ }
629
+ break;
630
+ case "chartFormats":
631
+ this.state.inChartFormats = false;
632
+ break;
633
+ case "chartFormat":
634
+ if (this.currentChartFormat && this.model) {
635
+ this.model.chartFormats.push(this.currentChartFormat);
636
+ this.currentChartFormat = null;
637
+ }
468
638
  break;
469
639
  }
470
640
  return true;
@@ -191,6 +191,23 @@ class WorkSheetXform extends base_xform_1.BaseXform {
191
191
  vmlDrawing: `vmlDrawing${model.id}`
192
192
  });
193
193
  }
194
+ // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content
195
+ // This preserves drawings through round-trip even if they don't contain images
196
+ // Note: Always assign a new rId since rels are rebuilt during write
197
+ if (model.drawing && model.drawing.anchors) {
198
+ // This is a loaded drawing that needs to be added to relationships
199
+ const drawing = model.drawing;
200
+ drawing.rId = nextRid(rels);
201
+ if (!drawing.name) {
202
+ drawing.name = `drawing${++options.drawingsCount}`;
203
+ }
204
+ options.drawings.push(drawing);
205
+ rels.push({
206
+ Id: drawing.rId,
207
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
208
+ Target: (0, ooxml_paths_1.drawingRelTargetFromWorksheet)(drawing.name)
209
+ });
210
+ }
194
211
  const drawingRelsHash = [];
195
212
  let bookImage;
196
213
  model.media.forEach(medium => {
@@ -602,17 +619,27 @@ class WorkSheetXform extends base_xform_1.BaseXform {
602
619
  if (match) {
603
620
  const drawingName = match[1];
604
621
  const drawing = options.drawings[drawingName];
605
- drawing.anchors.forEach(anchor => {
606
- if (anchor.medium) {
607
- const image = {
608
- type: "image",
609
- imageId: anchor.medium.index,
610
- range: anchor.range,
611
- hyperlinks: anchor.picture.hyperlinks
612
- };
613
- model.media.push(image);
614
- }
615
- });
622
+ if (drawing) {
623
+ // Preserve the drawing object for round-trip (charts, etc.)
624
+ // This includes the name, anchors, and rels
625
+ model.drawing = {
626
+ ...drawing,
627
+ name: drawingName,
628
+ rels: options.drawingRels?.[drawingName] || drawing.rels || []
629
+ };
630
+ // Also extract images to model.media for backward compatibility
631
+ drawing.anchors.forEach(anchor => {
632
+ if (anchor.medium) {
633
+ const image = {
634
+ type: "image",
635
+ imageId: anchor.medium.index,
636
+ range: anchor.range,
637
+ hyperlinks: anchor.picture.hyperlinks
638
+ };
639
+ model.media.push(image);
640
+ }
641
+ });
642
+ }
616
643
  }
617
644
  }
618
645
  const backgroundRel = model.background && rels[model.background.rId];