@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
@@ -146,12 +146,6 @@ const XML_ENTITIES = {
146
146
  quot: '"',
147
147
  apos: "'"
148
148
  };
149
- // HAN CELL namespace prefix normalization
150
- // HAN CELL uses non-standard namespace prefixes (ep:, cp:, dc:, etc.)
151
- // The x: prefix for spreadsheetml is detected dynamically from xmlns declarations
152
- // See: https://github.com/exceljs/exceljs/issues/3014
153
- const HAN_CELL_PREFIXES = /^(ep|cp|dc|dcterms|dcmitype|vt):/;
154
- const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
155
149
  // ============================================================================
156
150
  // Parser States
157
151
  // ============================================================================
@@ -212,8 +206,6 @@ class SaxesParser {
212
206
  this.chunkPosition = 0;
213
207
  // Entity storage
214
208
  this.ENTITIES = { ...XML_ENTITIES };
215
- // HAN CELL compatibility: spreadsheetml namespace prefix (e.g., "x")
216
- this.nsPrefix = null;
217
209
  this.trackPosition = opt?.position !== false;
218
210
  this.fileName = opt?.fileName;
219
211
  this.fragment = opt?.fragment ?? false;
@@ -248,14 +240,6 @@ class SaxesParser {
248
240
  this.chunk = "";
249
241
  this.i = 0;
250
242
  this.prevI = 0;
251
- this.nsPrefix = null;
252
- }
253
- // Strip HAN CELL namespace prefixes from element names
254
- stripNsPrefix(name) {
255
- const n = name.replace(HAN_CELL_PREFIXES, "");
256
- return this.nsPrefix && n.startsWith(this.nsPrefix + ":")
257
- ? n.slice(this.nsPrefix.length + 1)
258
- : n;
259
243
  }
260
244
  on(name, handler) {
261
245
  switch (name) {
@@ -671,8 +655,9 @@ class SaxesParser {
671
655
  this.name += charFromCode(c);
672
656
  return;
673
657
  }
658
+ // Tag name complete
674
659
  this.tag = {
675
- name: this.stripNsPrefix(this.name),
660
+ name: this.name,
676
661
  attributes: Object.create(null),
677
662
  isSelfClosing: false
678
663
  };
@@ -1111,7 +1096,11 @@ class SaxesParser {
1111
1096
  openTag() {
1112
1097
  const tag = this.tag;
1113
1098
  tag.isSelfClosing = false;
1114
- this.processAttributes(tag);
1099
+ // Copy attributes from list to object
1100
+ for (const { name, value } of this.attribList) {
1101
+ tag.attributes[name] = value;
1102
+ }
1103
+ this.attribList = [];
1115
1104
  this.openTagHandler?.(tag);
1116
1105
  this.tags.push(tag);
1117
1106
  this.name = "";
@@ -1120,7 +1109,11 @@ class SaxesParser {
1120
1109
  openSelfClosingTag() {
1121
1110
  const tag = this.tag;
1122
1111
  tag.isSelfClosing = true;
1123
- this.processAttributes(tag);
1112
+ // Copy attributes from list to object
1113
+ for (const { name, value } of this.attribList) {
1114
+ tag.attributes[name] = value;
1115
+ }
1116
+ this.attribList = [];
1124
1117
  this.openTagHandler?.(tag);
1125
1118
  this.closeTagHandler?.(tag);
1126
1119
  if (this.tags.length === 0) {
@@ -1129,20 +1122,8 @@ class SaxesParser {
1129
1122
  this.name = "";
1130
1123
  this.state = S_TEXT;
1131
1124
  }
1132
- // Process attributes and detect spreadsheetml namespace prefix
1133
- processAttributes(tag) {
1134
- for (const { name, value } of this.attribList) {
1135
- tag.attributes[name] = value;
1136
- if (name.startsWith("xmlns:") && value === SPREADSHEETML_NS) {
1137
- this.nsPrefix = name.slice(6);
1138
- tag.name = this.stripNsPrefix(tag.name);
1139
- }
1140
- }
1141
- this.attribList = [];
1142
- }
1143
1125
  closeTag() {
1144
- const { tags } = this;
1145
- const name = this.stripNsPrefix(this.name);
1126
+ const { tags, name } = this;
1146
1127
  this.state = S_TEXT;
1147
1128
  this.name = "";
1148
1129
  if (name === "") {
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * PassthroughManager - Manages passthrough files for round-trip preservation
4
+ *
5
+ * This module handles files that are not fully parsed by the library but need to be
6
+ * preserved during read/write cycles (e.g., charts, sparklines, slicers).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.PassthroughManager = void 0;
10
+ // Pre-compiled regex patterns for content type detection (performance optimization)
11
+ const chartXmlRegex = /^xl\/charts\/chart\d+\.xml$/;
12
+ const chartStyleXmlRegex = /^xl\/charts\/style\d+\.xml$/;
13
+ const chartColorsXmlRegex = /^xl\/charts\/colors\d+\.xml$/;
14
+ /**
15
+ * Content type definitions for passthrough files
16
+ */
17
+ const PASSTHROUGH_CONTENT_TYPES = new Map([
18
+ [chartXmlRegex, "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"],
19
+ [chartStyleXmlRegex, "application/vnd.ms-office.chartstyle+xml"],
20
+ [chartColorsXmlRegex, "application/vnd.ms-office.chartcolorstyle+xml"]
21
+ ]);
22
+ /**
23
+ * Passthrough path prefixes that should be preserved
24
+ */
25
+ const PASSTHROUGH_PREFIXES = ["xl/charts/"];
26
+ /**
27
+ * PassthroughManager handles storage and retrieval of passthrough files
28
+ * that need to be preserved during Excel read/write cycles.
29
+ */
30
+ class PassthroughManager {
31
+ constructor() {
32
+ this.files = new Map();
33
+ }
34
+ /**
35
+ * Check if a path should be treated as passthrough
36
+ */
37
+ static isPassthroughPath(path) {
38
+ return PASSTHROUGH_PREFIXES.some(prefix => path.startsWith(prefix));
39
+ }
40
+ /**
41
+ * Get the content type for a passthrough file path
42
+ * @returns Content type string or undefined if unknown
43
+ */
44
+ static getContentType(path) {
45
+ // Chart relationships are handled by Default extension="rels"
46
+ if (path.startsWith("xl/charts/_rels/")) {
47
+ return undefined;
48
+ }
49
+ for (const [regex, contentType] of PASSTHROUGH_CONTENT_TYPES) {
50
+ if (regex.test(path)) {
51
+ return contentType;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ /**
57
+ * Add a file to passthrough storage
58
+ */
59
+ add(path, data) {
60
+ this.files.set(path, data);
61
+ }
62
+ /**
63
+ * Get a file from passthrough storage
64
+ */
65
+ get(path) {
66
+ return this.files.get(path);
67
+ }
68
+ /**
69
+ * Check if a file exists in passthrough storage
70
+ */
71
+ has(path) {
72
+ return this.files.has(path);
73
+ }
74
+ /**
75
+ * Get all stored paths
76
+ */
77
+ getPaths() {
78
+ return [...this.files.keys()];
79
+ }
80
+ /**
81
+ * Get all files as a record (for serialization)
82
+ */
83
+ toRecord() {
84
+ const record = {};
85
+ for (const [path, data] of this.files) {
86
+ record[path] = data;
87
+ }
88
+ return record;
89
+ }
90
+ /**
91
+ * Load files from a record (for deserialization)
92
+ */
93
+ fromRecord(record) {
94
+ this.files.clear();
95
+ for (const [path, data] of Object.entries(record)) {
96
+ this.files.set(path, data);
97
+ }
98
+ }
99
+ /**
100
+ * Get content types for all stored files that have known types
101
+ */
102
+ getContentTypes() {
103
+ const contentTypes = [];
104
+ for (const path of this.files.keys()) {
105
+ const contentType = PassthroughManager.getContentType(path);
106
+ if (contentType) {
107
+ contentTypes.push({ partName: path, contentType });
108
+ }
109
+ }
110
+ return contentTypes;
111
+ }
112
+ /**
113
+ * Write all passthrough files to a ZIP writer
114
+ */
115
+ writeToZip(zip) {
116
+ for (const [path, data] of this.files) {
117
+ zip.append(data, { name: path });
118
+ }
119
+ }
120
+ /**
121
+ * Clear all stored files
122
+ */
123
+ clear() {
124
+ this.files.clear();
125
+ }
126
+ /**
127
+ * Get the number of stored files
128
+ */
129
+ get size() {
130
+ return this.files.size;
131
+ }
132
+ }
133
+ exports.PassthroughManager = PassthroughManager;
@@ -41,6 +41,8 @@ class Workbook {
41
41
  this.views = [];
42
42
  this.media = [];
43
43
  this.pivotTables = [];
44
+ this._passthrough = {};
45
+ this._rawDrawings = {};
44
46
  this._definedNames = new defined_names_1.DefinedNames();
45
47
  }
46
48
  // ===========================================================================
@@ -254,7 +256,10 @@ class Workbook {
254
256
  themes: this._themes,
255
257
  media: this.media,
256
258
  pivotTables: this.pivotTables,
257
- calcProperties: this.calcProperties
259
+ calcProperties: this.calcProperties,
260
+ passthrough: this._passthrough,
261
+ rawDrawings: this._rawDrawings,
262
+ defaultFont: this._defaultFont
258
263
  };
259
264
  }
260
265
  set model(value) {
@@ -295,6 +300,12 @@ class Workbook {
295
300
  // Handle pivot tables - either newly created or loaded from file
296
301
  // Loaded pivot tables come from loadedPivotTables after reconciliation
297
302
  this.pivotTables = value.pivotTables || value.loadedPivotTables || [];
303
+ // Preserve passthrough files (charts, etc.) for round-trip preservation
304
+ this._passthrough = value.passthrough || {};
305
+ // Preserve raw drawing data for drawings with chart references
306
+ this._rawDrawings = value.rawDrawings || {};
307
+ // Preserve default font for round-trip fidelity
308
+ this._defaultFont = value.defaultFont;
298
309
  }
299
310
  }
300
311
  exports.Workbook = Workbook;
@@ -900,7 +900,8 @@ class Worksheet {
900
900
  tables: Object.values(this.tables).map(table => table.model),
901
901
  pivotTables: this.pivotTables,
902
902
  conditionalFormattings: this.conditionalFormattings,
903
- formControls: this.formControls.map(fc => fc.model)
903
+ formControls: this.formControls.map(fc => fc.model),
904
+ drawing: this._drawing
904
905
  };
905
906
  // =================================================
906
907
  // columns
@@ -968,6 +969,8 @@ class Worksheet {
968
969
  this.conditionalFormattings = value.conditionalFormattings;
969
970
  // Form controls are currently write-only (not parsed from XLSX)
970
971
  this.formControls = [];
972
+ // Preserve loaded drawing data (charts, etc.)
973
+ this._drawing = value.drawing;
971
974
  }
972
975
  }
973
976
  exports.Worksheet = Worksheet;
@@ -3,6 +3,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaseXform = void 0;
4
4
  const parse_sax_1 = require("../../utils/parse-sax.js");
5
5
  const xml_stream_1 = require("../../utils/xml-stream.js");
6
+ // HAN CELL namespace prefix normalization
7
+ // HAN CELL uses non-standard namespace prefixes (ep:, cp:, dc:, etc.)
8
+ // See: https://github.com/exceljs/exceljs/issues/3014
9
+ const HAN_CELL_PREFIXES = new Set(["ep", "cp", "dc", "dcterms", "dcmitype", "vt"]);
10
+ const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
11
+ // Detect HAN CELL mode from first tag. Returns:
12
+ // - undefined: normal file (no prefix handling needed)
13
+ // - null: HAN CELL file without spreadsheetml prefix (uses static prefixes only)
14
+ // - string: HAN CELL file with spreadsheetml prefix (e.g., "x")
15
+ function detectHanCellPrefix(tagName, attrs) {
16
+ for (const key in attrs) {
17
+ if (key.length > 6 && key.startsWith("xmlns:")) {
18
+ const prefix = key.slice(6);
19
+ // Check for spreadsheetml namespace prefix
20
+ if (attrs[key] === SPREADSHEETML_NS) {
21
+ return prefix;
22
+ }
23
+ // Check if xmlns declares a known HAN CELL prefix (e.g., xmlns:dc, xmlns:dcterms)
24
+ if (HAN_CELL_PREFIXES.has(prefix)) {
25
+ return null;
26
+ }
27
+ }
28
+ }
29
+ // Check if tag name has a known static prefix
30
+ const i = tagName.indexOf(":");
31
+ return i !== -1 && HAN_CELL_PREFIXES.has(tagName.slice(0, i)) ? null : undefined;
32
+ }
33
+ // Strip known namespace prefix from element name
34
+ function stripPrefix(name, nsPrefix) {
35
+ const i = name.indexOf(":");
36
+ if (i === -1) {
37
+ return name;
38
+ }
39
+ const p = name.slice(0, i);
40
+ return p === nsPrefix || HAN_CELL_PREFIXES.has(p) ? name.slice(i + 1) : name;
41
+ }
6
42
  // Base class for Xforms
7
43
  class BaseXform {
8
44
  // ============================================================
@@ -54,19 +90,50 @@ class BaseXform {
54
90
  // destroys the underlying stream and can surface as AbortError (ABORT_ERR).
55
91
  let done = false;
56
92
  let finalModel;
93
+ // HAN CELL compatibility: 0 = not checked, 1 = normal file, 2 = HAN CELL file
94
+ let nsMode = 0;
95
+ let nsPrefix = null;
57
96
  for await (const events of saxParser) {
58
97
  if (done) {
59
98
  continue;
60
99
  }
61
100
  for (const { eventType, value } of events) {
62
101
  if (eventType === "opentag") {
102
+ // Fast path for normal Excel files (majority case)
103
+ if (nsMode === 1) {
104
+ this.parseOpen(value);
105
+ continue;
106
+ }
107
+ // First tag - detect mode
108
+ if (nsMode === 0) {
109
+ const prefix = detectHanCellPrefix(value.name, value.attributes);
110
+ if (prefix === undefined) {
111
+ nsMode = 1;
112
+ this.parseOpen(value);
113
+ continue;
114
+ }
115
+ nsMode = 2;
116
+ nsPrefix = prefix;
117
+ }
118
+ // HAN CELL mode - strip prefix
119
+ value.name = stripPrefix(value.name, nsPrefix);
63
120
  this.parseOpen(value);
64
121
  }
65
122
  else if (eventType === "text") {
66
123
  this.parseText(value);
67
124
  }
68
125
  else if (eventType === "closetag") {
69
- if (!this.parseClose(value.name)) {
126
+ // Fast path for normal files
127
+ if (nsMode === 1) {
128
+ if (!this.parseClose(value.name)) {
129
+ done = true;
130
+ finalModel = this.model;
131
+ break;
132
+ }
133
+ continue;
134
+ }
135
+ // HAN CELL mode - strip prefix
136
+ if (!this.parseClose(stripPrefix(value.name, nsPrefix))) {
70
137
  done = true;
71
138
  finalModel = this.model;
72
139
  break;
@@ -96,11 +96,16 @@ class ContentTypesXform extends base_xform_1.BaseXform {
96
96
  });
97
97
  });
98
98
  }
99
- if (model.commentRefs) {
99
+ // VML extension is needed for comments or form controls
100
+ const hasComments = model.commentRefs && model.commentRefs.length > 0;
101
+ const hasFormControls = model.formControlRefs && model.formControlRefs.length > 0;
102
+ if (hasComments || hasFormControls) {
100
103
  xmlStream.leafNode("Default", {
101
104
  Extension: "vml",
102
105
  ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
103
106
  });
107
+ }
108
+ if (hasComments) {
104
109
  model.commentRefs.forEach(({ commentName }) => {
105
110
  xmlStream.leafNode("Override", {
106
111
  PartName: (0, ooxml_paths_1.toContentTypesPartName)((0, ooxml_paths_1.commentsPathFromName)(commentName)),
@@ -108,15 +113,7 @@ class ContentTypesXform extends base_xform_1.BaseXform {
108
113
  });
109
114
  });
110
115
  }
111
- // Add form control (ctrlProps) content types
112
- if (model.formControlRefs) {
113
- // Ensure vml extension is declared (may already be declared for comments)
114
- if (!model.commentRefs) {
115
- xmlStream.leafNode("Default", {
116
- Extension: "vml",
117
- ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
118
- });
119
- }
116
+ if (hasFormControls) {
120
117
  for (const ctrlPropId of model.formControlRefs) {
121
118
  xmlStream.leafNode("Override", {
122
119
  PartName: (0, ooxml_paths_1.toContentTypesPartName)((0, ooxml_paths_1.ctrlPropPath)(ctrlPropId)),
@@ -124,6 +121,15 @@ class ContentTypesXform extends base_xform_1.BaseXform {
124
121
  });
125
122
  }
126
123
  }
124
+ // Add passthrough content types (charts, etc.)
125
+ if (model.passthroughContentTypes) {
126
+ for (const { partName, contentType } of model.passthroughContentTypes) {
127
+ xmlStream.leafNode("Override", {
128
+ PartName: (0, ooxml_paths_1.toContentTypesPartName)(partName),
129
+ ContentType: contentType
130
+ });
131
+ }
132
+ }
127
133
  xmlStream.leafNode("Override", {
128
134
  PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.docPropsCore),
129
135
  ContentType: "application/vnd.openxmlformats-package.core-properties+xml"