@cj-tech-master/excelts 4.2.2 → 4.2.3-canary.20260122073152.a9bb6b0

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 (63) 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 +3 -0
  4. package/dist/browser/modules/excel/utils/parse-sax.js +32 -13
  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/core/app-xform.js +3 -3
  12. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  13. package/dist/browser/modules/excel/xlsx/xform/core/core-xform.js +56 -68
  14. package/dist/browser/modules/excel/xlsx/xform/list-xform.js +8 -10
  15. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
  16. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
  17. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
  18. package/dist/browser/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
  19. package/dist/browser/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
  20. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
  21. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +213 -127
  22. package/dist/browser/modules/stream/streams.browser.js +0 -3
  23. package/dist/cjs/modules/csv/csv.browser.js +3 -3
  24. package/dist/cjs/modules/excel/utils/parse-sax.js +32 -13
  25. package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
  26. package/dist/cjs/modules/excel/workbook.js +9 -1
  27. package/dist/cjs/modules/excel/worksheet.js +4 -1
  28. package/dist/cjs/modules/excel/xlsx/xform/core/app-xform.js +3 -3
  29. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  30. package/dist/cjs/modules/excel/xlsx/xform/core/core-xform.js +56 -68
  31. package/dist/cjs/modules/excel/xlsx/xform/list-xform.js +8 -10
  32. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
  33. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
  34. package/dist/cjs/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
  35. package/dist/cjs/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
  36. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +213 -127
  37. package/dist/cjs/modules/stream/streams.browser.js +0 -3
  38. package/dist/esm/modules/csv/csv.browser.js +3 -3
  39. package/dist/esm/modules/excel/utils/parse-sax.js +32 -13
  40. package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
  41. package/dist/esm/modules/excel/workbook.js +9 -1
  42. package/dist/esm/modules/excel/worksheet.js +4 -1
  43. package/dist/esm/modules/excel/xlsx/xform/core/app-xform.js +3 -3
  44. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
  45. package/dist/esm/modules/excel/xlsx/xform/core/core-xform.js +56 -68
  46. package/dist/esm/modules/excel/xlsx/xform/list-xform.js +8 -10
  47. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
  48. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
  49. package/dist/esm/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
  50. package/dist/esm/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
  51. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +213 -127
  52. package/dist/esm/modules/stream/streams.browser.js +0 -3
  53. package/dist/iife/excelts.iife.js +603 -333
  54. package/dist/iife/excelts.iife.js.map +1 -1
  55. package/dist/iife/excelts.iife.min.js +25 -52
  56. package/dist/types/modules/csv/csv-core.d.ts +0 -9
  57. package/dist/types/modules/excel/utils/parse-sax.d.ts +3 -0
  58. package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
  59. package/dist/types/modules/excel/workbook.d.ts +8 -0
  60. package/dist/types/modules/excel/worksheet.d.ts +4 -0
  61. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
  62. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
  63. package/package.json +2 -2
@@ -37,6 +37,7 @@ const stream_1 = require("../../archive/zip/stream.js");
37
37
  const zip_parser_1 = require("../../archive/unzip/zip-parser.js");
38
38
  const _stream_1 = require("../../stream/index.js");
39
39
  const ooxml_paths_1 = require("../utils/ooxml-paths.js");
40
+ const passthrough_manager_1 = require("../utils/passthrough-manager.js");
40
41
  class StreamingZipWriterAdapter {
41
42
  constructor(options) {
42
43
  this.events = new Map();
@@ -204,6 +205,7 @@ class XLSX {
204
205
  this.addDrawings(zip, model);
205
206
  this.addTables(zip, model);
206
207
  this.addPivotTables(zip, model);
208
+ this.addPassthrough(zip, model);
207
209
  await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
208
210
  await this.addFeaturePropertyBag(zip, model);
209
211
  await this.addMedia(zip, model);
@@ -303,8 +305,12 @@ class XLSX {
303
305
  * This is the foundation for TRUE streaming reads on platforms that have a
304
306
  * streaming ZIP parser (e.g. Node.js `modules/archive` Parse).
305
307
  */
306
- async loadFromZipEntries(entries, options) {
307
- const model = {
308
+ /**
309
+ * Create an empty model for parsing XLSX files.
310
+ * Shared by loadFromZipEntries and loadFromFiles.
311
+ */
312
+ createEmptyModel() {
313
+ return {
308
314
  worksheets: [],
309
315
  worksheetHash: {},
310
316
  worksheetRels: [],
@@ -313,6 +319,8 @@ class XLSX {
313
319
  mediaIndex: {},
314
320
  drawings: {},
315
321
  drawingRels: {},
322
+ // Raw drawing XML data for passthrough (when drawing contains chart references)
323
+ rawDrawings: {},
316
324
  comments: {},
317
325
  tables: {},
318
326
  vmlDrawings: {},
@@ -320,8 +328,104 @@ class XLSX {
320
328
  pivotTableRels: {},
321
329
  pivotCacheDefinitions: {},
322
330
  pivotCacheDefinitionRels: {},
323
- pivotCacheRecords: {}
331
+ pivotCacheRecords: {},
332
+ // Passthrough storage for unknown/unsupported files (charts, etc.)
333
+ passthrough: {}
324
334
  };
335
+ }
336
+ /**
337
+ * Collect all data from a stream into a single Uint8Array.
338
+ * Reusable helper for passthrough and drawing processing.
339
+ */
340
+ async collectStreamData(stream) {
341
+ const chunks = [];
342
+ await new Promise((resolve, reject) => {
343
+ stream.on("data", (chunk) => {
344
+ if (typeof chunk === "string") {
345
+ chunks.push(new TextEncoder().encode(chunk));
346
+ }
347
+ else if (chunk instanceof Uint8Array) {
348
+ chunks.push(chunk);
349
+ }
350
+ else {
351
+ chunks.push(new Uint8Array(chunk));
352
+ }
353
+ });
354
+ stream.on("end", () => resolve());
355
+ stream.on("error", reject);
356
+ });
357
+ return (0, _stream_1.concatUint8Arrays)(chunks);
358
+ }
359
+ /**
360
+ * Check if a drawing has chart references in its relationships
361
+ */
362
+ drawingHasChartReference(drawing) {
363
+ return (drawing.rels && drawing.rels.some((rel) => rel.Target && rel.Target.includes("/charts/")));
364
+ }
365
+ /**
366
+ * Check if a drawing rels list references charts.
367
+ * Used to decide whether we need to keep raw drawing XML for passthrough.
368
+ */
369
+ drawingRelsHasChartReference(drawingRels) {
370
+ return (Array.isArray(drawingRels) &&
371
+ drawingRels.some(rel => typeof rel?.Target === "string" && rel.Target.includes("/charts/")));
372
+ }
373
+ /**
374
+ * Process a known OOXML entry (workbook, styles, shared strings, etc.)
375
+ * Returns true if handled, false if should be passed to _processDefaultEntry
376
+ */
377
+ async _processKnownEntry(stream, model, entryName, options) {
378
+ const sheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetPath)(entryName);
379
+ if (sheetNo !== undefined) {
380
+ await this._processWorksheetEntry(stream, model, sheetNo, options, entryName);
381
+ return true;
382
+ }
383
+ switch (entryName) {
384
+ case ooxml_paths_1.OOXML_PATHS.rootRels:
385
+ model.globalRels = await this.parseRels(stream);
386
+ return true;
387
+ case ooxml_paths_1.OOXML_PATHS.xlWorkbook: {
388
+ const workbook = await this.parseWorkbook(stream);
389
+ model.sheets = workbook.sheets;
390
+ model.definedNames = workbook.definedNames;
391
+ model.views = workbook.views;
392
+ model.properties = workbook.properties;
393
+ model.calcProperties = workbook.calcProperties;
394
+ model.pivotCaches = workbook.pivotCaches;
395
+ return true;
396
+ }
397
+ case ooxml_paths_1.OOXML_PATHS.xlSharedStrings:
398
+ model.sharedStrings = new shared_strings_xform_1.SharedStringsXform();
399
+ await model.sharedStrings.parseStream(stream);
400
+ return true;
401
+ case ooxml_paths_1.OOXML_PATHS.xlWorkbookRels:
402
+ model.workbookRels = await this.parseRels(stream);
403
+ return true;
404
+ case ooxml_paths_1.OOXML_PATHS.docPropsApp: {
405
+ const appXform = new app_xform_1.AppXform();
406
+ const appProperties = await appXform.parseStream(stream);
407
+ if (appProperties) {
408
+ model.company = appProperties.company;
409
+ model.manager = appProperties.manager;
410
+ }
411
+ return true;
412
+ }
413
+ case ooxml_paths_1.OOXML_PATHS.docPropsCore: {
414
+ const coreXform = new core_xform_1.CoreXform();
415
+ const coreProperties = await coreXform.parseStream(stream);
416
+ Object.assign(model, coreProperties);
417
+ return true;
418
+ }
419
+ case ooxml_paths_1.OOXML_PATHS.xlStyles:
420
+ model.styles = new styles_xform_1.StylesXform();
421
+ await model.styles.parseStream(stream);
422
+ return true;
423
+ default:
424
+ return false;
425
+ }
426
+ }
427
+ async loadFromZipEntries(entries, options) {
428
+ const model = this.createEmptyModel();
325
429
  for await (const entry of entries) {
326
430
  let drained = false;
327
431
  const drainEntry = async () => {
@@ -338,56 +442,12 @@ class XLSX {
338
442
  const entryName = (0, ooxml_paths_1.normalizeZipPath)(entry.name);
339
443
  const stream = entry.stream;
340
444
  try {
341
- const sheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetPath)(entryName);
342
- if (sheetNo !== undefined) {
343
- await this._processWorksheetEntry(stream, model, sheetNo, options, entryName);
344
- continue;
345
- }
346
- switch (entryName) {
347
- case ooxml_paths_1.OOXML_PATHS.rootRels:
348
- model.globalRels = await this.parseRels(stream);
349
- break;
350
- case ooxml_paths_1.OOXML_PATHS.xlWorkbook: {
351
- const workbook = await this.parseWorkbook(stream);
352
- model.sheets = workbook.sheets;
353
- model.definedNames = workbook.definedNames;
354
- model.views = workbook.views;
355
- model.properties = workbook.properties;
356
- model.calcProperties = workbook.calcProperties;
357
- model.pivotCaches = workbook.pivotCaches;
358
- break;
359
- }
360
- case ooxml_paths_1.OOXML_PATHS.xlSharedStrings:
361
- model.sharedStrings = new shared_strings_xform_1.SharedStringsXform();
362
- await model.sharedStrings.parseStream(stream);
363
- break;
364
- case ooxml_paths_1.OOXML_PATHS.xlWorkbookRels:
365
- model.workbookRels = await this.parseRels(stream);
366
- break;
367
- case ooxml_paths_1.OOXML_PATHS.docPropsApp: {
368
- const appXform = new app_xform_1.AppXform();
369
- const appProperties = await appXform.parseStream(stream);
370
- model.company = appProperties.company;
371
- model.manager = appProperties.manager;
372
- break;
373
- }
374
- case ooxml_paths_1.OOXML_PATHS.docPropsCore: {
375
- const coreXform = new core_xform_1.CoreXform();
376
- const coreProperties = await coreXform.parseStream(stream);
377
- Object.assign(model, coreProperties);
378
- break;
379
- }
380
- case ooxml_paths_1.OOXML_PATHS.xlStyles:
381
- model.styles = new styles_xform_1.StylesXform();
382
- await model.styles.parseStream(stream);
383
- break;
384
- default: {
385
- const handled = await this._processDefaultEntry(stream, model, entryName);
386
- if (!handled) {
387
- // Important for true streaming parsers: always consume unknown entries
388
- await drainEntry();
389
- }
390
- break;
445
+ const handled = await this._processKnownEntry(stream, model, entryName, options);
446
+ if (!handled) {
447
+ const defaultHandled = await this._processDefaultEntry(stream, model, entryName);
448
+ if (!defaultHandled) {
449
+ // Important for true streaming parsers: always consume unknown entries
450
+ await drainEntry();
391
451
  }
392
452
  }
393
453
  }
@@ -501,6 +561,15 @@ class XLSX {
501
561
  drawingXform.reconcile(drawing, drawingOptions);
502
562
  }
503
563
  });
564
+ // Trim raw drawings for non-chart drawings to avoid bloating the serialized workbook model.
565
+ if (model.rawDrawings && model.drawingRels) {
566
+ for (const name of Object.keys(model.rawDrawings)) {
567
+ const drawingRel = model.drawingRels[name];
568
+ if (drawingRel && !this.drawingRelsHasChartReference(drawingRel)) {
569
+ delete model.rawDrawings[name];
570
+ }
571
+ }
572
+ }
504
573
  // reconcile tables with the default styles
505
574
  const tableOptions = {
506
575
  styles: model.styles
@@ -517,6 +586,7 @@ class XLSX {
517
586
  mediaIndex: model.mediaIndex,
518
587
  date1904: model.properties && model.properties.date1904,
519
588
  drawings: model.drawings,
589
+ drawingRels: model.drawingRels,
520
590
  comments: model.comments,
521
591
  tables: model.tables,
522
592
  vmlDrawings: model.vmlDrawings,
@@ -693,9 +763,15 @@ class XLSX {
693
763
  }
694
764
  }
695
765
  async _processDrawingEntry(entry, model, name) {
766
+ // Collect raw data first so we can preserve drawings that reference charts.
767
+ const rawData = await this.collectStreamData(entry);
768
+ // Parse the drawing for normal processing (images, etc.)
696
769
  const xform = new drawing_xform_1.DrawingXform();
697
- const drawing = await xform.parseStream(entry);
770
+ const xmlString = this.bufferToString(rawData);
771
+ const drawing = await xform.parseStream(this.createTextStream(xmlString));
698
772
  model.drawings[name] = drawing;
773
+ // Store raw data; reconcile() may later drop it if charts are not referenced.
774
+ model.rawDrawings[name] = rawData;
699
775
  }
700
776
  async _processDrawingRelsEntry(entry, model, name) {
701
777
  const xform = new relationships_xform_1.RelationshipsXform();
@@ -770,24 +846,7 @@ class XLSX {
770
846
  // loadFromFiles - shared logic for loading from pre-extracted ZIP data
771
847
  // ===========================================================================
772
848
  async loadFromFiles(zipData, options) {
773
- const model = {
774
- worksheets: [],
775
- worksheetHash: {},
776
- worksheetRels: [],
777
- themes: {},
778
- media: [],
779
- mediaIndex: {},
780
- drawings: {},
781
- drawingRels: {},
782
- comments: {},
783
- tables: {},
784
- vmlDrawings: {},
785
- pivotTables: {},
786
- pivotTableRels: {},
787
- pivotCacheDefinitions: {},
788
- pivotCacheDefinitionRels: {},
789
- pivotCacheRecords: {}
790
- };
849
+ const model = this.createEmptyModel();
791
850
  const entries = Object.keys(zipData).map(name => ({
792
851
  name,
793
852
  dir: name.endsWith("/"),
@@ -801,52 +860,10 @@ class XLSX {
801
860
  const stream = isBinaryEntry
802
861
  ? this.createBinaryStream(entry.data)
803
862
  : this.createTextStream(this.bufferToString(entry.data));
804
- const sheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetPath)(entryName);
805
- if (sheetNo !== undefined) {
806
- await this._processWorksheetEntry(stream, model, sheetNo, options, entryName);
807
- }
808
- else {
809
- switch (entryName) {
810
- case ooxml_paths_1.OOXML_PATHS.rootRels:
811
- model.globalRels = await this.parseRels(stream);
812
- break;
813
- case ooxml_paths_1.OOXML_PATHS.xlWorkbook: {
814
- const workbook = await this.parseWorkbook(stream);
815
- model.sheets = workbook.sheets;
816
- model.definedNames = workbook.definedNames;
817
- model.views = workbook.views;
818
- model.properties = workbook.properties;
819
- model.calcProperties = workbook.calcProperties;
820
- model.pivotCaches = workbook.pivotCaches;
821
- break;
822
- }
823
- case ooxml_paths_1.OOXML_PATHS.xlSharedStrings:
824
- model.sharedStrings = new shared_strings_xform_1.SharedStringsXform();
825
- await model.sharedStrings.parseStream(stream);
826
- break;
827
- case ooxml_paths_1.OOXML_PATHS.xlWorkbookRels:
828
- model.workbookRels = await this.parseRels(stream);
829
- break;
830
- case ooxml_paths_1.OOXML_PATHS.docPropsApp: {
831
- const appXform = new app_xform_1.AppXform();
832
- const appProperties = await appXform.parseStream(stream);
833
- model.company = appProperties.company;
834
- model.manager = appProperties.manager;
835
- break;
836
- }
837
- case ooxml_paths_1.OOXML_PATHS.docPropsCore: {
838
- const coreXform = new core_xform_1.CoreXform();
839
- const coreProperties = await coreXform.parseStream(stream);
840
- Object.assign(model, coreProperties);
841
- break;
842
- }
843
- case ooxml_paths_1.OOXML_PATHS.xlStyles:
844
- model.styles = new styles_xform_1.StylesXform();
845
- await model.styles.parseStream(stream);
846
- break;
847
- default:
848
- await this._processDefaultEntry(stream, model, entryName);
849
- }
863
+ const handled = await this._processKnownEntry(stream, model, entryName, options);
864
+ if (!handled) {
865
+ // Pass raw entry data for drawings to enable passthrough
866
+ await this._processDefaultEntry(stream, model, entryName, entry.data);
850
867
  }
851
868
  }
852
869
  }
@@ -856,11 +873,11 @@ class XLSX {
856
873
  }
857
874
  /**
858
875
  * Process default entries (drawings, comments, tables, etc.)
876
+ * @param rawData Optional raw entry data for passthrough preservation (used by loadFromFiles)
859
877
  */
860
- async _processDefaultEntry(stream, model, entryName) {
861
- const worksheetRelsSheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetRelsPath)(entryName);
862
- if (worksheetRelsSheetNo !== undefined) {
863
- const sheetNo = worksheetRelsSheetNo;
878
+ async _processDefaultEntry(stream, model, entryName, rawData) {
879
+ const sheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetRelsPath)(entryName);
880
+ if (sheetNo !== undefined) {
864
881
  await this._processWorksheetRelsEntry(stream, model, sheetNo);
865
882
  return true;
866
883
  }
@@ -872,6 +889,10 @@ class XLSX {
872
889
  const drawingName = (0, ooxml_paths_1.getDrawingNameFromPath)(entryName);
873
890
  if (drawingName) {
874
891
  await this._processDrawingEntry(stream, model, drawingName);
892
+ // For loadFromFiles path, store raw data for passthrough (drawings with charts)
893
+ if (rawData) {
894
+ model.rawDrawings[drawingName] = rawData;
895
+ }
875
896
  return true;
876
897
  }
877
898
  const drawingRelsName = (0, ooxml_paths_1.getDrawingNameFromRelsPath)(entryName);
@@ -926,8 +947,27 @@ class XLSX {
926
947
  await this._processPivotCacheRecordsEntry(stream, model, pivotCacheRecordsName);
927
948
  return true;
928
949
  }
950
+ // Store passthrough files (charts, etc.) for preservation
951
+ if (passthrough_manager_1.PassthroughManager.isPassthroughPath(entryName)) {
952
+ // If raw data is available (loadFromFiles path), use it directly
953
+ if (rawData) {
954
+ model.passthrough[entryName] = rawData;
955
+ }
956
+ else {
957
+ await this._processPassthroughEntry(stream, model, entryName);
958
+ }
959
+ return true;
960
+ }
929
961
  return false;
930
962
  }
963
+ /**
964
+ * Store a passthrough file for preservation during read/write cycles.
965
+ * These files are not parsed but stored as raw bytes to be written back unchanged.
966
+ */
967
+ async _processPassthroughEntry(stream, model, entryName) {
968
+ const data = await this.collectStreamData(stream);
969
+ model.passthrough[entryName] = data;
970
+ }
931
971
  // ===========================================================================
932
972
  // Write methods - shared by all platforms
933
973
  // ===========================================================================
@@ -1070,14 +1110,46 @@ class XLSX {
1070
1110
  addDrawings(zip, model) {
1071
1111
  const drawingXform = new drawing_xform_1.DrawingXform();
1072
1112
  const relsXform = new relationships_xform_1.RelationshipsXform();
1113
+ const rawDrawings = model.rawDrawings || {};
1073
1114
  model.worksheets.forEach((worksheet) => {
1074
1115
  const { drawing } = worksheet;
1075
1116
  if (drawing) {
1076
- drawingXform.prepare(drawing);
1077
- let xml = drawingXform.toXml(drawing);
1078
- zip.append(xml, { name: (0, ooxml_paths_1.drawingPath)(drawing.name) });
1079
- xml = relsXform.toXml(drawing.rels);
1080
- zip.append(xml, { name: (0, ooxml_paths_1.drawingRelsPath)(drawing.name) });
1117
+ // Check if drawing rels contain chart references using helper
1118
+ const hasChartReference = this.drawingHasChartReference(drawing);
1119
+ if (hasChartReference && rawDrawings[drawing.name]) {
1120
+ // Use raw data for drawings with chart references (passthrough)
1121
+ zip.append(rawDrawings[drawing.name], { name: (0, ooxml_paths_1.drawingPath)(drawing.name) });
1122
+ }
1123
+ else {
1124
+ // Use regenerated XML for normal drawings (images, shapes)
1125
+ // Filter out invalid anchors (null, undefined, or missing content)
1126
+ const filteredAnchors = (drawing.anchors || []).filter((a) => {
1127
+ if (a == null) {
1128
+ return false;
1129
+ }
1130
+ // Form controls have range.br and shape properties
1131
+ if (a.range?.br && a.shape) {
1132
+ return true;
1133
+ }
1134
+ // One-cell anchors need a valid picture
1135
+ if (!a.br && !a.picture) {
1136
+ return false;
1137
+ }
1138
+ // Two-cell anchors need either picture or shape
1139
+ if (a.br && !a.picture && !a.shape) {
1140
+ return false;
1141
+ }
1142
+ return true;
1143
+ });
1144
+ const drawingForWrite = drawing.anchors
1145
+ ? { ...drawing, anchors: filteredAnchors }
1146
+ : drawing;
1147
+ drawingXform.prepare(drawingForWrite);
1148
+ const xml = drawingXform.toXml(drawingForWrite);
1149
+ zip.append(xml, { name: (0, ooxml_paths_1.drawingPath)(drawing.name) });
1150
+ }
1151
+ const relsXml = relsXform.toXml(drawing.rels);
1152
+ zip.append(relsXml, { name: (0, ooxml_paths_1.drawingRelsPath)(drawing.name) });
1081
1153
  }
1082
1154
  });
1083
1155
  }
@@ -1092,6 +1164,15 @@ class XLSX {
1092
1164
  });
1093
1165
  });
1094
1166
  }
1167
+ /**
1168
+ * Write passthrough files (charts, etc.) that were preserved during read.
1169
+ * These files are written back unchanged to preserve unsupported features.
1170
+ */
1171
+ addPassthrough(zip, model) {
1172
+ const passthroughManager = new passthrough_manager_1.PassthroughManager();
1173
+ passthroughManager.fromRecord(model.passthrough || {});
1174
+ passthroughManager.writeToZip(zip);
1175
+ }
1095
1176
  addPivotTables(zip, model) {
1096
1177
  if (!model.pivotTables.length) {
1097
1178
  return;
@@ -1184,6 +1265,11 @@ class XLSX {
1184
1265
  });
1185
1266
  // ContentTypesXform expects this flag
1186
1267
  model.hasCheckboxes = model.styles.hasCheckboxes;
1268
+ // Build passthroughContentTypes for ContentTypesXform using PassthroughManager
1269
+ const passthrough = model.passthrough || {};
1270
+ const passthroughManager = new passthrough_manager_1.PassthroughManager();
1271
+ passthroughManager.fromRecord(passthrough);
1272
+ model.passthroughContentTypes = passthroughManager.getContentTypes();
1187
1273
  }
1188
1274
  }
1189
1275
  exports.XLSX = XLSX;
@@ -890,9 +890,6 @@ class Readable extends event_emitter_1.EventEmitter {
890
890
  yield chunk;
891
891
  }
892
892
  }
893
- if (streamError) {
894
- throw streamError;
895
- }
896
893
  }
897
894
  finally {
898
895
  this.off("data", dataHandler);
@@ -112,7 +112,7 @@ class CSV {
112
112
  const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
113
113
  const map = options?.map ||
114
114
  createDefaultValueMapper(dateFormats, {
115
- decimalSeparator: options?.valueMapperOptions?.decimalSeparator ?? options?.parserOptions?.decimalSeparator
115
+ decimalSeparator: options?.valueMapperOptions?.decimalSeparator
116
116
  });
117
117
  const rows = parseCsv(str, options?.parserOptions);
118
118
  for (const row of rows) {
@@ -154,7 +154,7 @@ class CSV {
154
154
  const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
155
155
  const map = options?.map ||
156
156
  createDefaultValueMapper(dateFormats, {
157
- decimalSeparator: options?.valueMapperOptions?.decimalSeparator ?? options?.parserOptions?.decimalSeparator
157
+ decimalSeparator: options?.valueMapperOptions?.decimalSeparator
158
158
  });
159
159
  const parser = new CsvParserStream(options?.parserOptions);
160
160
  return new Promise((resolve, reject) => {
@@ -224,7 +224,7 @@ class CSV {
224
224
  const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
225
225
  const map = options?.map ||
226
226
  createDefaultValueMapper(dateFormats, {
227
- decimalSeparator: options?.valueMapperOptions?.decimalSeparator ?? options?.parserOptions?.decimalSeparator
227
+ decimalSeparator: options?.valueMapperOptions?.decimalSeparator
228
228
  });
229
229
  const parser = new CsvParserStream(options?.parserOptions);
230
230
  parser.on("data", (row) => worksheet.addRow(row.map(map)));
@@ -142,6 +142,12 @@ const XML_ENTITIES = {
142
142
  quot: '"',
143
143
  apos: "'"
144
144
  };
145
+ // HAN CELL namespace prefix normalization
146
+ // HAN CELL uses non-standard namespace prefixes (ep:, cp:, dc:, etc.)
147
+ // The x: prefix for spreadsheetml is detected dynamically from xmlns declarations
148
+ // See: https://github.com/exceljs/exceljs/issues/3014
149
+ const HAN_CELL_PREFIXES = /^(ep|cp|dc|dcterms|dcmitype|vt):/;
150
+ const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
145
151
  // ============================================================================
146
152
  // Parser States
147
153
  // ============================================================================
@@ -202,6 +208,8 @@ export class SaxesParser {
202
208
  this.chunkPosition = 0;
203
209
  // Entity storage
204
210
  this.ENTITIES = { ...XML_ENTITIES };
211
+ // HAN CELL compatibility: spreadsheetml namespace prefix (e.g., "x")
212
+ this.nsPrefix = null;
205
213
  this.trackPosition = opt?.position !== false;
206
214
  this.fileName = opt?.fileName;
207
215
  this.fragment = opt?.fragment ?? false;
@@ -236,6 +244,14 @@ export class SaxesParser {
236
244
  this.chunk = "";
237
245
  this.i = 0;
238
246
  this.prevI = 0;
247
+ this.nsPrefix = null;
248
+ }
249
+ // Strip HAN CELL namespace prefixes from element names
250
+ stripNsPrefix(name) {
251
+ const n = name.replace(HAN_CELL_PREFIXES, "");
252
+ return this.nsPrefix && n.startsWith(this.nsPrefix + ":")
253
+ ? n.slice(this.nsPrefix.length + 1)
254
+ : n;
239
255
  }
240
256
  on(name, handler) {
241
257
  switch (name) {
@@ -651,9 +667,8 @@ export class SaxesParser {
651
667
  this.name += charFromCode(c);
652
668
  return;
653
669
  }
654
- // Tag name complete
655
670
  this.tag = {
656
- name: this.name,
671
+ name: this.stripNsPrefix(this.name),
657
672
  attributes: Object.create(null),
658
673
  isSelfClosing: false
659
674
  };
@@ -1092,11 +1107,7 @@ export class SaxesParser {
1092
1107
  openTag() {
1093
1108
  const tag = this.tag;
1094
1109
  tag.isSelfClosing = false;
1095
- // Copy attributes from list to object
1096
- for (const { name, value } of this.attribList) {
1097
- tag.attributes[name] = value;
1098
- }
1099
- this.attribList = [];
1110
+ this.processAttributes(tag);
1100
1111
  this.openTagHandler?.(tag);
1101
1112
  this.tags.push(tag);
1102
1113
  this.name = "";
@@ -1105,11 +1116,7 @@ export class SaxesParser {
1105
1116
  openSelfClosingTag() {
1106
1117
  const tag = this.tag;
1107
1118
  tag.isSelfClosing = true;
1108
- // Copy attributes from list to object
1109
- for (const { name, value } of this.attribList) {
1110
- tag.attributes[name] = value;
1111
- }
1112
- this.attribList = [];
1119
+ this.processAttributes(tag);
1113
1120
  this.openTagHandler?.(tag);
1114
1121
  this.closeTagHandler?.(tag);
1115
1122
  if (this.tags.length === 0) {
@@ -1118,8 +1125,20 @@ export class SaxesParser {
1118
1125
  this.name = "";
1119
1126
  this.state = S_TEXT;
1120
1127
  }
1128
+ // Process attributes and detect spreadsheetml namespace prefix
1129
+ processAttributes(tag) {
1130
+ for (const { name, value } of this.attribList) {
1131
+ tag.attributes[name] = value;
1132
+ if (name.startsWith("xmlns:") && value === SPREADSHEETML_NS) {
1133
+ this.nsPrefix = name.slice(6);
1134
+ tag.name = this.stripNsPrefix(tag.name);
1135
+ }
1136
+ }
1137
+ this.attribList = [];
1138
+ }
1121
1139
  closeTag() {
1122
- const { tags, name } = this;
1140
+ const { tags } = this;
1141
+ const name = this.stripNsPrefix(this.name);
1123
1142
  this.state = S_TEXT;
1124
1143
  this.name = "";
1125
1144
  if (name === "") {