@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.
- package/dist/browser/modules/csv/csv-core.d.ts +0 -9
- package/dist/browser/modules/csv/csv.browser.js +3 -3
- package/dist/browser/modules/excel/column.d.ts +5 -0
- package/dist/browser/modules/excel/column.js +10 -2
- package/dist/browser/modules/excel/row.d.ts +2 -0
- package/dist/browser/modules/excel/row.js +3 -1
- 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/utils/passthrough-manager.d.ts +77 -0
- package/dist/browser/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/browser/modules/excel/workbook.d.ts +12 -0
- package/dist/browser/modules/excel/workbook.js +12 -1
- package/dist/browser/modules/excel/worksheet.d.ts +4 -0
- package/dist/browser/modules/excel/worksheet.js +4 -1
- package/dist/browser/modules/excel/xlsx/xform/base-xform.js +68 -1
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +35 -11
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +271 -94
- package/dist/browser/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/row-xform.js +7 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +9 -4
- package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +4 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +40 -12
- package/dist/browser/modules/excel/xlsx/xform/style/style-xform.d.ts +7 -0
- package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +26 -6
- package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.d.ts +6 -0
- package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.js +52 -4
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +220 -131
- package/dist/browser/modules/stream/streams.browser.js +0 -3
- package/dist/cjs/modules/csv/csv.browser.js +3 -3
- package/dist/cjs/modules/excel/column.js +10 -2
- package/dist/cjs/modules/excel/row.js +3 -1
- package/dist/cjs/modules/excel/utils/parse-sax.js +13 -32
- package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
- package/dist/cjs/modules/excel/workbook.js +12 -1
- package/dist/cjs/modules/excel/worksheet.js +4 -1
- package/dist/cjs/modules/excel/xlsx/xform/base-xform.js +68 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +271 -94
- package/dist/cjs/modules/excel/xlsx/xform/sheet/row-xform.js +7 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +9 -4
- package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +4 -2
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +40 -12
- package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +26 -6
- package/dist/cjs/modules/excel/xlsx/xform/style/styles-xform.js +52 -4
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +220 -131
- package/dist/cjs/modules/stream/streams.browser.js +0 -3
- package/dist/esm/modules/csv/csv.browser.js +3 -3
- package/dist/esm/modules/excel/column.js +10 -2
- package/dist/esm/modules/excel/row.js +3 -1
- package/dist/esm/modules/excel/utils/parse-sax.js +13 -32
- package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/esm/modules/excel/workbook.js +12 -1
- package/dist/esm/modules/excel/worksheet.js +4 -1
- package/dist/esm/modules/excel/xlsx/xform/base-xform.js +68 -1
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +271 -94
- package/dist/esm/modules/excel/xlsx/xform/sheet/row-xform.js +7 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +9 -4
- package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +4 -2
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +40 -12
- package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +26 -6
- package/dist/esm/modules/excel/xlsx/xform/style/styles-xform.js +52 -4
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +220 -131
- package/dist/esm/modules/stream/streams.browser.js +0 -3
- package/dist/iife/excelts.iife.js +1009 -650
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +25 -52
- package/dist/types/modules/csv/csv-core.d.ts +0 -9
- package/dist/types/modules/excel/column.d.ts +5 -0
- package/dist/types/modules/excel/row.d.ts +2 -0
- package/dist/types/modules/excel/utils/parse-sax.d.ts +0 -3
- package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
- package/dist/types/modules/excel/workbook.d.ts +12 -0
- package/dist/types/modules/excel/worksheet.d.ts +4 -0
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +35 -11
- package/dist/types/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/style/style-xform.d.ts +7 -0
- package/dist/types/modules/excel/xlsx/xform/style/styles-xform.d.ts +6 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- 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.
|
|
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 === "") {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|