@office-open/xlsx 0.6.5 → 0.6.6
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/README.md +96 -0
- package/dist/index.d.ts +349 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1628 -0
- package/dist/index.js.map +1 -0
- package/package.json +13 -6
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,1628 @@
|
|
|
1
|
+
import { AppProperties, BaseXmlComponent, ChartCollection, ChartSpace, Formatter, IgnoreIfEmptyXmlComponent, OoxmlMimeType, Relationships, buildCorePropertiesXml, compileMapping, createPacker, parseArchive, parseCorePropsElement, strFromU8, toJson, unzipSync, zipAndConvert } from "@office-open/core";
|
|
2
|
+
import { attr, attrNum, attrs, escapeXml, findChild, js2xml, selfCloseElement, textOf } from "@office-open/xml";
|
|
3
|
+
import { toUint8Array } from "undio";
|
|
4
|
+
//#region src/file/content-types.ts
|
|
5
|
+
/**
|
|
6
|
+
* Content Types module for XLSX packages.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
const XLSX_MAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
|
|
11
|
+
const XLSX_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
12
|
+
const XLSX_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
|
|
13
|
+
const XLSX_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
|
|
14
|
+
const XLSX_THEME = "application/vnd.openxmlformats-officedocument.theme+xml";
|
|
15
|
+
const XLSX_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
|
|
16
|
+
const STATIC_CHILDREN = [{ _attr: { xmlns: "http://schemas.openxmlformats.org/package/2006/content-types" } }, ...[
|
|
17
|
+
{
|
|
18
|
+
type: "Default",
|
|
19
|
+
contentType: "application/vnd.openxmlformats-package.relationships+xml",
|
|
20
|
+
key: "rels"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: "Default",
|
|
24
|
+
contentType: "application/xml",
|
|
25
|
+
key: "xml"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "Default",
|
|
29
|
+
contentType: "image/png",
|
|
30
|
+
key: "png"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: "Default",
|
|
34
|
+
contentType: "image/jpeg",
|
|
35
|
+
key: "jpeg"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: "Override",
|
|
39
|
+
contentType: XLSX_MAIN,
|
|
40
|
+
key: "/xl/workbook.xml"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "Override",
|
|
44
|
+
contentType: "application/vnd.openxmlformats-package.core-properties+xml",
|
|
45
|
+
key: "/docProps/core.xml"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: "Override",
|
|
49
|
+
contentType: "application/vnd.openxmlformats-officedocument.extended-properties+xml",
|
|
50
|
+
key: "/docProps/app.xml"
|
|
51
|
+
}
|
|
52
|
+
].map((e) => {
|
|
53
|
+
if (e.type === "Default") return { Default: { _attr: {
|
|
54
|
+
ContentType: e.contentType,
|
|
55
|
+
Extension: e.key
|
|
56
|
+
} } };
|
|
57
|
+
return { Override: { _attr: {
|
|
58
|
+
ContentType: e.contentType,
|
|
59
|
+
PartName: e.key
|
|
60
|
+
} } };
|
|
61
|
+
})];
|
|
62
|
+
var ContentTypes = class extends BaseXmlComponent {
|
|
63
|
+
dynamicEntries = [];
|
|
64
|
+
constructor() {
|
|
65
|
+
super("Types");
|
|
66
|
+
}
|
|
67
|
+
addWorksheet(index) {
|
|
68
|
+
this.dynamicEntries.push({
|
|
69
|
+
type: "Override",
|
|
70
|
+
contentType: XLSX_WORKSHEET,
|
|
71
|
+
key: `/xl/worksheets/sheet${index}.xml`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
addStyles() {
|
|
75
|
+
this.dynamicEntries.push({
|
|
76
|
+
type: "Override",
|
|
77
|
+
contentType: XLSX_STYLES,
|
|
78
|
+
key: "/xl/styles.xml"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
addSharedStrings() {
|
|
82
|
+
this.dynamicEntries.push({
|
|
83
|
+
type: "Override",
|
|
84
|
+
contentType: XLSX_SHARED_STRINGS,
|
|
85
|
+
key: "/xl/sharedStrings.xml"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
addTheme(index = 1) {
|
|
89
|
+
this.dynamicEntries.push({
|
|
90
|
+
type: "Override",
|
|
91
|
+
contentType: XLSX_THEME,
|
|
92
|
+
key: `/xl/theme/theme${index}.xml`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
addChart(index) {
|
|
96
|
+
this.dynamicEntries.push({
|
|
97
|
+
type: "Override",
|
|
98
|
+
contentType: XLSX_CHART,
|
|
99
|
+
key: `/xl/charts/chart${index}.xml`
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
addDrawing(index) {
|
|
103
|
+
this.dynamicEntries.push({
|
|
104
|
+
type: "Override",
|
|
105
|
+
contentType: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
106
|
+
key: `/xl/drawings/drawing${index}.xml`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
prepForXml(_context) {
|
|
110
|
+
const children = [...STATIC_CHILDREN];
|
|
111
|
+
for (const e of this.dynamicEntries) if (e.type === "Default") children.push({ Default: { _attr: {
|
|
112
|
+
ContentType: e.contentType,
|
|
113
|
+
Extension: e.key
|
|
114
|
+
} } });
|
|
115
|
+
else children.push({ Override: { _attr: {
|
|
116
|
+
ContentType: e.contentType,
|
|
117
|
+
PartName: e.key
|
|
118
|
+
} } });
|
|
119
|
+
return { Types: children };
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/file/core-properties.ts
|
|
124
|
+
/**
|
|
125
|
+
* Core Properties module for SpreadsheetML documents.
|
|
126
|
+
*
|
|
127
|
+
* @module
|
|
128
|
+
*/
|
|
129
|
+
var CoreProperties = class extends BaseXmlComponent {
|
|
130
|
+
options;
|
|
131
|
+
constructor(options) {
|
|
132
|
+
super("cp:coreProperties");
|
|
133
|
+
this.options = options;
|
|
134
|
+
}
|
|
135
|
+
prepForXml(_context) {
|
|
136
|
+
return buildCorePropertiesXml(this.options);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/file/media/media.ts
|
|
141
|
+
var Media = class {
|
|
142
|
+
map = /* @__PURE__ */ new Map();
|
|
143
|
+
addImage(key, data) {
|
|
144
|
+
this.map.set(key, data);
|
|
145
|
+
}
|
|
146
|
+
get array() {
|
|
147
|
+
return [...this.map.values()];
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/file/shared-strings.ts
|
|
152
|
+
/**
|
|
153
|
+
* Shared Strings Table — generates xl/sharedStrings.xml.
|
|
154
|
+
*
|
|
155
|
+
* XLSX stores repeated string values in a central table to reduce file size.
|
|
156
|
+
* Cells reference strings by index into this table.
|
|
157
|
+
*
|
|
158
|
+
* @module
|
|
159
|
+
*/
|
|
160
|
+
var SharedStrings = class extends BaseXmlComponent {
|
|
161
|
+
strings = [];
|
|
162
|
+
indexMap = /* @__PURE__ */ new Map();
|
|
163
|
+
constructor() {
|
|
164
|
+
super("sst");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Register a string and return its index.
|
|
168
|
+
* Returns existing index if the string is already registered.
|
|
169
|
+
*/
|
|
170
|
+
register(s) {
|
|
171
|
+
const existing = this.indexMap.get(s);
|
|
172
|
+
if (existing !== void 0) return existing;
|
|
173
|
+
const idx = this.strings.length;
|
|
174
|
+
this.strings.push(s);
|
|
175
|
+
this.indexMap.set(s, idx);
|
|
176
|
+
return idx;
|
|
177
|
+
}
|
|
178
|
+
get count() {
|
|
179
|
+
return this.strings.length;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Zero-allocation fast path: directly concatenate XML string.
|
|
183
|
+
* Bypasses the IXmlableObject intermediate tree entirely.
|
|
184
|
+
*/
|
|
185
|
+
toXml(_context) {
|
|
186
|
+
const p = ["<sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", ` count="${this.strings.length}" uniqueCount="${this.indexMap.size}">`];
|
|
187
|
+
for (const s of this.strings) p.push(`<si><t>${escapeXml(s)}</t></si>`);
|
|
188
|
+
p.push("</sst>");
|
|
189
|
+
return p.join("");
|
|
190
|
+
}
|
|
191
|
+
prepForXml(_context) {
|
|
192
|
+
const children = [{ _attr: {
|
|
193
|
+
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
194
|
+
count: this.strings.length,
|
|
195
|
+
uniqueCount: this.indexMap.size
|
|
196
|
+
} }];
|
|
197
|
+
for (const s of this.strings) children.push({ si: [{ t: [s] }] });
|
|
198
|
+
return { sst: children };
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/file/styles.ts
|
|
203
|
+
/**
|
|
204
|
+
* Styles component — generates xl/styles.xml.
|
|
205
|
+
*
|
|
206
|
+
* XLSX uses an index-based style system: cells reference style entries
|
|
207
|
+
* via the `s` attribute, which is an index into `cellXfs`.
|
|
208
|
+
*
|
|
209
|
+
* @module
|
|
210
|
+
*/
|
|
211
|
+
function fontKey(f) {
|
|
212
|
+
return `b${f.bold ? 1 : 0}i${f.italic ? 1 : 0}u${f.underline ? 1 : 0}s${f.strike ? 1 : 0}z${f.size ?? 0}c${f.color ?? ""}n${f.fontName ?? ""}`;
|
|
213
|
+
}
|
|
214
|
+
function fillKey(f) {
|
|
215
|
+
return `t${f.type ?? ""}c${f.color ?? ""}p${f.patternType ?? ""}`;
|
|
216
|
+
}
|
|
217
|
+
function borderKey(b) {
|
|
218
|
+
const sk = (o) => `${o?.style ?? ""}_${o?.color ?? ""}`;
|
|
219
|
+
return `t${sk(b.top)}b${sk(b.bottom)}l${sk(b.left)}r${sk(b.right)}d${sk(b.diagonal)}`;
|
|
220
|
+
}
|
|
221
|
+
const BUILTIN_NUMFMTS = {
|
|
222
|
+
General: 0,
|
|
223
|
+
"0": 1,
|
|
224
|
+
"0.00": 2,
|
|
225
|
+
"#,##0": 3,
|
|
226
|
+
"#,##0.00": 4,
|
|
227
|
+
"0%": 9,
|
|
228
|
+
"0.00%": 10,
|
|
229
|
+
"0.00E+00": 11,
|
|
230
|
+
"mm-dd-yy": 14,
|
|
231
|
+
"d-mmm-yy": 15,
|
|
232
|
+
"d-mmm": 16,
|
|
233
|
+
"mmm-yy": 17,
|
|
234
|
+
"h:mm AM/PM": 18,
|
|
235
|
+
"h:mm:ss AM/PM": 19,
|
|
236
|
+
"h:mm": 20,
|
|
237
|
+
"h:mm:ss": 21,
|
|
238
|
+
"m/d/yy h:mm": 22,
|
|
239
|
+
"#,##0 ;(#,##0)": 37,
|
|
240
|
+
"#,##0 ;[Red](#,##0)": 38,
|
|
241
|
+
"#,##0.00;(#,##0.00)": 39,
|
|
242
|
+
"#,##0.00;[Red](#,##0.00)": 40,
|
|
243
|
+
"mm:ss": 45,
|
|
244
|
+
"[h]:mm:ss": 46,
|
|
245
|
+
"mmss.0": 47,
|
|
246
|
+
"##0.0E+0": 48,
|
|
247
|
+
"@": 49
|
|
248
|
+
};
|
|
249
|
+
var Styles = class extends BaseXmlComponent {
|
|
250
|
+
fonts = [{
|
|
251
|
+
size: 11,
|
|
252
|
+
fontName: "Calibri"
|
|
253
|
+
}];
|
|
254
|
+
fontKeys = /* @__PURE__ */ new Map();
|
|
255
|
+
fills = [{ patternType: "none" }, { patternType: "gray125" }];
|
|
256
|
+
fillKeys = /* @__PURE__ */ new Map();
|
|
257
|
+
borders = [{}];
|
|
258
|
+
borderKeys = /* @__PURE__ */ new Map();
|
|
259
|
+
customNumFmts = /* @__PURE__ */ new Map();
|
|
260
|
+
nextCustomNumFmtId = 164;
|
|
261
|
+
cellXfs = [{
|
|
262
|
+
fontId: 0,
|
|
263
|
+
fillId: 0,
|
|
264
|
+
borderId: 0,
|
|
265
|
+
numFmtId: 0
|
|
266
|
+
}];
|
|
267
|
+
cellXfKeys = /* @__PURE__ */ new Map();
|
|
268
|
+
constructor() {
|
|
269
|
+
super("styleSheet");
|
|
270
|
+
this.fontKeys.set(fontKey(this.fonts[0]), 0);
|
|
271
|
+
this.fillKeys.set(fillKey(this.fills[0]), 0);
|
|
272
|
+
this.fillKeys.set(fillKey(this.fills[1]), 1);
|
|
273
|
+
this.borderKeys.set(borderKey(this.borders[0]), 0);
|
|
274
|
+
this.cellXfKeys.set(this.cellXfKey(this.cellXfs[0]), 0);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Register a style and return its index (for the cell `s` attribute).
|
|
278
|
+
* Deduplicates across fonts, fills, borders, numFmts, and cellXfs.
|
|
279
|
+
*/
|
|
280
|
+
register(opts) {
|
|
281
|
+
const xf = {
|
|
282
|
+
fontId: this.registerFont(opts.font),
|
|
283
|
+
fillId: this.registerFill(opts.fill),
|
|
284
|
+
borderId: this.registerBorder(opts.border),
|
|
285
|
+
numFmtId: this.registerNumFmt(opts.numFmt),
|
|
286
|
+
alignment: opts.alignment
|
|
287
|
+
};
|
|
288
|
+
const key = this.cellXfKey(xf);
|
|
289
|
+
const existing = this.cellXfKeys.get(key);
|
|
290
|
+
if (existing !== void 0) return existing;
|
|
291
|
+
const idx = this.cellXfs.length;
|
|
292
|
+
this.cellXfs.push(xf);
|
|
293
|
+
this.cellXfKeys.set(key, idx);
|
|
294
|
+
return idx;
|
|
295
|
+
}
|
|
296
|
+
registerFont(opts) {
|
|
297
|
+
if (!opts) return 0;
|
|
298
|
+
const key = fontKey(opts);
|
|
299
|
+
const existing = this.fontKeys.get(key);
|
|
300
|
+
if (existing !== void 0) return existing;
|
|
301
|
+
const idx = this.fonts.length;
|
|
302
|
+
this.fonts.push(opts);
|
|
303
|
+
this.fontKeys.set(key, idx);
|
|
304
|
+
return idx;
|
|
305
|
+
}
|
|
306
|
+
registerFill(opts) {
|
|
307
|
+
if (!opts) return 0;
|
|
308
|
+
const key = fillKey(opts);
|
|
309
|
+
const existing = this.fillKeys.get(key);
|
|
310
|
+
if (existing !== void 0) return existing;
|
|
311
|
+
const idx = this.fills.length;
|
|
312
|
+
this.fills.push(opts);
|
|
313
|
+
this.fillKeys.set(key, idx);
|
|
314
|
+
return idx;
|
|
315
|
+
}
|
|
316
|
+
registerBorder(opts) {
|
|
317
|
+
if (!opts) return 0;
|
|
318
|
+
const key = borderKey(opts);
|
|
319
|
+
const existing = this.borderKeys.get(key);
|
|
320
|
+
if (existing !== void 0) return existing;
|
|
321
|
+
const idx = this.borders.length;
|
|
322
|
+
this.borders.push(opts);
|
|
323
|
+
this.borderKeys.set(key, idx);
|
|
324
|
+
return idx;
|
|
325
|
+
}
|
|
326
|
+
registerNumFmt(fmt) {
|
|
327
|
+
if (!fmt) return 0;
|
|
328
|
+
const builtin = BUILTIN_NUMFMTS[fmt];
|
|
329
|
+
if (builtin !== void 0) return builtin;
|
|
330
|
+
const existing = this.customNumFmts.get(fmt);
|
|
331
|
+
if (existing !== void 0) return existing;
|
|
332
|
+
const id = this.nextCustomNumFmtId++;
|
|
333
|
+
this.customNumFmts.set(fmt, id);
|
|
334
|
+
return id;
|
|
335
|
+
}
|
|
336
|
+
cellXfKey(xf) {
|
|
337
|
+
const a = xf.alignment;
|
|
338
|
+
const ak = a ? `h${a.horizontal ?? ""}v${a.vertical ?? ""}w${a.wrapText ? 1 : 0}r${a.textRotation ?? ""}i${a.indent ?? ""}` : "";
|
|
339
|
+
return `${xf.fontId}|${xf.fillId}|${xf.borderId}|${xf.numFmtId}|${ak}`;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Zero-allocation fast path: directly concatenate XML string.
|
|
343
|
+
* Bypasses the IXmlableObject intermediate tree entirely.
|
|
344
|
+
*/
|
|
345
|
+
toXml(_context) {
|
|
346
|
+
const p = ["<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">"];
|
|
347
|
+
if (this.customNumFmts.size > 0) {
|
|
348
|
+
p.push(`<numFmts count="${this.customNumFmts.size}">`);
|
|
349
|
+
for (const [fmt, id] of this.customNumFmts) p.push(`<numFmt numFmtId="${id}" formatCode="${escapeXml(fmt)}"/>`);
|
|
350
|
+
p.push("</numFmts>");
|
|
351
|
+
}
|
|
352
|
+
p.push(`<fonts count="${this.fonts.length}">`);
|
|
353
|
+
for (const f of this.fonts) p.push(`<font>${this.fontXmlStr(f)}</font>`);
|
|
354
|
+
p.push("</fonts>");
|
|
355
|
+
p.push(`<fills count="${this.fills.length}">`);
|
|
356
|
+
for (const f of this.fills) {
|
|
357
|
+
const patternAttrs = attrs({ patternType: f.patternType ?? "solid" });
|
|
358
|
+
const fgColor = f.color ? `<fgColor rgb="FF${f.color}"/>` : "";
|
|
359
|
+
p.push(`<fill><patternFill${patternAttrs}>${fgColor}</patternFill></fill>`);
|
|
360
|
+
}
|
|
361
|
+
p.push("</fills>");
|
|
362
|
+
p.push(`<borders count="${this.borders.length}">`);
|
|
363
|
+
for (const b of this.borders) p.push(`<border>${this.borderXmlStr(b)}</border>`);
|
|
364
|
+
p.push("</borders>");
|
|
365
|
+
p.push("<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>");
|
|
366
|
+
p.push(`<cellXfs count="${this.cellXfs.length}">`);
|
|
367
|
+
for (const xf of this.cellXfs) {
|
|
368
|
+
const xAttrs = {
|
|
369
|
+
numFmtId: xf.numFmtId,
|
|
370
|
+
fontId: xf.fontId,
|
|
371
|
+
fillId: xf.fillId,
|
|
372
|
+
borderId: xf.borderId
|
|
373
|
+
};
|
|
374
|
+
if (xf.alignment) xAttrs.applyAlignment = 1;
|
|
375
|
+
if (xf.fontId > 0) xAttrs.applyFont = 1;
|
|
376
|
+
if (xf.fillId > 0) xAttrs.applyFill = 1;
|
|
377
|
+
if (xf.borderId > 0) xAttrs.applyBorder = 1;
|
|
378
|
+
const alignStr = xf.alignment ? this.alignmentXmlStr(xf.alignment) : "";
|
|
379
|
+
p.push(`<xf${attrs(xAttrs)}>${alignStr}</xf>`);
|
|
380
|
+
}
|
|
381
|
+
p.push("</cellXfs>");
|
|
382
|
+
p.push("<cellStyles count=\"1\"><cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\"/></cellStyles>");
|
|
383
|
+
p.push("</styleSheet>");
|
|
384
|
+
return p.join("");
|
|
385
|
+
}
|
|
386
|
+
fontXmlStr(f) {
|
|
387
|
+
const parts = [];
|
|
388
|
+
if (f.bold) parts.push("<b/>");
|
|
389
|
+
if (f.italic) parts.push("<i/>");
|
|
390
|
+
if (f.underline) parts.push("<u/>");
|
|
391
|
+
if (f.strike) parts.push("<strike/>");
|
|
392
|
+
if (f.size) parts.push(`<sz val="${f.size}"/>`);
|
|
393
|
+
if (f.color) parts.push(`<color rgb="FF${f.color}"/>`);
|
|
394
|
+
if (f.fontName) parts.push(`<name val="${escapeXml(f.fontName)}"/>`);
|
|
395
|
+
return parts.join("");
|
|
396
|
+
}
|
|
397
|
+
borderXmlStr(b) {
|
|
398
|
+
const parts = [];
|
|
399
|
+
for (const side of [
|
|
400
|
+
"left",
|
|
401
|
+
"right",
|
|
402
|
+
"top",
|
|
403
|
+
"bottom",
|
|
404
|
+
"diagonal"
|
|
405
|
+
]) {
|
|
406
|
+
const opts = b[side];
|
|
407
|
+
if (opts && opts.style && opts.style !== "none") {
|
|
408
|
+
const colorStr = opts.color ? `<color rgb="FF${opts.color}"/>` : "";
|
|
409
|
+
parts.push(`<${side} style="${opts.style}">${colorStr}</${side}>`);
|
|
410
|
+
} else parts.push(`<${side}/>`);
|
|
411
|
+
}
|
|
412
|
+
return parts.join("");
|
|
413
|
+
}
|
|
414
|
+
alignmentXmlStr(a) {
|
|
415
|
+
const aAttrs = {};
|
|
416
|
+
if (a.horizontal) aAttrs.horizontal = a.horizontal;
|
|
417
|
+
if (a.vertical) aAttrs.vertical = a.vertical;
|
|
418
|
+
if (a.wrapText) aAttrs.wrapText = 1;
|
|
419
|
+
if (a.textRotation !== void 0) aAttrs.textRotation = a.textRotation;
|
|
420
|
+
if (a.indent !== void 0) aAttrs.indent = a.indent;
|
|
421
|
+
return `<alignment${attrs(aAttrs)}/>`;
|
|
422
|
+
}
|
|
423
|
+
prepForXml(_context) {
|
|
424
|
+
const children = [{ _attr: { xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main" } }];
|
|
425
|
+
if (this.customNumFmts.size > 0) {
|
|
426
|
+
const fmtElements = [{ _attr: { count: this.customNumFmts.size } }];
|
|
427
|
+
for (const [fmt, id] of this.customNumFmts) fmtElements.push({ numFmt: { _attr: {
|
|
428
|
+
numFmtId: id,
|
|
429
|
+
formatCode: fmt
|
|
430
|
+
} } });
|
|
431
|
+
children.push({ numFmts: fmtElements });
|
|
432
|
+
}
|
|
433
|
+
children.push(this.buildFonts());
|
|
434
|
+
children.push(this.buildFills());
|
|
435
|
+
children.push(this.buildBorders());
|
|
436
|
+
children.push({ cellStyleXfs: [{ _attr: { count: 1 } }, { xf: [{ _attr: {
|
|
437
|
+
numFmtId: 0,
|
|
438
|
+
fontId: 0,
|
|
439
|
+
fillId: 0,
|
|
440
|
+
borderId: 0
|
|
441
|
+
} }] }] });
|
|
442
|
+
children.push(this.buildCellXfs());
|
|
443
|
+
children.push({ cellStyles: [{ _attr: { count: 1 } }, { cellStyle: [{ _attr: {
|
|
444
|
+
name: "Normal",
|
|
445
|
+
xfId: 0,
|
|
446
|
+
builtinId: 0
|
|
447
|
+
} }] }] });
|
|
448
|
+
return { styleSheet: children };
|
|
449
|
+
}
|
|
450
|
+
buildFonts() {
|
|
451
|
+
const elements = [{ _attr: { count: this.fonts.length } }];
|
|
452
|
+
for (const f of this.fonts) elements.push({ font: this.fontXml(f) });
|
|
453
|
+
return { fonts: elements };
|
|
454
|
+
}
|
|
455
|
+
fontXml(f) {
|
|
456
|
+
const parts = [];
|
|
457
|
+
if (f.bold) parts.push({ b: [] });
|
|
458
|
+
if (f.italic) parts.push({ i: [] });
|
|
459
|
+
if (f.underline) parts.push({ u: [] });
|
|
460
|
+
if (f.strike) parts.push({ strike: [] });
|
|
461
|
+
if (f.size) parts.push({ sz: [{ _attr: { val: f.size } }] });
|
|
462
|
+
if (f.color) parts.push({ color: [{ _attr: { rgb: `FF${f.color}` } }] });
|
|
463
|
+
if (f.fontName) parts.push({ name: [{ _attr: { val: f.fontName } }] });
|
|
464
|
+
return parts;
|
|
465
|
+
}
|
|
466
|
+
buildFills() {
|
|
467
|
+
const elements = [{ _attr: { count: this.fills.length } }];
|
|
468
|
+
for (const f of this.fills) elements.push({ fill: [{ patternFill: [{ _attr: { patternType: f.patternType ?? "solid" } }, ...f.color ? [{ fgColor: [{ _attr: { rgb: `FF${f.color}` } }] }] : []] }] });
|
|
469
|
+
return { fills: elements };
|
|
470
|
+
}
|
|
471
|
+
buildBorders() {
|
|
472
|
+
const elements = [{ _attr: { count: this.borders.length } }];
|
|
473
|
+
for (const b of this.borders) elements.push({ border: this.borderXml(b) });
|
|
474
|
+
return { borders: elements };
|
|
475
|
+
}
|
|
476
|
+
borderXml(b) {
|
|
477
|
+
const parts = [];
|
|
478
|
+
for (const side of [
|
|
479
|
+
"left",
|
|
480
|
+
"right",
|
|
481
|
+
"top",
|
|
482
|
+
"bottom",
|
|
483
|
+
"diagonal"
|
|
484
|
+
]) {
|
|
485
|
+
const opts = b[side];
|
|
486
|
+
if (opts && opts.style && opts.style !== "none") {
|
|
487
|
+
const children = [{ _attr: { style: opts.style } }];
|
|
488
|
+
if (opts.color) children.push({ color: [{ _attr: { rgb: `FF${opts.color}` } }] });
|
|
489
|
+
parts.push({ [side]: children });
|
|
490
|
+
} else parts.push({ [side]: [] });
|
|
491
|
+
}
|
|
492
|
+
return parts;
|
|
493
|
+
}
|
|
494
|
+
buildCellXfs() {
|
|
495
|
+
const elements = [{ _attr: { count: this.cellXfs.length } }];
|
|
496
|
+
for (const xf of this.cellXfs) {
|
|
497
|
+
const attrs = {
|
|
498
|
+
numFmtId: xf.numFmtId,
|
|
499
|
+
fontId: xf.fontId,
|
|
500
|
+
fillId: xf.fillId,
|
|
501
|
+
borderId: xf.borderId
|
|
502
|
+
};
|
|
503
|
+
if (xf.alignment) attrs.applyAlignment = 1;
|
|
504
|
+
if (xf.fontId > 0) attrs.applyFont = 1;
|
|
505
|
+
if (xf.fillId > 0) attrs.applyFill = 1;
|
|
506
|
+
if (xf.borderId > 0) attrs.applyBorder = 1;
|
|
507
|
+
const children = [{ _attr: attrs }];
|
|
508
|
+
if (xf.alignment) children.push(this.alignmentXml(xf.alignment));
|
|
509
|
+
elements.push({ xf: children });
|
|
510
|
+
}
|
|
511
|
+
return { cellXfs: elements };
|
|
512
|
+
}
|
|
513
|
+
alignmentXml(a) {
|
|
514
|
+
const attrs = {};
|
|
515
|
+
if (a.horizontal) attrs.horizontal = a.horizontal;
|
|
516
|
+
if (a.vertical) attrs.vertical = a.vertical;
|
|
517
|
+
if (a.wrapText) attrs.wrapText = 1;
|
|
518
|
+
if (a.textRotation !== void 0) attrs.textRotation = a.textRotation;
|
|
519
|
+
if (a.indent !== void 0) attrs.indent = a.indent;
|
|
520
|
+
return { alignment: [{ _attr: attrs }] };
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/file/theme.ts
|
|
525
|
+
/**
|
|
526
|
+
* Default theme for XLSX files — matches Microsoft Office's output structure.
|
|
527
|
+
* Produces xl/theme/theme1.xml that Excel accepts without repair warnings.
|
|
528
|
+
*
|
|
529
|
+
* The theme XML is completely static — identical for every XLSX file.
|
|
530
|
+
* Pre-serialized as a string constant to avoid building the IXmlableObject
|
|
531
|
+
* tree and re-serializing on every compile.
|
|
532
|
+
*
|
|
533
|
+
* @module
|
|
534
|
+
*/
|
|
535
|
+
const THEME_XML = "<a:theme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" name=\"Office Theme\"><a:themeElements><a:clrScheme name=\"Office\"><a:dk1><a:sysClr val=\"windowText\" lastClr=\"000000\"/></a:dk1><a:lt1><a:sysClr val=\"window\" lastClr=\"FFFFFF\"/></a:lt1><a:dk2><a:srgbClr val=\"44546A\"/></a:dk2><a:lt2><a:srgbClr val=\"E7E6E6\"/></a:lt2><a:accent1><a:srgbClr val=\"5B9BD5\"/></a:accent1><a:accent2><a:srgbClr val=\"ED7D31\"/></a:accent2><a:accent3><a:srgbClr val=\"A5A5A5\"/></a:accent3><a:accent4><a:srgbClr val=\"FFC000\"/></a:accent4><a:accent5><a:srgbClr val=\"4472C4\"/></a:accent5><a:accent6><a:srgbClr val=\"70AD47\"/></a:accent6><a:hlink><a:srgbClr val=\"0563C1\"/></a:hlink><a:folHlink><a:srgbClr val=\"954F72\"/></a:folHlink></a:clrScheme><a:fontScheme name=\"Office\"><a:majorFont><a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/><a:ea typeface=\"\"/><a:cs typeface=\"\"/></a:majorFont><a:minorFont><a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/><a:ea typeface=\"\"/><a:cs typeface=\"\"/></a:minorFont></a:fontScheme><a:fmtScheme name=\"Office\"><a:fillStyleLst><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:gradFill rotWithShape=\"1\"><a:gsLst><a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"110000\"/><a:satMod val=\"105000\"/><a:tint val=\"67000\"/></a:schemeClr></a:gs><a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"105000\"/><a:satMod val=\"103000\"/><a:tint val=\"73000\"/></a:schemeClr></a:gs><a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"105000\"/><a:satMod val=\"109000\"/><a:tint val=\"81000\"/></a:schemeClr></a:gs></a:gsLst><a:lin ang=\"5400000\" scaled=\"0\"/></a:gradFill><a:gradFill rotWithShape=\"1\"><a:gsLst><a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"102000\"/><a:satMod val=\"103000\"/><a:tint val=\"94000\"/></a:schemeClr></a:gs><a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"100000\"/><a:satMod val=\"110000\"/><a:shade val=\"100000\"/></a:schemeClr></a:gs><a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"99000\"/><a:satMod val=\"120000\"/><a:shade val=\"78000\"/></a:schemeClr></a:gs></a:gsLst><a:lin ang=\"5400000\" scaled=\"0\"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w=\"6350\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln><a:ln w=\"12700\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln><a:ln w=\"19050\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad=\"57150\" dist=\"19050\" dir=\"5400000\" algn=\"ctr\" rotWithShape=\"0\"><a:srgbClr val=\"000000\"><a:alpha val=\"63000\"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:solidFill><a:schemeClr val=\"phClr\"><a:tint val=\"95000\"/><a:satMod val=\"170000\"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape=\"1\"><a:gsLst><a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"102000\"/><a:satMod val=\"150000\"/><a:tint val=\"93000\"/><a:shade val=\"98000\"/></a:schemeClr></a:gs><a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"103000\"/><a:satMod val=\"130000\"/><a:tint val=\"98000\"/><a:shade val=\"90000\"/></a:schemeClr></a:gs><a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:satMod val=\"120000\"/><a:shade val=\"63000\"/></a:schemeClr></a:gs></a:gsLst><a:lin ang=\"5400000\" scaled=\"0\"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/></a:theme>";
|
|
536
|
+
var DefaultTheme = class extends BaseXmlComponent {
|
|
537
|
+
constructor() {
|
|
538
|
+
super("a:theme");
|
|
539
|
+
}
|
|
540
|
+
/** Return pre-cached static theme XML — zero allocation. */
|
|
541
|
+
toXml(_context) {
|
|
542
|
+
return THEME_XML;
|
|
543
|
+
}
|
|
544
|
+
prepForXml(_context) {
|
|
545
|
+
return { "a:theme": [{ _attr: {
|
|
546
|
+
"xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
547
|
+
name: "Office Theme"
|
|
548
|
+
} }] };
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/file/workbook.ts
|
|
553
|
+
/**
|
|
554
|
+
* Workbook component — generates xl/workbook.xml.
|
|
555
|
+
*
|
|
556
|
+
* @module
|
|
557
|
+
*/
|
|
558
|
+
var WorkbookXml = class extends BaseXmlComponent {
|
|
559
|
+
sheets;
|
|
560
|
+
constructor(sheets) {
|
|
561
|
+
super("workbook");
|
|
562
|
+
this.sheets = sheets;
|
|
563
|
+
}
|
|
564
|
+
prepForXml(_context) {
|
|
565
|
+
const sheetElements = [];
|
|
566
|
+
for (const s of this.sheets) {
|
|
567
|
+
const attrs = {
|
|
568
|
+
name: s.name,
|
|
569
|
+
sheetId: String(s.sheetId),
|
|
570
|
+
"r:id": s.rId
|
|
571
|
+
};
|
|
572
|
+
if (s.state && s.state !== "visible") attrs.state = s.state;
|
|
573
|
+
sheetElements.push({ sheet: { _attr: attrs } });
|
|
574
|
+
}
|
|
575
|
+
return { workbook: [{ _attr: {
|
|
576
|
+
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
577
|
+
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
578
|
+
} }, { sheets: sheetElements }] };
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/file/worksheet.ts
|
|
583
|
+
/**
|
|
584
|
+
* Worksheet component — generates xl/worksheets/sheet{n}.xml.
|
|
585
|
+
*
|
|
586
|
+
* @module
|
|
587
|
+
*/
|
|
588
|
+
var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
589
|
+
rows;
|
|
590
|
+
columns;
|
|
591
|
+
mergeCells;
|
|
592
|
+
freezePanes;
|
|
593
|
+
autoFilter;
|
|
594
|
+
images;
|
|
595
|
+
chartOptions;
|
|
596
|
+
dataValidations;
|
|
597
|
+
conditionalFormats;
|
|
598
|
+
constructor(options) {
|
|
599
|
+
super("worksheet");
|
|
600
|
+
this.rows = options.children ?? [];
|
|
601
|
+
this.columns = options.columns ?? [];
|
|
602
|
+
this.mergeCells = options.mergeCells ?? [];
|
|
603
|
+
this.freezePanes = options.freezePanes;
|
|
604
|
+
this.autoFilter = options.autoFilter;
|
|
605
|
+
this.images = options.images ?? [];
|
|
606
|
+
this.chartOptions = options.charts ?? [];
|
|
607
|
+
this.dataValidations = options.dataValidations ?? [];
|
|
608
|
+
this.conditionalFormats = options.conditionalFormats ?? [];
|
|
609
|
+
}
|
|
610
|
+
get imageOptions() {
|
|
611
|
+
return this.images;
|
|
612
|
+
}
|
|
613
|
+
get charts() {
|
|
614
|
+
return this.chartOptions;
|
|
615
|
+
}
|
|
616
|
+
prepForXml(context) {
|
|
617
|
+
const fileData = context.fileData;
|
|
618
|
+
const sharedStrings = fileData?.sharedStrings;
|
|
619
|
+
const styles = fileData?.styles;
|
|
620
|
+
const children = [{ _attr: {
|
|
621
|
+
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
622
|
+
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
623
|
+
} }];
|
|
624
|
+
if (this.freezePanes) {
|
|
625
|
+
const fp = this.freezePanes;
|
|
626
|
+
const ySplit = fp.row ? fp.row : 0;
|
|
627
|
+
const xSplit = fp.col ? fp.col : 0;
|
|
628
|
+
const topRow = fp.row ? fp.row + 1 : 1;
|
|
629
|
+
const leftCol = fp.col ? fp.col + 1 : 1;
|
|
630
|
+
const attr = {
|
|
631
|
+
ySplit,
|
|
632
|
+
xSplit,
|
|
633
|
+
topLeftCell: this.defaultCellRef(topRow, leftCol),
|
|
634
|
+
activePane: ySplit > 0 && xSplit > 0 ? "bottomRight" : ySplit > 0 ? "bottomLeft" : "topRight",
|
|
635
|
+
state: "frozen"
|
|
636
|
+
};
|
|
637
|
+
children.push({ sheetViews: [{ sheetView: [{ _attr: {
|
|
638
|
+
tabSelected: 1,
|
|
639
|
+
workbookViewId: 0
|
|
640
|
+
} }, { pane: { _attr: attr } }] }] });
|
|
641
|
+
}
|
|
642
|
+
if (this.columns.length > 0) {
|
|
643
|
+
const colElements = [];
|
|
644
|
+
for (const col of this.columns) {
|
|
645
|
+
const colAttrs = {
|
|
646
|
+
min: col.min,
|
|
647
|
+
max: col.max
|
|
648
|
+
};
|
|
649
|
+
if (col.width !== void 0) {
|
|
650
|
+
colAttrs.width = col.width;
|
|
651
|
+
colAttrs.customWidth = 1;
|
|
652
|
+
}
|
|
653
|
+
if (col.hidden) colAttrs.hidden = 1;
|
|
654
|
+
colElements.push({ col: { _attr: colAttrs } });
|
|
655
|
+
}
|
|
656
|
+
children.push({ cols: colElements });
|
|
657
|
+
}
|
|
658
|
+
const sheetDataChildren = [];
|
|
659
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
660
|
+
const rowOpts = this.rows[i];
|
|
661
|
+
const rowNumber = rowOpts.rowNumber ?? i + 1;
|
|
662
|
+
const rowAttrs = { r: rowNumber };
|
|
663
|
+
if (rowOpts.height !== void 0) {
|
|
664
|
+
rowAttrs.ht = rowOpts.height;
|
|
665
|
+
rowAttrs.customHeight = 1;
|
|
666
|
+
}
|
|
667
|
+
if (rowOpts.hidden) rowAttrs.hidden = 1;
|
|
668
|
+
const cellElements = [];
|
|
669
|
+
if (rowOpts.cells) for (let j = 0; j < rowOpts.cells.length; j++) {
|
|
670
|
+
const cell = rowOpts.cells[j];
|
|
671
|
+
const ref = cell.reference ?? this.defaultCellRef(rowNumber, j + 1);
|
|
672
|
+
const cellObj = this.buildCell(ref, cell, sharedStrings, styles);
|
|
673
|
+
if (cellObj) cellElements.push(cellObj);
|
|
674
|
+
}
|
|
675
|
+
sheetDataChildren.push({ row: [{ _attr: rowAttrs }, ...cellElements] });
|
|
676
|
+
}
|
|
677
|
+
children.push({ sheetData: sheetDataChildren });
|
|
678
|
+
if (this.autoFilter) children.push({ autoFilter: { _attr: { ref: this.autoFilter } } });
|
|
679
|
+
if (this.mergeCells.length > 0) {
|
|
680
|
+
const mergeElements = [{ _attr: { count: this.mergeCells.length } }];
|
|
681
|
+
for (const mc of this.mergeCells) {
|
|
682
|
+
const fromRef = this.defaultCellRef(mc.from.row, mc.from.col);
|
|
683
|
+
const toRef = this.defaultCellRef(mc.to.row, mc.to.col);
|
|
684
|
+
mergeElements.push({ mergeCell: { _attr: { ref: `${fromRef}:${toRef}` } } });
|
|
685
|
+
}
|
|
686
|
+
children.push({ mergeCells: mergeElements });
|
|
687
|
+
}
|
|
688
|
+
if (this.conditionalFormats.length > 0) for (const cf of this.conditionalFormats) {
|
|
689
|
+
const rules = [];
|
|
690
|
+
for (let ri = 0; ri < cf.rules.length; ri++) {
|
|
691
|
+
const rule = cf.rules[ri];
|
|
692
|
+
const ruleAttrs = {
|
|
693
|
+
type: rule.type,
|
|
694
|
+
priority: rule.priority ?? ri + 1
|
|
695
|
+
};
|
|
696
|
+
if (rule.operator) ruleAttrs.operator = rule.operator;
|
|
697
|
+
if (rule.dxfId !== void 0) ruleAttrs.dxfId = rule.dxfId;
|
|
698
|
+
const ruleChildren = [{ _attr: ruleAttrs }];
|
|
699
|
+
if (rule.formulas) for (const f of rule.formulas) ruleChildren.push({ formula: [f] });
|
|
700
|
+
rules.push({ cfRule: ruleChildren });
|
|
701
|
+
}
|
|
702
|
+
children.push({ conditionalFormatting: [{ _attr: { sqref: cf.sqref } }, ...rules] });
|
|
703
|
+
}
|
|
704
|
+
if (this.dataValidations.length > 0) {
|
|
705
|
+
const dvElements = [{ _attr: { count: this.dataValidations.length } }];
|
|
706
|
+
for (const dv of this.dataValidations) {
|
|
707
|
+
const dvAttrs = { sqref: dv.sqref };
|
|
708
|
+
if (dv.type && dv.type !== "none") dvAttrs.type = dv.type;
|
|
709
|
+
if (dv.operator) dvAttrs.operator = dv.operator;
|
|
710
|
+
if (dv.allowBlank) dvAttrs.allowBlank = 1;
|
|
711
|
+
if (dv.showErrorMessage) dvAttrs.showErrorMessage = 1;
|
|
712
|
+
if (dv.showInputMessage) dvAttrs.showInputMessage = 1;
|
|
713
|
+
if (dv.errorTitle) dvAttrs.errorTitle = dv.errorTitle;
|
|
714
|
+
if (dv.error) dvAttrs.error = dv.error;
|
|
715
|
+
if (dv.promptTitle) dvAttrs.promptTitle = dv.promptTitle;
|
|
716
|
+
if (dv.prompt) dvAttrs.prompt = dv.prompt;
|
|
717
|
+
const dvChildren = [{ _attr: dvAttrs }];
|
|
718
|
+
if (dv.formula1 !== void 0) dvChildren.push({ formula1: [dv.formula1] });
|
|
719
|
+
if (dv.formula2 !== void 0) dvChildren.push({ formula2: [dv.formula2] });
|
|
720
|
+
dvElements.push({ dataValidation: dvChildren });
|
|
721
|
+
}
|
|
722
|
+
children.push({ dataValidations: dvElements });
|
|
723
|
+
}
|
|
724
|
+
return { worksheet: children };
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Zero-allocation fast path: directly concatenate XML string.
|
|
728
|
+
* Bypasses the IXmlableObject intermediate tree entirely.
|
|
729
|
+
*/
|
|
730
|
+
toXml(context) {
|
|
731
|
+
const fileData = context.fileData;
|
|
732
|
+
const sharedStrings = fileData?.sharedStrings;
|
|
733
|
+
const styles = fileData?.styles;
|
|
734
|
+
const p = ["<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">"];
|
|
735
|
+
if (this.freezePanes) {
|
|
736
|
+
const fp = this.freezePanes;
|
|
737
|
+
const ySplit = fp.row ? fp.row : 0;
|
|
738
|
+
const xSplit = fp.col ? fp.col : 0;
|
|
739
|
+
const topRow = fp.row ? fp.row + 1 : 1;
|
|
740
|
+
const leftCol = fp.col ? fp.col + 1 : 1;
|
|
741
|
+
const topLeftCell = this.defaultCellRef(topRow, leftCol);
|
|
742
|
+
const activePane = ySplit > 0 && xSplit > 0 ? "bottomRight" : ySplit > 0 ? "bottomLeft" : "topRight";
|
|
743
|
+
p.push("<sheetViews><sheetView tabSelected=\"1\" workbookViewId=\"0\">", `<pane ySplit="${ySplit}" xSplit="${xSplit}" topLeftCell="${topLeftCell}" activePane="${activePane}" state="frozen"/>`, "</sheetView></sheetViews>");
|
|
744
|
+
}
|
|
745
|
+
if (this.columns.length > 0) {
|
|
746
|
+
p.push("<cols>");
|
|
747
|
+
for (const col of this.columns) {
|
|
748
|
+
const colAttrs = {
|
|
749
|
+
min: col.min,
|
|
750
|
+
max: col.max
|
|
751
|
+
};
|
|
752
|
+
if (col.width !== void 0) {
|
|
753
|
+
colAttrs.width = col.width;
|
|
754
|
+
colAttrs.customWidth = 1;
|
|
755
|
+
}
|
|
756
|
+
if (col.hidden) colAttrs.hidden = 1;
|
|
757
|
+
p.push(selfCloseElement("col", attrs(colAttrs)));
|
|
758
|
+
}
|
|
759
|
+
p.push("</cols>");
|
|
760
|
+
}
|
|
761
|
+
p.push("<sheetData>");
|
|
762
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
763
|
+
const rowOpts = this.rows[i];
|
|
764
|
+
const rowNumber = rowOpts.rowNumber ?? i + 1;
|
|
765
|
+
const rowAttrs = { r: rowNumber };
|
|
766
|
+
if (rowOpts.height !== void 0) {
|
|
767
|
+
rowAttrs.ht = rowOpts.height;
|
|
768
|
+
rowAttrs.customHeight = 1;
|
|
769
|
+
}
|
|
770
|
+
if (rowOpts.hidden) rowAttrs.hidden = 1;
|
|
771
|
+
if (rowOpts.cells) {
|
|
772
|
+
const rowParts = [];
|
|
773
|
+
for (let j = 0; j < rowOpts.cells.length; j++) {
|
|
774
|
+
const cell = rowOpts.cells[j];
|
|
775
|
+
const ref = cell.reference ?? this.defaultCellRef(rowNumber, j + 1);
|
|
776
|
+
const cellStr = this.buildCellString(ref, cell, sharedStrings, styles);
|
|
777
|
+
if (cellStr) rowParts.push(cellStr);
|
|
778
|
+
}
|
|
779
|
+
p.push(`<row${attrs(rowAttrs)}>`, ...rowParts, "</row>");
|
|
780
|
+
} else p.push(`<row${attrs(rowAttrs)}></row>`);
|
|
781
|
+
}
|
|
782
|
+
p.push("</sheetData>");
|
|
783
|
+
if (this.autoFilter) p.push(selfCloseElement("autoFilter", attrs({ ref: this.autoFilter })));
|
|
784
|
+
if (this.mergeCells.length > 0) {
|
|
785
|
+
p.push(`<mergeCells count="${this.mergeCells.length}">`);
|
|
786
|
+
for (const mc of this.mergeCells) {
|
|
787
|
+
const fromRef = this.defaultCellRef(mc.from.row, mc.from.col);
|
|
788
|
+
const toRef = this.defaultCellRef(mc.to.row, mc.to.col);
|
|
789
|
+
p.push(selfCloseElement("mergeCell", attrs({ ref: `${fromRef}:${toRef}` })));
|
|
790
|
+
}
|
|
791
|
+
p.push("</mergeCells>");
|
|
792
|
+
}
|
|
793
|
+
if (this.conditionalFormats.length > 0) for (const cf of this.conditionalFormats) {
|
|
794
|
+
p.push(`<conditionalFormatting sqref="${cf.sqref}">`);
|
|
795
|
+
for (let ri = 0; ri < cf.rules.length; ri++) {
|
|
796
|
+
const rule = cf.rules[ri];
|
|
797
|
+
const ruleAttrs = {
|
|
798
|
+
type: rule.type,
|
|
799
|
+
priority: rule.priority ?? ri + 1
|
|
800
|
+
};
|
|
801
|
+
if (rule.operator) ruleAttrs.operator = rule.operator;
|
|
802
|
+
if (rule.dxfId !== void 0) ruleAttrs.dxfId = rule.dxfId;
|
|
803
|
+
if (rule.formulas && rule.formulas.length > 0) {
|
|
804
|
+
const formulaParts = rule.formulas.map((f) => `<formula>${escapeXml(f)}</formula>`);
|
|
805
|
+
p.push(`<cfRule${attrs(ruleAttrs)}>`, ...formulaParts, "</cfRule>");
|
|
806
|
+
} else p.push(selfCloseElement("cfRule", attrs(ruleAttrs)));
|
|
807
|
+
}
|
|
808
|
+
p.push("</conditionalFormatting>");
|
|
809
|
+
}
|
|
810
|
+
if (this.dataValidations.length > 0) {
|
|
811
|
+
p.push(`<dataValidations count="${this.dataValidations.length}">`);
|
|
812
|
+
for (const dv of this.dataValidations) {
|
|
813
|
+
const dvAttrs = { sqref: dv.sqref };
|
|
814
|
+
if (dv.type && dv.type !== "none") dvAttrs.type = dv.type;
|
|
815
|
+
if (dv.operator) dvAttrs.operator = dv.operator;
|
|
816
|
+
if (dv.allowBlank) dvAttrs.allowBlank = 1;
|
|
817
|
+
if (dv.showErrorMessage) dvAttrs.showErrorMessage = 1;
|
|
818
|
+
if (dv.showInputMessage) dvAttrs.showInputMessage = 1;
|
|
819
|
+
if (dv.errorTitle) dvAttrs.errorTitle = dv.errorTitle;
|
|
820
|
+
if (dv.error) dvAttrs.error = dv.error;
|
|
821
|
+
if (dv.promptTitle) dvAttrs.promptTitle = dv.promptTitle;
|
|
822
|
+
if (dv.prompt) dvAttrs.prompt = dv.prompt;
|
|
823
|
+
const inner = [];
|
|
824
|
+
if (dv.formula1 !== void 0) inner.push(`<formula1>${escapeXml(dv.formula1)}</formula1>`);
|
|
825
|
+
if (dv.formula2 !== void 0) inner.push(`<formula2>${escapeXml(dv.formula2)}</formula2>`);
|
|
826
|
+
if (inner.length > 0) p.push(`<dataValidation${attrs(dvAttrs)}>`, ...inner, "</dataValidation>");
|
|
827
|
+
else p.push(selfCloseElement("dataValidation", attrs(dvAttrs)));
|
|
828
|
+
}
|
|
829
|
+
p.push("</dataValidations>");
|
|
830
|
+
}
|
|
831
|
+
p.push("</worksheet>");
|
|
832
|
+
return p.join("");
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Direct string serialization of a single cell — zero intermediate objects.
|
|
836
|
+
*/
|
|
837
|
+
buildCellString(ref, cell, sharedStrings, styles) {
|
|
838
|
+
const cellAttrs = { r: ref };
|
|
839
|
+
if (cell.style !== void 0 && styles) cellAttrs.s = styles.register(cell.style);
|
|
840
|
+
else if (cell.styleIndex !== void 0) cellAttrs.s = cell.styleIndex;
|
|
841
|
+
const value = cell.value;
|
|
842
|
+
if (value === null || value === void 0) {
|
|
843
|
+
if (cell.styleIndex !== void 0) return selfCloseElement("c", attrs(cellAttrs));
|
|
844
|
+
return "";
|
|
845
|
+
}
|
|
846
|
+
if (typeof value === "string") {
|
|
847
|
+
if (sharedStrings) {
|
|
848
|
+
cellAttrs.t = "s";
|
|
849
|
+
const idx = sharedStrings.register(value);
|
|
850
|
+
return `<c${attrs(cellAttrs)}><v>${idx}</v></c>`;
|
|
851
|
+
}
|
|
852
|
+
cellAttrs.t = "inlineStr";
|
|
853
|
+
return `<c${attrs(cellAttrs)}><is><t>${escapeXml(value)}</t></is></c>`;
|
|
854
|
+
}
|
|
855
|
+
if (typeof value === "number") return `<c${attrs(cellAttrs)}><v>${value}</v></c>`;
|
|
856
|
+
if (typeof value === "boolean") {
|
|
857
|
+
cellAttrs.t = "b";
|
|
858
|
+
return `<c${attrs(cellAttrs)}><v>${value ? 1 : 0}</v></c>`;
|
|
859
|
+
}
|
|
860
|
+
if (value instanceof Date) {
|
|
861
|
+
const serial = this.dateToSerialNumber(value);
|
|
862
|
+
return `<c${attrs(cellAttrs)}><v>${serial}</v></c>`;
|
|
863
|
+
}
|
|
864
|
+
return "";
|
|
865
|
+
}
|
|
866
|
+
defaultCellRef(row, col) {
|
|
867
|
+
return this.columnToLetter(col) + row;
|
|
868
|
+
}
|
|
869
|
+
columnToLetter(col) {
|
|
870
|
+
let result = "";
|
|
871
|
+
let n = col;
|
|
872
|
+
while (n > 0) {
|
|
873
|
+
const remainder = (n - 1) % 26;
|
|
874
|
+
result = String.fromCharCode(65 + remainder) + result;
|
|
875
|
+
n = Math.floor((n - 1) / 26);
|
|
876
|
+
}
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
buildCell(ref, cell, sharedStrings, styles) {
|
|
880
|
+
const attrs = { r: ref };
|
|
881
|
+
if (cell.style !== void 0 && styles) attrs.s = styles.register(cell.style);
|
|
882
|
+
else if (cell.styleIndex !== void 0) attrs.s = cell.styleIndex;
|
|
883
|
+
const value = cell.value;
|
|
884
|
+
if (value === null || value === void 0) {
|
|
885
|
+
if (cell.styleIndex !== void 0) return { c: [{ _attr: attrs }] };
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (typeof value === "string") {
|
|
889
|
+
if (sharedStrings) {
|
|
890
|
+
attrs.t = "s";
|
|
891
|
+
const idx = sharedStrings.register(value);
|
|
892
|
+
return { c: [{ _attr: attrs }, { v: [idx] }] };
|
|
893
|
+
}
|
|
894
|
+
attrs.t = "inlineStr";
|
|
895
|
+
return { c: [{ _attr: attrs }, { is: [{ t: [value] }] }] };
|
|
896
|
+
}
|
|
897
|
+
if (typeof value === "number") return { c: [{ _attr: attrs }, { v: [value] }] };
|
|
898
|
+
if (typeof value === "boolean") {
|
|
899
|
+
attrs.t = "b";
|
|
900
|
+
return { c: [{ _attr: attrs }, { v: [value ? 1 : 0] }] };
|
|
901
|
+
}
|
|
902
|
+
if (value instanceof Date) {
|
|
903
|
+
const serial = this.dateToSerialNumber(value);
|
|
904
|
+
return { c: [{ _attr: attrs }, { v: [serial] }] };
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
dateToSerialNumber(date) {
|
|
908
|
+
const epoch = new Date(1899, 11, 30);
|
|
909
|
+
return (date.getTime() - epoch.getTime()) / 864e5;
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
//#endregion
|
|
913
|
+
//#region src/file/file.ts
|
|
914
|
+
/**
|
|
915
|
+
* File class (exported as Workbook) — the top-level container for XLSX documents.
|
|
916
|
+
*
|
|
917
|
+
* @module
|
|
918
|
+
*/
|
|
919
|
+
var File = class {
|
|
920
|
+
worksheetOptions;
|
|
921
|
+
corePropsOptions;
|
|
922
|
+
_coreProperties;
|
|
923
|
+
_appProperties;
|
|
924
|
+
_contentTypes;
|
|
925
|
+
_styles;
|
|
926
|
+
_theme;
|
|
927
|
+
_workbookXml;
|
|
928
|
+
_worksheets;
|
|
929
|
+
_sharedStrings;
|
|
930
|
+
_media;
|
|
931
|
+
_fileRels;
|
|
932
|
+
_workbookRels;
|
|
933
|
+
_charts;
|
|
934
|
+
constructor(options) {
|
|
935
|
+
this.worksheetOptions = options.worksheets ?? [];
|
|
936
|
+
this.corePropsOptions = options;
|
|
937
|
+
}
|
|
938
|
+
get coreProperties() {
|
|
939
|
+
return this._coreProperties ??= new CoreProperties(this.corePropsOptions);
|
|
940
|
+
}
|
|
941
|
+
get appProperties() {
|
|
942
|
+
return this._appProperties ??= new AppProperties();
|
|
943
|
+
}
|
|
944
|
+
get contentTypes() {
|
|
945
|
+
if (!this._contentTypes) {
|
|
946
|
+
this._contentTypes = new ContentTypes();
|
|
947
|
+
for (let i = 0; i < this.worksheetOptions.length; i++) this._contentTypes.addWorksheet(i + 1);
|
|
948
|
+
this._contentTypes.addStyles();
|
|
949
|
+
this._contentTypes.addSharedStrings();
|
|
950
|
+
this._contentTypes.addTheme();
|
|
951
|
+
}
|
|
952
|
+
return this._contentTypes;
|
|
953
|
+
}
|
|
954
|
+
get styles() {
|
|
955
|
+
return this._styles ??= new Styles();
|
|
956
|
+
}
|
|
957
|
+
get theme() {
|
|
958
|
+
return this._theme ??= new DefaultTheme();
|
|
959
|
+
}
|
|
960
|
+
get workbookXml() {
|
|
961
|
+
if (!this._workbookXml) {
|
|
962
|
+
const sheets = this.worksheetOptions.map((ws, i) => ({
|
|
963
|
+
name: ws.name ?? `Sheet${i + 1}`,
|
|
964
|
+
sheetId: i + 1,
|
|
965
|
+
rId: `rId${i + 1}`
|
|
966
|
+
}));
|
|
967
|
+
this._workbookXml = new WorkbookXml(sheets);
|
|
968
|
+
}
|
|
969
|
+
return this._workbookXml;
|
|
970
|
+
}
|
|
971
|
+
get sharedStrings() {
|
|
972
|
+
return this._sharedStrings ??= new SharedStrings();
|
|
973
|
+
}
|
|
974
|
+
get media() {
|
|
975
|
+
return this._media ??= new Media();
|
|
976
|
+
}
|
|
977
|
+
get charts() {
|
|
978
|
+
return this._charts ??= new ChartCollection();
|
|
979
|
+
}
|
|
980
|
+
get worksheets() {
|
|
981
|
+
if (!this._worksheets) this._worksheets = this.worksheetOptions.map((ws) => new Worksheet(ws));
|
|
982
|
+
return this._worksheets;
|
|
983
|
+
}
|
|
984
|
+
get fileRelationships() {
|
|
985
|
+
if (!this._fileRels) {
|
|
986
|
+
this._fileRels = new Relationships();
|
|
987
|
+
this._fileRels.addRelationship(1, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml");
|
|
988
|
+
this._fileRels.addRelationship(2, "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml");
|
|
989
|
+
this._fileRels.addRelationship(3, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml");
|
|
990
|
+
}
|
|
991
|
+
return this._fileRels;
|
|
992
|
+
}
|
|
993
|
+
get workbookRelationships() {
|
|
994
|
+
if (!this._workbookRels) {
|
|
995
|
+
this._workbookRels = new Relationships();
|
|
996
|
+
let rid = 1;
|
|
997
|
+
for (let i = 0; i < this.worksheetOptions.length; i++) this._workbookRels.addRelationship(rid++, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet", `worksheets/sheet${i + 1}.xml`);
|
|
998
|
+
this._workbookRels.addRelationship(rid++, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "styles.xml");
|
|
999
|
+
this._workbookRels.addRelationship(rid++, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", "theme/theme1.xml");
|
|
1000
|
+
this._workbookRels.addRelationship(rid++, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings", "sharedStrings.xml");
|
|
1001
|
+
}
|
|
1002
|
+
return this._workbookRels;
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
//#endregion
|
|
1006
|
+
//#region src/file/drawing/drawing.ts
|
|
1007
|
+
/**
|
|
1008
|
+
* XLSX Drawing component — generates xl/drawings/drawing{n}.xml.
|
|
1009
|
+
*
|
|
1010
|
+
* Uses the spreadsheetDrawing namespace (default, no prefix) for anchoring
|
|
1011
|
+
* images and charts to worksheet cells.
|
|
1012
|
+
*
|
|
1013
|
+
* @module
|
|
1014
|
+
*/
|
|
1015
|
+
const XDR_NS = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
|
|
1016
|
+
const A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
1017
|
+
const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
1018
|
+
const C_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
1019
|
+
var Drawing = class extends BaseXmlComponent {
|
|
1020
|
+
images;
|
|
1021
|
+
charts;
|
|
1022
|
+
constructor(images, charts = []) {
|
|
1023
|
+
super("wsDr");
|
|
1024
|
+
this.images = images;
|
|
1025
|
+
this.charts = charts;
|
|
1026
|
+
}
|
|
1027
|
+
prepForXml(_context) {
|
|
1028
|
+
const children = [{ _attr: {
|
|
1029
|
+
xmlns: XDR_NS,
|
|
1030
|
+
"xmlns:a": A_NS,
|
|
1031
|
+
"xmlns:r": R_NS
|
|
1032
|
+
} }];
|
|
1033
|
+
let nextId = 1;
|
|
1034
|
+
for (const img of this.images) children.push(this.buildImageAnchor(img, nextId++));
|
|
1035
|
+
for (const chart of this.charts) children.push(this.buildChartAnchor(chart, nextId++));
|
|
1036
|
+
return { wsDr: children };
|
|
1037
|
+
}
|
|
1038
|
+
buildFromAnchor(col, row, colOffset, rowOffset) {
|
|
1039
|
+
return { from: [
|
|
1040
|
+
{ col: [col - 1] },
|
|
1041
|
+
{ colOff: [colOffset ?? 0] },
|
|
1042
|
+
{ row: [row - 1] },
|
|
1043
|
+
{ rowOff: [rowOffset ?? 0] }
|
|
1044
|
+
] };
|
|
1045
|
+
}
|
|
1046
|
+
buildToAnchor(col, row) {
|
|
1047
|
+
return { to: [
|
|
1048
|
+
{ col: [col] },
|
|
1049
|
+
{ colOff: [0] },
|
|
1050
|
+
{ row: [row] },
|
|
1051
|
+
{ rowOff: [0] }
|
|
1052
|
+
] };
|
|
1053
|
+
}
|
|
1054
|
+
buildImageAnchor(img, id) {
|
|
1055
|
+
return { twoCellAnchor: [
|
|
1056
|
+
{ _attr: { editAs: "oneCell" } },
|
|
1057
|
+
this.buildFromAnchor(img.col, img.row, img.colOffset, img.rowOffset),
|
|
1058
|
+
this.buildToAnchor(img.col, img.row),
|
|
1059
|
+
{ pic: [
|
|
1060
|
+
{ nvPicPr: [{ cNvPr: { _attr: {
|
|
1061
|
+
id,
|
|
1062
|
+
name: `Picture ${id}`
|
|
1063
|
+
} } }, { cNvPicPr: [{ _attr: { preferRelativeResize: 1 } }] }] },
|
|
1064
|
+
{ blipFill: [{ "a:blip": { _attr: { "r:embed": img.rId } } }, { "a:stretch": [{ "a:fillRect": [] }] }] },
|
|
1065
|
+
{ spPr: [{ "a:xfrm": [{ "a:off": { _attr: {
|
|
1066
|
+
x: 0,
|
|
1067
|
+
y: 0
|
|
1068
|
+
} } }, { "a:ext": { _attr: {
|
|
1069
|
+
cx: 4e5,
|
|
1070
|
+
cy: 3e5
|
|
1071
|
+
} } }] }, { "a:prstGeom": [{ _attr: { prst: "rect" } }, { "a:avLst": [] }] }] }
|
|
1072
|
+
] },
|
|
1073
|
+
{ clientData: [] }
|
|
1074
|
+
] };
|
|
1075
|
+
}
|
|
1076
|
+
buildChartAnchor(chart, id) {
|
|
1077
|
+
return { twoCellAnchor: [
|
|
1078
|
+
{ _attr: { editAs: "oneCell" } },
|
|
1079
|
+
this.buildFromAnchor(chart.col, chart.row, chart.colOffset, chart.rowOffset),
|
|
1080
|
+
this.buildToAnchor(chart.col + 8, chart.row + 15),
|
|
1081
|
+
{ graphicFrame: [
|
|
1082
|
+
{ nvGraphicFramePr: [{ cNvPr: { _attr: {
|
|
1083
|
+
id,
|
|
1084
|
+
name: `Chart ${id}`
|
|
1085
|
+
} } }, { cNvGraphicFramePr: [{ "a:graphicFrameLocks": { _attr: { noGrp: 1 } } }] }] },
|
|
1086
|
+
{ xfrm: [{ "a:off": { _attr: {
|
|
1087
|
+
x: 0,
|
|
1088
|
+
y: 0
|
|
1089
|
+
} } }, { "a:ext": { _attr: {
|
|
1090
|
+
cx: 0,
|
|
1091
|
+
cy: 0
|
|
1092
|
+
} } }] },
|
|
1093
|
+
{ "a:graphic": [{ "a:graphicData": [{ _attr: { uri: C_URI } }, { "c:chart": { _attr: {
|
|
1094
|
+
"xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
|
|
1095
|
+
"xmlns:r": R_NS,
|
|
1096
|
+
"r:id": chart.rId
|
|
1097
|
+
} } }] }] }
|
|
1098
|
+
] },
|
|
1099
|
+
{ clientData: [] }
|
|
1100
|
+
] };
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
//#endregion
|
|
1104
|
+
//#region src/export/packer/next-compiler.ts
|
|
1105
|
+
/**
|
|
1106
|
+
* XLSX Compiler — compiles a File object into a Zippable structure.
|
|
1107
|
+
*
|
|
1108
|
+
* @module
|
|
1109
|
+
*/
|
|
1110
|
+
var Compiler = class {
|
|
1111
|
+
formatter = new Formatter();
|
|
1112
|
+
compile(file, overrides = []) {
|
|
1113
|
+
const context = {
|
|
1114
|
+
fileData: file,
|
|
1115
|
+
stack: []
|
|
1116
|
+
};
|
|
1117
|
+
const f = this.formatter;
|
|
1118
|
+
const mapping = {};
|
|
1119
|
+
const fmt = (component, declaration) => f.formatToXml(component, context, declaration);
|
|
1120
|
+
mapping["Properties"] = {
|
|
1121
|
+
data: fmt(file.coreProperties, true),
|
|
1122
|
+
path: "docProps/core.xml"
|
|
1123
|
+
};
|
|
1124
|
+
mapping["AppProperties"] = {
|
|
1125
|
+
data: fmt(file.appProperties, true),
|
|
1126
|
+
path: "docProps/app.xml"
|
|
1127
|
+
};
|
|
1128
|
+
mapping["FileRelationships"] = {
|
|
1129
|
+
data: fmt(file.fileRelationships),
|
|
1130
|
+
path: "_rels/.rels"
|
|
1131
|
+
};
|
|
1132
|
+
mapping["Workbook"] = {
|
|
1133
|
+
data: fmt(file.workbookXml, true),
|
|
1134
|
+
path: "xl/workbook.xml"
|
|
1135
|
+
};
|
|
1136
|
+
mapping["WorkbookRelationships"] = {
|
|
1137
|
+
data: fmt(file.workbookRelationships),
|
|
1138
|
+
path: "xl/_rels/workbook.xml.rels"
|
|
1139
|
+
};
|
|
1140
|
+
const worksheets = file.worksheets;
|
|
1141
|
+
let globalMediaIdx = 0;
|
|
1142
|
+
let globalChartIdx = 0;
|
|
1143
|
+
for (let i = 0; i < worksheets.length; i++) {
|
|
1144
|
+
const ws = worksheets[i];
|
|
1145
|
+
const imgOpts = ws.imageOptions;
|
|
1146
|
+
const chartOpts = ws.charts;
|
|
1147
|
+
let sheetXml = fmt(ws, true);
|
|
1148
|
+
if (imgOpts.length > 0 || chartOpts.length > 0) {
|
|
1149
|
+
const drawingImages = [];
|
|
1150
|
+
const drawingCharts = [];
|
|
1151
|
+
const drawingRels = new Relationships();
|
|
1152
|
+
let rid = 1;
|
|
1153
|
+
for (const img of imgOpts) {
|
|
1154
|
+
const mediaKey = `image_${globalMediaIdx}`;
|
|
1155
|
+
const ext = img.type === "jpeg" || img.type === "jpg" ? "jpeg" : "png";
|
|
1156
|
+
file.media.addImage(mediaKey, {
|
|
1157
|
+
fileName: `image${globalMediaIdx + 1}.${ext}`,
|
|
1158
|
+
type: ext,
|
|
1159
|
+
data: img.data,
|
|
1160
|
+
width: 0,
|
|
1161
|
+
height: 0
|
|
1162
|
+
});
|
|
1163
|
+
drawingRels.addRelationship(rid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", `../media/image${globalMediaIdx + 1}.${ext}`);
|
|
1164
|
+
drawingImages.push({
|
|
1165
|
+
col: img.col,
|
|
1166
|
+
row: img.row,
|
|
1167
|
+
rId: `rId${rid}`
|
|
1168
|
+
});
|
|
1169
|
+
rid++;
|
|
1170
|
+
globalMediaIdx++;
|
|
1171
|
+
}
|
|
1172
|
+
for (const chart of chartOpts) {
|
|
1173
|
+
const chartKey = `chart_${globalChartIdx}`;
|
|
1174
|
+
file.charts.addChart(chartKey, {
|
|
1175
|
+
key: chartKey,
|
|
1176
|
+
chartSpace: new ChartSpace(chart)
|
|
1177
|
+
});
|
|
1178
|
+
drawingRels.addRelationship(rid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart", `../charts/chart${globalChartIdx + 1}.xml`);
|
|
1179
|
+
drawingCharts.push({
|
|
1180
|
+
col: chart.col,
|
|
1181
|
+
row: chart.row,
|
|
1182
|
+
rId: `rId${rid}`
|
|
1183
|
+
});
|
|
1184
|
+
rid++;
|
|
1185
|
+
globalChartIdx++;
|
|
1186
|
+
}
|
|
1187
|
+
const drawing = new Drawing(drawingImages, drawingCharts);
|
|
1188
|
+
const drawingIdx = i + 1;
|
|
1189
|
+
mapping[`Drawing${i}`] = {
|
|
1190
|
+
data: fmt(drawing, true),
|
|
1191
|
+
path: `xl/drawings/drawing${drawingIdx}.xml`
|
|
1192
|
+
};
|
|
1193
|
+
mapping[`DrawingRels${i}`] = {
|
|
1194
|
+
data: fmt(drawingRels),
|
|
1195
|
+
path: `xl/drawings/_rels/drawing${drawingIdx}.xml.rels`
|
|
1196
|
+
};
|
|
1197
|
+
sheetXml = sheetXml.slice(0, -12) + `<drawing r:id="rId${rid}"/></worksheet>`;
|
|
1198
|
+
const wsRels = new Relationships();
|
|
1199
|
+
wsRels.addRelationship(rid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing", `../drawings/drawing${drawingIdx}.xml`);
|
|
1200
|
+
mapping[`WorksheetRels${i}`] = {
|
|
1201
|
+
data: fmt(wsRels),
|
|
1202
|
+
path: `xl/worksheets/_rels/sheet${i + 1}.xml.rels`
|
|
1203
|
+
};
|
|
1204
|
+
file.contentTypes.addDrawing(drawingIdx);
|
|
1205
|
+
}
|
|
1206
|
+
mapping[`Worksheet${i}`] = {
|
|
1207
|
+
data: sheetXml,
|
|
1208
|
+
path: `xl/worksheets/sheet${i + 1}.xml`
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
const sharedStrings = file.sharedStrings;
|
|
1212
|
+
if (sharedStrings.count > 0) mapping["SharedStrings"] = {
|
|
1213
|
+
data: fmt(sharedStrings, true),
|
|
1214
|
+
path: "xl/sharedStrings.xml"
|
|
1215
|
+
};
|
|
1216
|
+
mapping["Styles"] = {
|
|
1217
|
+
data: fmt(file.styles, true),
|
|
1218
|
+
path: "xl/styles.xml"
|
|
1219
|
+
};
|
|
1220
|
+
mapping["Theme"] = {
|
|
1221
|
+
data: fmt(file.theme, true),
|
|
1222
|
+
path: "xl/theme/theme1.xml"
|
|
1223
|
+
};
|
|
1224
|
+
for (let i = 0; i < file.charts.array.length; i++) {
|
|
1225
|
+
const chartData = file.charts.array[i];
|
|
1226
|
+
mapping[`Chart${i}`] = {
|
|
1227
|
+
data: fmt(chartData.chartSpace, true),
|
|
1228
|
+
path: `xl/charts/chart${i + 1}.xml`
|
|
1229
|
+
};
|
|
1230
|
+
file.contentTypes.addChart(i + 1);
|
|
1231
|
+
}
|
|
1232
|
+
mapping["ContentTypes"] = {
|
|
1233
|
+
data: fmt(file.contentTypes),
|
|
1234
|
+
path: "[Content_Types].xml"
|
|
1235
|
+
};
|
|
1236
|
+
return compileMapping(mapping, overrides, file.media.array.map((img) => ({
|
|
1237
|
+
data: img.data,
|
|
1238
|
+
path: `xl/media/${img.fileName}`
|
|
1239
|
+
})));
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
//#endregion
|
|
1243
|
+
//#region src/export/packer/packer.ts
|
|
1244
|
+
/**
|
|
1245
|
+
* Packer module — export API for XLSX files.
|
|
1246
|
+
*
|
|
1247
|
+
* @module
|
|
1248
|
+
*/
|
|
1249
|
+
const compiler = new Compiler();
|
|
1250
|
+
const Packer = createPacker({
|
|
1251
|
+
compile: (file, overrides) => compiler.compile(file, overrides),
|
|
1252
|
+
mimeType: OoxmlMimeType.XLSX
|
|
1253
|
+
});
|
|
1254
|
+
//#endregion
|
|
1255
|
+
//#region src/util/index.ts
|
|
1256
|
+
/**
|
|
1257
|
+
* Convert a 1-based column number to Excel column letter(s).
|
|
1258
|
+
* 1 → "A", 26 → "Z", 27 → "AA", 28 → "AB"
|
|
1259
|
+
*/
|
|
1260
|
+
function columnToLetter(col) {
|
|
1261
|
+
let result = "";
|
|
1262
|
+
let n = col;
|
|
1263
|
+
while (n > 0) {
|
|
1264
|
+
const remainder = (n - 1) % 26;
|
|
1265
|
+
result = String.fromCharCode(65 + remainder) + result;
|
|
1266
|
+
n = Math.floor((n - 1) / 26);
|
|
1267
|
+
}
|
|
1268
|
+
return result;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Convert Excel column letter(s) to a 1-based column number.
|
|
1272
|
+
* "A" → 1, "Z" → 26, "AA" → 27
|
|
1273
|
+
*/
|
|
1274
|
+
function letterToColumn(s) {
|
|
1275
|
+
let result = 0;
|
|
1276
|
+
for (let i = 0; i < s.length; i++) result = result * 26 + (s.charCodeAt(i) - 64);
|
|
1277
|
+
return result;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Convert a JavaScript Date to an Excel serial number.
|
|
1281
|
+
* Excel epoch: January 1, 1900 = 1 (with the 1900 leap year bug).
|
|
1282
|
+
*/
|
|
1283
|
+
function dateToSerialNumber(date) {
|
|
1284
|
+
const epoch = new Date(1899, 11, 30);
|
|
1285
|
+
return (date.getTime() - epoch.getTime()) / 864e5;
|
|
1286
|
+
}
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/parse.ts
|
|
1289
|
+
/**
|
|
1290
|
+
* XLSX parsing — parse .xlsx files into structured data.
|
|
1291
|
+
*
|
|
1292
|
+
* @module
|
|
1293
|
+
*/
|
|
1294
|
+
function sortByNumber(paths) {
|
|
1295
|
+
return paths.sort((a, b) => {
|
|
1296
|
+
return parseInt(a.match(/(\d+)/)?.[1] ?? "0", 10) - parseInt(b.match(/(\d+)/)?.[1] ?? "0", 10);
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Parse raw .xlsx data into a low-level XlsxDocument.
|
|
1301
|
+
*/
|
|
1302
|
+
function parseXlsx(data) {
|
|
1303
|
+
const doc = parseArchive(toUint8Array(data));
|
|
1304
|
+
const workbook = doc.get("xl/workbook.xml");
|
|
1305
|
+
const styles = doc.get("xl/styles.xml");
|
|
1306
|
+
const sharedStrings = doc.get("xl/sharedStrings.xml");
|
|
1307
|
+
const worksheets = [];
|
|
1308
|
+
const charts = [];
|
|
1309
|
+
const drawings = [];
|
|
1310
|
+
const media = [];
|
|
1311
|
+
const wbRels = doc.get("xl/_rels/workbook.xml.rels");
|
|
1312
|
+
if (wbRels) for (const child of wbRels.elements ?? []) {
|
|
1313
|
+
if (child.name !== "Relationship") continue;
|
|
1314
|
+
const type = attr(child, "Type") ?? "";
|
|
1315
|
+
const target = attr(child, "Target") ?? "";
|
|
1316
|
+
if (!target) continue;
|
|
1317
|
+
if (type.includes("/worksheet")) worksheets.push(target.startsWith("/") ? target.slice(1) : `xl/${target}`);
|
|
1318
|
+
}
|
|
1319
|
+
sortByNumber(worksheets);
|
|
1320
|
+
drawings.push(...doc.keys("xl/drawings/").filter((k) => k.endsWith(".xml")));
|
|
1321
|
+
charts.push(...doc.keys("xl/charts/").filter((k) => k.endsWith(".xml")));
|
|
1322
|
+
media.push(...doc.keys("xl/media/"));
|
|
1323
|
+
sortByNumber(drawings);
|
|
1324
|
+
sortByNumber(charts);
|
|
1325
|
+
let coreProps;
|
|
1326
|
+
let appProps;
|
|
1327
|
+
const rootRels = doc.get("_rels/.rels");
|
|
1328
|
+
if (rootRels) for (const child of rootRels.elements ?? []) {
|
|
1329
|
+
if (child.name !== "Relationship") continue;
|
|
1330
|
+
const type = attr(child, "Type") ?? "";
|
|
1331
|
+
const target = attr(child, "Target") ?? "";
|
|
1332
|
+
if (type.includes("/core-properties")) coreProps = target;
|
|
1333
|
+
else if (type.includes("/extended-properties")) appProps = target;
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
doc,
|
|
1337
|
+
workbook,
|
|
1338
|
+
worksheets,
|
|
1339
|
+
styles,
|
|
1340
|
+
sharedStrings,
|
|
1341
|
+
partRefs: {
|
|
1342
|
+
worksheets,
|
|
1343
|
+
charts,
|
|
1344
|
+
media,
|
|
1345
|
+
drawings
|
|
1346
|
+
},
|
|
1347
|
+
coreProps,
|
|
1348
|
+
appProps
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
function parseSharedStrings(el) {
|
|
1352
|
+
if (!el) return [];
|
|
1353
|
+
const strings = [];
|
|
1354
|
+
for (const si of el.elements ?? []) {
|
|
1355
|
+
if (si.name !== "si") continue;
|
|
1356
|
+
const t = findChild(si, "t");
|
|
1357
|
+
if (t) strings.push(textOf(t) ?? "");
|
|
1358
|
+
else {
|
|
1359
|
+
const parts = [];
|
|
1360
|
+
for (const r of si.elements ?? []) {
|
|
1361
|
+
if (r.name !== "r") continue;
|
|
1362
|
+
const rt = findChild(r, "t");
|
|
1363
|
+
if (rt) parts.push(textOf(rt) ?? "");
|
|
1364
|
+
}
|
|
1365
|
+
strings.push(parts.join(""));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return strings;
|
|
1369
|
+
}
|
|
1370
|
+
function colFromRef(ref) {
|
|
1371
|
+
const match = ref.match(/^([A-Z]+)/);
|
|
1372
|
+
return match ? letterToColumn(match[1]) : 1;
|
|
1373
|
+
}
|
|
1374
|
+
function rowFromRef(ref) {
|
|
1375
|
+
const match = ref.match(/(\d+)$/);
|
|
1376
|
+
return match ? parseInt(match[1], 10) : 1;
|
|
1377
|
+
}
|
|
1378
|
+
function parseWorksheetElement(wsEl, strings) {
|
|
1379
|
+
const opts = {};
|
|
1380
|
+
const colsEl = findChild(wsEl, "cols") ?? findChildByLocalName(wsEl, "cols");
|
|
1381
|
+
if (colsEl) {
|
|
1382
|
+
const columns = [];
|
|
1383
|
+
for (const col of colsEl.elements ?? []) {
|
|
1384
|
+
if (localName$1(col) !== "col") continue;
|
|
1385
|
+
const min = attrNum(col, "min");
|
|
1386
|
+
const max = attrNum(col, "max");
|
|
1387
|
+
if (min === void 0 || max === void 0) continue;
|
|
1388
|
+
const colOpts = {
|
|
1389
|
+
min,
|
|
1390
|
+
max
|
|
1391
|
+
};
|
|
1392
|
+
const width = attrNum(col, "width");
|
|
1393
|
+
if (width !== void 0) colOpts.width = width;
|
|
1394
|
+
if (attr(col, "hidden") === "1") colOpts.hidden = true;
|
|
1395
|
+
columns.push(colOpts);
|
|
1396
|
+
}
|
|
1397
|
+
if (columns.length > 0) opts.columns = columns;
|
|
1398
|
+
}
|
|
1399
|
+
const sheetViews = findChildByLocalName(wsEl, "sheetViews");
|
|
1400
|
+
if (sheetViews) {
|
|
1401
|
+
const sheetView = findChildByLocalName(sheetViews, "sheetView");
|
|
1402
|
+
if (sheetView) {
|
|
1403
|
+
const pane = findChildByLocalName(sheetView, "pane");
|
|
1404
|
+
if (pane) {
|
|
1405
|
+
if (attr(pane, "state") === "frozen") {
|
|
1406
|
+
const freezePanes = {};
|
|
1407
|
+
const ySplit = attrNum(pane, "ySplit");
|
|
1408
|
+
const xSplit = attrNum(pane, "xSplit");
|
|
1409
|
+
if (ySplit && ySplit > 0) freezePanes.row = ySplit;
|
|
1410
|
+
if (xSplit && xSplit > 0) freezePanes.col = xSplit;
|
|
1411
|
+
if (Object.keys(freezePanes).length > 0) opts.freezePanes = freezePanes;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
const sheetData = findChildByLocalName(wsEl, "sheetData");
|
|
1417
|
+
const rows = [];
|
|
1418
|
+
if (sheetData) for (const rowEl of sheetData.elements ?? []) {
|
|
1419
|
+
if (localName$1(rowEl) !== "row") continue;
|
|
1420
|
+
const rowNumber = attrNum(rowEl, "r");
|
|
1421
|
+
const rowOpts = {};
|
|
1422
|
+
if (rowNumber !== void 0) rowOpts.rowNumber = rowNumber;
|
|
1423
|
+
const ht = attrNum(rowEl, "ht");
|
|
1424
|
+
if (ht !== void 0) rowOpts.height = ht;
|
|
1425
|
+
if (attr(rowEl, "hidden") === "1") rowOpts.hidden = true;
|
|
1426
|
+
const cells = [];
|
|
1427
|
+
for (const cellEl of rowEl.elements ?? []) {
|
|
1428
|
+
if (localName$1(cellEl) !== "c") continue;
|
|
1429
|
+
const ref = attr(cellEl, "r");
|
|
1430
|
+
const type = attr(cellEl, "t");
|
|
1431
|
+
const cellOpts = {};
|
|
1432
|
+
if (ref) cellOpts.reference = ref;
|
|
1433
|
+
const styleIdx = attrNum(cellEl, "s");
|
|
1434
|
+
if (styleIdx !== void 0) cellOpts.styleIndex = styleIdx;
|
|
1435
|
+
const vEl = findChildByLocalName(cellEl, "v");
|
|
1436
|
+
const isEl = findChildByLocalName(cellEl, "is");
|
|
1437
|
+
if (type === "s" && vEl) cellOpts.value = strings[parseInt(textOf(vEl) ?? "", 10)] ?? "";
|
|
1438
|
+
else if (type === "b" && vEl) cellOpts.value = textOf(vEl) === "1";
|
|
1439
|
+
else if (type === "inlineStr" && isEl) cellOpts.value = textOf(findChildByLocalName(isEl, "t")) ?? "";
|
|
1440
|
+
else if (vEl) {
|
|
1441
|
+
const raw = textOf(vEl) ?? "";
|
|
1442
|
+
const num = Number(raw);
|
|
1443
|
+
cellOpts.value = isNaN(num) ? raw : num;
|
|
1444
|
+
}
|
|
1445
|
+
cells.push(cellOpts);
|
|
1446
|
+
}
|
|
1447
|
+
rowOpts.cells = cells;
|
|
1448
|
+
rows.push(rowOpts);
|
|
1449
|
+
}
|
|
1450
|
+
opts.children = rows;
|
|
1451
|
+
const mergeCellsEl = findChildByLocalName(wsEl, "mergeCells");
|
|
1452
|
+
if (mergeCellsEl) {
|
|
1453
|
+
const mergeCells = [];
|
|
1454
|
+
for (const mc of mergeCellsEl.elements ?? []) {
|
|
1455
|
+
if (localName$1(mc) !== "mergeCell") continue;
|
|
1456
|
+
const ref = attr(mc, "ref");
|
|
1457
|
+
if (!ref) continue;
|
|
1458
|
+
const parts = ref.split(":");
|
|
1459
|
+
if (parts.length === 2) mergeCells.push({
|
|
1460
|
+
from: {
|
|
1461
|
+
row: rowFromRef(parts[0]),
|
|
1462
|
+
col: colFromRef(parts[0])
|
|
1463
|
+
},
|
|
1464
|
+
to: {
|
|
1465
|
+
row: rowFromRef(parts[1]),
|
|
1466
|
+
col: colFromRef(parts[1])
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
if (mergeCells.length > 0) opts.mergeCells = mergeCells;
|
|
1471
|
+
}
|
|
1472
|
+
const autoFilterEl = findChildByLocalName(wsEl, "autoFilter");
|
|
1473
|
+
if (autoFilterEl) {
|
|
1474
|
+
const ref = attr(autoFilterEl, "ref");
|
|
1475
|
+
if (ref) opts.autoFilter = ref;
|
|
1476
|
+
}
|
|
1477
|
+
return opts;
|
|
1478
|
+
}
|
|
1479
|
+
function localName$1(el) {
|
|
1480
|
+
const name = el.name ?? "";
|
|
1481
|
+
const colonIdx = name.indexOf(":");
|
|
1482
|
+
return colonIdx >= 0 ? name.slice(colonIdx + 1) : name;
|
|
1483
|
+
}
|
|
1484
|
+
function findChildByLocalName(parent, name) {
|
|
1485
|
+
return (parent.elements ?? []).find((el) => localName$1(el) === name);
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Parse a .xlsx file and convert it into WorkbookOptions.
|
|
1489
|
+
*
|
|
1490
|
+
* The returned options can be passed to `new Workbook(parsed)`.
|
|
1491
|
+
*/
|
|
1492
|
+
function parseWorkbook(data) {
|
|
1493
|
+
const xlsx = parseXlsx(data);
|
|
1494
|
+
const opts = {};
|
|
1495
|
+
if (xlsx.coreProps) {
|
|
1496
|
+
const corePropsEl = xlsx.doc.get(xlsx.coreProps);
|
|
1497
|
+
if (corePropsEl) {
|
|
1498
|
+
const cp = parseCorePropsElement(corePropsEl);
|
|
1499
|
+
if (cp.title) opts.title = cp.title;
|
|
1500
|
+
if (cp.subject) opts.subject = cp.subject;
|
|
1501
|
+
if (cp.creator) opts.creator = cp.creator;
|
|
1502
|
+
if (cp.keywords) opts.keywords = cp.keywords;
|
|
1503
|
+
if (cp.description) opts.description = cp.description;
|
|
1504
|
+
if (cp.lastModifiedBy) opts.lastModifiedBy = cp.lastModifiedBy;
|
|
1505
|
+
if (cp.revision) opts.revision = parseInt(cp.revision, 10);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
const strings = parseSharedStrings(xlsx.sharedStrings);
|
|
1509
|
+
const sheetNames = [];
|
|
1510
|
+
if (xlsx.workbook) {
|
|
1511
|
+
const sheetsEl = findChildByLocalName(xlsx.workbook, "sheets");
|
|
1512
|
+
if (sheetsEl) for (const s of sheetsEl.elements ?? []) {
|
|
1513
|
+
if (localName$1(s) !== "sheet") continue;
|
|
1514
|
+
sheetNames.push(attr(s, "name") ?? "");
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const worksheets = [];
|
|
1518
|
+
for (let i = 0; i < xlsx.worksheets.length; i++) {
|
|
1519
|
+
const wsPath = xlsx.worksheets[i];
|
|
1520
|
+
const wsEl = xlsx.doc.get(wsPath);
|
|
1521
|
+
if (!wsEl) continue;
|
|
1522
|
+
const wsOpts = parseWorksheetElement(wsEl, strings);
|
|
1523
|
+
if (sheetNames[i]) wsOpts.name = sheetNames[i];
|
|
1524
|
+
worksheets.push(wsOpts);
|
|
1525
|
+
}
|
|
1526
|
+
opts.worksheets = worksheets;
|
|
1527
|
+
return opts;
|
|
1528
|
+
}
|
|
1529
|
+
//#endregion
|
|
1530
|
+
//#region src/patcher.ts
|
|
1531
|
+
/**
|
|
1532
|
+
* XLSX patching — replace placeholders in existing .xlsx files.
|
|
1533
|
+
*
|
|
1534
|
+
* Unlike DOCX/PPTX (paragraph-based), XLSX patching targets cell values:
|
|
1535
|
+
* - Replaces placeholders in the shared strings table (most common)
|
|
1536
|
+
* - Replaces placeholders in inline strings within worksheet cells
|
|
1537
|
+
*
|
|
1538
|
+
* @module
|
|
1539
|
+
*/
|
|
1540
|
+
/** Reusable TextEncoder (stateless, safe to share). */
|
|
1541
|
+
const encoder = new TextEncoder();
|
|
1542
|
+
const PatchType = { CELL: "cell" };
|
|
1543
|
+
/**
|
|
1544
|
+
* Patch an existing .xlsx workbook by replacing placeholder text in cells.
|
|
1545
|
+
*
|
|
1546
|
+
* Placeholders are matched in shared strings and inline strings.
|
|
1547
|
+
* For string replacements, the shared string value is updated in-place.
|
|
1548
|
+
* For non-string replacements, the cell type and value are updated.
|
|
1549
|
+
*/
|
|
1550
|
+
const patchWorkbook = async ({ outputType, data, patches, placeholderDelimiters = {
|
|
1551
|
+
start: "{{",
|
|
1552
|
+
end: "}}"
|
|
1553
|
+
} }) => {
|
|
1554
|
+
const zipContent = unzipSync(toUint8Array(data));
|
|
1555
|
+
const xmlMap = /* @__PURE__ */ new Map();
|
|
1556
|
+
const binaryMap = /* @__PURE__ */ new Map();
|
|
1557
|
+
for (const [key, value] of Object.entries(zipContent)) if (key.endsWith(".xml") || key.endsWith(".rels")) xmlMap.set(key, toJson(strFromU8(value)));
|
|
1558
|
+
else binaryMap.set(key, value);
|
|
1559
|
+
const { start, end } = placeholderDelimiters;
|
|
1560
|
+
const patchMap = /* @__PURE__ */ new Map();
|
|
1561
|
+
for (const [key, patch] of Object.entries(patches)) patchMap.set(`${start}${key}${end}`, patch);
|
|
1562
|
+
const sst = xmlMap.get("xl/sharedStrings.xml");
|
|
1563
|
+
if (sst) patchSharedStrings(sst, patchMap);
|
|
1564
|
+
const worksheetKeys = Object.keys(zipContent).filter((k) => k.startsWith("xl/worksheets/sheet") && k.endsWith(".xml"));
|
|
1565
|
+
for (const wsKey of worksheetKeys) {
|
|
1566
|
+
const ws = xmlMap.get(wsKey);
|
|
1567
|
+
if (ws) patchWorksheetInlineStrings(ws, patchMap);
|
|
1568
|
+
}
|
|
1569
|
+
const files = {};
|
|
1570
|
+
for (const [key, value] of xmlMap) files[key] = encoder.encode(js2xml(value));
|
|
1571
|
+
for (const [key, value] of binaryMap) files[key] = value;
|
|
1572
|
+
return await zipAndConvert(files, outputType, OoxmlMimeType.XLSX);
|
|
1573
|
+
};
|
|
1574
|
+
function patchSharedStrings(sst, patchMap) {
|
|
1575
|
+
const root = sst.name ? sst : sst.elements?.[0];
|
|
1576
|
+
if (!root) return;
|
|
1577
|
+
for (const si of root.elements ?? []) {
|
|
1578
|
+
if (si.name !== "si") continue;
|
|
1579
|
+
const t = findLocalChild(si, "t");
|
|
1580
|
+
if (t) patchTextElement(t, patchMap);
|
|
1581
|
+
else for (const r of si.elements ?? []) {
|
|
1582
|
+
if (r.name !== "r") continue;
|
|
1583
|
+
const rt = findLocalChild(r, "t");
|
|
1584
|
+
if (rt) patchTextElement(rt, patchMap);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
function patchWorksheetInlineStrings(ws, patchMap) {
|
|
1589
|
+
const root = ws.name ? ws : ws.elements?.[0];
|
|
1590
|
+
if (!root) return;
|
|
1591
|
+
const sheetData = findLocalChild(root, "sheetData");
|
|
1592
|
+
if (!sheetData) return;
|
|
1593
|
+
for (const row of sheetData.elements ?? []) {
|
|
1594
|
+
if (localName(row) !== "row") continue;
|
|
1595
|
+
for (const cell of row.elements ?? []) {
|
|
1596
|
+
if (localName(cell) !== "c") continue;
|
|
1597
|
+
const isEl = findLocalChild(cell, "is");
|
|
1598
|
+
if (isEl) {
|
|
1599
|
+
const t = findLocalChild(isEl, "t");
|
|
1600
|
+
if (t) patchTextElement(t, patchMap);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
function patchTextElement(tEl, patchMap) {
|
|
1606
|
+
const text = tEl.elements?.[0]?.text;
|
|
1607
|
+
if (typeof text !== "string") return;
|
|
1608
|
+
for (const [placeholder, patch] of patchMap) if (text.includes(placeholder)) {
|
|
1609
|
+
const newValue = String(patch.value);
|
|
1610
|
+
const replaced = text.replace(placeholder, newValue);
|
|
1611
|
+
if (tEl.elements) tEl.elements[0] = {
|
|
1612
|
+
...tEl.elements[0],
|
|
1613
|
+
text: replaced
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function localName(el) {
|
|
1618
|
+
const name = el.name ?? "";
|
|
1619
|
+
const colonIdx = name.indexOf(":");
|
|
1620
|
+
return colonIdx >= 0 ? name.slice(colonIdx + 1) : name;
|
|
1621
|
+
}
|
|
1622
|
+
function findLocalChild(parent, name) {
|
|
1623
|
+
return (parent.elements ?? []).find((el) => localName(el) === name);
|
|
1624
|
+
}
|
|
1625
|
+
//#endregion
|
|
1626
|
+
export { File, File as Workbook, Packer, PatchType, SharedStrings, Styles, columnToLetter, dateToSerialNumber, letterToColumn, parseWorkbook, parseXlsx, patchWorkbook };
|
|
1627
|
+
|
|
1628
|
+
//# sourceMappingURL=index.js.map
|