@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
|
@@ -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 === "") {
|
|
@@ -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
|
+
}
|
|
@@ -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
|
// ===========================================================================
|
|
@@ -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
|
-
|
|
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;
|
|
@@ -93,11 +93,16 @@ class ContentTypesXform extends BaseXform {
|
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
// VML extension is needed for comments or form controls
|
|
97
|
+
const hasComments = model.commentRefs && model.commentRefs.length > 0;
|
|
98
|
+
const hasFormControls = model.formControlRefs && model.formControlRefs.length > 0;
|
|
99
|
+
if (hasComments || hasFormControls) {
|
|
97
100
|
xmlStream.leafNode("Default", {
|
|
98
101
|
Extension: "vml",
|
|
99
102
|
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
100
103
|
});
|
|
104
|
+
}
|
|
105
|
+
if (hasComments) {
|
|
101
106
|
model.commentRefs.forEach(({ commentName }) => {
|
|
102
107
|
xmlStream.leafNode("Override", {
|
|
103
108
|
PartName: toContentTypesPartName(commentsPathFromName(commentName)),
|
|
@@ -105,15 +110,7 @@ class ContentTypesXform extends BaseXform {
|
|
|
105
110
|
});
|
|
106
111
|
});
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
if (model.formControlRefs) {
|
|
110
|
-
// Ensure vml extension is declared (may already be declared for comments)
|
|
111
|
-
if (!model.commentRefs) {
|
|
112
|
-
xmlStream.leafNode("Default", {
|
|
113
|
-
Extension: "vml",
|
|
114
|
-
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
115
|
-
});
|
|
116
|
-
}
|
|
113
|
+
if (hasFormControls) {
|
|
117
114
|
for (const ctrlPropId of model.formControlRefs) {
|
|
118
115
|
xmlStream.leafNode("Override", {
|
|
119
116
|
PartName: toContentTypesPartName(ctrlPropPath(ctrlPropId)),
|
|
@@ -121,6 +118,15 @@ class ContentTypesXform extends BaseXform {
|
|
|
121
118
|
});
|
|
122
119
|
}
|
|
123
120
|
}
|
|
121
|
+
// Add passthrough content types (charts, etc.)
|
|
122
|
+
if (model.passthroughContentTypes) {
|
|
123
|
+
for (const { partName, contentType } of model.passthroughContentTypes) {
|
|
124
|
+
xmlStream.leafNode("Override", {
|
|
125
|
+
PartName: toContentTypesPartName(partName),
|
|
126
|
+
ContentType: contentType
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
124
130
|
xmlStream.leafNode("Override", {
|
|
125
131
|
PartName: toContentTypesPartName(OOXML_PATHS.docPropsCore),
|
|
126
132
|
ContentType: "application/vnd.openxmlformats-package.core-properties+xml"
|