@cj-tech-master/excelts 4.2.2 → 4.2.3-canary.20260122073152.a9bb6b0
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/utils/parse-sax.d.ts +3 -0
- package/dist/browser/modules/excel/utils/parse-sax.js +32 -13
- 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 +8 -0
- package/dist/browser/modules/excel/workbook.js +9 -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/core/app-xform.js +3 -3
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/browser/modules/excel/xlsx/xform/core/core-xform.js +56 -68
- package/dist/browser/modules/excel/xlsx/xform/list-xform.js +8 -10
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
- package/dist/browser/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +213 -127
- 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/utils/parse-sax.js +32 -13
- package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
- package/dist/cjs/modules/excel/workbook.js +9 -1
- package/dist/cjs/modules/excel/worksheet.js +4 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/app-xform.js +3 -3
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/cjs/modules/excel/xlsx/xform/core/core-xform.js +56 -68
- package/dist/cjs/modules/excel/xlsx/xform/list-xform.js +8 -10
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
- package/dist/cjs/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +213 -127
- 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/utils/parse-sax.js +32 -13
- package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/esm/modules/excel/workbook.js +9 -1
- package/dist/esm/modules/excel/worksheet.js +4 -1
- package/dist/esm/modules/excel/xlsx/xform/core/app-xform.js +3 -3
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/esm/modules/excel/xlsx/xform/core/core-xform.js +56 -68
- package/dist/esm/modules/excel/xlsx/xform/list-xform.js +8 -10
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
- package/dist/esm/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +213 -127
- package/dist/esm/modules/stream/streams.browser.js +0 -3
- package/dist/iife/excelts.iife.js +603 -333
- 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/utils/parse-sax.d.ts +3 -0
- package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
- package/dist/types/modules/excel/workbook.d.ts +8 -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 +34 -11
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/package.json +2 -2
|
@@ -58,15 +58,6 @@ export interface CsvParseOptions {
|
|
|
58
58
|
renameHeaders?: boolean;
|
|
59
59
|
/** Comment character - lines starting with this are ignored */
|
|
60
60
|
comment?: string;
|
|
61
|
-
/**
|
|
62
|
-
* Decimal separator used when parsing numbers from CSV (default: ".").
|
|
63
|
-
*
|
|
64
|
-
* Note: core CSV parsing returns strings; number conversion is handled by higher-level
|
|
65
|
-
* consumers (e.g. the default value mapper in the CSV module).
|
|
66
|
-
*
|
|
67
|
-
* @deprecated Use CsvReadOptions.valueMapperOptions.decimalSeparator instead.
|
|
68
|
-
*/
|
|
69
|
-
decimalSeparator?: "." | ",";
|
|
70
61
|
/** Maximum number of data rows to parse (excluding header) */
|
|
71
62
|
maxRows?: number;
|
|
72
63
|
/** Number of lines to skip at the beginning (before header detection) */
|
|
@@ -112,7 +112,7 @@ class CSV {
|
|
|
112
112
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
113
113
|
const map = options?.map ||
|
|
114
114
|
createDefaultValueMapper(dateFormats, {
|
|
115
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
115
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
116
116
|
});
|
|
117
117
|
const rows = parseCsv(str, options?.parserOptions);
|
|
118
118
|
for (const row of rows) {
|
|
@@ -154,7 +154,7 @@ class CSV {
|
|
|
154
154
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
155
155
|
const map = options?.map ||
|
|
156
156
|
createDefaultValueMapper(dateFormats, {
|
|
157
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
157
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
158
158
|
});
|
|
159
159
|
const parser = new CsvParserStream(options?.parserOptions);
|
|
160
160
|
return new Promise((resolve, reject) => {
|
|
@@ -224,7 +224,7 @@ class CSV {
|
|
|
224
224
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
225
225
|
const map = options?.map ||
|
|
226
226
|
createDefaultValueMapper(dateFormats, {
|
|
227
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
227
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
228
228
|
});
|
|
229
229
|
const parser = new CsvParserStream(options?.parserOptions);
|
|
230
230
|
parser.on("data", (row) => worksheet.addRow(row.map(map)));
|
|
@@ -54,6 +54,7 @@ export declare class SaxesParser {
|
|
|
54
54
|
private positionAtNewLine;
|
|
55
55
|
private chunkPosition;
|
|
56
56
|
ENTITIES: Record<string, string>;
|
|
57
|
+
private nsPrefix;
|
|
57
58
|
private textHandler?;
|
|
58
59
|
private openTagHandler?;
|
|
59
60
|
private closeTagHandler?;
|
|
@@ -62,6 +63,7 @@ export declare class SaxesParser {
|
|
|
62
63
|
get closed(): boolean;
|
|
63
64
|
get position(): number;
|
|
64
65
|
private _init;
|
|
66
|
+
private stripNsPrefix;
|
|
65
67
|
on(name: "text", handler: TextHandler): void;
|
|
66
68
|
on(name: "opentag", handler: OpenTagHandler): void;
|
|
67
69
|
on(name: "closetag", handler: CloseTagHandler): void;
|
|
@@ -106,6 +108,7 @@ export declare class SaxesParser {
|
|
|
106
108
|
private skipSpaces;
|
|
107
109
|
private openTag;
|
|
108
110
|
private openSelfClosingTag;
|
|
111
|
+
private processAttributes;
|
|
109
112
|
private closeTag;
|
|
110
113
|
private end;
|
|
111
114
|
}
|
|
@@ -142,6 +142,12 @@ 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";
|
|
145
151
|
// ============================================================================
|
|
146
152
|
// Parser States
|
|
147
153
|
// ============================================================================
|
|
@@ -202,6 +208,8 @@ export class SaxesParser {
|
|
|
202
208
|
this.chunkPosition = 0;
|
|
203
209
|
// Entity storage
|
|
204
210
|
this.ENTITIES = { ...XML_ENTITIES };
|
|
211
|
+
// HAN CELL compatibility: spreadsheetml namespace prefix (e.g., "x")
|
|
212
|
+
this.nsPrefix = null;
|
|
205
213
|
this.trackPosition = opt?.position !== false;
|
|
206
214
|
this.fileName = opt?.fileName;
|
|
207
215
|
this.fragment = opt?.fragment ?? false;
|
|
@@ -236,6 +244,14 @@ export class SaxesParser {
|
|
|
236
244
|
this.chunk = "";
|
|
237
245
|
this.i = 0;
|
|
238
246
|
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;
|
|
239
255
|
}
|
|
240
256
|
on(name, handler) {
|
|
241
257
|
switch (name) {
|
|
@@ -651,9 +667,8 @@ export class SaxesParser {
|
|
|
651
667
|
this.name += charFromCode(c);
|
|
652
668
|
return;
|
|
653
669
|
}
|
|
654
|
-
// Tag name complete
|
|
655
670
|
this.tag = {
|
|
656
|
-
name: this.name,
|
|
671
|
+
name: this.stripNsPrefix(this.name),
|
|
657
672
|
attributes: Object.create(null),
|
|
658
673
|
isSelfClosing: false
|
|
659
674
|
};
|
|
@@ -1092,11 +1107,7 @@ export class SaxesParser {
|
|
|
1092
1107
|
openTag() {
|
|
1093
1108
|
const tag = this.tag;
|
|
1094
1109
|
tag.isSelfClosing = false;
|
|
1095
|
-
|
|
1096
|
-
for (const { name, value } of this.attribList) {
|
|
1097
|
-
tag.attributes[name] = value;
|
|
1098
|
-
}
|
|
1099
|
-
this.attribList = [];
|
|
1110
|
+
this.processAttributes(tag);
|
|
1100
1111
|
this.openTagHandler?.(tag);
|
|
1101
1112
|
this.tags.push(tag);
|
|
1102
1113
|
this.name = "";
|
|
@@ -1105,11 +1116,7 @@ export class SaxesParser {
|
|
|
1105
1116
|
openSelfClosingTag() {
|
|
1106
1117
|
const tag = this.tag;
|
|
1107
1118
|
tag.isSelfClosing = true;
|
|
1108
|
-
|
|
1109
|
-
for (const { name, value } of this.attribList) {
|
|
1110
|
-
tag.attributes[name] = value;
|
|
1111
|
-
}
|
|
1112
|
-
this.attribList = [];
|
|
1119
|
+
this.processAttributes(tag);
|
|
1113
1120
|
this.openTagHandler?.(tag);
|
|
1114
1121
|
this.closeTagHandler?.(tag);
|
|
1115
1122
|
if (this.tags.length === 0) {
|
|
@@ -1118,8 +1125,20 @@ export class SaxesParser {
|
|
|
1118
1125
|
this.name = "";
|
|
1119
1126
|
this.state = S_TEXT;
|
|
1120
1127
|
}
|
|
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
|
+
}
|
|
1121
1139
|
closeTag() {
|
|
1122
|
-
const { tags
|
|
1140
|
+
const { tags } = this;
|
|
1141
|
+
const name = this.stripNsPrefix(this.name);
|
|
1123
1142
|
this.state = S_TEXT;
|
|
1124
1143
|
this.name = "";
|
|
1125
1144
|
if (name === "") {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PassthroughManager - Manages passthrough files for round-trip preservation
|
|
3
|
+
*
|
|
4
|
+
* This module handles files that are not fully parsed by the library but need to be
|
|
5
|
+
* preserved during read/write cycles (e.g., charts, sparklines, slicers).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Content type entry for ZIP content types
|
|
9
|
+
*/
|
|
10
|
+
export interface PassthroughContentType {
|
|
11
|
+
partName: string;
|
|
12
|
+
contentType: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* ZIP writer interface for passthrough files
|
|
16
|
+
*/
|
|
17
|
+
export interface IPassthroughZipWriter {
|
|
18
|
+
append(data: Uint8Array, options: {
|
|
19
|
+
name: string;
|
|
20
|
+
}): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* PassthroughManager handles storage and retrieval of passthrough files
|
|
24
|
+
* that need to be preserved during Excel read/write cycles.
|
|
25
|
+
*/
|
|
26
|
+
export declare class PassthroughManager {
|
|
27
|
+
private files;
|
|
28
|
+
/**
|
|
29
|
+
* Check if a path should be treated as passthrough
|
|
30
|
+
*/
|
|
31
|
+
static isPassthroughPath(path: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Get the content type for a passthrough file path
|
|
34
|
+
* @returns Content type string or undefined if unknown
|
|
35
|
+
*/
|
|
36
|
+
static getContentType(path: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Add a file to passthrough storage
|
|
39
|
+
*/
|
|
40
|
+
add(path: string, data: Uint8Array): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get a file from passthrough storage
|
|
43
|
+
*/
|
|
44
|
+
get(path: string): Uint8Array | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a file exists in passthrough storage
|
|
47
|
+
*/
|
|
48
|
+
has(path: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Get all stored paths
|
|
51
|
+
*/
|
|
52
|
+
getPaths(): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Get all files as a record (for serialization)
|
|
55
|
+
*/
|
|
56
|
+
toRecord(): Record<string, Uint8Array>;
|
|
57
|
+
/**
|
|
58
|
+
* Load files from a record (for deserialization)
|
|
59
|
+
*/
|
|
60
|
+
fromRecord(record: Record<string, Uint8Array>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get content types for all stored files that have known types
|
|
63
|
+
*/
|
|
64
|
+
getContentTypes(): PassthroughContentType[];
|
|
65
|
+
/**
|
|
66
|
+
* Write all passthrough files to a ZIP writer
|
|
67
|
+
*/
|
|
68
|
+
writeToZip(zip: IPassthroughZipWriter): void;
|
|
69
|
+
/**
|
|
70
|
+
* Clear all stored files
|
|
71
|
+
*/
|
|
72
|
+
clear(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Get the number of stored files
|
|
75
|
+
*/
|
|
76
|
+
get size(): number;
|
|
77
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PassthroughManager - Manages passthrough files for round-trip preservation
|
|
3
|
+
*
|
|
4
|
+
* This module handles files that are not fully parsed by the library but need to be
|
|
5
|
+
* preserved during read/write cycles (e.g., charts, sparklines, slicers).
|
|
6
|
+
*/
|
|
7
|
+
// Pre-compiled regex patterns for content type detection (performance optimization)
|
|
8
|
+
const chartXmlRegex = /^xl\/charts\/chart\d+\.xml$/;
|
|
9
|
+
const chartStyleXmlRegex = /^xl\/charts\/style\d+\.xml$/;
|
|
10
|
+
const chartColorsXmlRegex = /^xl\/charts\/colors\d+\.xml$/;
|
|
11
|
+
/**
|
|
12
|
+
* Content type definitions for passthrough files
|
|
13
|
+
*/
|
|
14
|
+
const PASSTHROUGH_CONTENT_TYPES = new Map([
|
|
15
|
+
[chartXmlRegex, "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"],
|
|
16
|
+
[chartStyleXmlRegex, "application/vnd.ms-office.chartstyle+xml"],
|
|
17
|
+
[chartColorsXmlRegex, "application/vnd.ms-office.chartcolorstyle+xml"]
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Passthrough path prefixes that should be preserved
|
|
21
|
+
*/
|
|
22
|
+
const PASSTHROUGH_PREFIXES = ["xl/charts/"];
|
|
23
|
+
/**
|
|
24
|
+
* PassthroughManager handles storage and retrieval of passthrough files
|
|
25
|
+
* that need to be preserved during Excel read/write cycles.
|
|
26
|
+
*/
|
|
27
|
+
export class PassthroughManager {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.files = new Map();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if a path should be treated as passthrough
|
|
33
|
+
*/
|
|
34
|
+
static isPassthroughPath(path) {
|
|
35
|
+
return PASSTHROUGH_PREFIXES.some(prefix => path.startsWith(prefix));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the content type for a passthrough file path
|
|
39
|
+
* @returns Content type string or undefined if unknown
|
|
40
|
+
*/
|
|
41
|
+
static getContentType(path) {
|
|
42
|
+
// Chart relationships are handled by Default extension="rels"
|
|
43
|
+
if (path.startsWith("xl/charts/_rels/")) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
for (const [regex, contentType] of PASSTHROUGH_CONTENT_TYPES) {
|
|
47
|
+
if (regex.test(path)) {
|
|
48
|
+
return contentType;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Add a file to passthrough storage
|
|
55
|
+
*/
|
|
56
|
+
add(path, data) {
|
|
57
|
+
this.files.set(path, data);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get a file from passthrough storage
|
|
61
|
+
*/
|
|
62
|
+
get(path) {
|
|
63
|
+
return this.files.get(path);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if a file exists in passthrough storage
|
|
67
|
+
*/
|
|
68
|
+
has(path) {
|
|
69
|
+
return this.files.has(path);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all stored paths
|
|
73
|
+
*/
|
|
74
|
+
getPaths() {
|
|
75
|
+
return [...this.files.keys()];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get all files as a record (for serialization)
|
|
79
|
+
*/
|
|
80
|
+
toRecord() {
|
|
81
|
+
const record = {};
|
|
82
|
+
for (const [path, data] of this.files) {
|
|
83
|
+
record[path] = data;
|
|
84
|
+
}
|
|
85
|
+
return record;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Load files from a record (for deserialization)
|
|
89
|
+
*/
|
|
90
|
+
fromRecord(record) {
|
|
91
|
+
this.files.clear();
|
|
92
|
+
for (const [path, data] of Object.entries(record)) {
|
|
93
|
+
this.files.set(path, data);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get content types for all stored files that have known types
|
|
98
|
+
*/
|
|
99
|
+
getContentTypes() {
|
|
100
|
+
const contentTypes = [];
|
|
101
|
+
for (const path of this.files.keys()) {
|
|
102
|
+
const contentType = PassthroughManager.getContentType(path);
|
|
103
|
+
if (contentType) {
|
|
104
|
+
contentTypes.push({ partName: path, contentType });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return contentTypes;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Write all passthrough files to a ZIP writer
|
|
111
|
+
*/
|
|
112
|
+
writeToZip(zip) {
|
|
113
|
+
for (const [path, data] of this.files) {
|
|
114
|
+
zip.append(data, { name: path });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Clear all stored files
|
|
119
|
+
*/
|
|
120
|
+
clear() {
|
|
121
|
+
this.files.clear();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get the number of stored files
|
|
125
|
+
*/
|
|
126
|
+
get size() {
|
|
127
|
+
return this.files.size;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -55,6 +55,10 @@ export interface WorkbookModel {
|
|
|
55
55
|
/** Loaded pivot tables from file - used during reconciliation */
|
|
56
56
|
loadedPivotTables?: any[];
|
|
57
57
|
calcProperties: Partial<CalculationProperties>;
|
|
58
|
+
/** Passthrough files (charts, etc.) preserved for round-trip */
|
|
59
|
+
passthrough?: Record<string, Uint8Array>;
|
|
60
|
+
/** Raw drawing XML data for passthrough (when drawing contains chart references) */
|
|
61
|
+
rawDrawings?: Record<string, Uint8Array>;
|
|
58
62
|
}
|
|
59
63
|
declare class Workbook {
|
|
60
64
|
/**
|
|
@@ -94,6 +98,10 @@ declare class Workbook {
|
|
|
94
98
|
private _worksheets;
|
|
95
99
|
private _definedNames;
|
|
96
100
|
private _themes?;
|
|
101
|
+
/** Passthrough files (charts, etc.) preserved for round-trip */
|
|
102
|
+
private _passthrough;
|
|
103
|
+
/** Raw drawing XML data for passthrough (when drawing contains chart references) */
|
|
104
|
+
private _rawDrawings;
|
|
97
105
|
private _xlsx?;
|
|
98
106
|
private _csv?;
|
|
99
107
|
constructor();
|
|
@@ -38,6 +38,8 @@ class Workbook {
|
|
|
38
38
|
this.views = [];
|
|
39
39
|
this.media = [];
|
|
40
40
|
this.pivotTables = [];
|
|
41
|
+
this._passthrough = {};
|
|
42
|
+
this._rawDrawings = {};
|
|
41
43
|
this._definedNames = new DefinedNames();
|
|
42
44
|
}
|
|
43
45
|
// ===========================================================================
|
|
@@ -251,7 +253,9 @@ 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
|
|
255
259
|
};
|
|
256
260
|
}
|
|
257
261
|
set model(value) {
|
|
@@ -292,6 +296,10 @@ class Workbook {
|
|
|
292
296
|
// Handle pivot tables - either newly created or loaded from file
|
|
293
297
|
// Loaded pivot tables come from loadedPivotTables after reconciliation
|
|
294
298
|
this.pivotTables = value.pivotTables || value.loadedPivotTables || [];
|
|
299
|
+
// Preserve passthrough files (charts, etc.) for round-trip preservation
|
|
300
|
+
this._passthrough = value.passthrough || {};
|
|
301
|
+
// Preserve raw drawing data for drawings with chart references
|
|
302
|
+
this._rawDrawings = value.rawDrawings || {};
|
|
295
303
|
}
|
|
296
304
|
}
|
|
297
305
|
// ===========================================================================
|
|
@@ -111,6 +111,8 @@ interface WorksheetModel {
|
|
|
111
111
|
dimensions?: Range;
|
|
112
112
|
merges?: string[];
|
|
113
113
|
mergeCells?: string[];
|
|
114
|
+
/** Loaded drawing data (for charts, etc.) - preserved for round-trip */
|
|
115
|
+
drawing?: any;
|
|
114
116
|
}
|
|
115
117
|
declare class Worksheet {
|
|
116
118
|
private _workbook;
|
|
@@ -139,6 +141,8 @@ declare class Worksheet {
|
|
|
139
141
|
conditionalFormattings: ConditionalFormattingOptions[];
|
|
140
142
|
formControls: FormCheckbox[];
|
|
141
143
|
private _headerRowCount?;
|
|
144
|
+
/** Loaded drawing data (for charts, etc.) - preserved for round-trip */
|
|
145
|
+
private _drawing;
|
|
142
146
|
constructor(options: WorksheetOptions);
|
|
143
147
|
get name(): string;
|
|
144
148
|
set name(name: string | undefined);
|
|
@@ -897,7 +897,8 @@ class Worksheet {
|
|
|
897
897
|
tables: Object.values(this.tables).map(table => table.model),
|
|
898
898
|
pivotTables: this.pivotTables,
|
|
899
899
|
conditionalFormattings: this.conditionalFormattings,
|
|
900
|
-
formControls: this.formControls.map(fc => fc.model)
|
|
900
|
+
formControls: this.formControls.map(fc => fc.model),
|
|
901
|
+
drawing: this._drawing
|
|
901
902
|
};
|
|
902
903
|
// =================================================
|
|
903
904
|
// columns
|
|
@@ -965,6 +966,8 @@ class Worksheet {
|
|
|
965
966
|
this.conditionalFormattings = value.conditionalFormattings;
|
|
966
967
|
// Form controls are currently write-only (not parsed from XLSX)
|
|
967
968
|
this.formControls = [];
|
|
969
|
+
// Preserve loaded drawing data (charts, etc.)
|
|
970
|
+
this._drawing = value.drawing;
|
|
968
971
|
}
|
|
969
972
|
}
|
|
970
973
|
export { Worksheet };
|
|
@@ -10,7 +10,7 @@ class AppXform extends BaseXform {
|
|
|
10
10
|
Company: new StringXform({ tag: "Company" }),
|
|
11
11
|
Manager: new StringXform({ tag: "Manager" }),
|
|
12
12
|
HeadingPairs: new AppHeadingPairsXform(),
|
|
13
|
-
|
|
13
|
+
TitlesOfParts: new AppTitlesOfPartsXform()
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
render(xmlStream, model) {
|
|
@@ -20,7 +20,7 @@ class AppXform extends BaseXform {
|
|
|
20
20
|
xmlStream.leafNode("DocSecurity", undefined, "0");
|
|
21
21
|
xmlStream.leafNode("ScaleCrop", undefined, "false");
|
|
22
22
|
this.map.HeadingPairs.render(xmlStream, model.worksheets);
|
|
23
|
-
this.map.
|
|
23
|
+
this.map.TitlesOfParts.render(xmlStream, model.worksheets);
|
|
24
24
|
this.map.Company.render(xmlStream, model.company || "");
|
|
25
25
|
this.map.Manager.render(xmlStream, model.manager);
|
|
26
26
|
xmlStream.leafNode("LinksUpToDate", undefined, "false");
|
|
@@ -62,7 +62,7 @@ class AppXform extends BaseXform {
|
|
|
62
62
|
switch (name) {
|
|
63
63
|
case "Properties":
|
|
64
64
|
this.model = {
|
|
65
|
-
worksheets: this.map.
|
|
65
|
+
worksheets: this.map.TitlesOfParts.model,
|
|
66
66
|
company: this.map.Company.model,
|
|
67
67
|
manager: this.map.Manager.model
|
|
68
68
|
};
|
|
@@ -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"
|