@ex-grid/data-xlsx 0.0.0-stage → 0.2.0-rc.1
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/LICENSE +21 -0
- package/README.md +26 -2
- package/dist/index.d.ts +810 -0
- package/dist/index.js +4222 -0
- package/dist/index.js.map +7 -0
- package/package.json +23 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,4222 @@
|
|
|
1
|
+
// src/text-casing.ts
|
|
2
|
+
function foldCase(text) {
|
|
3
|
+
let folded = "";
|
|
4
|
+
let changed = false;
|
|
5
|
+
for (const character of text) {
|
|
6
|
+
const upper = upperOf(character);
|
|
7
|
+
if (upper !== character) changed = true;
|
|
8
|
+
folded += upper;
|
|
9
|
+
}
|
|
10
|
+
return changed ? folded : text;
|
|
11
|
+
}
|
|
12
|
+
function upperOf(character) {
|
|
13
|
+
const code = character.charCodeAt(0);
|
|
14
|
+
if (code < 128) return code >= 97 && code <= 122 ? String.fromCharCode(code - 32) : character;
|
|
15
|
+
const upper = character.toUpperCase();
|
|
16
|
+
const point = upper.codePointAt(0);
|
|
17
|
+
const single = point !== void 0 && upper.length === (point > 65535 ? 2 : 1);
|
|
18
|
+
if (!single) return simpleUpperOf(code, character);
|
|
19
|
+
if (upper === character || point < 128) return character;
|
|
20
|
+
return upper;
|
|
21
|
+
}
|
|
22
|
+
function simpleUpperOf(code, character) {
|
|
23
|
+
const pairs = code >= 8064 && code <= 8071 || code >= 8080 && code <= 8087 || code >= 8096 && code <= 8103;
|
|
24
|
+
if (pairs) return String.fromCharCode(code + 8);
|
|
25
|
+
if (code === 8115 || code === 8131 || code === 8179) return String.fromCharCode(code + 9);
|
|
26
|
+
return character;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/invariant.ts
|
|
30
|
+
function parseInt32(text) {
|
|
31
|
+
if (!/^[+-]?[0-9]+$/.test(text)) return void 0;
|
|
32
|
+
const value = Number(text);
|
|
33
|
+
return value >= -2147483648 && value <= 2147483647 ? value : void 0;
|
|
34
|
+
}
|
|
35
|
+
function parseDigits(text) {
|
|
36
|
+
if (!/^[0-9]+$/.test(text)) return void 0;
|
|
37
|
+
const value = Number(text);
|
|
38
|
+
return value <= 2147483647 ? value : void 0;
|
|
39
|
+
}
|
|
40
|
+
function parseHex32(text) {
|
|
41
|
+
if (!/^[0-9A-Fa-f]+$/.test(text)) return void 0;
|
|
42
|
+
const value = Number.parseInt(text, 16);
|
|
43
|
+
return value <= 4294967295 ? value : void 0;
|
|
44
|
+
}
|
|
45
|
+
function parseDouble(text) {
|
|
46
|
+
const trimmed = text.replace(/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g, "");
|
|
47
|
+
if (/^[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/.test(trimmed)) return Number(trimmed);
|
|
48
|
+
const lower = trimmed.toLowerCase();
|
|
49
|
+
if (lower === "infinity" || lower === "+infinity") return Number.POSITIVE_INFINITY;
|
|
50
|
+
if (lower === "-infinity") return Number.NEGATIVE_INFINITY;
|
|
51
|
+
if (lower === "nan" || lower === "+nan" || lower === "-nan") return Number.NaN;
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
function grouped(value) {
|
|
55
|
+
const digits = String(Math.abs(Math.trunc(value)));
|
|
56
|
+
const groups = [];
|
|
57
|
+
for (let end = digits.length; end > 0; end -= 3) groups.unshift(digits.slice(Math.max(0, end - 3), end));
|
|
58
|
+
return (value < 0 ? "-" : "") + groups.join(",");
|
|
59
|
+
}
|
|
60
|
+
function hex8(value) {
|
|
61
|
+
return (value >>> 0).toString(16).toUpperCase().padStart(8, "0");
|
|
62
|
+
}
|
|
63
|
+
function roundTrip(value) {
|
|
64
|
+
if (Number.isNaN(value)) return "NaN";
|
|
65
|
+
if (!Number.isFinite(value)) return value > 0 ? "Infinity" : "-Infinity";
|
|
66
|
+
if (value === 0) return Object.is(value, -0) ? "-0" : "0";
|
|
67
|
+
const match = /^(\d)(?:\.(\d+))?e([+-]\d+)$/.exec(Math.abs(value).toExponential());
|
|
68
|
+
if (match === null) throw new Error(`Internal: ${String(value)} is not written in exponent form.`);
|
|
69
|
+
const [, first = "", rest = "", exponent = ""] = match;
|
|
70
|
+
const digits = first + rest;
|
|
71
|
+
const point = Number(exponent) + 1;
|
|
72
|
+
const sign = value < 0 ? "-" : "";
|
|
73
|
+
if (point > 17 || point < -3) {
|
|
74
|
+
const e = point - 1;
|
|
75
|
+
const mantissa = digits.length > 1 ? `${digits.slice(0, 1)}.${digits.slice(1)}` : digits;
|
|
76
|
+
return `${sign}${mantissa}E${e < 0 ? "-" : "+"}${String(Math.abs(e)).padStart(2, "0")}`;
|
|
77
|
+
}
|
|
78
|
+
if (point <= 0) return `${sign}0.${"0".repeat(-point)}${digits}`;
|
|
79
|
+
if (point >= digits.length) return sign + digits + "0".repeat(point - digits.length);
|
|
80
|
+
return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
81
|
+
}
|
|
82
|
+
function equalsIgnoreCase(a, b) {
|
|
83
|
+
return a.length === b.length && foldCase(a) === foldCase(b);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/cell-reference.ts
|
|
87
|
+
var maxRows = 1048576;
|
|
88
|
+
var maxColumns = 16384;
|
|
89
|
+
function columnName(column) {
|
|
90
|
+
if (!Number.isInteger(column) || column < 0) throw new RangeError(`The column ${String(column)} is not a column from zero.`);
|
|
91
|
+
let letters = "";
|
|
92
|
+
let n = column + 1;
|
|
93
|
+
while (n > 0) {
|
|
94
|
+
const rem = (n - 1) % 26;
|
|
95
|
+
letters = String.fromCharCode(65 + rem) + letters;
|
|
96
|
+
n = Math.floor((n - 1) / 26);
|
|
97
|
+
}
|
|
98
|
+
return letters;
|
|
99
|
+
}
|
|
100
|
+
function formatCell(row, column) {
|
|
101
|
+
if (!Number.isInteger(row) || row < 0) throw new RangeError(`The row ${String(row)} is not a row from zero.`);
|
|
102
|
+
return columnName(column) + String(row + 1);
|
|
103
|
+
}
|
|
104
|
+
function parseCell(text) {
|
|
105
|
+
let i = 0;
|
|
106
|
+
if (text.charAt(i) === "$") i++;
|
|
107
|
+
let col = 0;
|
|
108
|
+
let letters = 0;
|
|
109
|
+
for (; i < text.length; i++) {
|
|
110
|
+
const c = text.charCodeAt(i);
|
|
111
|
+
const upper = c >= 97 && c <= 122 ? c - 32 : c;
|
|
112
|
+
if (upper < 65 || upper > 90) break;
|
|
113
|
+
col = col * 26 + (upper - 65 + 1);
|
|
114
|
+
if (++letters > 3) return void 0;
|
|
115
|
+
}
|
|
116
|
+
if (letters === 0 || col > maxColumns) return void 0;
|
|
117
|
+
if (text.charAt(i) === "$") i++;
|
|
118
|
+
let r = 0;
|
|
119
|
+
let digits = 0;
|
|
120
|
+
for (; i < text.length; i++) {
|
|
121
|
+
const c = text.charCodeAt(i);
|
|
122
|
+
if (c < 48 || c > 57) break;
|
|
123
|
+
r = r * 10 + (c - 48);
|
|
124
|
+
if (++digits > 7) return void 0;
|
|
125
|
+
}
|
|
126
|
+
if (digits === 0 || i !== text.length || r < 1 || r > maxRows) return void 0;
|
|
127
|
+
return { row: r - 1, column: col - 1 };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/names.ts
|
|
131
|
+
var mainNamespace = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
132
|
+
var relationshipsNamespace = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
133
|
+
var packageRelationshipsNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
134
|
+
var contentTypesNamespace = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
135
|
+
var drawingNamespace = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
136
|
+
var strictPrefix = "http://purl.oclc.org/ooxml/";
|
|
137
|
+
var officeDocumentType = `${relationshipsNamespace}/officeDocument`;
|
|
138
|
+
var worksheetType = `${relationshipsNamespace}/worksheet`;
|
|
139
|
+
var sharedStringsType = `${relationshipsNamespace}/sharedStrings`;
|
|
140
|
+
var stylesType = `${relationshipsNamespace}/styles`;
|
|
141
|
+
var themeType = `${relationshipsNamespace}/theme`;
|
|
142
|
+
var sheetMetadataType = `${relationshipsNamespace}/sheetMetadata`;
|
|
143
|
+
var tableType = `${relationshipsNamespace}/table`;
|
|
144
|
+
var workbookContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
|
|
145
|
+
var templateContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml";
|
|
146
|
+
var binaryContent = "application/vnd.ms-excel.sheet.binary.macroEnabled.main";
|
|
147
|
+
var worksheetContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
148
|
+
var sharedStringsContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
|
|
149
|
+
var stylesContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
|
|
150
|
+
var sheetMetadataContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml";
|
|
151
|
+
var tableContent = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml";
|
|
152
|
+
var relationshipsContent = "application/vnd.openxmlformats-package.relationships+xml";
|
|
153
|
+
var vbaProjectContent = "application/vnd.ms-office.vbaProject";
|
|
154
|
+
var dynamicArrayExtension = "{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}";
|
|
155
|
+
|
|
156
|
+
// src/xlsx-error.ts
|
|
157
|
+
var XlsxError = class extends Error {
|
|
158
|
+
/** Why the file was refused. */
|
|
159
|
+
reason;
|
|
160
|
+
/** The package part the refusal met, as `xl/worksheets/sheet1.xml`; `undefined` when it is about the file as a whole. */
|
|
161
|
+
part;
|
|
162
|
+
/** The name of the Sheet the refusal met; `undefined` when it is not about one Sheet. */
|
|
163
|
+
sheet;
|
|
164
|
+
/** The cell the refusal met, as `B7`; `undefined` when it is not about one cell. */
|
|
165
|
+
cell;
|
|
166
|
+
/**
|
|
167
|
+
* A refusal naming what it met.
|
|
168
|
+
* @param reason - Why the file is refused.
|
|
169
|
+
* @param message - What was wrong, as a sentence that follows the Sheet and the cell.
|
|
170
|
+
* @param where - The part, the Sheet and the cell it met, as far as it is about one.
|
|
171
|
+
* @param options - What caused it, when it was another error.
|
|
172
|
+
*/
|
|
173
|
+
constructor(reason, message, where = {}, options) {
|
|
174
|
+
super(formatted(where.sheet, where.cell, where.part, message), options);
|
|
175
|
+
this.name = "XlsxError";
|
|
176
|
+
this.reason = reason;
|
|
177
|
+
this.part = where.part;
|
|
178
|
+
this.sheet = where.sheet;
|
|
179
|
+
this.cell = where.cell;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function formatted(sheet, cell, part, message) {
|
|
183
|
+
if (sheet !== void 0) return cell !== void 0 ? `Sheet '${sheet}', cell ${cell}: ${message}` : `Sheet '${sheet}': ${message}`;
|
|
184
|
+
if (part !== void 0) return `Part '${part}': ${message}`;
|
|
185
|
+
return message;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/must.ts
|
|
189
|
+
function elementAt(list, index) {
|
|
190
|
+
const value = list[index];
|
|
191
|
+
if (value === void 0) {
|
|
192
|
+
throw new Error(`Internal: index ${String(index)} is outside a list of ${String(list.length)}.`);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
function unreachable(value) {
|
|
197
|
+
throw new Error(`Internal: ${String(value)} is not a value this switch takes.`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/xml-reader.ts
|
|
201
|
+
var XmlError = class extends Error {
|
|
202
|
+
constructor(message, options) {
|
|
203
|
+
super(message, options);
|
|
204
|
+
this.name = "XmlError";
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var NeedMore = class extends Error {
|
|
208
|
+
constructor() {
|
|
209
|
+
super("Internal: the XML reader needs more text than it holds.");
|
|
210
|
+
this.name = "NeedMore";
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
|
|
214
|
+
var xmlNamespace = "http://www.w3.org/XML/1998/namespace";
|
|
215
|
+
var invalidCharacter = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]/;
|
|
216
|
+
var nameStart = /[A-Z_a-z:\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\uD800-\uDBFF]/;
|
|
217
|
+
var nameRest = /^[A-Z_a-z:\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\uD800-\uDFFF.\-0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/;
|
|
218
|
+
function split(qualified) {
|
|
219
|
+
const colon = qualified.indexOf(":");
|
|
220
|
+
return colon < 0 ? ["", qualified] : [qualified.slice(0, colon), qualified.slice(colon + 1)];
|
|
221
|
+
}
|
|
222
|
+
function isSpace(c) {
|
|
223
|
+
return c === 32 || c === 9 || c === 10 || c === 13;
|
|
224
|
+
}
|
|
225
|
+
function lineFeeds(text) {
|
|
226
|
+
return text.includes("\r") ? text.replace(/\r\n?/g, "\n") : text;
|
|
227
|
+
}
|
|
228
|
+
function checkCharacters(text) {
|
|
229
|
+
const bad = invalidCharacter.exec(text);
|
|
230
|
+
if (bad !== null) {
|
|
231
|
+
const code = bad[0].charCodeAt(0);
|
|
232
|
+
throw new XmlError(`'${bad[0]}', hexadecimal value 0x${code.toString(16).toUpperCase().padStart(2, "0")}, is an invalid character.`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function checkName(name) {
|
|
236
|
+
if (name === "" || !nameStart.test(name.charAt(0)) || !nameRest.test(name.slice(1))) {
|
|
237
|
+
throw new XmlError(`'${name}' is not a name XML allows.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function unescape(text) {
|
|
241
|
+
if (!text.includes("&")) return text;
|
|
242
|
+
let out = "";
|
|
243
|
+
let at = 0;
|
|
244
|
+
for (; ; ) {
|
|
245
|
+
const amp = text.indexOf("&", at);
|
|
246
|
+
if (amp < 0) return out + text.slice(at);
|
|
247
|
+
const semi = text.indexOf(";", amp + 1);
|
|
248
|
+
if (semi < 0) throw new XmlError("An entity reference is not ended by a semicolon.");
|
|
249
|
+
out += text.slice(at, amp);
|
|
250
|
+
const name = text.slice(amp + 1, semi);
|
|
251
|
+
switch (name) {
|
|
252
|
+
case "lt":
|
|
253
|
+
out += "<";
|
|
254
|
+
break;
|
|
255
|
+
case "gt":
|
|
256
|
+
out += ">";
|
|
257
|
+
break;
|
|
258
|
+
case "amp":
|
|
259
|
+
out += "&";
|
|
260
|
+
break;
|
|
261
|
+
case "quot":
|
|
262
|
+
out += '"';
|
|
263
|
+
break;
|
|
264
|
+
case "apos":
|
|
265
|
+
out += "'";
|
|
266
|
+
break;
|
|
267
|
+
default: {
|
|
268
|
+
const decimal = /^#([0-9]+)$/.exec(name);
|
|
269
|
+
const hexadecimal = /^#x([0-9a-fA-F]+)$/.exec(name);
|
|
270
|
+
const digits = decimal?.[1] ?? hexadecimal?.[1];
|
|
271
|
+
if (digits === void 0) {
|
|
272
|
+
if (name.startsWith("#")) throw new XmlError(`'&${name};' is not a character reference.`);
|
|
273
|
+
throw new XmlError(`Reference to undeclared entity '${name}'.`);
|
|
274
|
+
}
|
|
275
|
+
const code = Number.parseInt(digits, decimal === null ? 16 : 10);
|
|
276
|
+
const valid = code === 9 || code === 10 || code === 13 || code >= 32 && code <= 55295 || code >= 57344 && code <= 65533 || code >= 65536 && code <= 1114111;
|
|
277
|
+
if (!valid) throw new XmlError(`The character reference '&${name};' is to a character XML does not allow.`);
|
|
278
|
+
out += String.fromCodePoint(code);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
at = semi + 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
var XmlReader = class _XmlReader {
|
|
285
|
+
#source;
|
|
286
|
+
#text = "";
|
|
287
|
+
#pos = 0;
|
|
288
|
+
#ended = false;
|
|
289
|
+
#nodeType = "none";
|
|
290
|
+
#localName = "";
|
|
291
|
+
#prefix = "";
|
|
292
|
+
#namespaceUri = "";
|
|
293
|
+
#value = "";
|
|
294
|
+
#isEmptyElement = false;
|
|
295
|
+
#depth = 0;
|
|
296
|
+
#attributes = [];
|
|
297
|
+
#stack = [];
|
|
298
|
+
#popOnNext = false;
|
|
299
|
+
#started = false;
|
|
300
|
+
#rootSeen = false;
|
|
301
|
+
#rootClosed = false;
|
|
302
|
+
#eof = false;
|
|
303
|
+
constructor(source) {
|
|
304
|
+
this.#source = source;
|
|
305
|
+
}
|
|
306
|
+
/** A reader of `text`, whole. */
|
|
307
|
+
static of(text) {
|
|
308
|
+
let given = false;
|
|
309
|
+
return new _XmlReader(() => {
|
|
310
|
+
if (given) return Promise.resolve(void 0);
|
|
311
|
+
given = true;
|
|
312
|
+
return Promise.resolve(text);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
nodeType() {
|
|
316
|
+
return this.#nodeType;
|
|
317
|
+
}
|
|
318
|
+
localName() {
|
|
319
|
+
return this.#localName;
|
|
320
|
+
}
|
|
321
|
+
prefix() {
|
|
322
|
+
return this.#prefix;
|
|
323
|
+
}
|
|
324
|
+
namespaceUri() {
|
|
325
|
+
return this.#namespaceUri;
|
|
326
|
+
}
|
|
327
|
+
/** The text of a text, CDATA or whitespace node; '' on an element. */
|
|
328
|
+
value() {
|
|
329
|
+
return this.#value;
|
|
330
|
+
}
|
|
331
|
+
isEmptyElement() {
|
|
332
|
+
return this.#isEmptyElement;
|
|
333
|
+
}
|
|
334
|
+
/** How deep the node lies: the root element is 0, its children 1, and an element's text one below it. */
|
|
335
|
+
depth() {
|
|
336
|
+
return this.#depth;
|
|
337
|
+
}
|
|
338
|
+
attributes() {
|
|
339
|
+
return this.#attributes;
|
|
340
|
+
}
|
|
341
|
+
/** Whether the reader has read past the part's end. */
|
|
342
|
+
eof() {
|
|
343
|
+
return this.#eof;
|
|
344
|
+
}
|
|
345
|
+
/** Whether every piece of the text has arrived. */
|
|
346
|
+
ended() {
|
|
347
|
+
return this.#ended;
|
|
348
|
+
}
|
|
349
|
+
/** The characters that have arrived and are not read yet. */
|
|
350
|
+
unread() {
|
|
351
|
+
return this.#text.length - this.#pos;
|
|
352
|
+
}
|
|
353
|
+
/** Reads pieces until at least `ahead` characters lie unread, or the text has all come. Done only where no mark
|
|
354
|
+
* is held: what has been read is let go. */
|
|
355
|
+
async fill(ahead2) {
|
|
356
|
+
if (this.#pos > 0) {
|
|
357
|
+
this.#text = this.#text.slice(this.#pos);
|
|
358
|
+
this.#pos = 0;
|
|
359
|
+
}
|
|
360
|
+
while (!this.#ended && this.#text.length < ahead2) {
|
|
361
|
+
const piece2 = await this.#source();
|
|
362
|
+
if (piece2 === void 0) this.#ended = true;
|
|
363
|
+
else this.#text += piece2;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
snapshot() {
|
|
367
|
+
return {
|
|
368
|
+
pos: this.#pos,
|
|
369
|
+
state: {
|
|
370
|
+
nodeType: this.#nodeType,
|
|
371
|
+
localName: this.#localName,
|
|
372
|
+
prefix: this.#prefix,
|
|
373
|
+
namespaceUri: this.#namespaceUri,
|
|
374
|
+
value: this.#value,
|
|
375
|
+
isEmptyElement: this.#isEmptyElement,
|
|
376
|
+
depth: this.#depth,
|
|
377
|
+
attributes: this.#attributes,
|
|
378
|
+
stack: [...this.#stack],
|
|
379
|
+
popOnNext: this.#popOnNext,
|
|
380
|
+
started: this.#started,
|
|
381
|
+
rootSeen: this.#rootSeen,
|
|
382
|
+
rootClosed: this.#rootClosed,
|
|
383
|
+
eof: this.#eof
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
restore(mark) {
|
|
388
|
+
const s = mark.state;
|
|
389
|
+
this.#pos = mark.pos;
|
|
390
|
+
this.#nodeType = s.nodeType;
|
|
391
|
+
this.#localName = s.localName;
|
|
392
|
+
this.#prefix = s.prefix;
|
|
393
|
+
this.#namespaceUri = s.namespaceUri;
|
|
394
|
+
this.#value = s.value;
|
|
395
|
+
this.#isEmptyElement = s.isEmptyElement;
|
|
396
|
+
this.#depth = s.depth;
|
|
397
|
+
this.#attributes = s.attributes;
|
|
398
|
+
this.#stack = [...s.stack];
|
|
399
|
+
this.#popOnNext = s.popOnNext;
|
|
400
|
+
this.#started = s.started;
|
|
401
|
+
this.#rootSeen = s.rootSeen;
|
|
402
|
+
this.#rootClosed = s.rootClosed;
|
|
403
|
+
this.#eof = s.eof;
|
|
404
|
+
}
|
|
405
|
+
/** The attribute by its qualified name, as written (`GetAttribute(name)`), or `undefined`. */
|
|
406
|
+
getAttribute(name) {
|
|
407
|
+
for (const attribute of this.#attributes) {
|
|
408
|
+
if (attribute.name === name) return attribute.value;
|
|
409
|
+
}
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
/** The attribute by its local name and namespace (`GetAttribute(localName, namespaceURI)`), or `undefined`. */
|
|
413
|
+
getAttributeNs(localName, namespaceUri) {
|
|
414
|
+
for (const attribute of this.#attributes) {
|
|
415
|
+
if (attribute.localName === localName && attribute.namespaceUri === namespaceUri) return attribute.value;
|
|
416
|
+
}
|
|
417
|
+
return void 0;
|
|
418
|
+
}
|
|
419
|
+
/** Moves to the next content node — an element, an element's end, or text that is not only white space — unless the
|
|
420
|
+
* reader is on one (`MoveToContent`). */
|
|
421
|
+
moveToContent() {
|
|
422
|
+
for (; ; ) {
|
|
423
|
+
switch (this.#nodeType) {
|
|
424
|
+
case "element":
|
|
425
|
+
case "endElement":
|
|
426
|
+
case "text":
|
|
427
|
+
case "cdata":
|
|
428
|
+
return this.#nodeType;
|
|
429
|
+
case "none":
|
|
430
|
+
case "whitespace":
|
|
431
|
+
case "significantWhitespace":
|
|
432
|
+
if (!this.read()) return this.#nodeType;
|
|
433
|
+
break;
|
|
434
|
+
default:
|
|
435
|
+
return unreachable(this.#nodeType);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/** The text of the element the reader is on, its text and CDATA joined, leaving the reader past its end
|
|
440
|
+
* (`ReadElementContentAsString`). An element that holds an element is not well-formed for it. */
|
|
441
|
+
readElementContentAsString() {
|
|
442
|
+
if (this.#nodeType !== "element") throw new Error(`Internal: the reader is on ${this.#nodeType}, not an element.`);
|
|
443
|
+
if (this.#isEmptyElement) {
|
|
444
|
+
this.read();
|
|
445
|
+
return "";
|
|
446
|
+
}
|
|
447
|
+
let text = "";
|
|
448
|
+
this.read();
|
|
449
|
+
for (; ; ) {
|
|
450
|
+
const node = this.nodeType();
|
|
451
|
+
switch (node) {
|
|
452
|
+
case "text":
|
|
453
|
+
case "cdata":
|
|
454
|
+
case "whitespace":
|
|
455
|
+
case "significantWhitespace":
|
|
456
|
+
text += this.#value;
|
|
457
|
+
this.read();
|
|
458
|
+
break;
|
|
459
|
+
case "element":
|
|
460
|
+
throw new XmlError("ReadElementContentAs() methods cannot be called on an element that has child elements.");
|
|
461
|
+
case "endElement":
|
|
462
|
+
this.read();
|
|
463
|
+
return text;
|
|
464
|
+
case "none":
|
|
465
|
+
throw new XmlError("Unexpected end of file.");
|
|
466
|
+
default:
|
|
467
|
+
return unreachable(node);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/** Reads on to the next element of `localName` in `namespaceUri`; false, at the part's end, when there is none
|
|
472
|
+
* (`ReadToFollowing`). */
|
|
473
|
+
readToFollowing(localName, namespaceUri) {
|
|
474
|
+
while (this.read()) {
|
|
475
|
+
if (this.#nodeType === "element" && this.#localName === localName && this.#namespaceUri === namespaceUri) return true;
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
/** Moves past the element the reader is on and all it holds, or past any other node (`Skip`). */
|
|
480
|
+
skip() {
|
|
481
|
+
if (this.#nodeType !== "element" || this.#isEmptyElement) {
|
|
482
|
+
this.read();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const depth = this.#depth;
|
|
486
|
+
while (this.read()) {
|
|
487
|
+
if (this.nodeType() === "endElement" && this.depth() === depth) break;
|
|
488
|
+
}
|
|
489
|
+
this.read();
|
|
490
|
+
}
|
|
491
|
+
/** Reads the next node; false at the part's end. */
|
|
492
|
+
read() {
|
|
493
|
+
if (this.#eof) return false;
|
|
494
|
+
if (this.#popOnNext) {
|
|
495
|
+
this.#stack.pop();
|
|
496
|
+
this.#popOnNext = false;
|
|
497
|
+
}
|
|
498
|
+
if (!this.#started) this.#prolog();
|
|
499
|
+
const text = this.#text;
|
|
500
|
+
for (; ; ) {
|
|
501
|
+
if (this.#pos >= text.length) {
|
|
502
|
+
if (!this.#ended) throw new NeedMore();
|
|
503
|
+
if (!this.#rootSeen) throw new XmlError("Root element is missing.");
|
|
504
|
+
if (this.#stack.length > 0) {
|
|
505
|
+
throw new XmlError(`Unexpected end of file has occurred. The following elements are not closed: ${this.#stack.map((f) => f.name).toReversed().join(", ")}.`);
|
|
506
|
+
}
|
|
507
|
+
this.#eof = true;
|
|
508
|
+
this.#node("none", "", "", "", "", this.#stack.length);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
if (text.charCodeAt(this.#pos) !== 60) {
|
|
512
|
+
this.#readText(text);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
const next = this.#charAt(text, this.#pos + 1);
|
|
516
|
+
if (next === "/") {
|
|
517
|
+
this.#readEnd(text);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
if (next === "?") {
|
|
521
|
+
this.#skipInstruction(text);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (next === "!") {
|
|
525
|
+
if (this.#readBang(text)) return true;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
this.#readStart(text);
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// ---- the nodes -------------------------------------------------------------------------------
|
|
533
|
+
#node(type, localName, prefix, namespaceUri, value, depth) {
|
|
534
|
+
this.#nodeType = type;
|
|
535
|
+
this.#localName = localName;
|
|
536
|
+
this.#prefix = prefix;
|
|
537
|
+
this.#namespaceUri = namespaceUri;
|
|
538
|
+
this.#value = value;
|
|
539
|
+
this.#depth = depth;
|
|
540
|
+
this.#isEmptyElement = false;
|
|
541
|
+
this.#attributes = [];
|
|
542
|
+
}
|
|
543
|
+
/** The character at `at`, asking for more text where it has not arrived; '' past the part's end. */
|
|
544
|
+
#charAt(text, at) {
|
|
545
|
+
if (at < text.length) return text.charAt(at);
|
|
546
|
+
if (!this.#ended) throw new NeedMore();
|
|
547
|
+
return "";
|
|
548
|
+
}
|
|
549
|
+
/** Where `token` next begins at or after `from`, asking for more text where it has not arrived; -1 past the part's end. */
|
|
550
|
+
#find(text, token, from) {
|
|
551
|
+
const at = text.indexOf(token, from);
|
|
552
|
+
if (at < 0 && !this.#ended) throw new NeedMore();
|
|
553
|
+
return at;
|
|
554
|
+
}
|
|
555
|
+
#prolog() {
|
|
556
|
+
const text = this.#text;
|
|
557
|
+
if (text.length < 6 && !this.#ended) throw new NeedMore();
|
|
558
|
+
if (text.startsWith("<?xml") && isSpace(text.charCodeAt(5))) {
|
|
559
|
+
const end = this.#find(text, "?>", 5);
|
|
560
|
+
if (end < 0) throw new XmlError("The XML declaration is not ended.");
|
|
561
|
+
const declaration2 = text.slice(5, end);
|
|
562
|
+
const encoding = /\bencoding\s*=\s*["']([^"']*)["']/.exec(declaration2)?.[1];
|
|
563
|
+
if (!/\bversion\s*=\s*["']1\.[0-9]+["']/.test(declaration2)) throw new XmlError("The XML declaration names no version 1.x.");
|
|
564
|
+
if (encoding !== void 0 && !/^(?:utf-?8|utf-?16|unicode)$/i.test(encoding)) {
|
|
565
|
+
throw new XmlError(`The part declares the encoding '${encoding}', which is not read.`);
|
|
566
|
+
}
|
|
567
|
+
this.#pos = end + 2;
|
|
568
|
+
}
|
|
569
|
+
this.#started = true;
|
|
570
|
+
}
|
|
571
|
+
#readText(text) {
|
|
572
|
+
const lt = this.#find(text, "<", this.#pos);
|
|
573
|
+
const end = lt < 0 ? text.length : lt;
|
|
574
|
+
const raw = text.slice(this.#pos, end);
|
|
575
|
+
checkCharacters(raw);
|
|
576
|
+
if (raw.includes("]]>")) throw new XmlError("']]>' is not allowed in text.");
|
|
577
|
+
const value = unescape(lineFeeds(raw));
|
|
578
|
+
let space = true;
|
|
579
|
+
for (let i = 0; i < raw.length; i++) {
|
|
580
|
+
if (!isSpace(raw.charCodeAt(i))) {
|
|
581
|
+
space = false;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const depth = this.#stack.length;
|
|
586
|
+
if (depth === 0 && !space) throw new XmlError("Data at the root level is invalid.");
|
|
587
|
+
if (depth > 0 && lt < 0) throw new XmlError(`Unexpected end of file has occurred. The following elements are not closed: ${this.#stack.map((f) => f.name).toReversed().join(", ")}.`);
|
|
588
|
+
const preserve = this.#stack.at(-1)?.preserve === true;
|
|
589
|
+
this.#node(space ? preserve ? "significantWhitespace" : "whitespace" : "text", "", "", "", value, depth);
|
|
590
|
+
this.#pos = end;
|
|
591
|
+
}
|
|
592
|
+
#skipInstruction(text) {
|
|
593
|
+
const end = this.#find(text, "?>", this.#pos + 2);
|
|
594
|
+
if (end < 0) throw new XmlError("A processing instruction is not ended.");
|
|
595
|
+
const body = text.slice(this.#pos + 2, end);
|
|
596
|
+
const space = body.search(/\s/);
|
|
597
|
+
const target = space < 0 ? body : body.slice(0, space);
|
|
598
|
+
if (target.toLowerCase() === "xml") throw new XmlError("Unexpected XML declaration. The XML declaration must be the first node in the document.");
|
|
599
|
+
checkName(target);
|
|
600
|
+
checkCharacters(body);
|
|
601
|
+
this.#pos = end + 2;
|
|
602
|
+
}
|
|
603
|
+
/** A comment (skipped), CDATA (a node: true) or a document type (refused). */
|
|
604
|
+
#readBang(text) {
|
|
605
|
+
const at = this.#pos;
|
|
606
|
+
if (text.length < at + 9 && !this.#ended) throw new NeedMore();
|
|
607
|
+
if (text.startsWith("<!--", at)) {
|
|
608
|
+
const end = this.#find(text, "-->", at + 4);
|
|
609
|
+
if (end < 0) throw new XmlError("A comment is not ended.");
|
|
610
|
+
const body = text.slice(at + 4, end);
|
|
611
|
+
if (body.includes("--") || body.endsWith("-")) throw new XmlError("An XML comment cannot contain '--', and '-' cannot be the last character.");
|
|
612
|
+
checkCharacters(body);
|
|
613
|
+
this.#pos = end + 3;
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
if (text.startsWith("<![CDATA[", at)) {
|
|
617
|
+
if (this.#stack.length === 0) throw new XmlError("Data at the root level is invalid.");
|
|
618
|
+
const end = this.#find(text, "]]>", at + 9);
|
|
619
|
+
if (end < 0) throw new XmlError("A CDATA section is not ended.");
|
|
620
|
+
const body = text.slice(at + 9, end);
|
|
621
|
+
checkCharacters(body);
|
|
622
|
+
this.#node("cdata", "", "", "", lineFeeds(body), this.#stack.length);
|
|
623
|
+
this.#pos = end + 3;
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
if (text.startsWith("<!DOCTYPE", at)) {
|
|
627
|
+
throw new XmlError("For security reasons DTD is prohibited in this XML document. To enable DTD processing set the DtdProcessing property on XmlReaderSettings to Parse and pass the settings into XmlReader.Create method.");
|
|
628
|
+
}
|
|
629
|
+
throw new XmlError("An unexpected markup declaration was met.");
|
|
630
|
+
}
|
|
631
|
+
#readEnd(text) {
|
|
632
|
+
const close = this.#find(text, ">", this.#pos + 2);
|
|
633
|
+
if (close < 0) throw new XmlError("An end tag is not ended.");
|
|
634
|
+
const name = text.slice(this.#pos + 2, close).replace(/[\t\n\r ]+$/, "");
|
|
635
|
+
const frame = this.#stack.at(-1);
|
|
636
|
+
if (frame === void 0) throw new XmlError(`The end tag '${name}' closes no element.`);
|
|
637
|
+
if (frame.name !== name) throw new XmlError(`The '${frame.name}' start tag does not match the end tag of '${name}'.`);
|
|
638
|
+
this.#node("endElement", frame.localName, frame.prefix, frame.namespaceUri, "", this.#stack.length - 1);
|
|
639
|
+
this.#stack.pop();
|
|
640
|
+
if (this.#stack.length === 0) this.#rootClosed = true;
|
|
641
|
+
this.#pos = close + 1;
|
|
642
|
+
}
|
|
643
|
+
#readStart(text) {
|
|
644
|
+
if (this.#rootClosed) throw new XmlError("There are multiple root elements.");
|
|
645
|
+
let at = this.#pos + 1;
|
|
646
|
+
const nameEnd = (from) => {
|
|
647
|
+
let i = from;
|
|
648
|
+
for (; ; ) {
|
|
649
|
+
const c = this.#charAt(text, i);
|
|
650
|
+
if (c === "" || c === ">" || c === "/" || c === "=" || isSpace(c.charCodeAt(0))) return i;
|
|
651
|
+
i++;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
const end = nameEnd(at);
|
|
655
|
+
const name = text.slice(at, end);
|
|
656
|
+
checkName(name);
|
|
657
|
+
at = end;
|
|
658
|
+
const raw = [];
|
|
659
|
+
let empty = false;
|
|
660
|
+
for (; ; ) {
|
|
661
|
+
let spaced = false;
|
|
662
|
+
while (isSpace(this.#charAt(text, at).charCodeAt(0))) {
|
|
663
|
+
at++;
|
|
664
|
+
spaced = true;
|
|
665
|
+
}
|
|
666
|
+
const c = this.#charAt(text, at);
|
|
667
|
+
if (c === ">") {
|
|
668
|
+
at++;
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
if (c === "/") {
|
|
672
|
+
if (this.#charAt(text, at + 1) !== ">") throw new XmlError(`The start tag '${name}' is not ended.`);
|
|
673
|
+
at += 2;
|
|
674
|
+
empty = true;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
if (c === "") throw new XmlError(`The start tag '${name}' is not ended.`);
|
|
678
|
+
if (!spaced) throw new XmlError(`The start tag '${name}' needs white space before an attribute.`);
|
|
679
|
+
const attributeEnd = nameEnd(at);
|
|
680
|
+
const attributeName = text.slice(at, attributeEnd);
|
|
681
|
+
checkName(attributeName);
|
|
682
|
+
at = attributeEnd;
|
|
683
|
+
while (isSpace(this.#charAt(text, at).charCodeAt(0))) at++;
|
|
684
|
+
if (this.#charAt(text, at) !== "=") throw new XmlError(`The attribute '${attributeName}' has no value.`);
|
|
685
|
+
at++;
|
|
686
|
+
while (isSpace(this.#charAt(text, at).charCodeAt(0))) at++;
|
|
687
|
+
const quote = this.#charAt(text, at);
|
|
688
|
+
if (quote !== '"' && quote !== "'") throw new XmlError(`The value of the attribute '${attributeName}' is not quoted.`);
|
|
689
|
+
const close = this.#find(text, quote, at + 1);
|
|
690
|
+
if (close < 0) throw new XmlError(`The value of the attribute '${attributeName}' is not ended.`);
|
|
691
|
+
const written = text.slice(at + 1, close);
|
|
692
|
+
if (written.includes("<")) throw new XmlError(`'<', hexadecimal value 0x3C, is an invalid attribute character.`);
|
|
693
|
+
checkCharacters(written);
|
|
694
|
+
const value = unescape(written.replace(/\r\n|[\t\n\r]/g, " "));
|
|
695
|
+
if (raw.some((a) => a.name === attributeName)) throw new XmlError(`'${attributeName}' is a duplicate attribute name.`);
|
|
696
|
+
raw.push({ name: attributeName, value });
|
|
697
|
+
at = close + 1;
|
|
698
|
+
}
|
|
699
|
+
let declared = null;
|
|
700
|
+
let preserve = this.#stack.at(-1)?.preserve === true;
|
|
701
|
+
for (const attribute of raw) {
|
|
702
|
+
if (attribute.name === "xmlns") (declared ??= /* @__PURE__ */ new Map()).set("", attribute.value);
|
|
703
|
+
else if (attribute.name.startsWith("xmlns:")) (declared ??= /* @__PURE__ */ new Map()).set(attribute.name.slice(6), attribute.value);
|
|
704
|
+
else if (attribute.name === "xml:space") preserve = attribute.value === "preserve" ? true : attribute.value === "default" ? false : preserve;
|
|
705
|
+
}
|
|
706
|
+
const lookup = (prefix2) => {
|
|
707
|
+
if (prefix2 === "xml") return xmlNamespace;
|
|
708
|
+
if (prefix2 === "xmlns") return xmlnsNamespace;
|
|
709
|
+
const own = declared?.get(prefix2);
|
|
710
|
+
if (own !== void 0) return own;
|
|
711
|
+
for (let i = this.#stack.length - 1; i >= 0; i--) {
|
|
712
|
+
const uri = this.#stack[i]?.declared?.get(prefix2);
|
|
713
|
+
if (uri !== void 0) return uri;
|
|
714
|
+
}
|
|
715
|
+
return prefix2 === "" ? "" : void 0;
|
|
716
|
+
};
|
|
717
|
+
const [prefix, localName] = split(name);
|
|
718
|
+
const namespaceUri = lookup(prefix);
|
|
719
|
+
if (namespaceUri === void 0) throw new XmlError(`'${prefix}' is an undeclared prefix.`);
|
|
720
|
+
const attributes = raw.map((attribute) => {
|
|
721
|
+
if (attribute.name === "xmlns") return { name: "xmlns", prefix: "", localName: "xmlns", namespaceUri: xmlnsNamespace, value: attribute.value };
|
|
722
|
+
const [p, local] = split(attribute.name);
|
|
723
|
+
if (p === "") return { name: attribute.name, prefix: "", localName: local, namespaceUri: "", value: attribute.value };
|
|
724
|
+
const uri = lookup(p);
|
|
725
|
+
if (uri === void 0) throw new XmlError(`'${p}' is an undeclared prefix.`);
|
|
726
|
+
return { name: attribute.name, prefix: p, localName: local, namespaceUri: uri, value: attribute.value };
|
|
727
|
+
});
|
|
728
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
729
|
+
const a = attributes[i];
|
|
730
|
+
if (a === void 0 || a.namespaceUri === "") continue;
|
|
731
|
+
for (let j = i + 1; j < attributes.length; j++) {
|
|
732
|
+
const b = attributes[j];
|
|
733
|
+
if (b !== void 0 && b.localName === a.localName && b.namespaceUri === a.namespaceUri) {
|
|
734
|
+
throw new XmlError(`The attribute '${b.localName}' of the namespace '${b.namespaceUri}' is written twice.`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const depth = this.#stack.length;
|
|
739
|
+
this.#node("element", localName, prefix, namespaceUri, "", depth);
|
|
740
|
+
this.#isEmptyElement = empty;
|
|
741
|
+
this.#attributes = attributes;
|
|
742
|
+
this.#stack.push({ name, localName, prefix, namespaceUri, declared, preserve });
|
|
743
|
+
this.#popOnNext = empty;
|
|
744
|
+
this.#rootSeen = true;
|
|
745
|
+
if (empty && depth === 0) this.#rootClosed = true;
|
|
746
|
+
this.#pos = at;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
// src/zip-read.ts
|
|
751
|
+
var piece = 1 << 20;
|
|
752
|
+
function byteSource(file) {
|
|
753
|
+
if (typeof Blob !== "undefined" && file instanceof Blob) {
|
|
754
|
+
return {
|
|
755
|
+
size: file.size,
|
|
756
|
+
read: async (offset, length) => new Uint8Array(await file.slice(offset, Math.min(file.size, offset + length)).arrayBuffer()),
|
|
757
|
+
stream: (offset, length) => file.slice(offset, offset + length).stream()
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (!(file instanceof Uint8Array) && !(file instanceof ArrayBuffer)) {
|
|
761
|
+
throw new TypeError("The file is a Uint8Array, an ArrayBuffer or a Blob.");
|
|
762
|
+
}
|
|
763
|
+
const bytes = file instanceof Uint8Array ? file : new Uint8Array(file);
|
|
764
|
+
return {
|
|
765
|
+
size: bytes.length,
|
|
766
|
+
read: (offset, length) => Promise.resolve(bytes.subarray(offset, Math.min(bytes.length, offset + length))),
|
|
767
|
+
stream: (offset, length) => {
|
|
768
|
+
let at = offset;
|
|
769
|
+
const end = Math.min(bytes.length, offset + length);
|
|
770
|
+
return new ReadableStream({
|
|
771
|
+
pull(controller) {
|
|
772
|
+
if (at >= end) {
|
|
773
|
+
controller.close();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const next = Math.min(end, at + piece);
|
|
777
|
+
controller.enqueue(bytes.slice(at, next));
|
|
778
|
+
at = next;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
var ZipFormatError = class extends Error {
|
|
785
|
+
constructor(message) {
|
|
786
|
+
super(message);
|
|
787
|
+
this.name = "ZipFormatError";
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
function u16(bytes, at) {
|
|
791
|
+
const low2 = bytes[at];
|
|
792
|
+
const high = bytes[at + 1];
|
|
793
|
+
if (low2 === void 0 || high === void 0) throw new ZipFormatError("A record of the zip is cut short.");
|
|
794
|
+
return low2 | high << 8;
|
|
795
|
+
}
|
|
796
|
+
function u32(bytes, at) {
|
|
797
|
+
return (u16(bytes, at) | u16(bytes, at + 2) << 16) >>> 0;
|
|
798
|
+
}
|
|
799
|
+
function u64(bytes, at) {
|
|
800
|
+
const value = u32(bytes, at) + u32(bytes, at + 4) * 4294967296;
|
|
801
|
+
return value > Number.MAX_SAFE_INTEGER ? Number.POSITIVE_INFINITY : value;
|
|
802
|
+
}
|
|
803
|
+
function need(bytes, length, what) {
|
|
804
|
+
if (bytes.length < length) throw new ZipFormatError(`${what} is cut short.`);
|
|
805
|
+
}
|
|
806
|
+
var utf8 = new TextDecoder();
|
|
807
|
+
async function readCentralDirectory(source) {
|
|
808
|
+
const tailLength = Math.min(source.size, 22 + 65535);
|
|
809
|
+
const tailStart = source.size - tailLength;
|
|
810
|
+
const tail = await source.read(tailStart, tailLength);
|
|
811
|
+
let end = -1;
|
|
812
|
+
for (let at2 = tail.length - 22; at2 >= 0; at2--) {
|
|
813
|
+
if (u32(tail, at2) === 101010256) {
|
|
814
|
+
end = at2;
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (end < 0) throw new ZipFormatError("End of Central Directory record could not be found.");
|
|
819
|
+
let count2 = u16(tail, end + 10);
|
|
820
|
+
let size = u32(tail, end + 12);
|
|
821
|
+
let offset = u32(tail, end + 16);
|
|
822
|
+
if (count2 === 65535 || size === 4294967295 || offset === 4294967295) {
|
|
823
|
+
const locator = end - 20;
|
|
824
|
+
if (locator < 0 || u32(tail, locator) !== 117853008) throw new ZipFormatError("The Zip64 end of central directory locator could not be found.");
|
|
825
|
+
const at2 = u64(tail, locator + 8);
|
|
826
|
+
const record = await source.read(at2, 56);
|
|
827
|
+
need(record, 56, "The Zip64 end of central directory record");
|
|
828
|
+
if (u32(record, 0) !== 101075792) throw new ZipFormatError("The Zip64 end of central directory record could not be found.");
|
|
829
|
+
count2 = u64(record, 32);
|
|
830
|
+
size = u64(record, 40);
|
|
831
|
+
offset = u64(record, 48);
|
|
832
|
+
}
|
|
833
|
+
if (!Number.isFinite(size) || !Number.isFinite(offset) || offset + size > source.size) {
|
|
834
|
+
throw new ZipFormatError("The central directory lies outside the file.");
|
|
835
|
+
}
|
|
836
|
+
const directory = await source.read(offset, size);
|
|
837
|
+
const entries = [];
|
|
838
|
+
let at = 0;
|
|
839
|
+
while (at + 46 <= directory.length && u32(directory, at) === 33639248) {
|
|
840
|
+
const flags = u16(directory, at + 8);
|
|
841
|
+
const method = u16(directory, at + 10);
|
|
842
|
+
let compressedSize = u32(directory, at + 20);
|
|
843
|
+
let uncompressedSize = u32(directory, at + 24);
|
|
844
|
+
const nameLength = u16(directory, at + 28);
|
|
845
|
+
const extraLength = u16(directory, at + 30);
|
|
846
|
+
const commentLength = u16(directory, at + 32);
|
|
847
|
+
let localHeaderOffset = u32(directory, at + 42);
|
|
848
|
+
const nameAt = at + 46;
|
|
849
|
+
need(directory, nameAt + nameLength + extraLength + commentLength, "The central directory");
|
|
850
|
+
const name = utf8.decode(directory.subarray(nameAt, nameAt + nameLength));
|
|
851
|
+
let extra = nameAt + nameLength;
|
|
852
|
+
const extraEnd = extra + extraLength;
|
|
853
|
+
while (extra + 4 <= extraEnd) {
|
|
854
|
+
const id = u16(directory, extra);
|
|
855
|
+
const length = u16(directory, extra + 2);
|
|
856
|
+
if (id === 1) {
|
|
857
|
+
let field = extra + 4;
|
|
858
|
+
if (uncompressedSize === 4294967295) {
|
|
859
|
+
uncompressedSize = u64(directory, field);
|
|
860
|
+
field += 8;
|
|
861
|
+
}
|
|
862
|
+
if (compressedSize === 4294967295) {
|
|
863
|
+
compressedSize = u64(directory, field);
|
|
864
|
+
field += 8;
|
|
865
|
+
}
|
|
866
|
+
if (localHeaderOffset === 4294967295) {
|
|
867
|
+
localHeaderOffset = u64(directory, field);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
extra += 4 + length;
|
|
871
|
+
}
|
|
872
|
+
entries.push({ name, method, flags, compressedSize, uncompressedSize, localHeaderOffset });
|
|
873
|
+
at = extraEnd + commentLength;
|
|
874
|
+
}
|
|
875
|
+
if (entries.length !== count2) {
|
|
876
|
+
throw new ZipFormatError("Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.");
|
|
877
|
+
}
|
|
878
|
+
return entries;
|
|
879
|
+
}
|
|
880
|
+
async function dataOffset(source, entry) {
|
|
881
|
+
const header2 = await source.read(entry.localHeaderOffset, 30);
|
|
882
|
+
if (header2.length < 30 || u32(header2, 0) !== 67324752) throw new ZipFormatError("A local file header is corrupt.");
|
|
883
|
+
const start = entry.localHeaderOffset + 30 + u16(header2, 26) + u16(header2, 28);
|
|
884
|
+
if (start + entry.compressedSize > source.size) throw new ZipFormatError("A local file header is corrupt.");
|
|
885
|
+
return start;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/package.ts
|
|
889
|
+
var headLength = 64 * 1024;
|
|
890
|
+
var PartBytes = class {
|
|
891
|
+
#reader;
|
|
892
|
+
#declared;
|
|
893
|
+
#deflated;
|
|
894
|
+
#part;
|
|
895
|
+
#sheet;
|
|
896
|
+
#head = new Uint8Array(headLength);
|
|
897
|
+
#headLength = 0;
|
|
898
|
+
#read = 0;
|
|
899
|
+
#done = false;
|
|
900
|
+
constructor(stream, entry, part, sheet) {
|
|
901
|
+
this.#deflated = entry.method === 8;
|
|
902
|
+
this.#reader = (this.#deflated ? stream.pipeThrough(new DecompressionStream("deflate-raw")) : stream).getReader();
|
|
903
|
+
this.#declared = entry.uncompressedSize;
|
|
904
|
+
this.#part = part;
|
|
905
|
+
this.#sheet = sheet;
|
|
906
|
+
}
|
|
907
|
+
/** The bytes inflated so far. */
|
|
908
|
+
get bytesRead() {
|
|
909
|
+
return this.#read;
|
|
910
|
+
}
|
|
911
|
+
/** The first bytes the part inflated to, as far as they have been read. */
|
|
912
|
+
get head() {
|
|
913
|
+
return this.#head.subarray(0, this.#headLength);
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* The next bytes, or `undefined` at the part's end. A deflated part is inflated no further than it declares, as .NET
|
|
917
|
+
* cuts it; a stored one, which .NET reads to its compressed size, is refused past it: a zip bomb's headers may lie.
|
|
918
|
+
* Bytes that cannot be inflated are refused as malformed, naming the part.
|
|
919
|
+
*/
|
|
920
|
+
async next() {
|
|
921
|
+
if (this.#done) return void 0;
|
|
922
|
+
let result;
|
|
923
|
+
try {
|
|
924
|
+
result = await this.#reader.read();
|
|
925
|
+
} catch (error) {
|
|
926
|
+
this.#done = true;
|
|
927
|
+
throw new XlsxError("malformed", `The part's compressed bytes cannot be inflated: ${inflateFailure(error)}`, { part: this.#part, sheet: this.#sheet }, { cause: error });
|
|
928
|
+
}
|
|
929
|
+
if (result.done) {
|
|
930
|
+
this.#done = true;
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
let bytes = result.value;
|
|
934
|
+
if (this.#read + bytes.length > this.#declared) {
|
|
935
|
+
if (!this.#deflated) {
|
|
936
|
+
throw new XlsxError(
|
|
937
|
+
"tooLarge",
|
|
938
|
+
`The part inflates to more than the ${grouped(this.#declared)} bytes the zip declares for it.`,
|
|
939
|
+
{ part: this.#part, sheet: this.#sheet }
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
bytes = bytes.subarray(0, this.#declared - this.#read);
|
|
943
|
+
this.#done = true;
|
|
944
|
+
await this.#reader.cancel();
|
|
945
|
+
}
|
|
946
|
+
if (this.#headLength < headLength) {
|
|
947
|
+
const kept = bytes.subarray(0, headLength - this.#headLength);
|
|
948
|
+
this.#head.set(kept, this.#headLength);
|
|
949
|
+
this.#headLength += kept.length;
|
|
950
|
+
}
|
|
951
|
+
this.#read += bytes.length;
|
|
952
|
+
return bytes;
|
|
953
|
+
}
|
|
954
|
+
/** Lets go of what the part has not read. */
|
|
955
|
+
async close() {
|
|
956
|
+
if (this.#done) return;
|
|
957
|
+
this.#done = true;
|
|
958
|
+
await this.#reader.cancel();
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
function inflateFailure(error) {
|
|
962
|
+
if (!(error instanceof Error)) return String(error);
|
|
963
|
+
if (error.message === "" && error.cause instanceof Error) return error.cause.message;
|
|
964
|
+
return error.message;
|
|
965
|
+
}
|
|
966
|
+
function encodingOf(head) {
|
|
967
|
+
const [a, b, c, d] = head;
|
|
968
|
+
if (a === 255 && b === 254) return "utf-16le";
|
|
969
|
+
if (a === 254 && b === 255) return "utf-16be";
|
|
970
|
+
if (a === 60 && b === 0 && c === 63 && d === 0) return "utf-16le";
|
|
971
|
+
if (a === 0 && b === 60 && c === 0 && d === 63) return "utf-16be";
|
|
972
|
+
return "utf-8";
|
|
973
|
+
}
|
|
974
|
+
function textOf(bytes) {
|
|
975
|
+
let decoder;
|
|
976
|
+
let held = new Uint8Array(0);
|
|
977
|
+
let finished = false;
|
|
978
|
+
const decode = (chunk, stream) => {
|
|
979
|
+
try {
|
|
980
|
+
return (decoder ??= new TextDecoder(encodingOf(chunk), { fatal: true })).decode(chunk, { stream });
|
|
981
|
+
} catch (error) {
|
|
982
|
+
throw new XmlError("Invalid character in the given encoding.", { cause: error });
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
return async () => {
|
|
986
|
+
if (finished) return void 0;
|
|
987
|
+
for (; ; ) {
|
|
988
|
+
const chunk = await bytes.next();
|
|
989
|
+
if (chunk === void 0) {
|
|
990
|
+
finished = true;
|
|
991
|
+
const rest = decode(held, false);
|
|
992
|
+
held = new Uint8Array(0);
|
|
993
|
+
return rest;
|
|
994
|
+
}
|
|
995
|
+
if (decoder === void 0 && held.length + chunk.length < 4) {
|
|
996
|
+
const more = new Uint8Array(held.length + chunk.length);
|
|
997
|
+
more.set(held);
|
|
998
|
+
more.set(chunk, held.length);
|
|
999
|
+
held = more;
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (held.length > 0) {
|
|
1003
|
+
const joined = new Uint8Array(held.length + chunk.length);
|
|
1004
|
+
joined.set(held);
|
|
1005
|
+
joined.set(chunk, held.length);
|
|
1006
|
+
held = new Uint8Array(0);
|
|
1007
|
+
return decode(joined, true);
|
|
1008
|
+
}
|
|
1009
|
+
return decode(chunk, true);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
var Package = class _Package {
|
|
1014
|
+
#source;
|
|
1015
|
+
#entries;
|
|
1016
|
+
/** The parts' names, in the zip's order, as the first entry of each name in any case writes it. */
|
|
1017
|
+
partNames;
|
|
1018
|
+
constructor(source, entries, names) {
|
|
1019
|
+
this.#source = source;
|
|
1020
|
+
this.#entries = entries;
|
|
1021
|
+
this.partNames = names;
|
|
1022
|
+
}
|
|
1023
|
+
/** The zip in `source`, refused when it cannot be read or holds more than `maxUncompressedBytes`. */
|
|
1024
|
+
static async open(source, maxUncompressedBytes) {
|
|
1025
|
+
let listed;
|
|
1026
|
+
try {
|
|
1027
|
+
listed = await readCentralDirectory(source);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (!(error instanceof ZipFormatError)) throw error;
|
|
1030
|
+
throw new XlsxError("notAZip", `The file is not a zip package that can be read: ${error.message}`, {}, { cause: error });
|
|
1031
|
+
}
|
|
1032
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1033
|
+
const names = [];
|
|
1034
|
+
for (const entry of listed) {
|
|
1035
|
+
if (entry.name.endsWith("/")) continue;
|
|
1036
|
+
const name = entry.name.replace(/^\/+/, "");
|
|
1037
|
+
const key = foldCase(name);
|
|
1038
|
+
if (entries.has(key)) continue;
|
|
1039
|
+
entries.set(key, entry);
|
|
1040
|
+
names.push(name);
|
|
1041
|
+
}
|
|
1042
|
+
let total = 0;
|
|
1043
|
+
for (const entry of entries.values()) {
|
|
1044
|
+
total += entry.uncompressedSize;
|
|
1045
|
+
if (total > maxUncompressedBytes) {
|
|
1046
|
+
throw new XlsxError(
|
|
1047
|
+
"tooLarge",
|
|
1048
|
+
`The file holds more than the ${grouped(maxUncompressedBytes)} uncompressed bytes a read is allowed (XlsxReadOptions.MaxUncompressedBytes).`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return new _Package(source, entries, Object.freeze(names));
|
|
1053
|
+
}
|
|
1054
|
+
has(part) {
|
|
1055
|
+
return this.#entries.has(foldCase(part));
|
|
1056
|
+
}
|
|
1057
|
+
lengthOf(part) {
|
|
1058
|
+
return this.#entries.get(foldCase(part))?.uncompressedSize ?? 0;
|
|
1059
|
+
}
|
|
1060
|
+
/** The part's bytes as they inflate, or `undefined` when the package has no such part.
|
|
1061
|
+
* @param sheet - The Sheet the part is, named by a refusal the read meets. */
|
|
1062
|
+
async openPart(part, sheet) {
|
|
1063
|
+
const entry = this.#entries.get(foldCase(part));
|
|
1064
|
+
if (entry === void 0) return void 0;
|
|
1065
|
+
if (entry.method !== 0 && entry.method !== 8) {
|
|
1066
|
+
throw new XlsxError("notAZip", "The part cannot be inflated: The archive entry was compressed using an unsupported compression method.", { part, sheet });
|
|
1067
|
+
}
|
|
1068
|
+
let start;
|
|
1069
|
+
try {
|
|
1070
|
+
start = await dataOffset(this.#source, entry);
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
if (!(error instanceof ZipFormatError)) throw error;
|
|
1073
|
+
throw new XlsxError("notAZip", `The part cannot be inflated: ${error.message}`, { part, sheet }, { cause: error });
|
|
1074
|
+
}
|
|
1075
|
+
return new PartBytes(this.#source.stream(start, entry.compressedSize), entry, part, sheet);
|
|
1076
|
+
}
|
|
1077
|
+
/** The part's XML, being read a piece at a time, or `undefined` when the package has no such part. */
|
|
1078
|
+
async openXml(part, sheet) {
|
|
1079
|
+
const bytes = await this.openPart(part, sheet);
|
|
1080
|
+
return bytes === void 0 ? void 0 : { xml: new XmlReader(textOf(bytes)), bytes };
|
|
1081
|
+
}
|
|
1082
|
+
/** The part's XML with all of its text read, for a part read whole, or `undefined` when the package has no such part. */
|
|
1083
|
+
async readXml(part) {
|
|
1084
|
+
const opened = await this.openXml(part);
|
|
1085
|
+
if (opened === void 0) return void 0;
|
|
1086
|
+
await opened.xml.fill(Number.POSITIVE_INFINITY);
|
|
1087
|
+
return opened;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* The refusal a part that is not well-formed stands for: one that declares a document type, which the first 64 KiB
|
|
1091
|
+
* show in any part a workbook has, or one that is not well-formed.
|
|
1092
|
+
*/
|
|
1093
|
+
static refuse(error, opened, part, sheet) {
|
|
1094
|
+
if (new TextDecoder().decode(opened.bytes.head).includes("<!DOCTYPE")) {
|
|
1095
|
+
return new XlsxError("dtd", "The part declares a document type (<!DOCTYPE), which a workbook never needs; it is not read.", { part, sheet }, { cause: error });
|
|
1096
|
+
}
|
|
1097
|
+
return new XlsxError("malformed", `The part is not well-formed XML: ${error.message}`, { part, sheet }, { cause: error });
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
var ole = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
1101
|
+
function isOle(head) {
|
|
1102
|
+
return head.length >= ole.length && ole.every((b, i) => head[i] === b);
|
|
1103
|
+
}
|
|
1104
|
+
function isZip(head) {
|
|
1105
|
+
return head.length >= 4 && head[0] === 80 && head[1] === 75 && (head[2] === 3 && head[3] === 4 || head[2] === 5 && head[3] === 6 || head[2] === 7 && head[3] === 8);
|
|
1106
|
+
}
|
|
1107
|
+
var lastSector = 4294967290;
|
|
1108
|
+
var endOfChain = 4294967294;
|
|
1109
|
+
var guard = 1 << 16;
|
|
1110
|
+
async function classifyOle(source) {
|
|
1111
|
+
if (await isEncrypted(source)) {
|
|
1112
|
+
return new XlsxError(
|
|
1113
|
+
"encrypted",
|
|
1114
|
+
"The file is an encrypted workbook. Remove its password in Excel (File, Info, Protect Workbook, Encrypt with Password) and save it again."
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return new XlsxError(
|
|
1118
|
+
"legacy",
|
|
1119
|
+
"The file is an Excel 97\u20132003 workbook (.xls), or another Office binary file, not an .xlsx. Save it from Excel as an Excel Workbook (.xlsx)."
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
async function readExactly(source, offset, length) {
|
|
1123
|
+
if (offset + length > source.size) return void 0;
|
|
1124
|
+
const bytes = await source.read(offset, length);
|
|
1125
|
+
return bytes.length === length ? bytes : void 0;
|
|
1126
|
+
}
|
|
1127
|
+
async function isEncrypted(source) {
|
|
1128
|
+
const header2 = await readExactly(source, 0, 512);
|
|
1129
|
+
if (header2 === void 0) return false;
|
|
1130
|
+
const shift = u16(header2, 30);
|
|
1131
|
+
if (shift !== 9 && shift !== 12) return false;
|
|
1132
|
+
const size = 1 << shift;
|
|
1133
|
+
const perSector = size / 4;
|
|
1134
|
+
const at = (sector) => (sector + 1) * size;
|
|
1135
|
+
const fatSectors = [];
|
|
1136
|
+
for (let i = 0; i < 109; i++) {
|
|
1137
|
+
const s = u32(header2, 76 + i * 4);
|
|
1138
|
+
if (s < lastSector) fatSectors.push(s);
|
|
1139
|
+
}
|
|
1140
|
+
let difat = u32(header2, 68);
|
|
1141
|
+
let difatRead = 0;
|
|
1142
|
+
const fatCache = /* @__PURE__ */ new Map();
|
|
1143
|
+
const next = async (sector) => {
|
|
1144
|
+
const fat = Math.floor(sector / perSector);
|
|
1145
|
+
while (fat >= fatSectors.length && difat < lastSector && difatRead++ < guard) {
|
|
1146
|
+
const block = await readExactly(source, at(difat), size);
|
|
1147
|
+
if (block === void 0) return endOfChain;
|
|
1148
|
+
for (let i = 0; i < size / 4 - 1; i++) {
|
|
1149
|
+
const s = u32(block, i * 4);
|
|
1150
|
+
if (s < lastSector) fatSectors.push(s);
|
|
1151
|
+
}
|
|
1152
|
+
difat = u32(block, size - 4);
|
|
1153
|
+
}
|
|
1154
|
+
const fatSector = fatSectors[fat];
|
|
1155
|
+
if (fatSector === void 0) return endOfChain;
|
|
1156
|
+
let entries = fatCache.get(fat);
|
|
1157
|
+
if (entries === void 0) {
|
|
1158
|
+
const read = await readExactly(source, at(fatSector), size);
|
|
1159
|
+
entries = read === void 0 ? null : read;
|
|
1160
|
+
fatCache.set(fat, entries);
|
|
1161
|
+
}
|
|
1162
|
+
return entries === null ? endOfChain : u32(entries, sector % perSector * 4);
|
|
1163
|
+
};
|
|
1164
|
+
const names = new TextDecoder("utf-16le");
|
|
1165
|
+
let dir = u32(header2, 48);
|
|
1166
|
+
for (let step = 0; dir < lastSector && step < guard; step++) {
|
|
1167
|
+
const directory = await readExactly(source, at(dir), size);
|
|
1168
|
+
if (directory === void 0) break;
|
|
1169
|
+
for (let e = 0; e < size / 128; e++) {
|
|
1170
|
+
const entry = directory.subarray(e * 128, e * 128 + 128);
|
|
1171
|
+
const length = u16(entry, 64);
|
|
1172
|
+
if (length >= 2 && length <= 64) {
|
|
1173
|
+
const name = names.decode(entry.subarray(0, length - 2));
|
|
1174
|
+
if (name === "EncryptedPackage" || name === "EncryptionInfo") return true;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
dir = await next(dir);
|
|
1178
|
+
}
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/pacer.ts
|
|
1183
|
+
var defaultMaxUncompressedBytes = 2 ** 30;
|
|
1184
|
+
var defaultMaxCells = 1e7;
|
|
1185
|
+
function macrotask() {
|
|
1186
|
+
return new Promise((resolve2) => {
|
|
1187
|
+
setTimeout(resolve2, 0);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
var Pacer = class {
|
|
1191
|
+
#budget;
|
|
1192
|
+
#yield;
|
|
1193
|
+
#onProgress;
|
|
1194
|
+
#signal;
|
|
1195
|
+
#sliceStart = performance.now();
|
|
1196
|
+
constructor(options) {
|
|
1197
|
+
const budget = options.sliceBudgetMs === void 0 ? 30 : options.sliceBudgetMs;
|
|
1198
|
+
if (typeof budget !== "number" || Number.isNaN(budget)) throw new TypeError(`The slice budget ${String(budget)} is not a number of milliseconds.`);
|
|
1199
|
+
this.#budget = Math.max(0, budget);
|
|
1200
|
+
this.#yield = options.yield === void 0 ? macrotask : options.yield;
|
|
1201
|
+
this.#onProgress = options.onProgress;
|
|
1202
|
+
this.#signal = options.signal;
|
|
1203
|
+
}
|
|
1204
|
+
cancelled() {
|
|
1205
|
+
this.#signal?.throwIfAborted();
|
|
1206
|
+
}
|
|
1207
|
+
/** A point between batches: when the slice has spent its budget, reports `progress` and yields. */
|
|
1208
|
+
async checkpoint(progress) {
|
|
1209
|
+
this.cancelled();
|
|
1210
|
+
if (performance.now() < this.#sliceStart + this.#budget) return;
|
|
1211
|
+
this.#onProgress?.(progress());
|
|
1212
|
+
await this.#yield();
|
|
1213
|
+
this.cancelled();
|
|
1214
|
+
this.#sliceStart = performance.now();
|
|
1215
|
+
}
|
|
1216
|
+
report(progress) {
|
|
1217
|
+
this.#onProgress?.(progress);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// src/shared-strings.ts
|
|
1222
|
+
var XlsxSharedStrings = class _XlsxSharedStrings {
|
|
1223
|
+
/** A workbook with no shared strings. */
|
|
1224
|
+
static empty = new _XlsxSharedStrings([], /* @__PURE__ */ new Set());
|
|
1225
|
+
#texts;
|
|
1226
|
+
#rich;
|
|
1227
|
+
/** The strings `texts`, those at the indexes in `rich` marked as having come in runs. */
|
|
1228
|
+
constructor(texts, rich) {
|
|
1229
|
+
this.#texts = Object.freeze(texts);
|
|
1230
|
+
this.#rich = rich;
|
|
1231
|
+
}
|
|
1232
|
+
/** The strings, in the part's order: a cell's `<v>` is an index into them. */
|
|
1233
|
+
get texts() {
|
|
1234
|
+
return this.#texts;
|
|
1235
|
+
}
|
|
1236
|
+
/** How many there are. */
|
|
1237
|
+
get count() {
|
|
1238
|
+
return this.#texts.length;
|
|
1239
|
+
}
|
|
1240
|
+
/** Whether the string at `index` came in runs of their own formatting, which are not carried; its text is. */
|
|
1241
|
+
isRich(index) {
|
|
1242
|
+
return this.#rich.has(index);
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
// src/model.ts
|
|
1247
|
+
function referenceOf(cell) {
|
|
1248
|
+
return formatCell(cell.row, cell.column);
|
|
1249
|
+
}
|
|
1250
|
+
function isValidTableName(name) {
|
|
1251
|
+
return name.length > 0 && name.length <= 255 && !/[\uD800-\uDFFF]/.test(name) && /^[\p{L}_\\][\p{L}\p{Nd}_.]*$/u.test(name) && parseCell(name) === void 0 && !/^[Rr][0-9]*[Cc][0-9]*$|^[RrCc]$/.test(name) && !equalsIgnoreCase(name, "TRUE") && !equalsIgnoreCase(name, "FALSE");
|
|
1252
|
+
}
|
|
1253
|
+
function tableReference(table2) {
|
|
1254
|
+
return `${formatCell(table2.headerRow, table2.firstColumn)}:${formatCell(table2.headerRow + table2.dataRows, table2.firstColumn + table2.columns.length - 1)}`;
|
|
1255
|
+
}
|
|
1256
|
+
var elementMaxDepth = 3;
|
|
1257
|
+
var elementMaxChildren = 16;
|
|
1258
|
+
var elementMaxText = 1024;
|
|
1259
|
+
|
|
1260
|
+
// src/x-string.ts
|
|
1261
|
+
function hexAt(text, at) {
|
|
1262
|
+
const digits = text.slice(at, at + 4);
|
|
1263
|
+
return /^[0-9A-Fa-f]{4}$/.test(digits) ? Number.parseInt(digits, 16) : void 0;
|
|
1264
|
+
}
|
|
1265
|
+
function decodeXString(text) {
|
|
1266
|
+
let at = text.indexOf("_x");
|
|
1267
|
+
if (at < 0) return text;
|
|
1268
|
+
let out = "";
|
|
1269
|
+
let start = 0;
|
|
1270
|
+
while (at >= 0) {
|
|
1271
|
+
const code = at + 7 <= text.length && text.charAt(at + 6) === "_" ? hexAt(text, at + 2) : void 0;
|
|
1272
|
+
if (code !== void 0) {
|
|
1273
|
+
out += text.slice(start, at) + String.fromCharCode(code);
|
|
1274
|
+
start = at + 7;
|
|
1275
|
+
at = text.indexOf("_x", start);
|
|
1276
|
+
} else {
|
|
1277
|
+
at = text.indexOf("_x", at + 2);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return out + text.slice(start);
|
|
1281
|
+
}
|
|
1282
|
+
function escaped(c) {
|
|
1283
|
+
return c < 32 && c !== 9 && c !== 10 || c === 65534 || c === 65535;
|
|
1284
|
+
}
|
|
1285
|
+
function looksEscaped(text, at) {
|
|
1286
|
+
return at + 7 <= text.length && text.charAt(at + 1) === "x" && text.charAt(at + 6) === "_" && hexAt(text, at + 2) !== void 0;
|
|
1287
|
+
}
|
|
1288
|
+
function encodeXString(text) {
|
|
1289
|
+
let needs = false;
|
|
1290
|
+
for (let i = 0; i < text.length; i++) {
|
|
1291
|
+
const c = text.charCodeAt(i);
|
|
1292
|
+
if (c === 95 || escaped(c)) {
|
|
1293
|
+
needs = true;
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (!needs) return text;
|
|
1298
|
+
let out = "";
|
|
1299
|
+
for (let i = 0; i < text.length; i++) {
|
|
1300
|
+
const c = text.charCodeAt(i);
|
|
1301
|
+
if (escaped(c)) out += `_x${c.toString(16).toUpperCase().padStart(4, "0")}_`;
|
|
1302
|
+
else if (c === 95 && looksEscaped(text, i)) out += "_x005F_";
|
|
1303
|
+
else out += text.charAt(i);
|
|
1304
|
+
}
|
|
1305
|
+
return out;
|
|
1306
|
+
}
|
|
1307
|
+
function carries(text, encoded) {
|
|
1308
|
+
for (let i = 0; i < text.length; i++) {
|
|
1309
|
+
const c = text.charCodeAt(i);
|
|
1310
|
+
if (c >= 55296 && c <= 56319) {
|
|
1311
|
+
const next = i + 1 < text.length ? text.charCodeAt(i + 1) : 0;
|
|
1312
|
+
if (next >= 56320 && next <= 57343) {
|
|
1313
|
+
i++;
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
if (c >= 56320 && c <= 57343) return false;
|
|
1319
|
+
if (!encoded && (c < 32 && c !== 9 && c !== 10 && c !== 13 || c === 65534 || c === 65535)) return false;
|
|
1320
|
+
}
|
|
1321
|
+
return true;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// src/string-item.ts
|
|
1325
|
+
function readText(xml) {
|
|
1326
|
+
if (xml.isEmptyElement()) return ["", false];
|
|
1327
|
+
const depth = xml.depth();
|
|
1328
|
+
let text = "";
|
|
1329
|
+
let rich = false;
|
|
1330
|
+
xml.read();
|
|
1331
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === depth)) {
|
|
1332
|
+
if (xml.nodeType() !== "element") {
|
|
1333
|
+
xml.read();
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
switch (xml.localName()) {
|
|
1337
|
+
case "t":
|
|
1338
|
+
text += xml.readElementContentAsString();
|
|
1339
|
+
break;
|
|
1340
|
+
case "r":
|
|
1341
|
+
rich = true;
|
|
1342
|
+
if (xml.isEmptyElement()) xml.skip();
|
|
1343
|
+
else xml.read();
|
|
1344
|
+
break;
|
|
1345
|
+
default:
|
|
1346
|
+
xml.skip();
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return [decodeXString(text), rich];
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/steps.ts
|
|
1354
|
+
var margin = 64 * 1024;
|
|
1355
|
+
var ahead = 1 << 20;
|
|
1356
|
+
function low(xml) {
|
|
1357
|
+
return !xml.ended() && xml.unread() < margin;
|
|
1358
|
+
}
|
|
1359
|
+
async function runSteps(xml, save, step) {
|
|
1360
|
+
let want = ahead;
|
|
1361
|
+
for (; ; ) {
|
|
1362
|
+
await xml.fill(want);
|
|
1363
|
+
const mark = xml.snapshot();
|
|
1364
|
+
const rewind = save();
|
|
1365
|
+
let end;
|
|
1366
|
+
try {
|
|
1367
|
+
end = step();
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
if (!(error instanceof NeedMore)) throw error;
|
|
1370
|
+
xml.restore(mark);
|
|
1371
|
+
rewind();
|
|
1372
|
+
want = Math.max(want, xml.unread()) * 2;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (end !== "more") return end;
|
|
1376
|
+
want = ahead;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/values.ts
|
|
1381
|
+
function bool(value) {
|
|
1382
|
+
return value === "1" || value === "true";
|
|
1383
|
+
}
|
|
1384
|
+
function int(value, where) {
|
|
1385
|
+
const n = parseInt32(value);
|
|
1386
|
+
if (n === void 0) throw new XlsxError("malformed", `'${value}' is not a whole number where the part needs one.`, where);
|
|
1387
|
+
return n;
|
|
1388
|
+
}
|
|
1389
|
+
function double(value, part) {
|
|
1390
|
+
const n = parseDouble(value);
|
|
1391
|
+
if (n === void 0) throw new XlsxError("malformed", `'${value}' is not a number where the part needs one.`, { part });
|
|
1392
|
+
return n;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/sheet-reader.ts
|
|
1396
|
+
var batchSize = 4096;
|
|
1397
|
+
var SheetReader = class {
|
|
1398
|
+
#owner;
|
|
1399
|
+
#index;
|
|
1400
|
+
#part;
|
|
1401
|
+
#opened;
|
|
1402
|
+
#batch = [];
|
|
1403
|
+
#columns = [];
|
|
1404
|
+
#rows = [];
|
|
1405
|
+
#other = [];
|
|
1406
|
+
#state = "start";
|
|
1407
|
+
#row = -1;
|
|
1408
|
+
#column = -1;
|
|
1409
|
+
#reported = 0;
|
|
1410
|
+
#customRowHeights = false;
|
|
1411
|
+
#hiddenRows = false;
|
|
1412
|
+
#frozenPanes = false;
|
|
1413
|
+
#closed = false;
|
|
1414
|
+
/** The sheet as the workbook lists it. */
|
|
1415
|
+
sheet;
|
|
1416
|
+
constructor(owner, index, sheet, part, opened) {
|
|
1417
|
+
this.#owner = owner;
|
|
1418
|
+
this.#index = index;
|
|
1419
|
+
this.sheet = sheet;
|
|
1420
|
+
this.#part = part;
|
|
1421
|
+
this.#opened = opened;
|
|
1422
|
+
}
|
|
1423
|
+
/** The cells of the batch the last `read` read, in the file's order. */
|
|
1424
|
+
get cells() {
|
|
1425
|
+
return this.#batch;
|
|
1426
|
+
}
|
|
1427
|
+
/** The rows the last `read` began that record a cell format of their own (`customFormat`), in the file's order. A row
|
|
1428
|
+
* comes in the batch that holds its first cell, or in an earlier one. */
|
|
1429
|
+
get rows() {
|
|
1430
|
+
return this.#rows;
|
|
1431
|
+
}
|
|
1432
|
+
/** The worksheet's column widths, complete once the first `read` has returned: they come before the cells. */
|
|
1433
|
+
get columns() {
|
|
1434
|
+
return this.#columns;
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Every element of the worksheet the reader does not read, beside its cells and columns, in the file's order, complete
|
|
1438
|
+
* once `read` has returned false: `dimension`, `sheetViews` (of which only `frozenPanes` is read), `sheetFormatPr`,
|
|
1439
|
+
* `mergeCells`, `conditionalFormatting`, `dataValidations`, `hyperlinks`, `drawing`, `legacyDrawing` (notes),
|
|
1440
|
+
* `tableParts`, `sheetProtection`, `pageMargins`, `pageSetup`, `extLst` and the rest, each as far as `XlsxElement`
|
|
1441
|
+
* keeps it. None of it is judged here: whether an element holds anything that matters is for the reader of the file to
|
|
1442
|
+
* say.
|
|
1443
|
+
*/
|
|
1444
|
+
get otherElements() {
|
|
1445
|
+
return this.#other;
|
|
1446
|
+
}
|
|
1447
|
+
/** Whether a row read so far sets a height of its own. */
|
|
1448
|
+
get customRowHeights() {
|
|
1449
|
+
return this.#customRowHeights;
|
|
1450
|
+
}
|
|
1451
|
+
/** Whether a row read so far is hidden. */
|
|
1452
|
+
get hiddenRows() {
|
|
1453
|
+
return this.#hiddenRows;
|
|
1454
|
+
}
|
|
1455
|
+
/** Whether the sheet's view freezes panes; known once the first `read` has returned. */
|
|
1456
|
+
get frozenPanes() {
|
|
1457
|
+
return this.#frozenPanes;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Reads the next batch of cells into `cells`; false when the worksheet has no more. Refuses the file by name
|
|
1461
|
+
* (`XlsxError`) on a cell it cannot read, or past the read's cells.
|
|
1462
|
+
*/
|
|
1463
|
+
async read() {
|
|
1464
|
+
if (this.#closed) throw new Error("The worksheet reader is closed.");
|
|
1465
|
+
this.#batch.length = 0;
|
|
1466
|
+
this.#rows.length = 0;
|
|
1467
|
+
if (this.#finished()) return false;
|
|
1468
|
+
await this.#owner.checkpoint(this.#index);
|
|
1469
|
+
const { xml, bytes } = this.#opened;
|
|
1470
|
+
try {
|
|
1471
|
+
await runSteps(xml, () => this.#save(), () => this.#step());
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
if (error instanceof XmlError) throw Package.refuse(error, this.#opened, this.#part, this.sheet.name);
|
|
1474
|
+
throw error;
|
|
1475
|
+
} finally {
|
|
1476
|
+
this.#owner.countBytes(bytes.bytesRead - this.#reported);
|
|
1477
|
+
this.#reported = bytes.bytesRead;
|
|
1478
|
+
}
|
|
1479
|
+
if (this.#finished()) {
|
|
1480
|
+
this.#owner.report(this.#index);
|
|
1481
|
+
await bytes.close();
|
|
1482
|
+
}
|
|
1483
|
+
return this.#batch.length > 0 || this.#rows.length > 0 || !this.#finished();
|
|
1484
|
+
}
|
|
1485
|
+
#finished() {
|
|
1486
|
+
return this.#stateIs("finished");
|
|
1487
|
+
}
|
|
1488
|
+
/** The state, read where a call before may have changed it. */
|
|
1489
|
+
#stateIs(state) {
|
|
1490
|
+
return this.#state === state;
|
|
1491
|
+
}
|
|
1492
|
+
/** Closes the worksheet's part. */
|
|
1493
|
+
async close() {
|
|
1494
|
+
if (this.#closed) return;
|
|
1495
|
+
this.#closed = true;
|
|
1496
|
+
this.#owner.closed();
|
|
1497
|
+
await this.#opened.bytes.close();
|
|
1498
|
+
}
|
|
1499
|
+
/** What puts the reader back where a step began, for a step read again with more text. */
|
|
1500
|
+
#save() {
|
|
1501
|
+
const state = this.#state;
|
|
1502
|
+
const row = this.#row;
|
|
1503
|
+
const column = this.#column;
|
|
1504
|
+
const batch = this.#batch.length;
|
|
1505
|
+
const rows = this.#rows.length;
|
|
1506
|
+
const columns = this.#columns.length;
|
|
1507
|
+
const other = this.#other.length;
|
|
1508
|
+
const flags = [this.#customRowHeights, this.#hiddenRows, this.#frozenPanes];
|
|
1509
|
+
const cells = this.#owner.cells();
|
|
1510
|
+
return () => {
|
|
1511
|
+
this.#state = state;
|
|
1512
|
+
this.#row = row;
|
|
1513
|
+
this.#column = column;
|
|
1514
|
+
this.#batch.length = batch;
|
|
1515
|
+
this.#rows.length = rows;
|
|
1516
|
+
this.#columns.length = columns;
|
|
1517
|
+
this.#other.length = other;
|
|
1518
|
+
[this.#customRowHeights, this.#hiddenRows, this.#frozenPanes] = flags;
|
|
1519
|
+
this.#owner.rewindCells(cells);
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
#step() {
|
|
1523
|
+
if (this.#state === "start") this.#readRoot();
|
|
1524
|
+
if (this.#state === "head" && this.#readHead() === "more") return "more";
|
|
1525
|
+
if (this.#state === "cells") {
|
|
1526
|
+
const end = this.#readCells();
|
|
1527
|
+
if (end !== "done" || this.#stateIs("cells")) return end;
|
|
1528
|
+
}
|
|
1529
|
+
if (this.#state === "tail" && this.#readTail() === "more") return "more";
|
|
1530
|
+
return "done";
|
|
1531
|
+
}
|
|
1532
|
+
#where(cell) {
|
|
1533
|
+
return cell === void 0 ? { part: this.#part, sheet: this.sheet.name } : { part: this.#part, sheet: this.sheet.name, cell };
|
|
1534
|
+
}
|
|
1535
|
+
/** Reads the root, and refuses a worksheet that is not one. */
|
|
1536
|
+
#readRoot() {
|
|
1537
|
+
const xml = this.#opened.xml;
|
|
1538
|
+
xml.moveToContent();
|
|
1539
|
+
if (xml.namespaceUri().startsWith(strictPrefix)) {
|
|
1540
|
+
throw new XlsxError("strictOpenXml", "The worksheet is saved as Strict Open XML, which is not read.", this.#where());
|
|
1541
|
+
}
|
|
1542
|
+
if (xml.localName() !== "worksheet") throw new XlsxError("malformed", `The worksheet part's root is <${xml.localName()}>, not <worksheet>.`, this.#where());
|
|
1543
|
+
if (xml.isEmptyElement()) {
|
|
1544
|
+
this.#state = "tail";
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
xml.read();
|
|
1548
|
+
this.#state = "head";
|
|
1549
|
+
}
|
|
1550
|
+
/** Reads the children before `<sheetData>`. */
|
|
1551
|
+
#readHead() {
|
|
1552
|
+
const xml = this.#opened.xml;
|
|
1553
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === 0)) {
|
|
1554
|
+
if (xml.nodeType() !== "element") {
|
|
1555
|
+
xml.read();
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (low(xml)) return "more";
|
|
1559
|
+
switch (xml.localName()) {
|
|
1560
|
+
case "sheetData":
|
|
1561
|
+
this.#state = xml.isEmptyElement() ? "tail" : "cells";
|
|
1562
|
+
xml.read();
|
|
1563
|
+
return "done";
|
|
1564
|
+
case "cols":
|
|
1565
|
+
this.#readColumns();
|
|
1566
|
+
break;
|
|
1567
|
+
case "sheetViews": {
|
|
1568
|
+
const views = this.#readElement(0);
|
|
1569
|
+
this.#other.push(views);
|
|
1570
|
+
this.#frozenPanes = views.children.some((view) => view.children.some((pane) => {
|
|
1571
|
+
const state = pane.attributes.get("state");
|
|
1572
|
+
return pane.name === "pane" && (state === "frozen" || state === "frozenSplit");
|
|
1573
|
+
}));
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
default:
|
|
1577
|
+
this.#other.push(this.#readElement(0));
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
this.#state = "tail";
|
|
1582
|
+
return "done";
|
|
1583
|
+
}
|
|
1584
|
+
#readColumns() {
|
|
1585
|
+
const xml = this.#opened.xml;
|
|
1586
|
+
if (xml.isEmptyElement()) {
|
|
1587
|
+
xml.read();
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const depth = xml.depth();
|
|
1591
|
+
const sheet = this.sheet.name;
|
|
1592
|
+
const part = this.#part;
|
|
1593
|
+
xml.read();
|
|
1594
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === depth)) {
|
|
1595
|
+
if (xml.nodeType() === "element" && xml.localName() === "col") {
|
|
1596
|
+
const a = xml.getAttribute("min");
|
|
1597
|
+
const b = xml.getAttribute("max");
|
|
1598
|
+
const min = a === void 0 ? 0 : int(a, { part, sheet });
|
|
1599
|
+
const max = b === void 0 ? min : int(b, { part, sheet });
|
|
1600
|
+
if (min < 1 || max < min || max > maxColumns) {
|
|
1601
|
+
throw new XlsxError("malformed", `A column range ${String(min)} to ${String(max)} lies outside the sheet's columns.`, { part, sheet });
|
|
1602
|
+
}
|
|
1603
|
+
const w = xml.getAttribute("width");
|
|
1604
|
+
const width = w === void 0 ? null : double(w, part);
|
|
1605
|
+
const hidden = bool(xml.getAttribute("hidden"));
|
|
1606
|
+
const s = xml.getAttribute("style");
|
|
1607
|
+
const style = s === void 0 ? 0 : int(s, { part, sheet });
|
|
1608
|
+
if (style < 0 || style >= this.#owner.styles.cellFormats.length) {
|
|
1609
|
+
throw new XlsxError(
|
|
1610
|
+
"malformed",
|
|
1611
|
+
`The columns ${String(min)} to ${String(max)} name the cell format ${String(style)}, which the styles do not hold.`,
|
|
1612
|
+
{ part, sheet }
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
this.#columns.push(Object.freeze({
|
|
1616
|
+
first: min - 1,
|
|
1617
|
+
last: max - 1,
|
|
1618
|
+
width,
|
|
1619
|
+
hidden,
|
|
1620
|
+
styleIndex: style,
|
|
1621
|
+
bestFit: bool(xml.getAttribute("bestFit")),
|
|
1622
|
+
customWidth: width === null || bool(xml.getAttribute("customWidth"))
|
|
1623
|
+
}));
|
|
1624
|
+
}
|
|
1625
|
+
if (xml.nodeType() === "element") xml.skip();
|
|
1626
|
+
else xml.read();
|
|
1627
|
+
}
|
|
1628
|
+
xml.read();
|
|
1629
|
+
}
|
|
1630
|
+
/** The element at the reader, kept as `XlsxElement` keeps it, `depth` levels below the worksheet's element; the reader
|
|
1631
|
+
* is left past its end. */
|
|
1632
|
+
#readElement(depth) {
|
|
1633
|
+
const xml = this.#opened.xml;
|
|
1634
|
+
const name = xml.localName();
|
|
1635
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
1636
|
+
for (const attribute of xml.attributes()) {
|
|
1637
|
+
if (attribute.namespaceUri !== "http://www.w3.org/2000/xmlns/" && !attributes.has(attribute.localName)) {
|
|
1638
|
+
attributes.set(attribute.localName, attribute.value);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (xml.isEmptyElement()) {
|
|
1642
|
+
xml.read();
|
|
1643
|
+
return Object.freeze({ name, attributes, children: Object.freeze([]), text: null, childrenCut: false });
|
|
1644
|
+
}
|
|
1645
|
+
const children2 = [];
|
|
1646
|
+
let text = null;
|
|
1647
|
+
let cut = false;
|
|
1648
|
+
const end = xml.depth();
|
|
1649
|
+
xml.read();
|
|
1650
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === end)) {
|
|
1651
|
+
const node = xml.nodeType();
|
|
1652
|
+
switch (node) {
|
|
1653
|
+
case "element":
|
|
1654
|
+
if (depth < elementMaxDepth && children2.length < elementMaxChildren) {
|
|
1655
|
+
children2.push(this.#readElement(depth + 1));
|
|
1656
|
+
} else {
|
|
1657
|
+
cut = true;
|
|
1658
|
+
xml.skip();
|
|
1659
|
+
}
|
|
1660
|
+
break;
|
|
1661
|
+
case "text":
|
|
1662
|
+
case "cdata":
|
|
1663
|
+
case "significantWhitespace": {
|
|
1664
|
+
const kept = text === null ? "" : text;
|
|
1665
|
+
text = kept + xml.value().slice(0, elementMaxText - kept.length);
|
|
1666
|
+
xml.read();
|
|
1667
|
+
break;
|
|
1668
|
+
}
|
|
1669
|
+
case "whitespace":
|
|
1670
|
+
case "endElement":
|
|
1671
|
+
case "none":
|
|
1672
|
+
xml.read();
|
|
1673
|
+
break;
|
|
1674
|
+
default:
|
|
1675
|
+
unreachable(node);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
xml.read();
|
|
1679
|
+
return Object.freeze({ name, attributes, children: Object.freeze(children2), text, childrenCut: cut });
|
|
1680
|
+
}
|
|
1681
|
+
/** Reads cells until the batch is full, `<sheetData>` ends, or the text ahead runs low. */
|
|
1682
|
+
#readCells() {
|
|
1683
|
+
const xml = this.#opened.xml;
|
|
1684
|
+
while (this.#batch.length + this.#rows.length < batchSize) {
|
|
1685
|
+
if (xml.eof()) throw new XlsxError("malformed", "The worksheet ends inside its cells.", this.#where());
|
|
1686
|
+
if (low(xml)) return "more";
|
|
1687
|
+
const node = xml.nodeType();
|
|
1688
|
+
switch (node) {
|
|
1689
|
+
case "element":
|
|
1690
|
+
if (xml.localName() === "row") {
|
|
1691
|
+
this.#startRow();
|
|
1692
|
+
if (xml.isEmptyElement()) xml.skip();
|
|
1693
|
+
else xml.read();
|
|
1694
|
+
} else if (xml.localName() === "c") {
|
|
1695
|
+
this.#readCell();
|
|
1696
|
+
} else {
|
|
1697
|
+
xml.skip();
|
|
1698
|
+
}
|
|
1699
|
+
break;
|
|
1700
|
+
case "endElement":
|
|
1701
|
+
if (xml.localName() === "sheetData") {
|
|
1702
|
+
xml.read();
|
|
1703
|
+
this.#state = "tail";
|
|
1704
|
+
return "done";
|
|
1705
|
+
}
|
|
1706
|
+
xml.read();
|
|
1707
|
+
break;
|
|
1708
|
+
case "text":
|
|
1709
|
+
case "cdata":
|
|
1710
|
+
case "whitespace":
|
|
1711
|
+
case "significantWhitespace":
|
|
1712
|
+
case "none":
|
|
1713
|
+
xml.read();
|
|
1714
|
+
break;
|
|
1715
|
+
default:
|
|
1716
|
+
unreachable(node);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return "done";
|
|
1720
|
+
}
|
|
1721
|
+
#startRow() {
|
|
1722
|
+
const xml = this.#opened.xml;
|
|
1723
|
+
const where = this.#where();
|
|
1724
|
+
let r = this.#row + 1;
|
|
1725
|
+
let style;
|
|
1726
|
+
let customFormat = false;
|
|
1727
|
+
for (const attribute of xml.attributes()) {
|
|
1728
|
+
switch (attribute.localName) {
|
|
1729
|
+
case "r":
|
|
1730
|
+
r = int(attribute.value, where) - 1;
|
|
1731
|
+
break;
|
|
1732
|
+
case "customHeight":
|
|
1733
|
+
if (bool(attribute.value)) this.#customRowHeights = true;
|
|
1734
|
+
break;
|
|
1735
|
+
case "hidden":
|
|
1736
|
+
if (bool(attribute.value)) this.#hiddenRows = true;
|
|
1737
|
+
break;
|
|
1738
|
+
case "s":
|
|
1739
|
+
style = attribute.value;
|
|
1740
|
+
break;
|
|
1741
|
+
case "customFormat":
|
|
1742
|
+
customFormat = bool(attribute.value);
|
|
1743
|
+
break;
|
|
1744
|
+
default:
|
|
1745
|
+
break;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (r < 0 || r >= maxRows) throw new XlsxError("malformed", `Row ${String(r + 1)} lies outside the sheet's rows.`, where);
|
|
1749
|
+
if (r <= this.#row) {
|
|
1750
|
+
throw new XlsxError("malformed", `Row ${String(r + 1)} comes after row ${String(this.#row + 1)}: a worksheet's rows are in order, each once.`, where);
|
|
1751
|
+
}
|
|
1752
|
+
this.#row = r;
|
|
1753
|
+
this.#column = -1;
|
|
1754
|
+
if (!customFormat) return;
|
|
1755
|
+
const index = style === void 0 ? 0 : int(style, where);
|
|
1756
|
+
if (index < 0 || index >= this.#owner.styles.cellFormats.length) {
|
|
1757
|
+
throw new XlsxError("malformed", `Row ${String(r + 1)} names the cell format ${String(index)}, which the styles do not hold.`, where);
|
|
1758
|
+
}
|
|
1759
|
+
this.#rows.push(Object.freeze({ row: r, styleIndex: index }));
|
|
1760
|
+
}
|
|
1761
|
+
#readCell() {
|
|
1762
|
+
const xml = this.#opened.xml;
|
|
1763
|
+
let reference;
|
|
1764
|
+
let type;
|
|
1765
|
+
let style;
|
|
1766
|
+
let metadata;
|
|
1767
|
+
for (const attribute of xml.attributes()) {
|
|
1768
|
+
switch (attribute.localName) {
|
|
1769
|
+
case "r":
|
|
1770
|
+
reference = attribute.value;
|
|
1771
|
+
break;
|
|
1772
|
+
case "t":
|
|
1773
|
+
type = attribute.value;
|
|
1774
|
+
break;
|
|
1775
|
+
case "s":
|
|
1776
|
+
style = attribute.value;
|
|
1777
|
+
break;
|
|
1778
|
+
case "cm":
|
|
1779
|
+
metadata = attribute.value;
|
|
1780
|
+
break;
|
|
1781
|
+
default:
|
|
1782
|
+
break;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
let r;
|
|
1786
|
+
let c;
|
|
1787
|
+
if (reference === void 0) {
|
|
1788
|
+
r = this.#row < 0 ? 0 : this.#row;
|
|
1789
|
+
c = this.#column + 1;
|
|
1790
|
+
if (c >= maxColumns) throw new XlsxError("malformed", "A cell lies past the sheet's last column.", this.#where());
|
|
1791
|
+
} else {
|
|
1792
|
+
const place = parseCell(reference);
|
|
1793
|
+
if (place === void 0) throw new XlsxError("malformed", `'${reference}' is not a cell of the sheet.`, this.#where());
|
|
1794
|
+
r = place.row;
|
|
1795
|
+
c = place.column;
|
|
1796
|
+
if (this.#row >= 0 && r !== this.#row) {
|
|
1797
|
+
throw new XlsxError("malformed", `The cell is written inside row ${String(this.#row + 1)}, not its own.`, this.#where(reference));
|
|
1798
|
+
}
|
|
1799
|
+
if (c <= this.#column) {
|
|
1800
|
+
throw new XlsxError(
|
|
1801
|
+
"malformed",
|
|
1802
|
+
`The cell comes after ${formatCell(r, this.#column)}: a row's cells are in order, each once.`,
|
|
1803
|
+
this.#where(reference)
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
this.#row = r;
|
|
1808
|
+
this.#column = c;
|
|
1809
|
+
this.#owner.countCell(this.sheet, r, c);
|
|
1810
|
+
const at = () => this.#where(formatCell(r, c));
|
|
1811
|
+
const cellType = cellTypeOf(type, at);
|
|
1812
|
+
const styleIndex = style === void 0 ? 0 : int(style, at());
|
|
1813
|
+
if (styleIndex < 0 || styleIndex >= this.#owner.styles.cellFormats.length) {
|
|
1814
|
+
throw new XlsxError("malformed", `The cell names the cell format ${String(styleIndex)}, which the styles do not hold.`, at());
|
|
1815
|
+
}
|
|
1816
|
+
let value = null;
|
|
1817
|
+
let text = null;
|
|
1818
|
+
let formula = null;
|
|
1819
|
+
let rich = false;
|
|
1820
|
+
if (xml.isEmptyElement()) {
|
|
1821
|
+
xml.read();
|
|
1822
|
+
} else {
|
|
1823
|
+
const depth = xml.depth();
|
|
1824
|
+
xml.read();
|
|
1825
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === depth)) {
|
|
1826
|
+
if (xml.nodeType() !== "element") {
|
|
1827
|
+
xml.read();
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
switch (xml.localName()) {
|
|
1831
|
+
case "v":
|
|
1832
|
+
value = xml.readElementContentAsString();
|
|
1833
|
+
break;
|
|
1834
|
+
case "f":
|
|
1835
|
+
formula = this.#readFormula(metadata, at);
|
|
1836
|
+
break;
|
|
1837
|
+
case "is":
|
|
1838
|
+
[text, rich] = readText(xml);
|
|
1839
|
+
xml.read();
|
|
1840
|
+
break;
|
|
1841
|
+
default:
|
|
1842
|
+
xml.skip();
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
xml.read();
|
|
1847
|
+
}
|
|
1848
|
+
switch (cellType) {
|
|
1849
|
+
case "sharedString": {
|
|
1850
|
+
const strings = this.#owner.sharedStrings();
|
|
1851
|
+
const index = value === null ? void 0 : parseDigits(value);
|
|
1852
|
+
if (index === void 0 || index >= strings.count) {
|
|
1853
|
+
throw new XlsxError("malformed", `The cell names the shared string '${value === null ? "" : value}', which the workbook does not hold.`, at());
|
|
1854
|
+
}
|
|
1855
|
+
text = elementAt(strings.texts, index);
|
|
1856
|
+
rich = strings.isRich(index);
|
|
1857
|
+
break;
|
|
1858
|
+
}
|
|
1859
|
+
case "inlineString":
|
|
1860
|
+
if (text === null) text = "";
|
|
1861
|
+
break;
|
|
1862
|
+
case "formulaString":
|
|
1863
|
+
if (value !== null) value = decodeXString(value);
|
|
1864
|
+
break;
|
|
1865
|
+
case "number":
|
|
1866
|
+
case "boolean":
|
|
1867
|
+
case "error":
|
|
1868
|
+
case "date":
|
|
1869
|
+
break;
|
|
1870
|
+
default:
|
|
1871
|
+
return unreachableType(cellType);
|
|
1872
|
+
}
|
|
1873
|
+
this.#batch.push(Object.freeze({ row: r, column: c, type: cellType, value, text, formula, styleIndex, isRichText: rich }));
|
|
1874
|
+
}
|
|
1875
|
+
#readFormula(metadata, at) {
|
|
1876
|
+
const xml = this.#opened.xml;
|
|
1877
|
+
let kind;
|
|
1878
|
+
let reference;
|
|
1879
|
+
let shared;
|
|
1880
|
+
for (const attribute of xml.attributes()) {
|
|
1881
|
+
switch (attribute.localName) {
|
|
1882
|
+
case "t":
|
|
1883
|
+
kind = attribute.value;
|
|
1884
|
+
break;
|
|
1885
|
+
case "ref":
|
|
1886
|
+
reference = attribute.value;
|
|
1887
|
+
break;
|
|
1888
|
+
case "si":
|
|
1889
|
+
shared = attribute.value;
|
|
1890
|
+
break;
|
|
1891
|
+
default:
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
const formulaKind = formulaKindOf(kind, at);
|
|
1896
|
+
const sharedIndex = shared === void 0 ? null : int(shared, at());
|
|
1897
|
+
if (formulaKind === "shared" && sharedIndex === null) throw new XlsxError("malformed", "A shared formula names no group (si).", at());
|
|
1898
|
+
const cm = metadata === void 0 ? void 0 : parseDigits(metadata);
|
|
1899
|
+
const isDynamicArray = formulaKind === "array" && cm !== void 0 && this.#owner.isDynamicArray(cm);
|
|
1900
|
+
const text = decodeXString(xml.readElementContentAsString());
|
|
1901
|
+
return Object.freeze({ text, kind: formulaKind, reference: reference === void 0 ? null : reference, sharedIndex, isDynamicArray });
|
|
1902
|
+
}
|
|
1903
|
+
/** Reads what follows the cells, to the part's end. */
|
|
1904
|
+
#readTail() {
|
|
1905
|
+
const xml = this.#opened.xml;
|
|
1906
|
+
while (!xml.eof()) {
|
|
1907
|
+
if (xml.nodeType() === "element") {
|
|
1908
|
+
if (low(xml)) return "more";
|
|
1909
|
+
this.#other.push(this.#readElement(0));
|
|
1910
|
+
} else {
|
|
1911
|
+
xml.read();
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
this.#state = "finished";
|
|
1915
|
+
return "done";
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
function cellTypeOf(type, at) {
|
|
1919
|
+
switch (type) {
|
|
1920
|
+
case void 0:
|
|
1921
|
+
case "n":
|
|
1922
|
+
return "number";
|
|
1923
|
+
case "s":
|
|
1924
|
+
return "sharedString";
|
|
1925
|
+
case "inlineStr":
|
|
1926
|
+
return "inlineString";
|
|
1927
|
+
case "str":
|
|
1928
|
+
return "formulaString";
|
|
1929
|
+
case "b":
|
|
1930
|
+
return "boolean";
|
|
1931
|
+
case "e":
|
|
1932
|
+
return "error";
|
|
1933
|
+
case "d":
|
|
1934
|
+
return "date";
|
|
1935
|
+
default:
|
|
1936
|
+
throw new XlsxError("malformed", `'${type}' is not a cell type.`, at());
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function formulaKindOf(kind, at) {
|
|
1940
|
+
switch (kind) {
|
|
1941
|
+
case void 0:
|
|
1942
|
+
case "normal":
|
|
1943
|
+
return "normal";
|
|
1944
|
+
case "shared":
|
|
1945
|
+
return "shared";
|
|
1946
|
+
case "array":
|
|
1947
|
+
return "array";
|
|
1948
|
+
case "dataTable":
|
|
1949
|
+
return "dataTable";
|
|
1950
|
+
default:
|
|
1951
|
+
throw new XlsxError("malformed", `'${kind}' is not a kind of Formula.`, at());
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
function unreachableType(type) {
|
|
1955
|
+
throw new Error(`Internal: ${String(type)} is not a cell's type.`);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/styles.ts
|
|
1959
|
+
var automaticColor = Object.freeze({ kind: "automatic", value: 0, tint: 0 });
|
|
1960
|
+
function rgbColor(argb2, tint = 0) {
|
|
1961
|
+
return Object.freeze({ kind: "rgb", value: argb2 >>> 0, tint });
|
|
1962
|
+
}
|
|
1963
|
+
function themeColor(slot, tint = 0) {
|
|
1964
|
+
return Object.freeze({ kind: "theme", value: slot >>> 0, tint });
|
|
1965
|
+
}
|
|
1966
|
+
function indexedColor(index, tint = 0) {
|
|
1967
|
+
return Object.freeze({ kind: "indexed", value: index >>> 0, tint });
|
|
1968
|
+
}
|
|
1969
|
+
var noBorderEdge = Object.freeze({ style: "none", color: null });
|
|
1970
|
+
var noBorder = Object.freeze({
|
|
1971
|
+
left: noBorderEdge,
|
|
1972
|
+
right: noBorderEdge,
|
|
1973
|
+
top: noBorderEdge,
|
|
1974
|
+
bottom: noBorderEdge,
|
|
1975
|
+
diagonal: noBorderEdge,
|
|
1976
|
+
diagonalUp: false,
|
|
1977
|
+
diagonalDown: false
|
|
1978
|
+
});
|
|
1979
|
+
var noFill = Object.freeze({ pattern: "none", foreground: null, background: null, isGradient: false });
|
|
1980
|
+
var gray125Fill = Object.freeze({ pattern: "gray125", foreground: null, background: null, isGradient: false });
|
|
1981
|
+
function solidFill(color) {
|
|
1982
|
+
return Object.freeze({ pattern: "solid", foreground: color, background: null, isGradient: false });
|
|
1983
|
+
}
|
|
1984
|
+
var defaultAlignment = Object.freeze({
|
|
1985
|
+
horizontal: "general",
|
|
1986
|
+
vertical: "bottom",
|
|
1987
|
+
wrapText: false,
|
|
1988
|
+
indent: 0,
|
|
1989
|
+
textRotation: 0,
|
|
1990
|
+
shrinkToFit: false
|
|
1991
|
+
});
|
|
1992
|
+
var FrozenMap = class extends Map {
|
|
1993
|
+
constructor(entries) {
|
|
1994
|
+
super(entries);
|
|
1995
|
+
Object.freeze(this);
|
|
1996
|
+
}
|
|
1997
|
+
set(key, value) {
|
|
1998
|
+
if (Object.isFrozen(this)) throw new TypeError("The default styles cannot be changed.");
|
|
1999
|
+
return super.set(key, value);
|
|
2000
|
+
}
|
|
2001
|
+
delete() {
|
|
2002
|
+
throw new TypeError("The default styles cannot be changed.");
|
|
2003
|
+
}
|
|
2004
|
+
clear() {
|
|
2005
|
+
throw new TypeError("The default styles cannot be changed.");
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
var defaultStyles = Object.freeze({
|
|
2009
|
+
numberFormats: new FrozenMap([]),
|
|
2010
|
+
fonts: Object.freeze([Object.freeze({
|
|
2011
|
+
name: "Calibri",
|
|
2012
|
+
size: 11,
|
|
2013
|
+
bold: false,
|
|
2014
|
+
italic: false,
|
|
2015
|
+
underline: "none",
|
|
2016
|
+
strike: false,
|
|
2017
|
+
color: themeColor(1)
|
|
2018
|
+
})]),
|
|
2019
|
+
fills: Object.freeze([noFill, gray125Fill]),
|
|
2020
|
+
borders: Object.freeze([noBorder]),
|
|
2021
|
+
cellFormats: Object.freeze([Object.freeze({ numberFormatId: 0, fontId: 0, fillId: 0, borderId: 0, alignment: null })]),
|
|
2022
|
+
indexedColors: null
|
|
2023
|
+
});
|
|
2024
|
+
var firstCustomNumberFormat = 164;
|
|
2025
|
+
var borderStyleNames = [
|
|
2026
|
+
"none",
|
|
2027
|
+
"thin",
|
|
2028
|
+
"medium",
|
|
2029
|
+
"dashed",
|
|
2030
|
+
"dotted",
|
|
2031
|
+
"thick",
|
|
2032
|
+
"double",
|
|
2033
|
+
"hair",
|
|
2034
|
+
"mediumDashed",
|
|
2035
|
+
"dashDot",
|
|
2036
|
+
"mediumDashDot",
|
|
2037
|
+
"dashDotDot",
|
|
2038
|
+
"mediumDashDotDot",
|
|
2039
|
+
"slantDashDot"
|
|
2040
|
+
];
|
|
2041
|
+
var horizontalNames = ["general", "left", "center", "right", "fill", "justify", "centerContinuous", "distributed"];
|
|
2042
|
+
var verticalNames = ["bottom", "top", "center", "justify", "distributed"];
|
|
2043
|
+
|
|
2044
|
+
// src/styles-reader.ts
|
|
2045
|
+
async function readStyles(pkg, part) {
|
|
2046
|
+
const opened = await pkg.readXml(part);
|
|
2047
|
+
if (opened === void 0) return defaultStyles;
|
|
2048
|
+
const numberFormats = /* @__PURE__ */ new Map();
|
|
2049
|
+
const fonts = [];
|
|
2050
|
+
const fills = [];
|
|
2051
|
+
const borders = [];
|
|
2052
|
+
const cellFormats = [];
|
|
2053
|
+
let indexed = null;
|
|
2054
|
+
const xml = opened.xml;
|
|
2055
|
+
try {
|
|
2056
|
+
xml.moveToContent();
|
|
2057
|
+
if (xml.isEmptyElement()) return defaultStyles;
|
|
2058
|
+
const depth = xml.depth();
|
|
2059
|
+
xml.read();
|
|
2060
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === depth)) {
|
|
2061
|
+
if (xml.nodeType() !== "element") {
|
|
2062
|
+
xml.read();
|
|
2063
|
+
continue;
|
|
2064
|
+
}
|
|
2065
|
+
switch (xml.localName()) {
|
|
2066
|
+
case "numFmts":
|
|
2067
|
+
children(xml, "numFmt", () => {
|
|
2068
|
+
const id = xml.getAttribute("numFmtId");
|
|
2069
|
+
const code = xml.getAttribute("formatCode");
|
|
2070
|
+
if (id !== void 0 && code !== void 0) numberFormats.set(int(id, { part }), decodeXString(code));
|
|
2071
|
+
});
|
|
2072
|
+
break;
|
|
2073
|
+
case "fonts":
|
|
2074
|
+
children(xml, "font", () => fonts.push(readFont(xml, part)));
|
|
2075
|
+
break;
|
|
2076
|
+
case "fills":
|
|
2077
|
+
children(xml, "fill", () => fills.push(readFill(xml, part)));
|
|
2078
|
+
break;
|
|
2079
|
+
case "borders":
|
|
2080
|
+
children(xml, "border", () => borders.push(readBorder(xml, part)));
|
|
2081
|
+
break;
|
|
2082
|
+
case "cellXfs":
|
|
2083
|
+
children(xml, "xf", () => cellFormats.push(readCellFormat(xml, part)));
|
|
2084
|
+
break;
|
|
2085
|
+
case "colors":
|
|
2086
|
+
indexed = readIndexedColors(xml, part);
|
|
2087
|
+
break;
|
|
2088
|
+
default:
|
|
2089
|
+
xml.skip();
|
|
2090
|
+
break;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
} catch (error) {
|
|
2094
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
2095
|
+
throw error;
|
|
2096
|
+
}
|
|
2097
|
+
if (fonts.length === 0) fonts.push(...defaultStyles.fonts);
|
|
2098
|
+
if (fills.length === 0) fills.push(...defaultStyles.fills);
|
|
2099
|
+
if (borders.length === 0) borders.push(...defaultStyles.borders);
|
|
2100
|
+
if (cellFormats.length === 0) cellFormats.push(...defaultStyles.cellFormats);
|
|
2101
|
+
cellFormats.forEach((f, i) => {
|
|
2102
|
+
const check = (what, id, count2) => {
|
|
2103
|
+
if (id < 0 || id >= count2) {
|
|
2104
|
+
throw new XlsxError("malformed", `The cell format ${String(i)} names the ${what} ${String(id)}, which the styles do not hold.`, { part });
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
check("font", f.fontId, fonts.length);
|
|
2108
|
+
check("fill", f.fillId, fills.length);
|
|
2109
|
+
check("border", f.borderId, borders.length);
|
|
2110
|
+
if (f.numberFormatId < 0 || f.numberFormatId >= firstCustomNumberFormat && !numberFormats.has(f.numberFormatId)) {
|
|
2111
|
+
throw new XlsxError(
|
|
2112
|
+
"malformed",
|
|
2113
|
+
`The cell format ${String(i)} names the Number Format ${String(f.numberFormatId)}, which the styles do not hold.`,
|
|
2114
|
+
{ part }
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
return Object.freeze({
|
|
2119
|
+
numberFormats,
|
|
2120
|
+
fonts: Object.freeze(fonts),
|
|
2121
|
+
fills: Object.freeze(fills),
|
|
2122
|
+
borders: Object.freeze(borders),
|
|
2123
|
+
cellFormats: Object.freeze(cellFormats),
|
|
2124
|
+
indexedColors: indexed === null ? null : Object.freeze(indexed)
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
function children(xml, name, each) {
|
|
2128
|
+
if (xml.isEmptyElement()) {
|
|
2129
|
+
xml.read();
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
const depth = xml.depth();
|
|
2133
|
+
xml.read();
|
|
2134
|
+
while (!xml.eof() && !(xml.nodeType() === "endElement" && xml.depth() === depth)) {
|
|
2135
|
+
if (xml.nodeType() === "element" && xml.depth() === depth + 1 && xml.localName() === name) {
|
|
2136
|
+
each();
|
|
2137
|
+
xml.skip();
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
if (xml.nodeType() === "element") xml.skip();
|
|
2141
|
+
else xml.read();
|
|
2142
|
+
}
|
|
2143
|
+
xml.read();
|
|
2144
|
+
}
|
|
2145
|
+
function subtree(xml, visit) {
|
|
2146
|
+
if (xml.isEmptyElement()) return;
|
|
2147
|
+
const top = xml.depth();
|
|
2148
|
+
while (xml.read() && !(xml.nodeType() === "endElement" && xml.depth() === top)) visit(xml.depth() - top);
|
|
2149
|
+
}
|
|
2150
|
+
function readFont(xml, part) {
|
|
2151
|
+
let name = null;
|
|
2152
|
+
let size = null;
|
|
2153
|
+
let bold = false;
|
|
2154
|
+
let italic = false;
|
|
2155
|
+
let strike = false;
|
|
2156
|
+
let underline = "none";
|
|
2157
|
+
let color = null;
|
|
2158
|
+
subtree(xml, (depth) => {
|
|
2159
|
+
if (xml.nodeType() !== "element" || depth !== 1) return;
|
|
2160
|
+
const val = xml.getAttribute("val");
|
|
2161
|
+
switch (xml.localName()) {
|
|
2162
|
+
case "name":
|
|
2163
|
+
name = val === void 0 ? null : val;
|
|
2164
|
+
break;
|
|
2165
|
+
case "sz":
|
|
2166
|
+
size = val === void 0 ? null : double(val, part);
|
|
2167
|
+
break;
|
|
2168
|
+
case "b":
|
|
2169
|
+
bold = val === void 0 || bool(val);
|
|
2170
|
+
break;
|
|
2171
|
+
case "i":
|
|
2172
|
+
italic = val === void 0 || bool(val);
|
|
2173
|
+
break;
|
|
2174
|
+
case "strike":
|
|
2175
|
+
strike = val === void 0 || bool(val);
|
|
2176
|
+
break;
|
|
2177
|
+
case "u":
|
|
2178
|
+
underline = underlineOf(val, part);
|
|
2179
|
+
break;
|
|
2180
|
+
case "color":
|
|
2181
|
+
color = readColor(xml, part);
|
|
2182
|
+
break;
|
|
2183
|
+
default:
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
return Object.freeze({ name, size, bold, italic, underline, strike, color });
|
|
2188
|
+
}
|
|
2189
|
+
function underlineOf(val, part) {
|
|
2190
|
+
switch (val) {
|
|
2191
|
+
case void 0:
|
|
2192
|
+
case "single":
|
|
2193
|
+
return "single";
|
|
2194
|
+
case "double":
|
|
2195
|
+
return "double";
|
|
2196
|
+
case "singleAccounting":
|
|
2197
|
+
return "singleAccounting";
|
|
2198
|
+
case "doubleAccounting":
|
|
2199
|
+
return "doubleAccounting";
|
|
2200
|
+
case "none":
|
|
2201
|
+
return "none";
|
|
2202
|
+
default:
|
|
2203
|
+
throw new XlsxError("malformed", `'${val}' is not an underline.`, { part });
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
function readFill(xml, part) {
|
|
2207
|
+
if (xml.isEmptyElement()) return noFill;
|
|
2208
|
+
const top = xml.depth();
|
|
2209
|
+
const fill = firstFill(xml, top, part);
|
|
2210
|
+
while (!(xml.nodeType() === "endElement" && xml.depth() === top) && xml.read()) ;
|
|
2211
|
+
return fill;
|
|
2212
|
+
}
|
|
2213
|
+
function firstFill(xml, top, part) {
|
|
2214
|
+
while (xml.read() && !(xml.nodeType() === "endElement" && xml.depth() === top)) {
|
|
2215
|
+
if (xml.nodeType() !== "element" || xml.depth() !== top + 1) continue;
|
|
2216
|
+
if (xml.localName() === "gradientFill") return Object.freeze({ pattern: null, foreground: null, background: null, isGradient: true });
|
|
2217
|
+
if (xml.localName() !== "patternFill") continue;
|
|
2218
|
+
const patternType = xml.getAttribute("patternType");
|
|
2219
|
+
const pattern = patternType === void 0 ? "none" : patternType;
|
|
2220
|
+
let foreground = null;
|
|
2221
|
+
let background = null;
|
|
2222
|
+
if (!xml.isEmptyElement()) {
|
|
2223
|
+
const at = xml.depth();
|
|
2224
|
+
while (xml.read() && xml.depth() > at) {
|
|
2225
|
+
if (xml.nodeType() !== "element") continue;
|
|
2226
|
+
if (xml.localName() === "fgColor") foreground = readColor(xml, part);
|
|
2227
|
+
else if (xml.localName() === "bgColor") background = readColor(xml, part);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return Object.freeze({ pattern, foreground, background, isGradient: false });
|
|
2231
|
+
}
|
|
2232
|
+
return noFill;
|
|
2233
|
+
}
|
|
2234
|
+
function readBorder(xml, part) {
|
|
2235
|
+
const diagonalUp = bool(xml.getAttribute("diagonalUp"));
|
|
2236
|
+
const diagonalDown = bool(xml.getAttribute("diagonalDown"));
|
|
2237
|
+
let left = noBorderEdge;
|
|
2238
|
+
let right = noBorderEdge;
|
|
2239
|
+
let top = noBorderEdge;
|
|
2240
|
+
let bottom = noBorderEdge;
|
|
2241
|
+
let diagonal = noBorderEdge;
|
|
2242
|
+
subtree(xml, (depth) => {
|
|
2243
|
+
if (xml.nodeType() !== "element" || depth !== 1) return;
|
|
2244
|
+
const name = xml.localName();
|
|
2245
|
+
const style = borderStyle(xml.getAttribute("style"), part);
|
|
2246
|
+
let color = null;
|
|
2247
|
+
if (!xml.isEmptyElement()) {
|
|
2248
|
+
const at = xml.depth();
|
|
2249
|
+
while (xml.read() && xml.depth() > at) {
|
|
2250
|
+
if (xml.nodeType() === "element" && xml.localName() === "color") color = readColor(xml, part);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
const edge = style === "none" && color === null ? noBorderEdge : Object.freeze({ style, color });
|
|
2254
|
+
switch (name) {
|
|
2255
|
+
case "left":
|
|
2256
|
+
case "start":
|
|
2257
|
+
left = edge;
|
|
2258
|
+
break;
|
|
2259
|
+
case "right":
|
|
2260
|
+
case "end":
|
|
2261
|
+
right = edge;
|
|
2262
|
+
break;
|
|
2263
|
+
case "top":
|
|
2264
|
+
top = edge;
|
|
2265
|
+
break;
|
|
2266
|
+
case "bottom":
|
|
2267
|
+
bottom = edge;
|
|
2268
|
+
break;
|
|
2269
|
+
case "diagonal":
|
|
2270
|
+
diagonal = edge;
|
|
2271
|
+
break;
|
|
2272
|
+
default:
|
|
2273
|
+
break;
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
return Object.freeze({ left, right, top, bottom, diagonal, diagonalUp, diagonalDown });
|
|
2277
|
+
}
|
|
2278
|
+
function borderStyle(name, part) {
|
|
2279
|
+
if (name === void 0) return "none";
|
|
2280
|
+
const style = borderStyleNames.find((s) => s === name);
|
|
2281
|
+
if (style === void 0) throw new XlsxError("malformed", `'${name}' is not a border style.`, { part });
|
|
2282
|
+
return style;
|
|
2283
|
+
}
|
|
2284
|
+
function readCellFormat(xml, part) {
|
|
2285
|
+
const id = (attribute) => {
|
|
2286
|
+
const v = xml.getAttribute(attribute);
|
|
2287
|
+
return v === void 0 ? 0 : int(v, { part });
|
|
2288
|
+
};
|
|
2289
|
+
const numberFormatId = id("numFmtId");
|
|
2290
|
+
const fontId = id("fontId");
|
|
2291
|
+
const fillId = id("fillId");
|
|
2292
|
+
const borderId = id("borderId");
|
|
2293
|
+
let alignment = null;
|
|
2294
|
+
subtree(xml, (depth) => {
|
|
2295
|
+
if (xml.nodeType() === "element" && depth === 1 && xml.localName() === "alignment") alignment = readAlignment(xml, part);
|
|
2296
|
+
});
|
|
2297
|
+
return Object.freeze({ numberFormatId, fontId, fillId, borderId, alignment });
|
|
2298
|
+
}
|
|
2299
|
+
function readAlignment(xml, part) {
|
|
2300
|
+
const hv = xml.getAttribute("horizontal");
|
|
2301
|
+
const vv = xml.getAttribute("vertical");
|
|
2302
|
+
const horizontal = hv === void 0 ? "general" : horizontalNames.find((n) => n === hv);
|
|
2303
|
+
const vertical = vv === void 0 ? "bottom" : verticalNames.find((n) => n === vv);
|
|
2304
|
+
if (horizontal === void 0 || vertical === void 0) throw new XlsxError("malformed", "A cell format names an alignment that is not one.", { part });
|
|
2305
|
+
const indent = xml.getAttribute("indent");
|
|
2306
|
+
const rotation = xml.getAttribute("textRotation");
|
|
2307
|
+
return Object.freeze({
|
|
2308
|
+
horizontal,
|
|
2309
|
+
vertical,
|
|
2310
|
+
wrapText: bool(xml.getAttribute("wrapText")),
|
|
2311
|
+
indent: indent === void 0 ? 0 : int(indent, { part }),
|
|
2312
|
+
textRotation: rotation === void 0 ? 0 : int(rotation, { part }),
|
|
2313
|
+
shrinkToFit: bool(xml.getAttribute("shrinkToFit"))
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
function readIndexedColors(xml, part) {
|
|
2317
|
+
let colors = null;
|
|
2318
|
+
subtree(xml, () => {
|
|
2319
|
+
if (xml.nodeType() !== "element") return;
|
|
2320
|
+
if (xml.localName() === "indexedColors") {
|
|
2321
|
+
colors = [];
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
const rgb = xml.getAttribute("rgb");
|
|
2325
|
+
if (xml.localName() === "rgbColor" && colors !== null && rgb !== void 0) colors.push(argb(rgb, part));
|
|
2326
|
+
});
|
|
2327
|
+
return colors;
|
|
2328
|
+
}
|
|
2329
|
+
function readColor(xml, part) {
|
|
2330
|
+
const t = xml.getAttribute("tint");
|
|
2331
|
+
const tint = t === void 0 ? 0 : double(t, part);
|
|
2332
|
+
if (bool(xml.getAttribute("auto"))) return Object.freeze({ kind: "automatic", value: 0, tint });
|
|
2333
|
+
const rgb = xml.getAttribute("rgb");
|
|
2334
|
+
if (rgb !== void 0) return Object.freeze({ kind: "rgb", value: argb(rgb, part), tint });
|
|
2335
|
+
const theme = xml.getAttribute("theme");
|
|
2336
|
+
if (theme !== void 0) return Object.freeze({ kind: "theme", value: int(theme, { part }) >>> 0, tint });
|
|
2337
|
+
const indexed = xml.getAttribute("indexed");
|
|
2338
|
+
if (indexed !== void 0) return Object.freeze({ kind: "indexed", value: int(indexed, { part }) >>> 0, tint });
|
|
2339
|
+
return Object.freeze({ kind: "automatic", value: 0, tint });
|
|
2340
|
+
}
|
|
2341
|
+
function argb(text, part) {
|
|
2342
|
+
const value = parseHex32(text);
|
|
2343
|
+
if (value === void 0 || text.length !== 6 && text.length !== 8) throw new XlsxError("malformed", `'${text}' is not an RGB colour.`, { part });
|
|
2344
|
+
return text.length === 6 ? (4278190080 | value) >>> 0 : value;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/theme.ts
|
|
2348
|
+
var defaultIndexedColors = Object.freeze([
|
|
2349
|
+
4278190080,
|
|
2350
|
+
4294967295,
|
|
2351
|
+
4294901760,
|
|
2352
|
+
4278255360,
|
|
2353
|
+
4278190335,
|
|
2354
|
+
4294967040,
|
|
2355
|
+
4294902015,
|
|
2356
|
+
4278255615,
|
|
2357
|
+
4278190080,
|
|
2358
|
+
4294967295,
|
|
2359
|
+
4294901760,
|
|
2360
|
+
4278255360,
|
|
2361
|
+
4278190335,
|
|
2362
|
+
4294967040,
|
|
2363
|
+
4294902015,
|
|
2364
|
+
4278255615,
|
|
2365
|
+
4286578688,
|
|
2366
|
+
4278222848,
|
|
2367
|
+
4278190208,
|
|
2368
|
+
4286611456,
|
|
2369
|
+
4286578816,
|
|
2370
|
+
4278222976,
|
|
2371
|
+
4290822336,
|
|
2372
|
+
4286611584,
|
|
2373
|
+
4288256511,
|
|
2374
|
+
4288230246,
|
|
2375
|
+
4294967244,
|
|
2376
|
+
4291624959,
|
|
2377
|
+
4284874854,
|
|
2378
|
+
4294934656,
|
|
2379
|
+
4278216396,
|
|
2380
|
+
4291611903,
|
|
2381
|
+
4278190208,
|
|
2382
|
+
4294902015,
|
|
2383
|
+
4294967040,
|
|
2384
|
+
4278255615,
|
|
2385
|
+
4286578816,
|
|
2386
|
+
4286578688,
|
|
2387
|
+
4278222976,
|
|
2388
|
+
4278190335,
|
|
2389
|
+
4278242559,
|
|
2390
|
+
4291624959,
|
|
2391
|
+
4291624908,
|
|
2392
|
+
4294967193,
|
|
2393
|
+
4288269567,
|
|
2394
|
+
4294941132,
|
|
2395
|
+
4291598847,
|
|
2396
|
+
4294954137,
|
|
2397
|
+
4281558783,
|
|
2398
|
+
4281584844,
|
|
2399
|
+
4288269312,
|
|
2400
|
+
4294953984,
|
|
2401
|
+
4294940928,
|
|
2402
|
+
4294927872,
|
|
2403
|
+
4284901017,
|
|
2404
|
+
4288059030,
|
|
2405
|
+
4278203238,
|
|
2406
|
+
4281571686,
|
|
2407
|
+
4278203136,
|
|
2408
|
+
4281545472,
|
|
2409
|
+
4288230144,
|
|
2410
|
+
4288230246,
|
|
2411
|
+
4281545625,
|
|
2412
|
+
4281545523
|
|
2413
|
+
]);
|
|
2414
|
+
var XlsxTheme = class _XlsxTheme {
|
|
2415
|
+
/**
|
|
2416
|
+
* The theme of Office 2013–2022, which a workbook with no theme part takes: dark 1 `000000`, light 1 `FFFFFF`,
|
|
2417
|
+
* `44546A`, `E7E6E6`, the accents `4472C4`, `ED7D31`, `A5A5A5`, `FFC000`, `5B9BD5`, `70AD47`, and the hyperlinks
|
|
2418
|
+
* `0563C1` and `954F72`.
|
|
2419
|
+
*/
|
|
2420
|
+
static office = new _XlsxTheme([
|
|
2421
|
+
4278190080,
|
|
2422
|
+
4294967295,
|
|
2423
|
+
4282668138,
|
|
2424
|
+
4293388006,
|
|
2425
|
+
4282675908,
|
|
2426
|
+
4293754161,
|
|
2427
|
+
4289045925,
|
|
2428
|
+
4294950912,
|
|
2429
|
+
4284193749,
|
|
2430
|
+
4285574471,
|
|
2431
|
+
4278543297,
|
|
2432
|
+
4287975282
|
|
2433
|
+
]);
|
|
2434
|
+
/** The scheme's twelve colours as ARGB values, in the scheme's order: dark 1, light 1, dark 2, light 2, accents 1 to 6,
|
|
2435
|
+
* hyperlink, followed hyperlink. */
|
|
2436
|
+
schemeColors;
|
|
2437
|
+
/** A theme of the twelve colours its scheme lists, as ARGB values in the scheme's own order. */
|
|
2438
|
+
constructor(schemeColors) {
|
|
2439
|
+
if (schemeColors.length !== 12) {
|
|
2440
|
+
throw new TypeError("A theme's colour scheme has twelve colours: dark 1, light 1, dark 2, light 2, six accents and two hyperlinks.");
|
|
2441
|
+
}
|
|
2442
|
+
this.schemeColors = Object.freeze(schemeColors.map((c) => c >>> 0));
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* The colour a style's theme slot names. A style numbers the first four slots in another order than the scheme lists
|
|
2446
|
+
* them — 0 is light 1 (Background 1), 1 dark 1 (Text 1), 2 light 2, 3 dark 2 — and the rest as listed.
|
|
2447
|
+
*/
|
|
2448
|
+
slotColor(slot) {
|
|
2449
|
+
switch (slot) {
|
|
2450
|
+
case 0:
|
|
2451
|
+
return elementAt(this.schemeColors, 1);
|
|
2452
|
+
case 1:
|
|
2453
|
+
return elementAt(this.schemeColors, 0);
|
|
2454
|
+
case 2:
|
|
2455
|
+
return elementAt(this.schemeColors, 3);
|
|
2456
|
+
case 3:
|
|
2457
|
+
return elementAt(this.schemeColors, 2);
|
|
2458
|
+
default:
|
|
2459
|
+
if (Number.isInteger(slot) && slot >= 4 && slot < 12) return elementAt(this.schemeColors, slot);
|
|
2460
|
+
throw new RangeError(`A theme has twelve slots, 0 to 11, not ${String(slot)}.`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* The RGB value, as `0xFFRRGGBB`, that `color` paints, or `undefined` for the automatic colour (and the indexed entries
|
|
2465
|
+
* 64 and 65, which name the window's text and background), whose meaning the place it colours decides.
|
|
2466
|
+
* @param indexedColors - The styles part's own indexed palette, or none for the default one.
|
|
2467
|
+
*/
|
|
2468
|
+
resolve(color, indexedColors) {
|
|
2469
|
+
let argb2;
|
|
2470
|
+
switch (color.kind) {
|
|
2471
|
+
case "rgb":
|
|
2472
|
+
argb2 = color.value;
|
|
2473
|
+
break;
|
|
2474
|
+
case "theme":
|
|
2475
|
+
argb2 = color.value < 12 ? this.slotColor(color.value) : void 0;
|
|
2476
|
+
break;
|
|
2477
|
+
case "indexed": {
|
|
2478
|
+
const palette = indexedColors ?? defaultIndexedColors;
|
|
2479
|
+
argb2 = color.value < palette.length ? elementAt(palette, color.value) : void 0;
|
|
2480
|
+
break;
|
|
2481
|
+
}
|
|
2482
|
+
case "automatic":
|
|
2483
|
+
argb2 = void 0;
|
|
2484
|
+
break;
|
|
2485
|
+
default:
|
|
2486
|
+
return unreachableKind(color.kind);
|
|
2487
|
+
}
|
|
2488
|
+
return argb2 !== void 0 && color.tint !== 0 ? applyTint(argb2, color.tint) : argb2;
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
function unreachableKind(kind) {
|
|
2492
|
+
throw new Error(`Internal: ${String(kind)} is not a colour's kind.`);
|
|
2493
|
+
}
|
|
2494
|
+
function applyTint(argb2, tint) {
|
|
2495
|
+
const t = Math.min(Math.max(tint, -1), 1);
|
|
2496
|
+
const r = (argb2 >>> 16 & 255) / 255;
|
|
2497
|
+
const g = (argb2 >>> 8 & 255) / 255;
|
|
2498
|
+
const b = (argb2 & 255) / 255;
|
|
2499
|
+
const max = Math.max(r, Math.max(g, b));
|
|
2500
|
+
const min = Math.min(r, Math.min(g, b));
|
|
2501
|
+
let l = (max + min) / 2;
|
|
2502
|
+
let h = 0;
|
|
2503
|
+
let s = 0;
|
|
2504
|
+
if (max !== min) {
|
|
2505
|
+
const d = max - min;
|
|
2506
|
+
s = l <= 0.5 ? d / (max + min) : d / (2 - max - min);
|
|
2507
|
+
h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
2508
|
+
h /= 6;
|
|
2509
|
+
}
|
|
2510
|
+
l = t < 0 ? l * (1 + t) : l * (1 - t) + t;
|
|
2511
|
+
let r2;
|
|
2512
|
+
let g2;
|
|
2513
|
+
let b2;
|
|
2514
|
+
if (s === 0) {
|
|
2515
|
+
r2 = g2 = b2 = l;
|
|
2516
|
+
} else {
|
|
2517
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
2518
|
+
const p = 2 * l - q;
|
|
2519
|
+
r2 = hue(p, q, h + 1 / 3);
|
|
2520
|
+
g2 = hue(p, q, h);
|
|
2521
|
+
b2 = hue(p, q, h - 1 / 3);
|
|
2522
|
+
}
|
|
2523
|
+
return (argb2 & 4278190080 | channel(r2) << 16 | channel(g2) << 8 | channel(b2)) >>> 0;
|
|
2524
|
+
}
|
|
2525
|
+
function hue(p, q, given) {
|
|
2526
|
+
let t = given;
|
|
2527
|
+
if (t < 0) t += 1;
|
|
2528
|
+
if (t > 1) t -= 1;
|
|
2529
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
2530
|
+
if (t < 0.5) return q;
|
|
2531
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
2532
|
+
return p;
|
|
2533
|
+
}
|
|
2534
|
+
function channel(v) {
|
|
2535
|
+
const x = v * 255;
|
|
2536
|
+
const whole2 = Math.trunc(x);
|
|
2537
|
+
const rounded = Math.abs(x - whole2) >= 0.5 ? whole2 + Math.sign(x) : whole2;
|
|
2538
|
+
return Math.min(Math.max(rounded, 0), 255);
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/reader.ts
|
|
2542
|
+
var sharedStringSlice = 256;
|
|
2543
|
+
var WorkbookReader = class _WorkbookReader {
|
|
2544
|
+
#package;
|
|
2545
|
+
#pacer;
|
|
2546
|
+
#maxCells;
|
|
2547
|
+
#dynamicArrayMetadata;
|
|
2548
|
+
#owner;
|
|
2549
|
+
#cells = 0;
|
|
2550
|
+
#sheetBytesRead = 0;
|
|
2551
|
+
#sharedStrings = XlsxSharedStrings.empty;
|
|
2552
|
+
#sheetOpen = false;
|
|
2553
|
+
/** The workbook part's name, as `xl/workbook.xml`. */
|
|
2554
|
+
workbookPart;
|
|
2555
|
+
/** The workbook's sheets, in its order: the order of its tabs. */
|
|
2556
|
+
sheets;
|
|
2557
|
+
/** The index into `sheets` of the sheet the workbook shows (`activeTab`), 0 when it names none. */
|
|
2558
|
+
activeSheet;
|
|
2559
|
+
/** Whether the workbook counts dates from 1904 (`date1904`), whose serials are 1,462 less than 1900's for the same day. */
|
|
2560
|
+
date1904;
|
|
2561
|
+
/** The workbook's defined names. */
|
|
2562
|
+
definedNames;
|
|
2563
|
+
/** The styles, as far as ADR-0330 §4 carries them; `defaultStyles` when the package has no styles part. */
|
|
2564
|
+
styles;
|
|
2565
|
+
/** The theme's colours; `XlsxTheme.office` when the package has no theme part (`hasTheme`). */
|
|
2566
|
+
theme;
|
|
2567
|
+
/** Whether the package has a theme part of its own. */
|
|
2568
|
+
hasTheme;
|
|
2569
|
+
/** The worksheets' uncompressed bytes, as the zip declares them. */
|
|
2570
|
+
sheetBytes;
|
|
2571
|
+
constructor(pkg, options, read) {
|
|
2572
|
+
this.#package = pkg;
|
|
2573
|
+
this.#pacer = new Pacer(options);
|
|
2574
|
+
this.#maxCells = options.maxCells === void 0 ? defaultMaxCells : options.maxCells;
|
|
2575
|
+
this.workbookPart = read.part;
|
|
2576
|
+
this.sheets = read.sheets;
|
|
2577
|
+
this.activeSheet = read.active;
|
|
2578
|
+
this.date1904 = read.date1904;
|
|
2579
|
+
this.definedNames = read.names;
|
|
2580
|
+
this.styles = read.styles;
|
|
2581
|
+
this.theme = read.theme;
|
|
2582
|
+
this.hasTheme = read.hasTheme;
|
|
2583
|
+
this.#dynamicArrayMetadata = read.dynamicArrayMetadata;
|
|
2584
|
+
this.sheetBytes = read.sheets.reduce((sum, s) => s.part === null ? sum : sum + pkg.lengthOf(s.part), 0);
|
|
2585
|
+
const progress = (sheet) => ({
|
|
2586
|
+
sheet,
|
|
2587
|
+
sheets: this.sheets.length,
|
|
2588
|
+
cells: this.#cells,
|
|
2589
|
+
bytes: this.#sheetBytesRead,
|
|
2590
|
+
totalBytes: this.sheetBytes
|
|
2591
|
+
});
|
|
2592
|
+
this.#owner = {
|
|
2593
|
+
styles: this.styles,
|
|
2594
|
+
sharedStrings: () => this.#sharedStrings,
|
|
2595
|
+
isDynamicArray: (cm) => this.#dynamicArrayMetadata.has(cm),
|
|
2596
|
+
countCell: (sheet, row, column) => {
|
|
2597
|
+
if (++this.#cells > this.#maxCells) {
|
|
2598
|
+
throw new XlsxError(
|
|
2599
|
+
"tooManyCells",
|
|
2600
|
+
`The workbook holds more than the ${grouped(this.#maxCells)} cells a read is allowed (XlsxReadOptions.MaxCells).`,
|
|
2601
|
+
{ part: sheet.part === null ? void 0 : sheet.part, sheet: sheet.name, cell: formatCell(row, column) }
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
},
|
|
2605
|
+
cells: () => this.#cells,
|
|
2606
|
+
rewindCells: (to) => {
|
|
2607
|
+
this.#cells = to;
|
|
2608
|
+
},
|
|
2609
|
+
countBytes: (bytes) => {
|
|
2610
|
+
this.#sheetBytesRead += bytes;
|
|
2611
|
+
},
|
|
2612
|
+
checkpoint: (sheet) => this.#pacer.checkpoint(() => progress(sheet)),
|
|
2613
|
+
report: (sheet) => this.#pacer.report(progress(sheet)),
|
|
2614
|
+
closed: () => {
|
|
2615
|
+
this.#sheetOpen = false;
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
/** Opens the workbook in `file`. */
|
|
2620
|
+
static async open(file, options = {}) {
|
|
2621
|
+
options.signal?.throwIfAborted();
|
|
2622
|
+
const max = options.maxUncompressedBytes === void 0 ? defaultMaxUncompressedBytes : options.maxUncompressedBytes;
|
|
2623
|
+
const source = byteSource(file);
|
|
2624
|
+
const head = await source.read(0, 8);
|
|
2625
|
+
if (isOle(head)) throw await classifyOle(source);
|
|
2626
|
+
if (!isZip(head)) throw new XlsxError("notAZip", head.length === 0 ? "The file is empty." : "The file is not a zip package, so it is not an .xlsx.");
|
|
2627
|
+
const pkg = await Package.open(source, max);
|
|
2628
|
+
const read = await readWorkbookParts(pkg);
|
|
2629
|
+
const reader = new _WorkbookReader(pkg, options, read);
|
|
2630
|
+
if (read.sharedStringsPart !== void 0) await reader.#readSharedStrings(read.sharedStringsPart);
|
|
2631
|
+
return reader;
|
|
2632
|
+
}
|
|
2633
|
+
/** The shared strings, held whole. */
|
|
2634
|
+
get sharedStrings() {
|
|
2635
|
+
return this.#sharedStrings;
|
|
2636
|
+
}
|
|
2637
|
+
/** Every part the package holds, by name, as `xl/comments1.xml`: what else it holds than what is read. */
|
|
2638
|
+
get parts() {
|
|
2639
|
+
return this.#package.partNames;
|
|
2640
|
+
}
|
|
2641
|
+
/** The cells read so far, every worksheet counted. */
|
|
2642
|
+
get cellsRead() {
|
|
2643
|
+
return this.#cells;
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Opens the worksheet at `index` for reading its cells as a stream. One worksheet is read at a time: close the reader
|
|
2647
|
+
* before opening the next.
|
|
2648
|
+
*/
|
|
2649
|
+
async openSheet(index) {
|
|
2650
|
+
if (!Number.isInteger(index) || index < 0 || index >= this.sheets.length) {
|
|
2651
|
+
throw new RangeError(`The workbook has ${String(this.sheets.length)} sheets; ${String(index)} is not the index of one.`);
|
|
2652
|
+
}
|
|
2653
|
+
const sheet = elementAt(this.sheets, index);
|
|
2654
|
+
if (sheet.part === null) throw new Error(`The sheet '${sheet.name}' is not a worksheet and holds no cells.`);
|
|
2655
|
+
if (this.#sheetOpen) throw new Error("Another worksheet is being read: close its reader first.");
|
|
2656
|
+
const opened = await this.#package.openXml(sheet.part, sheet.name);
|
|
2657
|
+
if (opened === void 0) {
|
|
2658
|
+
throw new XlsxError("malformed", "The workbook names a worksheet part the package does not hold.", { part: sheet.part, sheet: sheet.name });
|
|
2659
|
+
}
|
|
2660
|
+
this.#sheetOpen = true;
|
|
2661
|
+
return new SheetReader(this.#owner, index, sheet, sheet.part, opened);
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* Reads the worksheet at `index` whole: its columns, its cells and what else it holds. For a sheet that is small, or a
|
|
2665
|
+
* test; a large one is read a batch at a time through `openSheet`.
|
|
2666
|
+
*/
|
|
2667
|
+
async readSheet(index) {
|
|
2668
|
+
const reader = await this.openSheet(index);
|
|
2669
|
+
try {
|
|
2670
|
+
const cells = [];
|
|
2671
|
+
const rows = [];
|
|
2672
|
+
while (await reader.read()) {
|
|
2673
|
+
cells.push(...reader.cells);
|
|
2674
|
+
rows.push(...reader.rows);
|
|
2675
|
+
}
|
|
2676
|
+
return Object.freeze({
|
|
2677
|
+
sheet: reader.sheet,
|
|
2678
|
+
columns: reader.columns,
|
|
2679
|
+
cells: Object.freeze(cells),
|
|
2680
|
+
otherElements: reader.otherElements,
|
|
2681
|
+
rows: Object.freeze(rows),
|
|
2682
|
+
customRowHeights: reader.customRowHeights,
|
|
2683
|
+
hiddenRows: reader.hiddenRows,
|
|
2684
|
+
frozenPanes: reader.frozenPanes
|
|
2685
|
+
});
|
|
2686
|
+
} finally {
|
|
2687
|
+
await reader.close();
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Reads the shared strings, the one part held whole, in slices as a worksheet is read (ADR-0330 §9): cancellation is
|
|
2692
|
+
* looked at, progress reported and the read yields between them. Its progress names no worksheet (`sheet` is -1) and
|
|
2693
|
+
* counts the part's bytes.
|
|
2694
|
+
*/
|
|
2695
|
+
async #readSharedStrings(part) {
|
|
2696
|
+
const opened = await this.#package.openXml(part);
|
|
2697
|
+
if (opened === void 0) return;
|
|
2698
|
+
const { xml, bytes } = opened;
|
|
2699
|
+
const total = this.#package.lengthOf(part);
|
|
2700
|
+
const progress = () => ({ sheet: -1, sheets: this.sheets.length, cells: 0, bytes: bytes.bytesRead, totalBytes: total });
|
|
2701
|
+
const texts = [];
|
|
2702
|
+
const rich = [];
|
|
2703
|
+
let started = false;
|
|
2704
|
+
const step = () => {
|
|
2705
|
+
if (!started) {
|
|
2706
|
+
xml.moveToContent();
|
|
2707
|
+
started = true;
|
|
2708
|
+
}
|
|
2709
|
+
let slice = 0;
|
|
2710
|
+
for (; ; ) {
|
|
2711
|
+
if (low(xml)) return "more";
|
|
2712
|
+
if (!xml.read()) return "done";
|
|
2713
|
+
if (xml.nodeType() === "element" && xml.localName() === "si") {
|
|
2714
|
+
const [text, isRich] = readText(xml);
|
|
2715
|
+
if (isRich) rich.push(texts.length);
|
|
2716
|
+
texts.push(text);
|
|
2717
|
+
if (++slice === sharedStringSlice) return "pause";
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2721
|
+
const save = () => {
|
|
2722
|
+
const count2 = texts.length;
|
|
2723
|
+
const richCount = rich.length;
|
|
2724
|
+
const wasStarted = started;
|
|
2725
|
+
return () => {
|
|
2726
|
+
texts.length = count2;
|
|
2727
|
+
rich.length = richCount;
|
|
2728
|
+
started = wasStarted;
|
|
2729
|
+
};
|
|
2730
|
+
};
|
|
2731
|
+
try {
|
|
2732
|
+
while (await runSteps(xml, save, step) === "pause") await this.#pacer.checkpoint(progress);
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
2735
|
+
throw error;
|
|
2736
|
+
} finally {
|
|
2737
|
+
await bytes.close();
|
|
2738
|
+
}
|
|
2739
|
+
this.#pacer.report(progress());
|
|
2740
|
+
this.#sharedStrings = new XlsxSharedStrings(texts, new Set(rich));
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
function openXlsx(file, options) {
|
|
2744
|
+
return WorkbookReader.open(file, options);
|
|
2745
|
+
}
|
|
2746
|
+
async function readWorkbookParts(pkg) {
|
|
2747
|
+
const part = await workbookPartOf(pkg);
|
|
2748
|
+
const rels = await relationships(pkg, part);
|
|
2749
|
+
const { sheets, active, date1904, names } = await readWorkbook(pkg, part, rels);
|
|
2750
|
+
const target = (type) => {
|
|
2751
|
+
for (const r of rels.values()) if (r.type === type) return r.target;
|
|
2752
|
+
return void 0;
|
|
2753
|
+
};
|
|
2754
|
+
const stylesPart = target(stylesType);
|
|
2755
|
+
const themePart = target(themeType);
|
|
2756
|
+
const metadataPart = target(sheetMetadataType);
|
|
2757
|
+
return {
|
|
2758
|
+
part,
|
|
2759
|
+
sheets,
|
|
2760
|
+
active,
|
|
2761
|
+
date1904,
|
|
2762
|
+
names,
|
|
2763
|
+
styles: stylesPart === void 0 ? defaultStyles : await readStyles(pkg, stylesPart),
|
|
2764
|
+
theme: themePart === void 0 ? XlsxTheme.office : await readTheme(pkg, themePart),
|
|
2765
|
+
hasTheme: themePart !== void 0,
|
|
2766
|
+
dynamicArrayMetadata: metadataPart === void 0 ? /* @__PURE__ */ new Set() : await readDynamicArrayMetadata(pkg, metadataPart),
|
|
2767
|
+
sharedStringsPart: target(sharedStringsType)
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
function strict() {
|
|
2771
|
+
return new XlsxError("strictOpenXml", "The workbook is saved as Strict Open XML, which is not read. Save it from Excel as an Excel Workbook (.xlsx).");
|
|
2772
|
+
}
|
|
2773
|
+
function extensionOf(name) {
|
|
2774
|
+
const dot = name.lastIndexOf(".");
|
|
2775
|
+
return dot < 0 || dot < name.lastIndexOf("/") ? "" : name.slice(dot + 1);
|
|
2776
|
+
}
|
|
2777
|
+
async function workbookPartOf(pkg) {
|
|
2778
|
+
const { defaults, overrides } = await readContentTypes(pkg);
|
|
2779
|
+
const root = await relationships(pkg, "");
|
|
2780
|
+
let office;
|
|
2781
|
+
for (const r of root.values()) {
|
|
2782
|
+
if (r.type.endsWith("/officeDocument")) {
|
|
2783
|
+
office = r;
|
|
2784
|
+
break;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
if (office === void 0) {
|
|
2788
|
+
if ([...root.values()].some((r) => r.type.startsWith(strictPrefix))) throw strict();
|
|
2789
|
+
throw new XlsxError("notAWorkbook", "The zip package holds no document, so it is not an .xlsx.");
|
|
2790
|
+
}
|
|
2791
|
+
if (office.type.startsWith(strictPrefix)) throw strict();
|
|
2792
|
+
const part = office.target;
|
|
2793
|
+
const contentType = overrides.get(foldCase(part)) ?? defaults.get(foldCase(extensionOf(part))) ?? "";
|
|
2794
|
+
if (contentType === binaryContent || foldCase(part).endsWith(".bin")) {
|
|
2795
|
+
throw new XlsxError("binary", "The file is an Excel Binary Workbook (.xlsb), not an .xlsx. Save it from Excel as an Excel Workbook (.xlsx).", { part });
|
|
2796
|
+
}
|
|
2797
|
+
if (foldCase(contentType).includes(foldCase("macroEnabled")) || [...overrides.values(), ...defaults.values()].includes(vbaProjectContent)) {
|
|
2798
|
+
throw new XlsxError(
|
|
2799
|
+
"macroEnabled",
|
|
2800
|
+
"The file is a workbook with macros (.xlsm), which is not read. Save it from Excel as an Excel Workbook (.xlsx), which leaves the macros out.",
|
|
2801
|
+
{ part }
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2804
|
+
if (contentType !== workbookContent && contentType !== templateContent) {
|
|
2805
|
+
throw new XlsxError("notAWorkbook", contentType === "" ? "The package's document has no content type, so it is not an .xlsx." : `The package's document is '${contentType}', not a workbook: the file is not an .xlsx.`, { part });
|
|
2806
|
+
}
|
|
2807
|
+
if (!pkg.has(part)) throw new XlsxError("malformed", "The package names a workbook part it does not hold.", { part });
|
|
2808
|
+
return part;
|
|
2809
|
+
}
|
|
2810
|
+
async function readContentTypes(pkg) {
|
|
2811
|
+
const part = "[Content_Types].xml";
|
|
2812
|
+
const defaults = /* @__PURE__ */ new Map();
|
|
2813
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
2814
|
+
const opened = await pkg.readXml(part);
|
|
2815
|
+
if (opened === void 0) throw new XlsxError("notAWorkbook", "The zip package has no [Content_Types].xml, so it is not an Office document.");
|
|
2816
|
+
const xml = opened.xml;
|
|
2817
|
+
try {
|
|
2818
|
+
while (xml.read()) {
|
|
2819
|
+
if (xml.nodeType() !== "element") continue;
|
|
2820
|
+
if (xml.localName() === "Default") {
|
|
2821
|
+
const ext = xml.getAttribute("Extension");
|
|
2822
|
+
const type = xml.getAttribute("ContentType");
|
|
2823
|
+
if (ext !== void 0 && type !== void 0) defaults.set(foldCase(ext), type);
|
|
2824
|
+
} else if (xml.localName() === "Override") {
|
|
2825
|
+
const name = xml.getAttribute("PartName");
|
|
2826
|
+
const type = xml.getAttribute("ContentType");
|
|
2827
|
+
if (name !== void 0 && type !== void 0) overrides.set(foldCase(name.replace(/^\/+/, "")), type);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
} catch (error) {
|
|
2831
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
2832
|
+
throw error;
|
|
2833
|
+
}
|
|
2834
|
+
return { defaults, overrides };
|
|
2835
|
+
}
|
|
2836
|
+
async function relationships(pkg, source) {
|
|
2837
|
+
const slash = source.lastIndexOf("/");
|
|
2838
|
+
const dir = slash < 0 ? "" : source.slice(0, slash);
|
|
2839
|
+
const part = `${dir === "" ? "" : `${dir}/`}_rels/${source.slice(slash + 1)}.rels`;
|
|
2840
|
+
const found = /* @__PURE__ */ new Map();
|
|
2841
|
+
const opened = await pkg.readXml(part);
|
|
2842
|
+
if (opened === void 0) return found;
|
|
2843
|
+
const xml = opened.xml;
|
|
2844
|
+
try {
|
|
2845
|
+
while (xml.read()) {
|
|
2846
|
+
if (xml.nodeType() !== "element" || xml.localName() !== "Relationship") continue;
|
|
2847
|
+
const id = xml.getAttribute("Id");
|
|
2848
|
+
const type = xml.getAttribute("Type");
|
|
2849
|
+
const target = xml.getAttribute("Target");
|
|
2850
|
+
if (id === void 0 || type === void 0 || target === void 0 || xml.getAttribute("TargetMode") === "External") continue;
|
|
2851
|
+
found.set(id, { type, target: resolve(dir, target) });
|
|
2852
|
+
}
|
|
2853
|
+
} catch (error) {
|
|
2854
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
2855
|
+
throw error;
|
|
2856
|
+
}
|
|
2857
|
+
return found;
|
|
2858
|
+
}
|
|
2859
|
+
function unescapeData(text) {
|
|
2860
|
+
return text.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {
|
|
2861
|
+
const bytes = Uint8Array.from(run.match(/%[0-9A-Fa-f]{2}/g) ?? [], (h) => Number.parseInt(h.slice(1), 16));
|
|
2862
|
+
let out = "";
|
|
2863
|
+
let at = 0;
|
|
2864
|
+
while (at < bytes.length) {
|
|
2865
|
+
const length = utf8Length(bytes, at);
|
|
2866
|
+
if (length === 0) {
|
|
2867
|
+
out += run.slice(at * 3, at * 3 + 3);
|
|
2868
|
+
at++;
|
|
2869
|
+
} else {
|
|
2870
|
+
out += new TextDecoder().decode(bytes.subarray(at, at + length));
|
|
2871
|
+
at += length;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
return out;
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
function utf8Length(bytes, at) {
|
|
2878
|
+
const b = elementAt(bytes, at);
|
|
2879
|
+
const length = b < 128 ? 1 : b >= 194 && b < 224 ? 2 : b >= 224 && b < 240 ? 3 : b >= 240 && b < 245 ? 4 : 0;
|
|
2880
|
+
if (length === 0 || at + length > bytes.length) return 0;
|
|
2881
|
+
for (let i = 1; i < length; i++) {
|
|
2882
|
+
if ((elementAt(bytes, at + i) & 192) !== 128) return 0;
|
|
2883
|
+
}
|
|
2884
|
+
const second = length > 1 ? elementAt(bytes, at + 1) : 0;
|
|
2885
|
+
if (b === 224 && second < 160 || b === 237 && second >= 160 || b === 240 && second < 144 || b === 244 && second >= 144) return 0;
|
|
2886
|
+
return length;
|
|
2887
|
+
}
|
|
2888
|
+
function resolve(dir, given) {
|
|
2889
|
+
const target = unescapeData(given.replaceAll("\\", "/"));
|
|
2890
|
+
const path = target.startsWith("/") ? target.replace(/^\/+/, "") : dir === "" ? target : `${dir}/${target}`;
|
|
2891
|
+
const parts = [];
|
|
2892
|
+
for (const segment of path.split("/")) {
|
|
2893
|
+
if (segment === "" || segment === ".") continue;
|
|
2894
|
+
if (segment === "..") {
|
|
2895
|
+
parts.pop();
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
parts.push(segment);
|
|
2899
|
+
}
|
|
2900
|
+
return parts.join("/");
|
|
2901
|
+
}
|
|
2902
|
+
async function readWorkbook(pkg, part, rels) {
|
|
2903
|
+
const sheets = [];
|
|
2904
|
+
const names = [];
|
|
2905
|
+
let active = 0;
|
|
2906
|
+
let date1904 = false;
|
|
2907
|
+
const opened = await pkg.readXml(part);
|
|
2908
|
+
if (opened === void 0) throw new Error(`Internal: the package was checked to hold '${part}'.`);
|
|
2909
|
+
const xml = opened.xml;
|
|
2910
|
+
try {
|
|
2911
|
+
xml.moveToContent();
|
|
2912
|
+
if (xml.namespaceUri().startsWith(strictPrefix)) throw strict();
|
|
2913
|
+
if (xml.localName() !== "workbook") throw new XlsxError("malformed", `The workbook part's root is <${xml.localName()}>, not <workbook>.`, { part });
|
|
2914
|
+
let more = xml.read();
|
|
2915
|
+
while (more) {
|
|
2916
|
+
if (xml.nodeType() !== "element") {
|
|
2917
|
+
more = xml.read();
|
|
2918
|
+
continue;
|
|
2919
|
+
}
|
|
2920
|
+
let advanced = false;
|
|
2921
|
+
switch (xml.localName()) {
|
|
2922
|
+
case "workbookPr":
|
|
2923
|
+
date1904 = bool(xml.getAttribute("date1904"));
|
|
2924
|
+
break;
|
|
2925
|
+
case "workbookView": {
|
|
2926
|
+
const tab = xml.getAttribute("activeTab");
|
|
2927
|
+
if (tab !== void 0) active = int(tab, { part });
|
|
2928
|
+
break;
|
|
2929
|
+
}
|
|
2930
|
+
case "sheet": {
|
|
2931
|
+
const name = xml.getAttribute("name");
|
|
2932
|
+
if (name === void 0) throw new XlsxError("malformed", "A sheet has no name.", { part });
|
|
2933
|
+
const id = xml.getAttributeNs("id", relationshipsNamespace);
|
|
2934
|
+
const state = xml.getAttribute("state");
|
|
2935
|
+
const visibility = state === "hidden" ? "hidden" : state === "veryHidden" ? "veryHidden" : "visible";
|
|
2936
|
+
const sid = xml.getAttribute("sheetId");
|
|
2937
|
+
const sheetId = sid === void 0 ? sheets.length + 1 : int(sid, { part });
|
|
2938
|
+
const rel = id === void 0 ? void 0 : rels.get(id);
|
|
2939
|
+
sheets.push(Object.freeze(rel !== void 0 && rel.type === worksheetType ? { name, sheetId, visibility, part: rel.target, kind: "worksheet" } : { name, sheetId, visibility, part: null, kind: "other" }));
|
|
2940
|
+
break;
|
|
2941
|
+
}
|
|
2942
|
+
case "definedName": {
|
|
2943
|
+
const given = xml.getAttribute("name");
|
|
2944
|
+
const name = given === void 0 ? "" : given;
|
|
2945
|
+
const l = xml.getAttribute("localSheetId");
|
|
2946
|
+
const localSheet = l === void 0 ? null : int(l, { part });
|
|
2947
|
+
const hidden = bool(xml.getAttribute("hidden"));
|
|
2948
|
+
names.push(Object.freeze({ name, text: decodeXString(xml.readElementContentAsString()), localSheet, hidden }));
|
|
2949
|
+
advanced = true;
|
|
2950
|
+
break;
|
|
2951
|
+
}
|
|
2952
|
+
default:
|
|
2953
|
+
break;
|
|
2954
|
+
}
|
|
2955
|
+
more = advanced ? !xml.eof() : xml.read();
|
|
2956
|
+
}
|
|
2957
|
+
} catch (error) {
|
|
2958
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
2959
|
+
throw error;
|
|
2960
|
+
}
|
|
2961
|
+
if (sheets.length === 0) throw new XlsxError("malformed", "The workbook lists no sheets.", { part });
|
|
2962
|
+
if (active < 0 || active >= sheets.length) active = 0;
|
|
2963
|
+
return { sheets: Object.freeze(sheets), active, date1904, names: Object.freeze(names) };
|
|
2964
|
+
}
|
|
2965
|
+
var schemeOrder = ["dk1", "lt1", "dk2", "lt2", "accent1", "accent2", "accent3", "accent4", "accent5", "accent6", "hlink", "folHlink"];
|
|
2966
|
+
async function readTheme(pkg, part) {
|
|
2967
|
+
const colors = [...XlsxTheme.office.schemeColors];
|
|
2968
|
+
const opened = await pkg.readXml(part);
|
|
2969
|
+
if (opened === void 0) return XlsxTheme.office;
|
|
2970
|
+
const xml = opened.xml;
|
|
2971
|
+
try {
|
|
2972
|
+
if (!xml.readToFollowing("clrScheme", drawingNamespace)) return XlsxTheme.office;
|
|
2973
|
+
const depth = xml.depth();
|
|
2974
|
+
let slot = -1;
|
|
2975
|
+
while (xml.read() && xml.depth() > depth) {
|
|
2976
|
+
if (xml.nodeType() !== "element") continue;
|
|
2977
|
+
if (xml.depth() === depth + 1) {
|
|
2978
|
+
slot = schemeOrder.indexOf(xml.localName());
|
|
2979
|
+
continue;
|
|
2980
|
+
}
|
|
2981
|
+
if (slot < 0) continue;
|
|
2982
|
+
const value = xml.localName() === "srgbClr" ? xml.getAttribute("val") : xml.localName() === "sysClr" ? xml.getAttribute("lastClr") : void 0;
|
|
2983
|
+
const rgb = value === void 0 ? void 0 : parseHex32(value);
|
|
2984
|
+
if (rgb !== void 0) colors[slot] = (4278190080 | rgb & 16777215) >>> 0;
|
|
2985
|
+
}
|
|
2986
|
+
} catch (error) {
|
|
2987
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
2988
|
+
throw error;
|
|
2989
|
+
}
|
|
2990
|
+
return new XlsxTheme(colors);
|
|
2991
|
+
}
|
|
2992
|
+
async function readDynamicArrayMetadata(pkg, part) {
|
|
2993
|
+
const dynamic = /* @__PURE__ */ new Set();
|
|
2994
|
+
const opened = await pkg.readXml(part);
|
|
2995
|
+
if (opened === void 0) return dynamic;
|
|
2996
|
+
const xml = opened.xml;
|
|
2997
|
+
const types = [];
|
|
2998
|
+
const futures = /* @__PURE__ */ new Map();
|
|
2999
|
+
const cellBlocks = [];
|
|
3000
|
+
try {
|
|
3001
|
+
let future = null;
|
|
3002
|
+
let inCellMetadata = false;
|
|
3003
|
+
let pendingRecord = false;
|
|
3004
|
+
while (xml.read()) {
|
|
3005
|
+
if (xml.nodeType() === "endElement") {
|
|
3006
|
+
if (xml.localName() === "futureMetadata") future = null;
|
|
3007
|
+
else if (xml.localName() === "cellMetadata") inCellMetadata = false;
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
if (xml.nodeType() !== "element") continue;
|
|
3011
|
+
const name = xml.localName();
|
|
3012
|
+
if (name === "metadataType") {
|
|
3013
|
+
const typeName = xml.getAttribute("name");
|
|
3014
|
+
types.push(typeName === void 0 ? "" : typeName);
|
|
3015
|
+
} else if (name === "futureMetadata") {
|
|
3016
|
+
const futureName = xml.getAttribute("name");
|
|
3017
|
+
const blocks2 = [];
|
|
3018
|
+
futures.set(futureName === void 0 ? "" : futureName, blocks2);
|
|
3019
|
+
future = xml.isEmptyElement() ? null : blocks2;
|
|
3020
|
+
} else if (name === "bk" && future !== null) {
|
|
3021
|
+
future.push(false);
|
|
3022
|
+
} else if (name === "dynamicArrayProperties" && future !== null && future.length > 0) {
|
|
3023
|
+
future[future.length - 1] = bool(xml.getAttribute("fDynamic"));
|
|
3024
|
+
} else if (name === "cellMetadata") {
|
|
3025
|
+
inCellMetadata = !xml.isEmptyElement();
|
|
3026
|
+
} else if (name === "bk" && inCellMetadata) {
|
|
3027
|
+
pendingRecord = true;
|
|
3028
|
+
cellBlocks.push([-1, -1]);
|
|
3029
|
+
} else if (name === "rc" && inCellMetadata && pendingRecord) {
|
|
3030
|
+
pendingRecord = false;
|
|
3031
|
+
const t = xml.getAttribute("t");
|
|
3032
|
+
const v = xml.getAttribute("v");
|
|
3033
|
+
cellBlocks[cellBlocks.length - 1] = [int(t === void 0 ? "0" : t, { part }), int(v === void 0 ? "0" : v, { part })];
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
} catch (error) {
|
|
3037
|
+
if (error instanceof XmlError) throw Package.refuse(error, opened, part);
|
|
3038
|
+
throw error;
|
|
3039
|
+
}
|
|
3040
|
+
const blocks = futures.get("XLDAPR");
|
|
3041
|
+
cellBlocks.forEach(([type, value], i) => {
|
|
3042
|
+
if (type < 1 || type > types.length || types[type - 1] !== "XLDAPR") return;
|
|
3043
|
+
if (blocks !== void 0 && value >= 0 && value < blocks.length && blocks[value] === true) dynamic.add(i + 1);
|
|
3044
|
+
});
|
|
3045
|
+
return dynamic;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// src/styles-writer.ts
|
|
3049
|
+
function writeStyles(w, styles) {
|
|
3050
|
+
w.start("styleSheet", mainNamespace);
|
|
3051
|
+
if (styles.numberFormats.size > 0) {
|
|
3052
|
+
w.start("numFmts");
|
|
3053
|
+
count(w, styles.numberFormats.size);
|
|
3054
|
+
for (const [id, code] of [...styles.numberFormats].toSorted(([a], [b]) => a - b)) {
|
|
3055
|
+
w.start("numFmt");
|
|
3056
|
+
w.attribute("numFmtId", String(id));
|
|
3057
|
+
w.attribute("formatCode", encodeXString(code));
|
|
3058
|
+
w.end();
|
|
3059
|
+
}
|
|
3060
|
+
w.end();
|
|
3061
|
+
}
|
|
3062
|
+
w.start("fonts");
|
|
3063
|
+
count(w, styles.fonts.length);
|
|
3064
|
+
for (const font of styles.fonts) writeFont(w, font);
|
|
3065
|
+
w.end();
|
|
3066
|
+
w.start("fills");
|
|
3067
|
+
count(w, styles.fills.length);
|
|
3068
|
+
for (const fill of styles.fills) writeFill(w, fill);
|
|
3069
|
+
w.end();
|
|
3070
|
+
w.start("borders");
|
|
3071
|
+
count(w, styles.borders.length);
|
|
3072
|
+
for (const border of styles.borders) writeBorder(w, border);
|
|
3073
|
+
w.end();
|
|
3074
|
+
w.start("cellStyleXfs");
|
|
3075
|
+
count(w, 1);
|
|
3076
|
+
w.start("xf");
|
|
3077
|
+
w.attribute("numFmtId", "0");
|
|
3078
|
+
w.attribute("fontId", "0");
|
|
3079
|
+
w.attribute("fillId", "0");
|
|
3080
|
+
w.attribute("borderId", "0");
|
|
3081
|
+
w.end();
|
|
3082
|
+
w.end();
|
|
3083
|
+
w.start("cellXfs");
|
|
3084
|
+
count(w, styles.cellFormats.length);
|
|
3085
|
+
for (const format of styles.cellFormats) writeCellFormat(w, format);
|
|
3086
|
+
w.end();
|
|
3087
|
+
w.start("cellStyles");
|
|
3088
|
+
count(w, 1);
|
|
3089
|
+
w.start("cellStyle");
|
|
3090
|
+
w.attribute("name", "Normal");
|
|
3091
|
+
w.attribute("xfId", "0");
|
|
3092
|
+
w.attribute("builtinId", "0");
|
|
3093
|
+
w.end();
|
|
3094
|
+
w.end();
|
|
3095
|
+
const indexed = styles.indexedColors;
|
|
3096
|
+
if (indexed !== null && indexed.length > 0) {
|
|
3097
|
+
w.start("colors");
|
|
3098
|
+
w.start("indexedColors");
|
|
3099
|
+
for (const argb2 of indexed) {
|
|
3100
|
+
w.start("rgbColor");
|
|
3101
|
+
w.attribute("rgb", hex8(argb2));
|
|
3102
|
+
w.end();
|
|
3103
|
+
}
|
|
3104
|
+
w.end();
|
|
3105
|
+
w.end();
|
|
3106
|
+
}
|
|
3107
|
+
w.end();
|
|
3108
|
+
}
|
|
3109
|
+
function count(w, n) {
|
|
3110
|
+
w.attribute("count", String(n));
|
|
3111
|
+
}
|
|
3112
|
+
function writeFont(w, font) {
|
|
3113
|
+
w.start("font");
|
|
3114
|
+
if (font.bold) w.element("b", null);
|
|
3115
|
+
if (font.italic) w.element("i", null);
|
|
3116
|
+
if (font.strike) w.element("strike", null);
|
|
3117
|
+
if (font.underline !== "none") {
|
|
3118
|
+
w.start("u");
|
|
3119
|
+
if (font.underline !== "single") w.attribute("val", font.underline);
|
|
3120
|
+
w.end();
|
|
3121
|
+
}
|
|
3122
|
+
if (font.size !== null) {
|
|
3123
|
+
w.start("sz");
|
|
3124
|
+
w.attribute("val", roundTrip(font.size));
|
|
3125
|
+
w.end();
|
|
3126
|
+
}
|
|
3127
|
+
if (font.color !== null) writeColor(w, "color", font.color);
|
|
3128
|
+
if (font.name !== null) {
|
|
3129
|
+
w.start("name");
|
|
3130
|
+
w.attribute("val", font.name);
|
|
3131
|
+
w.end();
|
|
3132
|
+
}
|
|
3133
|
+
w.end();
|
|
3134
|
+
}
|
|
3135
|
+
function writeFill(w, fill) {
|
|
3136
|
+
w.start("fill");
|
|
3137
|
+
if (fill.isGradient) {
|
|
3138
|
+
w.start("gradientFill");
|
|
3139
|
+
w.end();
|
|
3140
|
+
} else {
|
|
3141
|
+
w.start("patternFill");
|
|
3142
|
+
w.attribute("patternType", fill.pattern === null ? "none" : fill.pattern);
|
|
3143
|
+
if (fill.foreground !== null) writeColor(w, "fgColor", fill.foreground);
|
|
3144
|
+
if (fill.background !== null) writeColor(w, "bgColor", fill.background);
|
|
3145
|
+
w.end();
|
|
3146
|
+
}
|
|
3147
|
+
w.end();
|
|
3148
|
+
}
|
|
3149
|
+
function writeBorder(w, border) {
|
|
3150
|
+
w.start("border");
|
|
3151
|
+
if (border.diagonalUp) w.attribute("diagonalUp", "1");
|
|
3152
|
+
if (border.diagonalDown) w.attribute("diagonalDown", "1");
|
|
3153
|
+
writeEdge(w, "left", border.left);
|
|
3154
|
+
writeEdge(w, "right", border.right);
|
|
3155
|
+
writeEdge(w, "top", border.top);
|
|
3156
|
+
writeEdge(w, "bottom", border.bottom);
|
|
3157
|
+
writeEdge(w, "diagonal", border.diagonal);
|
|
3158
|
+
w.end();
|
|
3159
|
+
}
|
|
3160
|
+
function writeEdge(w, name, edge) {
|
|
3161
|
+
w.start(name);
|
|
3162
|
+
if (edge.style !== "none") w.attribute("style", edge.style);
|
|
3163
|
+
if (edge.color !== null) writeColor(w, "color", edge.color);
|
|
3164
|
+
w.end();
|
|
3165
|
+
}
|
|
3166
|
+
function writeCellFormat(w, format) {
|
|
3167
|
+
w.start("xf");
|
|
3168
|
+
w.attribute("numFmtId", String(format.numberFormatId));
|
|
3169
|
+
w.attribute("fontId", String(format.fontId));
|
|
3170
|
+
w.attribute("fillId", String(format.fillId));
|
|
3171
|
+
w.attribute("borderId", String(format.borderId));
|
|
3172
|
+
w.attribute("xfId", "0");
|
|
3173
|
+
if (format.numberFormatId !== 0) w.attribute("applyNumberFormat", "1");
|
|
3174
|
+
if (format.fontId !== 0) w.attribute("applyFont", "1");
|
|
3175
|
+
if (format.fillId !== 0) w.attribute("applyFill", "1");
|
|
3176
|
+
if (format.borderId !== 0) w.attribute("applyBorder", "1");
|
|
3177
|
+
const a = format.alignment;
|
|
3178
|
+
if (a !== null) {
|
|
3179
|
+
w.attribute("applyAlignment", "1");
|
|
3180
|
+
w.start("alignment");
|
|
3181
|
+
if (a.horizontal !== "general") w.attribute("horizontal", a.horizontal);
|
|
3182
|
+
if (a.vertical !== "bottom") w.attribute("vertical", a.vertical);
|
|
3183
|
+
if (a.textRotation !== 0) w.attribute("textRotation", String(a.textRotation));
|
|
3184
|
+
if (a.wrapText) w.attribute("wrapText", "1");
|
|
3185
|
+
if (a.indent !== 0) w.attribute("indent", String(a.indent));
|
|
3186
|
+
if (a.shrinkToFit) w.attribute("shrinkToFit", "1");
|
|
3187
|
+
w.end();
|
|
3188
|
+
}
|
|
3189
|
+
w.end();
|
|
3190
|
+
}
|
|
3191
|
+
function writeColor(w, name, color) {
|
|
3192
|
+
w.start(name);
|
|
3193
|
+
switch (color.kind) {
|
|
3194
|
+
case "automatic":
|
|
3195
|
+
w.attribute("auto", "1");
|
|
3196
|
+
break;
|
|
3197
|
+
case "rgb":
|
|
3198
|
+
w.attribute("rgb", hex8(color.value));
|
|
3199
|
+
break;
|
|
3200
|
+
case "theme":
|
|
3201
|
+
w.attribute("theme", String(color.value));
|
|
3202
|
+
break;
|
|
3203
|
+
case "indexed":
|
|
3204
|
+
w.attribute("indexed", String(color.value));
|
|
3205
|
+
break;
|
|
3206
|
+
default:
|
|
3207
|
+
unreachable(color.kind);
|
|
3208
|
+
}
|
|
3209
|
+
if (color.tint !== 0) w.attribute("tint", roundTrip(color.tint));
|
|
3210
|
+
w.end();
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// src/xml-writer.ts
|
|
3214
|
+
var declaration = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
|
|
3215
|
+
function escapeText(text) {
|
|
3216
|
+
return text.replace(/[&<>]|\r\n?/g, (c) => {
|
|
3217
|
+
switch (c) {
|
|
3218
|
+
case "&":
|
|
3219
|
+
return "&";
|
|
3220
|
+
case "<":
|
|
3221
|
+
return "<";
|
|
3222
|
+
case ">":
|
|
3223
|
+
return ">";
|
|
3224
|
+
default:
|
|
3225
|
+
return "\n";
|
|
3226
|
+
}
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
function escapeAttribute(text) {
|
|
3230
|
+
return text.replace(/[&<>"\t\n\r]/g, (c) => {
|
|
3231
|
+
switch (c) {
|
|
3232
|
+
case "&":
|
|
3233
|
+
return "&";
|
|
3234
|
+
case "<":
|
|
3235
|
+
return "<";
|
|
3236
|
+
case ">":
|
|
3237
|
+
return ">";
|
|
3238
|
+
case '"':
|
|
3239
|
+
return """;
|
|
3240
|
+
case " ":
|
|
3241
|
+
return "	";
|
|
3242
|
+
case "\n":
|
|
3243
|
+
return "
";
|
|
3244
|
+
default:
|
|
3245
|
+
return "
";
|
|
3246
|
+
}
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
var XmlTextWriter = class {
|
|
3250
|
+
#text = "";
|
|
3251
|
+
#names = [];
|
|
3252
|
+
/** Whether the last element's start tag is not closed yet: its attributes may still come. */
|
|
3253
|
+
#open = false;
|
|
3254
|
+
/** The default namespace the open start tag declares after its attributes, or `undefined`. */
|
|
3255
|
+
#namespace;
|
|
3256
|
+
/** The text written and not taken. */
|
|
3257
|
+
get length() {
|
|
3258
|
+
return this.#text.length;
|
|
3259
|
+
}
|
|
3260
|
+
/** Takes the text written so far. */
|
|
3261
|
+
take() {
|
|
3262
|
+
const text = this.#text;
|
|
3263
|
+
this.#text = "";
|
|
3264
|
+
return text;
|
|
3265
|
+
}
|
|
3266
|
+
/** `WriteStartDocument(standalone: true)`. */
|
|
3267
|
+
startDocument() {
|
|
3268
|
+
this.#text += declaration;
|
|
3269
|
+
}
|
|
3270
|
+
/** `WriteStartElement`: the element `name`, as written, declaring `namespace` as the default one when given. */
|
|
3271
|
+
start(name, namespace) {
|
|
3272
|
+
this.#close();
|
|
3273
|
+
this.#text += `<${name}`;
|
|
3274
|
+
this.#names.push(name);
|
|
3275
|
+
this.#open = true;
|
|
3276
|
+
this.#namespace = namespace;
|
|
3277
|
+
}
|
|
3278
|
+
/** `WriteAttributeString`: an attribute of the open start tag, its name as written (`r:id`, `xmlns:r`). */
|
|
3279
|
+
attribute(name, value) {
|
|
3280
|
+
if (!this.#open) throw new Error(`Internal: the attribute ${name} is written outside a start tag.`);
|
|
3281
|
+
this.#text += ` ${name}="${escapeAttribute(value)}"`;
|
|
3282
|
+
}
|
|
3283
|
+
/** `WriteString`: text inside the element. Even empty, it closes the start tag, so the element ends with its own tag. */
|
|
3284
|
+
text(value) {
|
|
3285
|
+
this.#close();
|
|
3286
|
+
this.#text += escapeText(value);
|
|
3287
|
+
}
|
|
3288
|
+
/** `WriteEndElement`. */
|
|
3289
|
+
end() {
|
|
3290
|
+
const name = this.#names.pop();
|
|
3291
|
+
if (name === void 0) throw new Error("Internal: an element is ended where none is open.");
|
|
3292
|
+
if (this.#open) {
|
|
3293
|
+
this.#declare();
|
|
3294
|
+
this.#text += " />";
|
|
3295
|
+
this.#open = false;
|
|
3296
|
+
} else {
|
|
3297
|
+
this.#text += `</${name}>`;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
/** `WriteElementString`: an element holding `value`, or empty for none or ''. */
|
|
3301
|
+
element(name, value) {
|
|
3302
|
+
this.start(name);
|
|
3303
|
+
if (value !== null && value !== "") this.text(value);
|
|
3304
|
+
this.end();
|
|
3305
|
+
}
|
|
3306
|
+
#declare() {
|
|
3307
|
+
if (this.#namespace !== void 0) this.#text += ` xmlns="${escapeAttribute(this.#namespace)}"`;
|
|
3308
|
+
this.#namespace = void 0;
|
|
3309
|
+
}
|
|
3310
|
+
#close() {
|
|
3311
|
+
if (!this.#open) return;
|
|
3312
|
+
this.#declare();
|
|
3313
|
+
this.#text += ">";
|
|
3314
|
+
this.#open = false;
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
|
|
3318
|
+
// src/crc32.ts
|
|
3319
|
+
var table = (() => {
|
|
3320
|
+
const t = new Uint32Array(256);
|
|
3321
|
+
for (let n = 0; n < 256; n++) {
|
|
3322
|
+
let c = n;
|
|
3323
|
+
for (let k = 0; k < 8; k++) c = (c & 1) !== 0 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
3324
|
+
t[n] = c >>> 0;
|
|
3325
|
+
}
|
|
3326
|
+
return t;
|
|
3327
|
+
})();
|
|
3328
|
+
function crc32(crc, bytes) {
|
|
3329
|
+
let c = ~crc >>> 0;
|
|
3330
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3331
|
+
c = table[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3332
|
+
}
|
|
3333
|
+
return ~c >>> 0;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
// src/zip-write.ts
|
|
3337
|
+
var dosDate = 0 << 9 | 1 << 5 | 1;
|
|
3338
|
+
var encoder = new TextEncoder();
|
|
3339
|
+
var ZipEntryWriter = class {
|
|
3340
|
+
#name;
|
|
3341
|
+
#writer;
|
|
3342
|
+
#compressed = [];
|
|
3343
|
+
#pumped;
|
|
3344
|
+
#written = Promise.resolve();
|
|
3345
|
+
#compressedSize = 0;
|
|
3346
|
+
#crc = 0;
|
|
3347
|
+
#size = 0;
|
|
3348
|
+
#closed = false;
|
|
3349
|
+
#result;
|
|
3350
|
+
constructor(name) {
|
|
3351
|
+
this.#name = name;
|
|
3352
|
+
const stream = new CompressionStream("deflate-raw");
|
|
3353
|
+
this.#writer = stream.writable.getWriter();
|
|
3354
|
+
const reader = stream.readable.getReader();
|
|
3355
|
+
this.#pumped = (async () => {
|
|
3356
|
+
for (; ; ) {
|
|
3357
|
+
const result = await reader.read();
|
|
3358
|
+
if (result.done) return;
|
|
3359
|
+
this.#compressed.push(result.value);
|
|
3360
|
+
this.#compressedSize += result.value.length;
|
|
3361
|
+
}
|
|
3362
|
+
})();
|
|
3363
|
+
}
|
|
3364
|
+
/** Adds `bytes` to the part. They are deflated in turn; `flush` awaits them. */
|
|
3365
|
+
write(bytes) {
|
|
3366
|
+
if (this.#closed) throw new Error(`Internal: the part '${this.#name}' is written after it was closed.`);
|
|
3367
|
+
if (bytes.length === 0) return;
|
|
3368
|
+
this.#crc = crc32(this.#crc, bytes);
|
|
3369
|
+
this.#size += bytes.length;
|
|
3370
|
+
const writer = this.#writer;
|
|
3371
|
+
this.#written = this.#written.then(() => writer.write(bytes));
|
|
3372
|
+
}
|
|
3373
|
+
/** Awaits the deflating of every byte written so far, so that memory holds them compressed. */
|
|
3374
|
+
async flush() {
|
|
3375
|
+
await this.#written;
|
|
3376
|
+
}
|
|
3377
|
+
/** Ends the part: what it was given, deflated. */
|
|
3378
|
+
async close() {
|
|
3379
|
+
this.#closed = true;
|
|
3380
|
+
await this.#written;
|
|
3381
|
+
await this.#writer.close();
|
|
3382
|
+
await this.#pumped;
|
|
3383
|
+
this.#result = { name: encoder.encode(this.#name), crc: this.#crc, size: this.#size, compressed: this.#compressed, compressedSize: this.#compressedSize };
|
|
3384
|
+
}
|
|
3385
|
+
/** The part as closed. */
|
|
3386
|
+
get result() {
|
|
3387
|
+
if (this.#result === void 0) throw new Error(`Internal: the part '${this.#name}' is put in the package before it was closed.`);
|
|
3388
|
+
return this.#result;
|
|
3389
|
+
}
|
|
3390
|
+
};
|
|
3391
|
+
function header(size) {
|
|
3392
|
+
const bytes = new Uint8Array(size);
|
|
3393
|
+
return { bytes, view: new DataView(bytes.buffer) };
|
|
3394
|
+
}
|
|
3395
|
+
var ZipWriter = class {
|
|
3396
|
+
#entries = [];
|
|
3397
|
+
/** Begins the part `name`, after every part begun before it. */
|
|
3398
|
+
entry(name) {
|
|
3399
|
+
const entry = new ZipEntryWriter(name);
|
|
3400
|
+
this.#entries.push(entry);
|
|
3401
|
+
return entry;
|
|
3402
|
+
}
|
|
3403
|
+
/** The package, every part of it closed. */
|
|
3404
|
+
finish() {
|
|
3405
|
+
const entries = this.#entries.map((e) => e.result);
|
|
3406
|
+
const parts = [];
|
|
3407
|
+
const offsets = [];
|
|
3408
|
+
let offset = 0;
|
|
3409
|
+
for (const e of entries) {
|
|
3410
|
+
offsets.push(offset);
|
|
3411
|
+
const { bytes: bytes2, view: view2 } = header(30 + e.name.length);
|
|
3412
|
+
view2.setUint32(0, 67324752, true);
|
|
3413
|
+
view2.setUint16(4, 20, true);
|
|
3414
|
+
view2.setUint16(6, 0, true);
|
|
3415
|
+
view2.setUint16(8, 8, true);
|
|
3416
|
+
view2.setUint16(10, 0, true);
|
|
3417
|
+
view2.setUint16(12, dosDate, true);
|
|
3418
|
+
view2.setUint32(14, e.crc, true);
|
|
3419
|
+
view2.setUint32(18, e.compressedSize, true);
|
|
3420
|
+
view2.setUint32(22, e.size, true);
|
|
3421
|
+
view2.setUint16(26, e.name.length, true);
|
|
3422
|
+
view2.setUint16(28, 0, true);
|
|
3423
|
+
bytes2.set(e.name, 30);
|
|
3424
|
+
parts.push(bytes2);
|
|
3425
|
+
parts.push(...e.compressed);
|
|
3426
|
+
offset += bytes2.length + e.compressedSize;
|
|
3427
|
+
}
|
|
3428
|
+
const directoryStart = offset;
|
|
3429
|
+
entries.forEach((e, i) => {
|
|
3430
|
+
const { bytes: bytes2, view: view2 } = header(46 + e.name.length);
|
|
3431
|
+
view2.setUint32(0, 33639248, true);
|
|
3432
|
+
view2.setUint16(4, 20, true);
|
|
3433
|
+
view2.setUint16(6, 20, true);
|
|
3434
|
+
view2.setUint16(8, 0, true);
|
|
3435
|
+
view2.setUint16(10, 8, true);
|
|
3436
|
+
view2.setUint16(12, 0, true);
|
|
3437
|
+
view2.setUint16(14, dosDate, true);
|
|
3438
|
+
view2.setUint32(16, e.crc, true);
|
|
3439
|
+
view2.setUint32(20, e.compressedSize, true);
|
|
3440
|
+
view2.setUint32(24, e.size, true);
|
|
3441
|
+
view2.setUint16(28, e.name.length, true);
|
|
3442
|
+
view2.setUint32(42, elementAt(offsets, i), true);
|
|
3443
|
+
bytes2.set(e.name, 46);
|
|
3444
|
+
parts.push(bytes2);
|
|
3445
|
+
offset += bytes2.length;
|
|
3446
|
+
});
|
|
3447
|
+
const { bytes, view } = header(22);
|
|
3448
|
+
view.setUint32(0, 101010256, true);
|
|
3449
|
+
view.setUint16(8, entries.length, true);
|
|
3450
|
+
view.setUint16(10, entries.length, true);
|
|
3451
|
+
view.setUint32(12, offset - directoryStart, true);
|
|
3452
|
+
view.setUint32(16, directoryStart, true);
|
|
3453
|
+
parts.push(bytes);
|
|
3454
|
+
return new Blob(parts, { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
|
|
3455
|
+
}
|
|
3456
|
+
};
|
|
3457
|
+
|
|
3458
|
+
// src/writer.ts
|
|
3459
|
+
var pieceLength = 64 * 1024;
|
|
3460
|
+
var writeBatch = 4096;
|
|
3461
|
+
var encoder2 = new TextEncoder();
|
|
3462
|
+
function hasControl(text) {
|
|
3463
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) < 32) return true;
|
|
3464
|
+
return false;
|
|
3465
|
+
}
|
|
3466
|
+
function isWhiteSpace(c) {
|
|
3467
|
+
return c >= 9 && c <= 13 || c === 32 || c === 133 || c === 160 || c === 5760 || c >= 8192 && c <= 8202 || c === 8232 || c === 8233 || c === 8239 || c === 8287 || c === 12288;
|
|
3468
|
+
}
|
|
3469
|
+
function writeText(w, text) {
|
|
3470
|
+
w.start("t");
|
|
3471
|
+
if (text.length > 0 && (isWhiteSpace(text.charCodeAt(0)) || isWhiteSpace(text.charCodeAt(text.length - 1)) || text.includes("\n") || text.includes(" "))) {
|
|
3472
|
+
w.attribute("xml:space", "preserve");
|
|
3473
|
+
}
|
|
3474
|
+
w.text(encodeXString(text));
|
|
3475
|
+
w.end();
|
|
3476
|
+
}
|
|
3477
|
+
var WorkbookWriter = class {
|
|
3478
|
+
#zip = new ZipWriter();
|
|
3479
|
+
#styles;
|
|
3480
|
+
#activeSheet;
|
|
3481
|
+
#date1904;
|
|
3482
|
+
#fullCalculation;
|
|
3483
|
+
#definedNames;
|
|
3484
|
+
#max;
|
|
3485
|
+
#sheets = [];
|
|
3486
|
+
#sharedIndex = /* @__PURE__ */ new Map();
|
|
3487
|
+
#shared = [];
|
|
3488
|
+
#tables = [];
|
|
3489
|
+
#closing = [];
|
|
3490
|
+
#side;
|
|
3491
|
+
#current;
|
|
3492
|
+
#completed = false;
|
|
3493
|
+
#refused = false;
|
|
3494
|
+
#sharedReferences = 0;
|
|
3495
|
+
#uncompressed = 0;
|
|
3496
|
+
#dynamic = false;
|
|
3497
|
+
constructor(options = {}) {
|
|
3498
|
+
this.#styles = options.styles === void 0 ? defaultStyles : options.styles;
|
|
3499
|
+
this.#activeSheet = options.activeSheet === void 0 ? 0 : whole(options.activeSheet, "The active sheet");
|
|
3500
|
+
this.#date1904 = options.date1904 === true;
|
|
3501
|
+
this.#fullCalculation = options.fullCalculationOnLoad === true;
|
|
3502
|
+
this.#definedNames = options.definedNames === void 0 ? [] : [...options.definedNames];
|
|
3503
|
+
this.#max = options.maxUncompressedBytes === void 0 ? defaultMaxUncompressedBytes : options.maxUncompressedBytes;
|
|
3504
|
+
if (!Number.isInteger(this.#max) || this.#max < 1 || this.#max > defaultMaxUncompressedBytes) {
|
|
3505
|
+
throw new RangeError(`MaxUncompressedBytes is 1 to ${grouped(defaultMaxUncompressedBytes)}.`);
|
|
3506
|
+
}
|
|
3507
|
+
validateStyles(this.#styles);
|
|
3508
|
+
validateNames(this.#definedNames);
|
|
3509
|
+
this.#side = {
|
|
3510
|
+
styles: this.#styles,
|
|
3511
|
+
share: (text) => {
|
|
3512
|
+
this.#sharedReferences++;
|
|
3513
|
+
let at = this.#sharedIndex.get(text);
|
|
3514
|
+
if (at === void 0) {
|
|
3515
|
+
at = this.#shared.length;
|
|
3516
|
+
this.#shared.push(text);
|
|
3517
|
+
this.#sharedIndex.set(text, at);
|
|
3518
|
+
}
|
|
3519
|
+
return at;
|
|
3520
|
+
},
|
|
3521
|
+
dynamicArray: () => {
|
|
3522
|
+
this.#dynamic = true;
|
|
3523
|
+
},
|
|
3524
|
+
addTable: (sheet, table2) => this.#addTable(sheet, table2),
|
|
3525
|
+
put: (entry, text, part, sheet) => this.#put(entry, text, part, sheet),
|
|
3526
|
+
checkUsable: () => {
|
|
3527
|
+
if (this.#refused) throw new Error("The workbook was refused as too large and is abandoned: nothing more is written to it.");
|
|
3528
|
+
}
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Begins the next worksheet, after the ones before it, ending the one being written. Its cells are then written in the
|
|
3533
|
+
* order of the sheet: row by row, left to right.
|
|
3534
|
+
* @param name - The sheet's name: 1 to 31 characters, none of `[ ] : * ? / \` and no control character or half of a
|
|
3535
|
+
* surrogate pair, not beginning or ending with `'`, and no other sheet's in any case.
|
|
3536
|
+
* @param columns - The column widths: ranges that do not overlap, each naming a cell format the styles hold.
|
|
3537
|
+
* @param visibility - Whether the sheet is shown; `visible` when left out.
|
|
3538
|
+
*/
|
|
3539
|
+
addSheet(name, columns = [], visibility = "visible") {
|
|
3540
|
+
this.#checkOpen();
|
|
3541
|
+
this.#checkName(name);
|
|
3542
|
+
const sorted = columns.toSorted((a, b) => a.first - b.first);
|
|
3543
|
+
let last = -1;
|
|
3544
|
+
for (const c of sorted) {
|
|
3545
|
+
const style = c.styleIndex === void 0 ? 0 : c.styleIndex;
|
|
3546
|
+
const range = `${String(c.first)} to ${String(c.last)}`;
|
|
3547
|
+
if (!Number.isInteger(c.first) || !Number.isInteger(c.last) || c.first < 0 || c.last < c.first || c.last >= maxColumns) {
|
|
3548
|
+
throw new TypeError(`The column range ${range} lies outside the sheet's columns.`);
|
|
3549
|
+
}
|
|
3550
|
+
if (c.first <= last) throw new TypeError(`The column range ${range} overlaps another, which ends at ${String(last)}.`);
|
|
3551
|
+
if (!Number.isInteger(style) || style < 0 || style >= this.#styles.cellFormats.length) {
|
|
3552
|
+
throw new TypeError(`The column range ${range} names the cell format ${String(style)}, which the styles do not hold.`);
|
|
3553
|
+
}
|
|
3554
|
+
last = c.last;
|
|
3555
|
+
}
|
|
3556
|
+
this.#endCurrent();
|
|
3557
|
+
const index = this.#sheets.length;
|
|
3558
|
+
this.#sheets.push({ name, visibility });
|
|
3559
|
+
const part = `xl/worksheets/sheet${String(index + 1)}.xml`;
|
|
3560
|
+
this.#current = new SheetWriter(
|
|
3561
|
+
this.#side,
|
|
3562
|
+
index,
|
|
3563
|
+
name,
|
|
3564
|
+
part,
|
|
3565
|
+
this.#zip.entry(part),
|
|
3566
|
+
sorted,
|
|
3567
|
+
index === this.#activeSheet,
|
|
3568
|
+
(closing) => this.#closing.push(closing)
|
|
3569
|
+
);
|
|
3570
|
+
return this.#current;
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Ends the last worksheet, writes the parts that need every sheet, and returns the package. The workbook needs a sheet,
|
|
3574
|
+
* and the options' active sheet must name one of them.
|
|
3575
|
+
*/
|
|
3576
|
+
async complete() {
|
|
3577
|
+
this.#checkOpen();
|
|
3578
|
+
if (this.#sheets.length === 0) throw new Error("A workbook holds at least one sheet; add one before completing it.");
|
|
3579
|
+
if (this.#activeSheet < 0 || this.#activeSheet >= this.#sheets.length) {
|
|
3580
|
+
throw new Error(`The active sheet ${String(this.#activeSheet)} is not one of the workbook's ${String(this.#sheets.length)} sheets.`);
|
|
3581
|
+
}
|
|
3582
|
+
this.#completed = true;
|
|
3583
|
+
this.#endCurrent();
|
|
3584
|
+
this.#current = void 0;
|
|
3585
|
+
const dynamic = this.#dynamic;
|
|
3586
|
+
if (this.#shared.length > 0) this.#part("xl/sharedStrings.xml", (w) => this.#writeSharedStrings(w));
|
|
3587
|
+
this.#part("xl/styles.xml", (w) => writeStyles(w, this.#styles));
|
|
3588
|
+
if (dynamic) this.#part("xl/metadata.xml", writeMetadata);
|
|
3589
|
+
this.#tables.forEach(({ table: table2 }, i) => {
|
|
3590
|
+
const id = i + 1;
|
|
3591
|
+
this.#part(`xl/tables/table${String(id)}.xml`, (w) => writeTable(w, table2, id));
|
|
3592
|
+
});
|
|
3593
|
+
for (const sheet of new Set(this.#tables.map((t) => t.sheet))) {
|
|
3594
|
+
this.#part(`xl/worksheets/_rels/sheet${String(sheet + 1)}.xml.rels`, (w) => this.#writeSheetRelationships(w, sheet));
|
|
3595
|
+
}
|
|
3596
|
+
this.#part("xl/workbook.xml", (w) => this.#writeWorkbook(w));
|
|
3597
|
+
this.#part("xl/_rels/workbook.xml.rels", (w) => this.#writeWorkbookRelationships(w, dynamic));
|
|
3598
|
+
this.#part("_rels/.rels", writeRootRelationships);
|
|
3599
|
+
this.#part("[Content_Types].xml", (w) => this.#writeContentTypes(w, dynamic));
|
|
3600
|
+
await Promise.all(this.#closing);
|
|
3601
|
+
return this.#zip.finish();
|
|
3602
|
+
}
|
|
3603
|
+
/** Abandons a workbook not completed, writing nothing more. */
|
|
3604
|
+
async abandon() {
|
|
3605
|
+
this.#completed = true;
|
|
3606
|
+
this.#current?.abandon();
|
|
3607
|
+
this.#current = void 0;
|
|
3608
|
+
await Promise.allSettled(this.#closing);
|
|
3609
|
+
}
|
|
3610
|
+
#checkOpen() {
|
|
3611
|
+
this.#side.checkUsable();
|
|
3612
|
+
if (this.#completed) throw new Error("The workbook is completed or abandoned: nothing more is written to it.");
|
|
3613
|
+
}
|
|
3614
|
+
#endCurrent() {
|
|
3615
|
+
this.#current?.end();
|
|
3616
|
+
}
|
|
3617
|
+
#put(entry, text, part, sheet) {
|
|
3618
|
+
this.#side.checkUsable();
|
|
3619
|
+
const bytes = encoder2.encode(text);
|
|
3620
|
+
this.#uncompressed += bytes.length;
|
|
3621
|
+
if (this.#uncompressed > this.#max) {
|
|
3622
|
+
this.#refused = true;
|
|
3623
|
+
throw new XlsxError(
|
|
3624
|
+
"tooLarge",
|
|
3625
|
+
`The workbook would hold more than the ${grouped(this.#max)} uncompressed bytes a file is written with (XlsxWriteOptions.MaxUncompressedBytes).`,
|
|
3626
|
+
{ part, sheet }
|
|
3627
|
+
);
|
|
3628
|
+
}
|
|
3629
|
+
entry.write(bytes);
|
|
3630
|
+
}
|
|
3631
|
+
#part(name, write) {
|
|
3632
|
+
const entry = this.#zip.entry(name);
|
|
3633
|
+
const w = new XmlTextWriter();
|
|
3634
|
+
w.startDocument();
|
|
3635
|
+
write(w);
|
|
3636
|
+
this.#put(entry, w.take(), name, void 0);
|
|
3637
|
+
this.#closing.push(entry.close());
|
|
3638
|
+
}
|
|
3639
|
+
/** Records a table on the sheet `sheet`, checked against every other table and defined name of the workbook: the id of
|
|
3640
|
+
* the relationship the sheet's `tablePart` names. */
|
|
3641
|
+
#addTable(sheet, table2) {
|
|
3642
|
+
checkTable(table2);
|
|
3643
|
+
if (this.#tables.some((t) => equalsIgnoreCase(t.table.name, table2.name)) || this.#definedNames.some((n) => equalsIgnoreCase(n.name, table2.name))) {
|
|
3644
|
+
throw new TypeError(`The workbook already has a table or a defined name named '${table2.name}', in some case.`);
|
|
3645
|
+
}
|
|
3646
|
+
const own = this.#tables.filter((t) => t.sheet === sheet).map((t) => t.table);
|
|
3647
|
+
const other = own.find((t) => overlaps(t, table2));
|
|
3648
|
+
if (other !== void 0) {
|
|
3649
|
+
throw new TypeError(`The table '${table2.name}' (${tableReference(table2)}) overlaps the table '${other.name}' (${tableReference(other)}).`);
|
|
3650
|
+
}
|
|
3651
|
+
this.#tables.push({ sheet, table: table2 });
|
|
3652
|
+
return `rId${String(own.length + 1)}`;
|
|
3653
|
+
}
|
|
3654
|
+
#checkName(name) {
|
|
3655
|
+
if (typeof name !== "string") throw new TypeError("A sheet's name is text.");
|
|
3656
|
+
if (name.length === 0 || name.length > 31) throw new TypeError(`A sheet's name is 1 to 31 characters; '${name}' is ${String(name.length)}.`);
|
|
3657
|
+
if (!carries(name, false) || hasControl(name)) {
|
|
3658
|
+
throw new TypeError("A sheet's name holds no control character and no half of a surrogate pair, which XML cannot carry.");
|
|
3659
|
+
}
|
|
3660
|
+
if (/[[\]:*?/\\]/.test(name)) throw new TypeError(`A sheet's name holds none of [ ] : * ? / \\; '${name}' does.`);
|
|
3661
|
+
if (name.startsWith("'") || name.endsWith("'")) throw new TypeError(`A sheet's name does not begin or end with an apostrophe; '${name}' does.`);
|
|
3662
|
+
if (this.#sheets.some((s) => equalsIgnoreCase(s.name, name))) throw new TypeError(`The workbook already has a sheet named '${name}', in some case.`);
|
|
3663
|
+
}
|
|
3664
|
+
#writeSharedStrings(w) {
|
|
3665
|
+
w.start("sst", mainNamespace);
|
|
3666
|
+
w.attribute("count", String(this.#sharedReferences));
|
|
3667
|
+
w.attribute("uniqueCount", String(this.#shared.length));
|
|
3668
|
+
for (const text of this.#shared) {
|
|
3669
|
+
w.start("si");
|
|
3670
|
+
writeText(w, text);
|
|
3671
|
+
w.end();
|
|
3672
|
+
}
|
|
3673
|
+
w.end();
|
|
3674
|
+
}
|
|
3675
|
+
#writeSheetRelationships(w, sheet) {
|
|
3676
|
+
w.start("Relationships", packageRelationshipsNamespace);
|
|
3677
|
+
let id = 1;
|
|
3678
|
+
this.#tables.forEach((t, i) => {
|
|
3679
|
+
if (t.sheet !== sheet) return;
|
|
3680
|
+
w.start("Relationship");
|
|
3681
|
+
w.attribute("Id", `rId${String(id++)}`);
|
|
3682
|
+
w.attribute("Type", tableType);
|
|
3683
|
+
w.attribute("Target", `../tables/table${String(i + 1)}.xml`);
|
|
3684
|
+
w.end();
|
|
3685
|
+
});
|
|
3686
|
+
w.end();
|
|
3687
|
+
}
|
|
3688
|
+
#writeWorkbook(w) {
|
|
3689
|
+
w.start("workbook", mainNamespace);
|
|
3690
|
+
w.attribute("xmlns:r", relationshipsNamespace);
|
|
3691
|
+
if (this.#date1904) {
|
|
3692
|
+
w.start("workbookPr");
|
|
3693
|
+
w.attribute("date1904", "1");
|
|
3694
|
+
w.end();
|
|
3695
|
+
}
|
|
3696
|
+
w.start("bookViews");
|
|
3697
|
+
w.start("workbookView");
|
|
3698
|
+
if (this.#activeSheet !== 0) w.attribute("activeTab", String(this.#activeSheet));
|
|
3699
|
+
w.end();
|
|
3700
|
+
w.end();
|
|
3701
|
+
w.start("sheets");
|
|
3702
|
+
this.#sheets.forEach((s, i) => {
|
|
3703
|
+
w.start("sheet");
|
|
3704
|
+
w.attribute("name", s.name);
|
|
3705
|
+
w.attribute("sheetId", String(i + 1));
|
|
3706
|
+
if (s.visibility !== "visible") w.attribute("state", s.visibility);
|
|
3707
|
+
w.attribute("r:id", `rId${String(i + 1)}`);
|
|
3708
|
+
w.end();
|
|
3709
|
+
});
|
|
3710
|
+
w.end();
|
|
3711
|
+
if (this.#definedNames.length > 0) {
|
|
3712
|
+
w.start("definedNames");
|
|
3713
|
+
for (const n of this.#definedNames) {
|
|
3714
|
+
w.start("definedName");
|
|
3715
|
+
w.attribute("name", n.name);
|
|
3716
|
+
if (n.localSheet !== void 0 && n.localSheet !== null) w.attribute("localSheetId", String(n.localSheet));
|
|
3717
|
+
if (n.hidden === true) w.attribute("hidden", "1");
|
|
3718
|
+
w.text(encodeXString(n.text));
|
|
3719
|
+
w.end();
|
|
3720
|
+
}
|
|
3721
|
+
w.end();
|
|
3722
|
+
}
|
|
3723
|
+
w.start("calcPr");
|
|
3724
|
+
w.attribute("calcId", "191029");
|
|
3725
|
+
if (this.#fullCalculation) w.attribute("fullCalcOnLoad", "1");
|
|
3726
|
+
w.end();
|
|
3727
|
+
w.end();
|
|
3728
|
+
}
|
|
3729
|
+
#writeWorkbookRelationships(w, dynamic) {
|
|
3730
|
+
w.start("Relationships", packageRelationshipsNamespace);
|
|
3731
|
+
let id = 1;
|
|
3732
|
+
const relationship = (type, target) => {
|
|
3733
|
+
w.start("Relationship");
|
|
3734
|
+
w.attribute("Id", `rId${String(id++)}`);
|
|
3735
|
+
w.attribute("Type", type);
|
|
3736
|
+
w.attribute("Target", target);
|
|
3737
|
+
w.end();
|
|
3738
|
+
};
|
|
3739
|
+
this.#sheets.forEach((_, i) => relationship(worksheetType, `worksheets/sheet${String(i + 1)}.xml`));
|
|
3740
|
+
relationship(stylesType, "styles.xml");
|
|
3741
|
+
if (this.#shared.length > 0) relationship(sharedStringsType, "sharedStrings.xml");
|
|
3742
|
+
if (dynamic) relationship(sheetMetadataType, "metadata.xml");
|
|
3743
|
+
w.end();
|
|
3744
|
+
}
|
|
3745
|
+
#writeContentTypes(w, dynamic) {
|
|
3746
|
+
w.start("Types", contentTypesNamespace);
|
|
3747
|
+
const entry = (element, key, value, type) => {
|
|
3748
|
+
w.start(element);
|
|
3749
|
+
w.attribute(key, value);
|
|
3750
|
+
w.attribute("ContentType", type);
|
|
3751
|
+
w.end();
|
|
3752
|
+
};
|
|
3753
|
+
entry("Default", "Extension", "rels", relationshipsContent);
|
|
3754
|
+
entry("Default", "Extension", "xml", "application/xml");
|
|
3755
|
+
entry("Override", "PartName", "/xl/workbook.xml", workbookContent);
|
|
3756
|
+
this.#sheets.forEach((_, i) => entry("Override", "PartName", `/xl/worksheets/sheet${String(i + 1)}.xml`, worksheetContent));
|
|
3757
|
+
entry("Override", "PartName", "/xl/styles.xml", stylesContent);
|
|
3758
|
+
if (this.#shared.length > 0) entry("Override", "PartName", "/xl/sharedStrings.xml", sharedStringsContent);
|
|
3759
|
+
if (dynamic) entry("Override", "PartName", "/xl/metadata.xml", sheetMetadataContent);
|
|
3760
|
+
this.#tables.forEach((_, i) => entry("Override", "PartName", `/xl/tables/table${String(i + 1)}.xml`, tableContent));
|
|
3761
|
+
w.end();
|
|
3762
|
+
}
|
|
3763
|
+
};
|
|
3764
|
+
function createXlsxWriter(options) {
|
|
3765
|
+
return new WorkbookWriter(options);
|
|
3766
|
+
}
|
|
3767
|
+
function overlaps(a, b) {
|
|
3768
|
+
return a.headerRow <= b.headerRow + b.dataRows && b.headerRow <= a.headerRow + a.dataRows && a.firstColumn < b.firstColumn + b.columns.length && b.firstColumn < a.firstColumn + a.columns.length;
|
|
3769
|
+
}
|
|
3770
|
+
function writeRootRelationships(w) {
|
|
3771
|
+
w.start("Relationships", packageRelationshipsNamespace);
|
|
3772
|
+
w.start("Relationship");
|
|
3773
|
+
w.attribute("Id", "rId1");
|
|
3774
|
+
w.attribute("Type", officeDocumentType);
|
|
3775
|
+
w.attribute("Target", "xl/workbook.xml");
|
|
3776
|
+
w.end();
|
|
3777
|
+
w.end();
|
|
3778
|
+
}
|
|
3779
|
+
function writeMetadata(w) {
|
|
3780
|
+
const xda = "http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray";
|
|
3781
|
+
w.start("metadata", mainNamespace);
|
|
3782
|
+
w.attribute("xmlns:xda", xda);
|
|
3783
|
+
w.start("metadataTypes");
|
|
3784
|
+
w.attribute("count", "1");
|
|
3785
|
+
w.start("metadataType");
|
|
3786
|
+
for (const [name, value] of [
|
|
3787
|
+
["name", "XLDAPR"],
|
|
3788
|
+
["minSupportedVersion", "120000"],
|
|
3789
|
+
["copy", "1"],
|
|
3790
|
+
["pasteAll", "1"],
|
|
3791
|
+
["pasteValues", "1"],
|
|
3792
|
+
["merge", "1"],
|
|
3793
|
+
["splitFirst", "1"],
|
|
3794
|
+
["rowColShift", "1"],
|
|
3795
|
+
["clearFormats", "1"],
|
|
3796
|
+
["clearComments", "1"],
|
|
3797
|
+
["assign", "1"],
|
|
3798
|
+
["coerce", "1"],
|
|
3799
|
+
["cellMeta", "1"]
|
|
3800
|
+
]) {
|
|
3801
|
+
w.attribute(name, value);
|
|
3802
|
+
}
|
|
3803
|
+
w.end();
|
|
3804
|
+
w.end();
|
|
3805
|
+
w.start("futureMetadata");
|
|
3806
|
+
w.attribute("name", "XLDAPR");
|
|
3807
|
+
w.attribute("count", "1");
|
|
3808
|
+
w.start("bk");
|
|
3809
|
+
w.start("extLst");
|
|
3810
|
+
w.start("ext");
|
|
3811
|
+
w.attribute("uri", dynamicArrayExtension);
|
|
3812
|
+
w.start("xda:dynamicArrayProperties");
|
|
3813
|
+
w.attribute("fDynamic", "1");
|
|
3814
|
+
w.attribute("fCollapsed", "0");
|
|
3815
|
+
w.end();
|
|
3816
|
+
w.end();
|
|
3817
|
+
w.end();
|
|
3818
|
+
w.end();
|
|
3819
|
+
w.end();
|
|
3820
|
+
w.start("cellMetadata");
|
|
3821
|
+
w.attribute("count", "1");
|
|
3822
|
+
w.start("bk");
|
|
3823
|
+
w.start("rc");
|
|
3824
|
+
w.attribute("t", "1");
|
|
3825
|
+
w.attribute("v", "0");
|
|
3826
|
+
w.end();
|
|
3827
|
+
w.end();
|
|
3828
|
+
w.end();
|
|
3829
|
+
w.end();
|
|
3830
|
+
}
|
|
3831
|
+
function writeTable(w, table2, id) {
|
|
3832
|
+
const reference = tableReference(table2);
|
|
3833
|
+
w.start("table", mainNamespace);
|
|
3834
|
+
w.attribute("id", String(id));
|
|
3835
|
+
w.attribute("name", table2.name);
|
|
3836
|
+
w.attribute("displayName", table2.name);
|
|
3837
|
+
w.attribute("ref", reference);
|
|
3838
|
+
w.attribute("totalsRowShown", "0");
|
|
3839
|
+
if (table2.autoFilter !== false) {
|
|
3840
|
+
w.start("autoFilter");
|
|
3841
|
+
w.attribute("ref", reference);
|
|
3842
|
+
w.end();
|
|
3843
|
+
}
|
|
3844
|
+
w.start("tableColumns");
|
|
3845
|
+
w.attribute("count", String(table2.columns.length));
|
|
3846
|
+
table2.columns.forEach((column, i) => {
|
|
3847
|
+
w.start("tableColumn");
|
|
3848
|
+
w.attribute("id", String(i + 1));
|
|
3849
|
+
w.attribute("name", encodeXString(column));
|
|
3850
|
+
w.end();
|
|
3851
|
+
});
|
|
3852
|
+
w.end();
|
|
3853
|
+
w.start("tableStyleInfo");
|
|
3854
|
+
w.attribute("name", "TableStyleMedium2");
|
|
3855
|
+
w.attribute("showFirstColumn", "0");
|
|
3856
|
+
w.attribute("showLastColumn", "0");
|
|
3857
|
+
w.attribute("showRowStripes", "1");
|
|
3858
|
+
w.attribute("showColumnStripes", "0");
|
|
3859
|
+
w.end();
|
|
3860
|
+
w.end();
|
|
3861
|
+
}
|
|
3862
|
+
function checkTable(table2) {
|
|
3863
|
+
if (!isValidTableName(table2.name)) {
|
|
3864
|
+
throw new TypeError(
|
|
3865
|
+
`'${table2.name}' is not a name a table can have: a letter, _ or \\, then letters, digits, _ and ., at most 255 characters, and not a cell's address.`
|
|
3866
|
+
);
|
|
3867
|
+
}
|
|
3868
|
+
const name = table2.name;
|
|
3869
|
+
if (table2.columns.length === 0) throw new TypeError(`The table '${name}' has at least one column.`);
|
|
3870
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3871
|
+
for (const column of table2.columns) {
|
|
3872
|
+
if (typeof column !== "string" || column === "") throw new TypeError(`A column of the table '${name}' has no name.`);
|
|
3873
|
+
if (!carries(column, true)) throw new TypeError(`A column's name in the table '${name}' holds half of a surrogate pair, which XML cannot carry.`);
|
|
3874
|
+
const folded = foldCase(column);
|
|
3875
|
+
if (seen.has(folded)) throw new TypeError(`The table '${name}' names the column '${column}' twice, in some case.`);
|
|
3876
|
+
seen.add(folded);
|
|
3877
|
+
}
|
|
3878
|
+
if (!Number.isInteger(table2.dataRows) || table2.dataRows < 1) {
|
|
3879
|
+
throw new TypeError(`The table '${name}' has at least one row of data; Excel holds a table of none as one empty row.`);
|
|
3880
|
+
}
|
|
3881
|
+
if (!Number.isInteger(table2.headerRow) || !Number.isInteger(table2.firstColumn) || table2.headerRow < 0 || table2.firstColumn < 0 || table2.headerRow + table2.dataRows >= maxRows || table2.firstColumn + table2.columns.length > maxColumns) {
|
|
3882
|
+
throw new TypeError(`The table '${name}' lies outside the sheet.`);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
function whole(value, what) {
|
|
3886
|
+
if (!Number.isInteger(value)) throw new TypeError(`${what} is a whole number, not ${String(value)}.`);
|
|
3887
|
+
return value;
|
|
3888
|
+
}
|
|
3889
|
+
function validateStyles(styles) {
|
|
3890
|
+
if (styles.fonts.length === 0 || styles.fills.length < 2 || styles.borders.length === 0 || styles.cellFormats.length === 0) {
|
|
3891
|
+
throw new TypeError("A styles part has at least one font, two fills (none and gray125, which Excel reserves), one border and one cell format.");
|
|
3892
|
+
}
|
|
3893
|
+
styles.cellFormats.forEach((f, i) => {
|
|
3894
|
+
for (const id of [f.numberFormatId, f.fontId, f.fillId, f.borderId]) whole(id, `An index of the cell format ${String(i)}`);
|
|
3895
|
+
if (f.alignment !== null) {
|
|
3896
|
+
whole(f.alignment.indent, `The indent of the cell format ${String(i)}`);
|
|
3897
|
+
whole(f.alignment.textRotation, `The text rotation of the cell format ${String(i)}`);
|
|
3898
|
+
}
|
|
3899
|
+
if (f.fontId < 0 || f.fontId >= styles.fonts.length || f.fillId < 0 || f.fillId >= styles.fills.length || f.borderId < 0 || f.borderId >= styles.borders.length || f.numberFormatId < 0 || f.numberFormatId >= firstCustomNumberFormat && !styles.numberFormats.has(f.numberFormatId)) {
|
|
3900
|
+
throw new TypeError(`The cell format ${String(i)} names a font, fill, border or Number Format the styles do not hold.`);
|
|
3901
|
+
}
|
|
3902
|
+
});
|
|
3903
|
+
if ([...styles.numberFormats.values()].some((code) => typeof code !== "string" || !carries(code, true)) || styles.fonts.some((f) => f.name !== null && !carries(f.name, false)) || styles.fills.some((f) => f.pattern !== null && !carries(f.pattern, false))) {
|
|
3904
|
+
throw new TypeError("A Number Format, a font's name or a fill's pattern holds text XML cannot carry: a control character, or half of a surrogate pair.");
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
function validateNames(names) {
|
|
3908
|
+
for (const n of names) {
|
|
3909
|
+
if (typeof n.name !== "string" || n.name === "" || typeof n.text !== "string") throw new TypeError("A defined name has a name and a text.");
|
|
3910
|
+
if (n.localSheet !== void 0 && n.localSheet !== null) whole(n.localSheet, `The defined name '${n.name}''s sheet`);
|
|
3911
|
+
if (!carries(n.name, false) || hasControl(n.name) || !carries(n.text, true)) {
|
|
3912
|
+
throw new TypeError(`The defined name '${n.name}' holds a control character or half of a surrogate pair, which XML cannot carry.`);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
var SheetWriter = class {
|
|
3917
|
+
#owner;
|
|
3918
|
+
#index;
|
|
3919
|
+
#part;
|
|
3920
|
+
#entry;
|
|
3921
|
+
#xml = new XmlTextWriter();
|
|
3922
|
+
#closed;
|
|
3923
|
+
#tables = [];
|
|
3924
|
+
#rowOpen = false;
|
|
3925
|
+
#ended = false;
|
|
3926
|
+
#row = -1;
|
|
3927
|
+
#column = -1;
|
|
3928
|
+
/** The sheet's name. */
|
|
3929
|
+
name;
|
|
3930
|
+
constructor(owner, index, name, part, entry, columns, active, closed) {
|
|
3931
|
+
this.#owner = owner;
|
|
3932
|
+
this.#index = index;
|
|
3933
|
+
this.name = name;
|
|
3934
|
+
this.#part = part;
|
|
3935
|
+
this.#entry = entry;
|
|
3936
|
+
this.#closed = closed;
|
|
3937
|
+
const xml = this.#xml;
|
|
3938
|
+
xml.startDocument();
|
|
3939
|
+
xml.start("worksheet", mainNamespace);
|
|
3940
|
+
xml.attribute("xmlns:r", relationshipsNamespace);
|
|
3941
|
+
xml.start("sheetViews");
|
|
3942
|
+
xml.start("sheetView");
|
|
3943
|
+
if (active) xml.attribute("tabSelected", "1");
|
|
3944
|
+
xml.attribute("workbookViewId", "0");
|
|
3945
|
+
xml.end();
|
|
3946
|
+
xml.end();
|
|
3947
|
+
xml.start("sheetFormatPr");
|
|
3948
|
+
xml.attribute("defaultRowHeight", "15");
|
|
3949
|
+
xml.end();
|
|
3950
|
+
if (columns.length > 0) {
|
|
3951
|
+
xml.start("cols");
|
|
3952
|
+
for (const c of columns) {
|
|
3953
|
+
xml.start("col");
|
|
3954
|
+
xml.attribute("min", String(c.first + 1));
|
|
3955
|
+
xml.attribute("max", String(c.last + 1));
|
|
3956
|
+
if (c.width !== void 0 && c.width !== null) {
|
|
3957
|
+
xml.attribute("width", roundTrip(c.width));
|
|
3958
|
+
if (c.customWidth !== false) xml.attribute("customWidth", "1");
|
|
3959
|
+
if (c.bestFit === true) xml.attribute("bestFit", "1");
|
|
3960
|
+
}
|
|
3961
|
+
if (c.styleIndex !== void 0 && c.styleIndex !== 0) xml.attribute("style", String(c.styleIndex));
|
|
3962
|
+
if (c.hidden === true) xml.attribute("hidden", "1");
|
|
3963
|
+
xml.end();
|
|
3964
|
+
}
|
|
3965
|
+
xml.end();
|
|
3966
|
+
}
|
|
3967
|
+
xml.start("sheetData");
|
|
3968
|
+
}
|
|
3969
|
+
#checkOpen() {
|
|
3970
|
+
this.#owner.checkUsable();
|
|
3971
|
+
if (this.#ended) throw new Error(`The sheet '${this.name}' is ended: another sheet was begun, or the workbook completed.`);
|
|
3972
|
+
}
|
|
3973
|
+
/**
|
|
3974
|
+
* Adds an Excel Table over cells of this sheet (`tableParts`, and a table part of its own), before the first cell of its
|
|
3975
|
+
* header's row is written. Each cell of its header must then be written, as text equal to its column's name (a shared
|
|
3976
|
+
* or inline string, no Formula), or the cell, or the sheet's end, is refused; its rows of data hold whatever is written
|
|
3977
|
+
* there. A table whose name is another table's or a defined name's in the workbook, in any case, or that overlaps
|
|
3978
|
+
* another table of the sheet, is refused.
|
|
3979
|
+
*/
|
|
3980
|
+
addTable(table2) {
|
|
3981
|
+
this.#checkOpen();
|
|
3982
|
+
const copy = Object.freeze({ ...table2, columns: Object.freeze([...table2.columns]) });
|
|
3983
|
+
if (copy.headerRow <= this.#row) {
|
|
3984
|
+
throw new TypeError(`The table '${copy.name}' is added after row ${String(this.#row + 1)} was begun: a table is added before its header's row.`);
|
|
3985
|
+
}
|
|
3986
|
+
const id = this.#owner.addTable(this.#index, copy);
|
|
3987
|
+
this.#tables.push({ table: copy, id, written: 0 });
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* Begins `row` with the cell format of its own that its empty cells take (`customFormat`), after every row written
|
|
3991
|
+
* before it; the row's cells, if any, are then written as any others. A row written before, or one that already holds
|
|
3992
|
+
* a cell, is refused.
|
|
3993
|
+
*/
|
|
3994
|
+
writeRow(row) {
|
|
3995
|
+
this.#checkOpen();
|
|
3996
|
+
if (!Number.isInteger(row.row) || row.row < 0 || row.row >= maxRows) throw new TypeError(`The row ${String(row.row)} lies outside the sheet.`);
|
|
3997
|
+
if (row.row <= this.#row) {
|
|
3998
|
+
throw new TypeError(`The row ${String(row.row + 1)} is begun after row ${String(this.#row + 1)}: a sheet's rows are written in order, each once.`);
|
|
3999
|
+
}
|
|
4000
|
+
if (!Number.isInteger(row.styleIndex) || row.styleIndex < 0 || row.styleIndex >= this.#owner.styles.cellFormats.length) {
|
|
4001
|
+
throw new TypeError(`The row ${String(row.row + 1)} names the cell format ${String(row.styleIndex)}, which the styles do not hold.`);
|
|
4002
|
+
}
|
|
4003
|
+
const xml = this.#xml;
|
|
4004
|
+
if (this.#rowOpen) xml.end();
|
|
4005
|
+
xml.start("row");
|
|
4006
|
+
xml.attribute("r", String(row.row + 1));
|
|
4007
|
+
xml.attribute("s", String(row.styleIndex));
|
|
4008
|
+
xml.attribute("customFormat", "1");
|
|
4009
|
+
this.#rowOpen = true;
|
|
4010
|
+
this.#row = row.row;
|
|
4011
|
+
this.#column = -1;
|
|
4012
|
+
this.#spill();
|
|
4013
|
+
}
|
|
4014
|
+
/**
|
|
4015
|
+
* Writes `cell`, after every cell written before it in the sheet's order. A shared string is written from its `text`
|
|
4016
|
+
* into the workbook's shared strings, whatever its `value` says.
|
|
4017
|
+
*/
|
|
4018
|
+
write(cell) {
|
|
4019
|
+
this.#checkOpen();
|
|
4020
|
+
if (!Number.isInteger(cell.row) || !Number.isInteger(cell.column) || cell.row < 0 || cell.row >= maxRows || cell.column < 0 || cell.column >= maxColumns) {
|
|
4021
|
+
throw new TypeError(`The cell at row ${String(cell.row)}, column ${String(cell.column)} lies outside the sheet.`);
|
|
4022
|
+
}
|
|
4023
|
+
const reference = formatCell(cell.row, cell.column);
|
|
4024
|
+
const type = typeAttribute(cell);
|
|
4025
|
+
if (cell.row < this.#row || cell.row === this.#row && cell.column <= this.#column) {
|
|
4026
|
+
const after = this.#column < 0 ? `row ${String(this.#row + 1)}` : formatCell(this.#row, this.#column);
|
|
4027
|
+
throw new TypeError(`The cell ${reference} is written after ${after}: a sheet's cells are written row by row, left to right, each once.`);
|
|
4028
|
+
}
|
|
4029
|
+
const styleIndex = cell.styleIndex === void 0 ? 0 : cell.styleIndex;
|
|
4030
|
+
if (!Number.isInteger(styleIndex) || styleIndex < 0 || styleIndex >= this.#owner.styles.cellFormats.length) {
|
|
4031
|
+
throw new TypeError(`The cell ${reference} names the cell format ${String(styleIndex)}, which the styles do not hold.`);
|
|
4032
|
+
}
|
|
4033
|
+
const value = cell.value === void 0 ? null : cell.value;
|
|
4034
|
+
const text = cell.text === void 0 ? null : cell.text;
|
|
4035
|
+
const formula = cell.formula === void 0 ? null : cell.formula;
|
|
4036
|
+
const kind = formula === null || formula.kind === void 0 ? "normal" : formula.kind;
|
|
4037
|
+
const sharedIndex = formula === null || formula.sharedIndex === void 0 ? null : formula.sharedIndex;
|
|
4038
|
+
if (kind === "shared" && sharedIndex === null) throw new TypeError(`The cell ${reference}'s shared formula names no group.`);
|
|
4039
|
+
if (sharedIndex !== null) whole(sharedIndex, `The cell ${reference}'s shared formula group`);
|
|
4040
|
+
if (formula !== null && (typeof formula.text !== "string" || !carries(formula.text, true) || formula.reference !== void 0 && formula.reference !== null && !carries(formula.reference, false))) {
|
|
4041
|
+
throw new TypeError(`The cell ${reference}'s Formula holds text XML cannot carry.`);
|
|
4042
|
+
}
|
|
4043
|
+
const carried = cell.type === "sharedString" || cell.type === "inlineString" ? text === null || carries(text, true) : cell.type === "formulaString" ? value === null || carries(value, true) : value === null || carries(value, false);
|
|
4044
|
+
if (!carried) throw new TypeError(`The cell ${reference} holds text XML cannot carry: a control character, or half of a surrogate pair.`);
|
|
4045
|
+
const header2 = this.#headerOf(cell, reference, text, formula);
|
|
4046
|
+
const xml = this.#xml;
|
|
4047
|
+
if (cell.row !== this.#row) {
|
|
4048
|
+
if (this.#rowOpen) xml.end();
|
|
4049
|
+
xml.start("row");
|
|
4050
|
+
xml.attribute("r", String(cell.row + 1));
|
|
4051
|
+
this.#rowOpen = true;
|
|
4052
|
+
this.#row = cell.row;
|
|
4053
|
+
}
|
|
4054
|
+
this.#column = cell.column;
|
|
4055
|
+
if (header2 !== void 0) header2.written++;
|
|
4056
|
+
xml.start("c");
|
|
4057
|
+
xml.attribute("r", reference);
|
|
4058
|
+
if (styleIndex !== 0) xml.attribute("s", String(styleIndex));
|
|
4059
|
+
if (type !== void 0) xml.attribute("t", type);
|
|
4060
|
+
if (formula !== null && formula.isDynamicArray === true) {
|
|
4061
|
+
xml.attribute("cm", "1");
|
|
4062
|
+
this.#owner.dynamicArray();
|
|
4063
|
+
}
|
|
4064
|
+
if (formula !== null) this.#writeFormula(formula, kind, sharedIndex, reference);
|
|
4065
|
+
switch (cell.type) {
|
|
4066
|
+
case "sharedString":
|
|
4067
|
+
xml.element("v", String(this.#owner.share(text === null ? "" : text)));
|
|
4068
|
+
break;
|
|
4069
|
+
case "inlineString":
|
|
4070
|
+
xml.start("is");
|
|
4071
|
+
writeText(xml, text === null ? "" : text);
|
|
4072
|
+
xml.end();
|
|
4073
|
+
break;
|
|
4074
|
+
case "formulaString":
|
|
4075
|
+
if (value !== null) xml.element("v", encodeXString(value));
|
|
4076
|
+
break;
|
|
4077
|
+
case "number":
|
|
4078
|
+
case "boolean":
|
|
4079
|
+
case "error":
|
|
4080
|
+
case "date":
|
|
4081
|
+
if (value !== null) xml.element("v", value);
|
|
4082
|
+
break;
|
|
4083
|
+
default:
|
|
4084
|
+
throw new TypeError(`'${String(cell.type)}' is not a cell's type.`);
|
|
4085
|
+
}
|
|
4086
|
+
xml.end();
|
|
4087
|
+
this.#spill();
|
|
4088
|
+
}
|
|
4089
|
+
#writeFormula(formula, kind, sharedIndex, reference) {
|
|
4090
|
+
const xml = this.#xml;
|
|
4091
|
+
xml.start("f");
|
|
4092
|
+
if (kind !== "normal") xml.attribute("t", kind);
|
|
4093
|
+
if (formula.reference !== void 0 && formula.reference !== null) xml.attribute("ref", formula.reference);
|
|
4094
|
+
else if (kind === "array") xml.attribute("ref", reference);
|
|
4095
|
+
if (sharedIndex !== null) xml.attribute("si", String(sharedIndex));
|
|
4096
|
+
if (formula.text.length > 0) xml.text(encodeXString(formula.text));
|
|
4097
|
+
xml.end();
|
|
4098
|
+
}
|
|
4099
|
+
/** Writes every cell of `cells` in turn, flushing between batches. */
|
|
4100
|
+
async writeAll(cells) {
|
|
4101
|
+
let n = 0;
|
|
4102
|
+
for (const cell of cells) {
|
|
4103
|
+
this.write(cell);
|
|
4104
|
+
if (++n % writeBatch === 0) await this.flush();
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
/** Awaits the deflating of what is written so far, so that memory holds it compressed: call it between batches of a
|
|
4108
|
+
* large sheet. */
|
|
4109
|
+
async flush() {
|
|
4110
|
+
if (!this.#ended) this.#spill(true);
|
|
4111
|
+
await this.#entry.flush();
|
|
4112
|
+
}
|
|
4113
|
+
/** The count of header cells written of the table whose header holds `cell`, or undefined when none does; a header's
|
|
4114
|
+
* cell that does not hold its column's name as text is refused. */
|
|
4115
|
+
#headerOf(cell, reference, text, formula) {
|
|
4116
|
+
for (const t of this.#tables) {
|
|
4117
|
+
const { table: table2 } = t;
|
|
4118
|
+
if (cell.row !== table2.headerRow || cell.column < table2.firstColumn || cell.column >= table2.firstColumn + table2.columns.length) continue;
|
|
4119
|
+
const name = elementAt(table2.columns, cell.column - table2.firstColumn);
|
|
4120
|
+
if (cell.type !== "sharedString" && cell.type !== "inlineString" || formula !== null || text !== name) {
|
|
4121
|
+
throw new TypeError(
|
|
4122
|
+
`The cell ${reference} is in the header of the table '${table2.name}', and holds its column's name, '${name}', as text, with no Formula.`
|
|
4123
|
+
);
|
|
4124
|
+
}
|
|
4125
|
+
return t;
|
|
4126
|
+
}
|
|
4127
|
+
return void 0;
|
|
4128
|
+
}
|
|
4129
|
+
/** Hands the text gathered to the package once it is long enough, or whatever there is when `all`. */
|
|
4130
|
+
#spill(all = false) {
|
|
4131
|
+
if (this.#xml.length === 0 || !all && this.#xml.length < pieceLength) return;
|
|
4132
|
+
this.#owner.put(this.#entry, this.#xml.take(), this.#part, this.name);
|
|
4133
|
+
}
|
|
4134
|
+
/** Ends the sheet. */
|
|
4135
|
+
end() {
|
|
4136
|
+
if (this.#ended) return;
|
|
4137
|
+
for (const { table: table2, written } of this.#tables) {
|
|
4138
|
+
if (written !== table2.columns.length) {
|
|
4139
|
+
throw new Error(`The sheet '${this.name}' ends with ${String(written)} of the ${String(table2.columns.length)} cells of the header of the table '${table2.name}' written; each holds its column's name.`);
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
this.#ended = true;
|
|
4143
|
+
const xml = this.#xml;
|
|
4144
|
+
if (this.#rowOpen) xml.end();
|
|
4145
|
+
xml.end();
|
|
4146
|
+
if (this.#tables.length > 0) {
|
|
4147
|
+
xml.start("tableParts");
|
|
4148
|
+
xml.attribute("count", String(this.#tables.length));
|
|
4149
|
+
for (const { id } of this.#tables) {
|
|
4150
|
+
xml.start("tablePart");
|
|
4151
|
+
xml.attribute("r:id", id);
|
|
4152
|
+
xml.end();
|
|
4153
|
+
}
|
|
4154
|
+
xml.end();
|
|
4155
|
+
}
|
|
4156
|
+
xml.end();
|
|
4157
|
+
this.#owner.put(this.#entry, xml.take(), this.#part, this.name);
|
|
4158
|
+
this.#closed(this.#entry.close());
|
|
4159
|
+
}
|
|
4160
|
+
/** Abandons the sheet. */
|
|
4161
|
+
abandon() {
|
|
4162
|
+
if (this.#ended) return;
|
|
4163
|
+
this.#ended = true;
|
|
4164
|
+
this.#closed(this.#entry.close());
|
|
4165
|
+
}
|
|
4166
|
+
};
|
|
4167
|
+
function typeAttribute(cell) {
|
|
4168
|
+
switch (cell.type) {
|
|
4169
|
+
case "sharedString":
|
|
4170
|
+
return "s";
|
|
4171
|
+
case "inlineString":
|
|
4172
|
+
return "inlineStr";
|
|
4173
|
+
case "formulaString":
|
|
4174
|
+
return "str";
|
|
4175
|
+
case "boolean":
|
|
4176
|
+
return "b";
|
|
4177
|
+
case "error":
|
|
4178
|
+
return "e";
|
|
4179
|
+
case "date":
|
|
4180
|
+
return "d";
|
|
4181
|
+
case "number":
|
|
4182
|
+
return void 0;
|
|
4183
|
+
default:
|
|
4184
|
+
throw new TypeError(`'${String(cell.type)}' is not a cell's type.`);
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
export {
|
|
4188
|
+
XlsxError,
|
|
4189
|
+
XlsxSharedStrings,
|
|
4190
|
+
XlsxTheme,
|
|
4191
|
+
applyTint,
|
|
4192
|
+
automaticColor,
|
|
4193
|
+
batchSize,
|
|
4194
|
+
columnName,
|
|
4195
|
+
createXlsxWriter,
|
|
4196
|
+
defaultAlignment,
|
|
4197
|
+
defaultIndexedColors,
|
|
4198
|
+
defaultMaxCells,
|
|
4199
|
+
defaultMaxUncompressedBytes,
|
|
4200
|
+
defaultStyles,
|
|
4201
|
+
elementMaxChildren,
|
|
4202
|
+
elementMaxDepth,
|
|
4203
|
+
elementMaxText,
|
|
4204
|
+
firstCustomNumberFormat,
|
|
4205
|
+
formatCell,
|
|
4206
|
+
gray125Fill,
|
|
4207
|
+
indexedColor,
|
|
4208
|
+
isValidTableName,
|
|
4209
|
+
maxColumns,
|
|
4210
|
+
maxRows,
|
|
4211
|
+
noBorder,
|
|
4212
|
+
noBorderEdge,
|
|
4213
|
+
noFill,
|
|
4214
|
+
openXlsx,
|
|
4215
|
+
parseCell,
|
|
4216
|
+
referenceOf,
|
|
4217
|
+
rgbColor,
|
|
4218
|
+
solidFill,
|
|
4219
|
+
tableReference,
|
|
4220
|
+
themeColor
|
|
4221
|
+
};
|
|
4222
|
+
//# sourceMappingURL=index.js.map
|