@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
@@ -58,15 +58,6 @@ export interface CsvParseOptions {
58
58
  renameHeaders?: boolean;
59
59
  /** Comment character - lines starting with this are ignored */
60
60
  comment?: string;
61
- /**
62
- * Decimal separator used when parsing numbers from CSV (default: ".").
63
- *
64
- * Note: core CSV parsing returns strings; number conversion is handled by higher-level
65
- * consumers (e.g. the default value mapper in the CSV module).
66
- *
67
- * @deprecated Use CsvReadOptions.valueMapperOptions.decimalSeparator instead.
68
- */
69
- decimalSeparator?: "." | ",";
70
61
  /** Maximum number of data rows to parse (excluding header) */
71
62
  maxRows?: number;
72
63
  /** Number of lines to skip at the beginning (before header detection) */
@@ -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)));
@@ -11,6 +11,8 @@ export interface ColumnDefn {
11
11
  outlineLevel?: number;
12
12
  hidden?: boolean;
13
13
  style?: Partial<Style>;
14
+ /** Whether the column width is auto-fitted to content */
15
+ bestFit?: boolean;
14
16
  }
15
17
  export interface ColumnModel {
16
18
  min: number;
@@ -21,6 +23,7 @@ export interface ColumnModel {
21
23
  hidden?: boolean;
22
24
  outlineLevel?: number;
23
25
  collapsed?: boolean;
26
+ bestFit?: boolean;
24
27
  }
25
28
  /**
26
29
  * Column defines the column properties for 1 column.
@@ -36,6 +39,8 @@ declare class Column {
36
39
  width?: number;
37
40
  private _hidden;
38
41
  private _outlineLevel;
42
+ /** Whether the column width is auto-fitted to content */
43
+ bestFit?: boolean;
39
44
  /** Styles applied to the column */
40
45
  style: Partial<Style>;
41
46
  constructor(worksheet: Worksheet, number: number, defn?: ColumnDefn | false);
@@ -38,7 +38,8 @@ class Column {
38
38
  width: this.width,
39
39
  style: this.style,
40
40
  hidden: this.hidden,
41
- outlineLevel: this.outlineLevel
41
+ outlineLevel: this.outlineLevel,
42
+ bestFit: this.bestFit
42
43
  };
43
44
  }
44
45
  set defn(value) {
@@ -55,6 +56,7 @@ class Column {
55
56
  // headers must be set after style
56
57
  this.header = value.header;
57
58
  this._hidden = !!value.hidden;
59
+ this.bestFit = value.bestFit;
58
60
  }
59
61
  else {
60
62
  delete this._header;
@@ -62,6 +64,7 @@ class Column {
62
64
  delete this.width;
63
65
  this.style = {};
64
66
  this.outlineLevel = 0;
67
+ delete this.bestFit;
65
68
  }
66
69
  }
67
70
  /**
@@ -151,6 +154,7 @@ class Column {
151
154
  return (this.width === model.width &&
152
155
  this.hidden === model.hidden &&
153
156
  this.outlineLevel === model.outlineLevel &&
157
+ this.bestFit === model.bestFit &&
154
158
  isEqual(this.style, model.style));
155
159
  }
156
160
  get isDefault() {
@@ -163,6 +167,9 @@ class Column {
163
167
  if (this.outlineLevel) {
164
168
  return false;
165
169
  }
170
+ if (this.bestFit) {
171
+ return false;
172
+ }
166
173
  const s = this.style;
167
174
  if (s && (s.font || s.numFmt || s.alignment || s.border || s.fill || s.protection)) {
168
175
  return false;
@@ -313,7 +320,8 @@ class Column {
313
320
  isCustomWidth: column.isCustomWidth,
314
321
  hidden: column.hidden,
315
322
  outlineLevel: column.outlineLevel,
316
- collapsed: column.collapsed
323
+ collapsed: column.collapsed,
324
+ bestFit: column.bestFit
317
325
  };
318
326
  cols.push(col);
319
327
  }
@@ -15,6 +15,7 @@ export interface RowModel {
15
15
  hidden: boolean;
16
16
  outlineLevel: number;
17
17
  collapsed: boolean;
18
+ dyDescent?: number;
18
19
  }
19
20
  declare class Row {
20
21
  private _worksheet;
@@ -24,6 +25,7 @@ declare class Row {
24
25
  private _hidden?;
25
26
  private _outlineLevel?;
26
27
  height?: number;
28
+ dyDescent?: number;
27
29
  constructor(worksheet: Worksheet, number: number);
28
30
  /**
29
31
  * The row number
@@ -388,7 +388,8 @@ class Row {
388
388
  style: this.style,
389
389
  hidden: this.hidden,
390
390
  outlineLevel: this.outlineLevel,
391
- collapsed: this.collapsed
391
+ collapsed: this.collapsed,
392
+ dyDescent: this.dyDescent
392
393
  }
393
394
  : null;
394
395
  }
@@ -435,6 +436,7 @@ class Row {
435
436
  }
436
437
  this.hidden = value.hidden;
437
438
  this.outlineLevel = value.outlineLevel || 0;
439
+ this.dyDescent = value.dyDescent;
438
440
  this.style = (value.style && JSON.parse(JSON.stringify(value.style))) || {};
439
441
  }
440
442
  }
@@ -54,7 +54,6 @@ export declare class SaxesParser {
54
54
  private positionAtNewLine;
55
55
  private chunkPosition;
56
56
  ENTITIES: Record<string, string>;
57
- private nsPrefix;
58
57
  private textHandler?;
59
58
  private openTagHandler?;
60
59
  private closeTagHandler?;
@@ -63,7 +62,6 @@ export declare class SaxesParser {
63
62
  get closed(): boolean;
64
63
  get position(): number;
65
64
  private _init;
66
- private stripNsPrefix;
67
65
  on(name: "text", handler: TextHandler): void;
68
66
  on(name: "opentag", handler: OpenTagHandler): void;
69
67
  on(name: "closetag", handler: CloseTagHandler): void;
@@ -108,7 +106,6 @@ export declare class SaxesParser {
108
106
  private skipSpaces;
109
107
  private openTag;
110
108
  private openSelfClosingTag;
111
- private processAttributes;
112
109
  private closeTag;
113
110
  private end;
114
111
  }
@@ -142,12 +142,6 @@ 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";
151
145
  // ============================================================================
152
146
  // Parser States
153
147
  // ============================================================================
@@ -208,8 +202,6 @@ export class SaxesParser {
208
202
  this.chunkPosition = 0;
209
203
  // Entity storage
210
204
  this.ENTITIES = { ...XML_ENTITIES };
211
- // HAN CELL compatibility: spreadsheetml namespace prefix (e.g., "x")
212
- this.nsPrefix = null;
213
205
  this.trackPosition = opt?.position !== false;
214
206
  this.fileName = opt?.fileName;
215
207
  this.fragment = opt?.fragment ?? false;
@@ -244,14 +236,6 @@ export class SaxesParser {
244
236
  this.chunk = "";
245
237
  this.i = 0;
246
238
  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;
255
239
  }
256
240
  on(name, handler) {
257
241
  switch (name) {
@@ -667,8 +651,9 @@ export class SaxesParser {
667
651
  this.name += charFromCode(c);
668
652
  return;
669
653
  }
654
+ // Tag name complete
670
655
  this.tag = {
671
- name: this.stripNsPrefix(this.name),
656
+ name: this.name,
672
657
  attributes: Object.create(null),
673
658
  isSelfClosing: false
674
659
  };
@@ -1107,7 +1092,11 @@ export class SaxesParser {
1107
1092
  openTag() {
1108
1093
  const tag = this.tag;
1109
1094
  tag.isSelfClosing = false;
1110
- this.processAttributes(tag);
1095
+ // Copy attributes from list to object
1096
+ for (const { name, value } of this.attribList) {
1097
+ tag.attributes[name] = value;
1098
+ }
1099
+ this.attribList = [];
1111
1100
  this.openTagHandler?.(tag);
1112
1101
  this.tags.push(tag);
1113
1102
  this.name = "";
@@ -1116,7 +1105,11 @@ export class SaxesParser {
1116
1105
  openSelfClosingTag() {
1117
1106
  const tag = this.tag;
1118
1107
  tag.isSelfClosing = true;
1119
- this.processAttributes(tag);
1108
+ // Copy attributes from list to object
1109
+ for (const { name, value } of this.attribList) {
1110
+ tag.attributes[name] = value;
1111
+ }
1112
+ this.attribList = [];
1120
1113
  this.openTagHandler?.(tag);
1121
1114
  this.closeTagHandler?.(tag);
1122
1115
  if (this.tags.length === 0) {
@@ -1125,20 +1118,8 @@ export class SaxesParser {
1125
1118
  this.name = "";
1126
1119
  this.state = S_TEXT;
1127
1120
  }
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
- }
1139
1121
  closeTag() {
1140
- const { tags } = this;
1141
- const name = this.stripNsPrefix(this.name);
1122
+ const { tags, name } = this;
1142
1123
  this.state = S_TEXT;
1143
1124
  this.name = "";
1144
1125
  if (name === "") {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * PassthroughManager - Manages passthrough files for round-trip preservation
3
+ *
4
+ * This module handles files that are not fully parsed by the library but need to be
5
+ * preserved during read/write cycles (e.g., charts, sparklines, slicers).
6
+ */
7
+ /**
8
+ * Content type entry for ZIP content types
9
+ */
10
+ export interface PassthroughContentType {
11
+ partName: string;
12
+ contentType: string;
13
+ }
14
+ /**
15
+ * ZIP writer interface for passthrough files
16
+ */
17
+ export interface IPassthroughZipWriter {
18
+ append(data: Uint8Array, options: {
19
+ name: string;
20
+ }): void;
21
+ }
22
+ /**
23
+ * PassthroughManager handles storage and retrieval of passthrough files
24
+ * that need to be preserved during Excel read/write cycles.
25
+ */
26
+ export declare class PassthroughManager {
27
+ private files;
28
+ /**
29
+ * Check if a path should be treated as passthrough
30
+ */
31
+ static isPassthroughPath(path: string): boolean;
32
+ /**
33
+ * Get the content type for a passthrough file path
34
+ * @returns Content type string or undefined if unknown
35
+ */
36
+ static getContentType(path: string): string | undefined;
37
+ /**
38
+ * Add a file to passthrough storage
39
+ */
40
+ add(path: string, data: Uint8Array): void;
41
+ /**
42
+ * Get a file from passthrough storage
43
+ */
44
+ get(path: string): Uint8Array | undefined;
45
+ /**
46
+ * Check if a file exists in passthrough storage
47
+ */
48
+ has(path: string): boolean;
49
+ /**
50
+ * Get all stored paths
51
+ */
52
+ getPaths(): string[];
53
+ /**
54
+ * Get all files as a record (for serialization)
55
+ */
56
+ toRecord(): Record<string, Uint8Array>;
57
+ /**
58
+ * Load files from a record (for deserialization)
59
+ */
60
+ fromRecord(record: Record<string, Uint8Array>): void;
61
+ /**
62
+ * Get content types for all stored files that have known types
63
+ */
64
+ getContentTypes(): PassthroughContentType[];
65
+ /**
66
+ * Write all passthrough files to a ZIP writer
67
+ */
68
+ writeToZip(zip: IPassthroughZipWriter): void;
69
+ /**
70
+ * Clear all stored files
71
+ */
72
+ clear(): void;
73
+ /**
74
+ * Get the number of stored files
75
+ */
76
+ get size(): number;
77
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * PassthroughManager - Manages passthrough files for round-trip preservation
3
+ *
4
+ * This module handles files that are not fully parsed by the library but need to be
5
+ * preserved during read/write cycles (e.g., charts, sparklines, slicers).
6
+ */
7
+ // Pre-compiled regex patterns for content type detection (performance optimization)
8
+ const chartXmlRegex = /^xl\/charts\/chart\d+\.xml$/;
9
+ const chartStyleXmlRegex = /^xl\/charts\/style\d+\.xml$/;
10
+ const chartColorsXmlRegex = /^xl\/charts\/colors\d+\.xml$/;
11
+ /**
12
+ * Content type definitions for passthrough files
13
+ */
14
+ const PASSTHROUGH_CONTENT_TYPES = new Map([
15
+ [chartXmlRegex, "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"],
16
+ [chartStyleXmlRegex, "application/vnd.ms-office.chartstyle+xml"],
17
+ [chartColorsXmlRegex, "application/vnd.ms-office.chartcolorstyle+xml"]
18
+ ]);
19
+ /**
20
+ * Passthrough path prefixes that should be preserved
21
+ */
22
+ const PASSTHROUGH_PREFIXES = ["xl/charts/"];
23
+ /**
24
+ * PassthroughManager handles storage and retrieval of passthrough files
25
+ * that need to be preserved during Excel read/write cycles.
26
+ */
27
+ export class PassthroughManager {
28
+ constructor() {
29
+ this.files = new Map();
30
+ }
31
+ /**
32
+ * Check if a path should be treated as passthrough
33
+ */
34
+ static isPassthroughPath(path) {
35
+ return PASSTHROUGH_PREFIXES.some(prefix => path.startsWith(prefix));
36
+ }
37
+ /**
38
+ * Get the content type for a passthrough file path
39
+ * @returns Content type string or undefined if unknown
40
+ */
41
+ static getContentType(path) {
42
+ // Chart relationships are handled by Default extension="rels"
43
+ if (path.startsWith("xl/charts/_rels/")) {
44
+ return undefined;
45
+ }
46
+ for (const [regex, contentType] of PASSTHROUGH_CONTENT_TYPES) {
47
+ if (regex.test(path)) {
48
+ return contentType;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ /**
54
+ * Add a file to passthrough storage
55
+ */
56
+ add(path, data) {
57
+ this.files.set(path, data);
58
+ }
59
+ /**
60
+ * Get a file from passthrough storage
61
+ */
62
+ get(path) {
63
+ return this.files.get(path);
64
+ }
65
+ /**
66
+ * Check if a file exists in passthrough storage
67
+ */
68
+ has(path) {
69
+ return this.files.has(path);
70
+ }
71
+ /**
72
+ * Get all stored paths
73
+ */
74
+ getPaths() {
75
+ return [...this.files.keys()];
76
+ }
77
+ /**
78
+ * Get all files as a record (for serialization)
79
+ */
80
+ toRecord() {
81
+ const record = {};
82
+ for (const [path, data] of this.files) {
83
+ record[path] = data;
84
+ }
85
+ return record;
86
+ }
87
+ /**
88
+ * Load files from a record (for deserialization)
89
+ */
90
+ fromRecord(record) {
91
+ this.files.clear();
92
+ for (const [path, data] of Object.entries(record)) {
93
+ this.files.set(path, data);
94
+ }
95
+ }
96
+ /**
97
+ * Get content types for all stored files that have known types
98
+ */
99
+ getContentTypes() {
100
+ const contentTypes = [];
101
+ for (const path of this.files.keys()) {
102
+ const contentType = PassthroughManager.getContentType(path);
103
+ if (contentType) {
104
+ contentTypes.push({ partName: path, contentType });
105
+ }
106
+ }
107
+ return contentTypes;
108
+ }
109
+ /**
110
+ * Write all passthrough files to a ZIP writer
111
+ */
112
+ writeToZip(zip) {
113
+ for (const [path, data] of this.files) {
114
+ zip.append(data, { name: path });
115
+ }
116
+ }
117
+ /**
118
+ * Clear all stored files
119
+ */
120
+ clear() {
121
+ this.files.clear();
122
+ }
123
+ /**
124
+ * Get the number of stored files
125
+ */
126
+ get size() {
127
+ return this.files.size;
128
+ }
129
+ }
@@ -55,6 +55,12 @@ export interface WorkbookModel {
55
55
  /** Loaded pivot tables from file - used during reconciliation */
56
56
  loadedPivotTables?: any[];
57
57
  calcProperties: Partial<CalculationProperties>;
58
+ /** Passthrough files (charts, etc.) preserved for round-trip */
59
+ passthrough?: Record<string, Uint8Array>;
60
+ /** Raw drawing XML data for passthrough (when drawing contains chart references) */
61
+ rawDrawings?: Record<string, Uint8Array>;
62
+ /** Default font preserved from the original file for round-trip fidelity */
63
+ defaultFont?: any;
58
64
  }
59
65
  declare class Workbook {
60
66
  /**
@@ -94,6 +100,12 @@ declare class Workbook {
94
100
  private _worksheets;
95
101
  private _definedNames;
96
102
  private _themes?;
103
+ /** Passthrough files (charts, etc.) preserved for round-trip */
104
+ private _passthrough;
105
+ /** Raw drawing XML data for passthrough (when drawing contains chart references) */
106
+ private _rawDrawings;
107
+ /** Default font preserved from original file for round-trip fidelity */
108
+ private _defaultFont?;
97
109
  private _xlsx?;
98
110
  private _csv?;
99
111
  constructor();
@@ -38,6 +38,8 @@ class Workbook {
38
38
  this.views = [];
39
39
  this.media = [];
40
40
  this.pivotTables = [];
41
+ this._passthrough = {};
42
+ this._rawDrawings = {};
41
43
  this._definedNames = new DefinedNames();
42
44
  }
43
45
  // ===========================================================================
@@ -251,7 +253,10 @@ class Workbook {
251
253
  themes: this._themes,
252
254
  media: this.media,
253
255
  pivotTables: this.pivotTables,
254
- calcProperties: this.calcProperties
256
+ calcProperties: this.calcProperties,
257
+ passthrough: this._passthrough,
258
+ rawDrawings: this._rawDrawings,
259
+ defaultFont: this._defaultFont
255
260
  };
256
261
  }
257
262
  set model(value) {
@@ -292,6 +297,12 @@ class Workbook {
292
297
  // Handle pivot tables - either newly created or loaded from file
293
298
  // Loaded pivot tables come from loadedPivotTables after reconciliation
294
299
  this.pivotTables = value.pivotTables || value.loadedPivotTables || [];
300
+ // Preserve passthrough files (charts, etc.) for round-trip preservation
301
+ this._passthrough = value.passthrough || {};
302
+ // Preserve raw drawing data for drawings with chart references
303
+ this._rawDrawings = value.rawDrawings || {};
304
+ // Preserve default font for round-trip fidelity
305
+ this._defaultFont = value.defaultFont;
295
306
  }
296
307
  }
297
308
  // ===========================================================================
@@ -111,6 +111,8 @@ interface WorksheetModel {
111
111
  dimensions?: Range;
112
112
  merges?: string[];
113
113
  mergeCells?: string[];
114
+ /** Loaded drawing data (for charts, etc.) - preserved for round-trip */
115
+ drawing?: any;
114
116
  }
115
117
  declare class Worksheet {
116
118
  private _workbook;
@@ -139,6 +141,8 @@ declare class Worksheet {
139
141
  conditionalFormattings: ConditionalFormattingOptions[];
140
142
  formControls: FormCheckbox[];
141
143
  private _headerRowCount?;
144
+ /** Loaded drawing data (for charts, etc.) - preserved for round-trip */
145
+ private _drawing;
142
146
  constructor(options: WorksheetOptions);
143
147
  get name(): string;
144
148
  set name(name: string | undefined);
@@ -897,7 +897,8 @@ class Worksheet {
897
897
  tables: Object.values(this.tables).map(table => table.model),
898
898
  pivotTables: this.pivotTables,
899
899
  conditionalFormattings: this.conditionalFormattings,
900
- formControls: this.formControls.map(fc => fc.model)
900
+ formControls: this.formControls.map(fc => fc.model),
901
+ drawing: this._drawing
901
902
  };
902
903
  // =================================================
903
904
  // columns
@@ -965,6 +966,8 @@ class Worksheet {
965
966
  this.conditionalFormattings = value.conditionalFormattings;
966
967
  // Form controls are currently write-only (not parsed from XLSX)
967
968
  this.formControls = [];
969
+ // Preserve loaded drawing data (charts, etc.)
970
+ this._drawing = value.drawing;
968
971
  }
969
972
  }
970
973
  export { Worksheet };
@@ -1,5 +1,41 @@
1
1
  import { parseSax } from "../../utils/parse-sax.js";
2
2
  import { XmlStream } from "../../utils/xml-stream.js";
3
+ // HAN CELL namespace prefix normalization
4
+ // HAN CELL uses non-standard namespace prefixes (ep:, cp:, dc:, etc.)
5
+ // See: https://github.com/exceljs/exceljs/issues/3014
6
+ const HAN_CELL_PREFIXES = new Set(["ep", "cp", "dc", "dcterms", "dcmitype", "vt"]);
7
+ const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
8
+ // Detect HAN CELL mode from first tag. Returns:
9
+ // - undefined: normal file (no prefix handling needed)
10
+ // - null: HAN CELL file without spreadsheetml prefix (uses static prefixes only)
11
+ // - string: HAN CELL file with spreadsheetml prefix (e.g., "x")
12
+ function detectHanCellPrefix(tagName, attrs) {
13
+ for (const key in attrs) {
14
+ if (key.length > 6 && key.startsWith("xmlns:")) {
15
+ const prefix = key.slice(6);
16
+ // Check for spreadsheetml namespace prefix
17
+ if (attrs[key] === SPREADSHEETML_NS) {
18
+ return prefix;
19
+ }
20
+ // Check if xmlns declares a known HAN CELL prefix (e.g., xmlns:dc, xmlns:dcterms)
21
+ if (HAN_CELL_PREFIXES.has(prefix)) {
22
+ return null;
23
+ }
24
+ }
25
+ }
26
+ // Check if tag name has a known static prefix
27
+ const i = tagName.indexOf(":");
28
+ return i !== -1 && HAN_CELL_PREFIXES.has(tagName.slice(0, i)) ? null : undefined;
29
+ }
30
+ // Strip known namespace prefix from element name
31
+ function stripPrefix(name, nsPrefix) {
32
+ const i = name.indexOf(":");
33
+ if (i === -1) {
34
+ return name;
35
+ }
36
+ const p = name.slice(0, i);
37
+ return p === nsPrefix || HAN_CELL_PREFIXES.has(p) ? name.slice(i + 1) : name;
38
+ }
3
39
  // Base class for Xforms
4
40
  class BaseXform {
5
41
  // ============================================================
@@ -51,19 +87,50 @@ class BaseXform {
51
87
  // destroys the underlying stream and can surface as AbortError (ABORT_ERR).
52
88
  let done = false;
53
89
  let finalModel;
90
+ // HAN CELL compatibility: 0 = not checked, 1 = normal file, 2 = HAN CELL file
91
+ let nsMode = 0;
92
+ let nsPrefix = null;
54
93
  for await (const events of saxParser) {
55
94
  if (done) {
56
95
  continue;
57
96
  }
58
97
  for (const { eventType, value } of events) {
59
98
  if (eventType === "opentag") {
99
+ // Fast path for normal Excel files (majority case)
100
+ if (nsMode === 1) {
101
+ this.parseOpen(value);
102
+ continue;
103
+ }
104
+ // First tag - detect mode
105
+ if (nsMode === 0) {
106
+ const prefix = detectHanCellPrefix(value.name, value.attributes);
107
+ if (prefix === undefined) {
108
+ nsMode = 1;
109
+ this.parseOpen(value);
110
+ continue;
111
+ }
112
+ nsMode = 2;
113
+ nsPrefix = prefix;
114
+ }
115
+ // HAN CELL mode - strip prefix
116
+ value.name = stripPrefix(value.name, nsPrefix);
60
117
  this.parseOpen(value);
61
118
  }
62
119
  else if (eventType === "text") {
63
120
  this.parseText(value);
64
121
  }
65
122
  else if (eventType === "closetag") {
66
- if (!this.parseClose(value.name)) {
123
+ // Fast path for normal files
124
+ if (nsMode === 1) {
125
+ if (!this.parseClose(value.name)) {
126
+ done = true;
127
+ finalModel = this.model;
128
+ break;
129
+ }
130
+ continue;
131
+ }
132
+ // HAN CELL mode - strip prefix
133
+ if (!this.parseClose(stripPrefix(value.name, nsPrefix))) {
67
134
  done = true;
68
135
  finalModel = this.model;
69
136
  break;