@cj-tech-master/excelts 4.2.3-canary.20260122073152.a9bb6b0 → 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.
@@ -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 === "") {
@@ -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;
@@ -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 === "") {
@@ -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;
@@ -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 === "") {
@@ -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;