@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.
- package/dist/browser/modules/excel/utils/parse-sax.d.ts +0 -3
- package/dist/browser/modules/excel/utils/parse-sax.js +13 -32
- package/dist/browser/modules/excel/xlsx/xform/base-xform.js +68 -1
- package/dist/cjs/modules/excel/utils/parse-sax.js +13 -32
- package/dist/cjs/modules/excel/xlsx/xform/base-xform.js +68 -1
- package/dist/esm/modules/excel/utils/parse-sax.js +13 -32
- package/dist/esm/modules/excel/xlsx/xform/base-xform.js +68 -1
- package/dist/iife/excelts.iife.js +60 -27
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +24 -24
- package/dist/types/modules/excel/utils/parse-sax.d.ts +0 -3
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|