@ex-grid/sheet-xlsx 0.0.0-stage → 0.2.0-beta.3
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 +28 -2
- package/dist/index.d.ts +368 -0
- package/dist/index.js +2708 -0
- package/dist/index.js.map +7 -0
- package/package.json +27 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,2708 @@
|
|
|
1
|
+
// src/reader.ts
|
|
2
|
+
import {
|
|
3
|
+
CellAddress,
|
|
4
|
+
CellBorders as CellBorders3,
|
|
5
|
+
CellFill as CellFill3,
|
|
6
|
+
CellFont as CellFont3,
|
|
7
|
+
CellRange,
|
|
8
|
+
Culture,
|
|
9
|
+
Entry,
|
|
10
|
+
FormulaSyntaxError,
|
|
11
|
+
NumberFormat as NumberFormat4,
|
|
12
|
+
Sheet as Sheet3,
|
|
13
|
+
SheetDocument,
|
|
14
|
+
SheetDocumentAxisFormat,
|
|
15
|
+
SheetDocumentCell,
|
|
16
|
+
SheetDocumentColumnWidth,
|
|
17
|
+
Value,
|
|
18
|
+
WorkbookDocument
|
|
19
|
+
} from "@ex-grid/sheet-engine";
|
|
20
|
+
import {
|
|
21
|
+
openXlsx,
|
|
22
|
+
parseCell,
|
|
23
|
+
formatCell,
|
|
24
|
+
XlsxError as XlsxError2
|
|
25
|
+
} from "@ex-grid/data-xlsx";
|
|
26
|
+
|
|
27
|
+
// src/column-width-unit.ts
|
|
28
|
+
import { Sheet } from "@ex-grid/sheet-engine";
|
|
29
|
+
var maxDigitWidth = 7;
|
|
30
|
+
var padding = 5;
|
|
31
|
+
var narrowest = 0.01;
|
|
32
|
+
function widthToFile(characters) {
|
|
33
|
+
return Math.trunc((characters * maxDigitWidth + padding) / maxDigitWidth * 256) / 256;
|
|
34
|
+
}
|
|
35
|
+
function widthFromFile(width) {
|
|
36
|
+
return Math.trunc((width * maxDigitWidth - padding) / maxDigitWidth * 100 + 0.5) / 100;
|
|
37
|
+
}
|
|
38
|
+
function readWidth(width) {
|
|
39
|
+
if (width <= 0) return void 0;
|
|
40
|
+
const characters = Math.min(Math.max(widthFromFile(width), narrowest), Sheet.maxColumnWidth);
|
|
41
|
+
return characters === Sheet.defaultColumnWidth ? void 0 : characters;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/text-casing.ts
|
|
45
|
+
function foldCase(text) {
|
|
46
|
+
let folded = "";
|
|
47
|
+
let changed = false;
|
|
48
|
+
for (const character of text) {
|
|
49
|
+
const upper = upperOf(character);
|
|
50
|
+
if (upper !== character) changed = true;
|
|
51
|
+
folded += upper;
|
|
52
|
+
}
|
|
53
|
+
return changed ? folded : text;
|
|
54
|
+
}
|
|
55
|
+
function upperOf(character) {
|
|
56
|
+
const code = character.charCodeAt(0);
|
|
57
|
+
if (code < 128) return code >= 97 && code <= 122 ? String.fromCharCode(code - 32) : character;
|
|
58
|
+
const upper = character.toUpperCase();
|
|
59
|
+
const point = upper.codePointAt(0);
|
|
60
|
+
const single = point !== void 0 && upper.length === (point > 65535 ? 2 : 1);
|
|
61
|
+
if (!single) return simpleUpperOf(code, character);
|
|
62
|
+
if (upper === character || point < 128) return character;
|
|
63
|
+
return upper;
|
|
64
|
+
}
|
|
65
|
+
function simpleUpperOf(code, character) {
|
|
66
|
+
const pairs = code >= 8064 && code <= 8071 || code >= 8080 && code <= 8087 || code >= 8096 && code <= 8103;
|
|
67
|
+
if (pairs) return String.fromCharCode(code + 8);
|
|
68
|
+
if (code === 8115 || code === 8131 || code === 8179) return String.fromCharCode(code + 9);
|
|
69
|
+
return character;
|
|
70
|
+
}
|
|
71
|
+
function equalsIgnoreCase(a, b) {
|
|
72
|
+
return a.length === b.length && foldCase(a) === foldCase(b);
|
|
73
|
+
}
|
|
74
|
+
function startsWithIgnoreCase(text, at, part) {
|
|
75
|
+
return at + part.length <= text.length && equalsIgnoreCase(text.slice(at, at + part.length), part);
|
|
76
|
+
}
|
|
77
|
+
var whiteSpaceEnds = /^[\t-\r \u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+|[\t-\r \u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+$/g;
|
|
78
|
+
function trimWhiteSpace(text) {
|
|
79
|
+
return text.replace(whiteSpaceEnds, "");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/date-formats.ts
|
|
83
|
+
var builtInDates = /* @__PURE__ */ new Set([14, 15, 16, 17, 22, 27, 28, 29, 30, 31, 34, 35, 36, 50, 51, 52, 53, 54, 55, 56, 57, 58]);
|
|
84
|
+
function showsDate(id, code) {
|
|
85
|
+
return code === void 0 ? builtInDates.has(id) : codeShowsDate(code);
|
|
86
|
+
}
|
|
87
|
+
function lower(c) {
|
|
88
|
+
return c >= "A" && c <= "Z" ? c.toLowerCase() : c;
|
|
89
|
+
}
|
|
90
|
+
function codeShowsDate(code) {
|
|
91
|
+
let hasMonthOrMinute = false;
|
|
92
|
+
let hasTime = false;
|
|
93
|
+
for (let i = 0; i < code.length; i++) {
|
|
94
|
+
const c = code.charAt(i);
|
|
95
|
+
switch (c) {
|
|
96
|
+
case ";":
|
|
97
|
+
return !hasTime && hasMonthOrMinute;
|
|
98
|
+
case '"': {
|
|
99
|
+
const end = code.indexOf('"', i + 1);
|
|
100
|
+
i = end >= 0 ? end : code.length;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
case "\\":
|
|
104
|
+
case "_":
|
|
105
|
+
case "*":
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
case "[": {
|
|
109
|
+
let close = code.indexOf("]", i + 1);
|
|
110
|
+
if (close < 0) close = code.length;
|
|
111
|
+
if (isElapsed(code.slice(i + 1, close))) hasTime = true;
|
|
112
|
+
i = close;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (startsWithIgnoreCase(code, i, "General")) {
|
|
119
|
+
i += 6;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (startsWithIgnoreCase(code, i, "AM/PM")) {
|
|
123
|
+
hasTime = true;
|
|
124
|
+
i += 4;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (startsWithIgnoreCase(code, i, "A/P")) {
|
|
128
|
+
hasTime = true;
|
|
129
|
+
i += 2;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
switch (lower(c)) {
|
|
133
|
+
case "y":
|
|
134
|
+
case "d":
|
|
135
|
+
return true;
|
|
136
|
+
case "e":
|
|
137
|
+
if (i + 1 >= code.length || code.charAt(i + 1) !== "+" && code.charAt(i + 1) !== "-") return true;
|
|
138
|
+
break;
|
|
139
|
+
case "m":
|
|
140
|
+
hasMonthOrMinute = true;
|
|
141
|
+
break;
|
|
142
|
+
case "h":
|
|
143
|
+
case "s":
|
|
144
|
+
hasTime = true;
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return !hasTime && hasMonthOrMinute;
|
|
151
|
+
}
|
|
152
|
+
function isElapsed(inside) {
|
|
153
|
+
if (inside.length === 0) return false;
|
|
154
|
+
const letter = lower(inside.charAt(0));
|
|
155
|
+
if (letter !== "h" && letter !== "m" && letter !== "s") return false;
|
|
156
|
+
for (const c of inside) {
|
|
157
|
+
if (lower(c) !== letter) return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/format-parts.ts
|
|
163
|
+
import { CellBorders, CellFill, CellFont, CellFormat, NumberFormat } from "@ex-grid/sheet-engine";
|
|
164
|
+
var defaultFormat = new CellFormat(NumberFormat.general, "general", CellFont.default, CellFill.none, CellBorders.none);
|
|
165
|
+
var FormatParts = class _FormatParts {
|
|
166
|
+
static empty = new _FormatParts(void 0, void 0, void 0, void 0, void 0);
|
|
167
|
+
numberFormat;
|
|
168
|
+
alignment;
|
|
169
|
+
font;
|
|
170
|
+
fill;
|
|
171
|
+
borders;
|
|
172
|
+
constructor(numberFormat, alignment, font, fill, borders) {
|
|
173
|
+
this.numberFormat = numberFormat;
|
|
174
|
+
this.alignment = alignment;
|
|
175
|
+
this.font = font;
|
|
176
|
+
this.fill = fill;
|
|
177
|
+
this.borders = borders;
|
|
178
|
+
}
|
|
179
|
+
get isEmpty() {
|
|
180
|
+
return this.numberFormat === void 0 && this.alignment === void 0 && this.font === void 0 && this.fill === void 0 && this.borders === void 0;
|
|
181
|
+
}
|
|
182
|
+
/** This level over `under`, part by part. */
|
|
183
|
+
over(under) {
|
|
184
|
+
return new CellFormat(
|
|
185
|
+
this.numberFormat === void 0 ? under.numberFormat : this.numberFormat,
|
|
186
|
+
this.alignment === void 0 ? under.alignment : this.alignment,
|
|
187
|
+
this.font === void 0 ? under.font : this.font,
|
|
188
|
+
this.fill === void 0 ? under.fill : this.fill,
|
|
189
|
+
this.borders === void 0 ? under.borders : this.borders
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
/** The parts of `format` that differ from `under`: what a level records so that it shows `format` over it. */
|
|
193
|
+
static differing(format, under) {
|
|
194
|
+
return new _FormatParts(
|
|
195
|
+
format.numberFormat.equals(under.numberFormat) ? void 0 : format.numberFormat,
|
|
196
|
+
format.alignment === under.alignment ? void 0 : format.alignment,
|
|
197
|
+
format.font.equals(under.font) ? void 0 : format.font,
|
|
198
|
+
format.fill.equals(under.fill) ? void 0 : format.fill,
|
|
199
|
+
format.borders.equals(under.borders) ? void 0 : format.borders
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
/** Whether the two levels record the same parts alike, as the C# record compares. */
|
|
203
|
+
equals(other) {
|
|
204
|
+
return same(this.numberFormat, other.numberFormat, (a, b) => a.equals(b)) && this.alignment === other.alignment && same(this.font, other.font, (a, b) => a.equals(b)) && same(this.fill, other.fill, (a, b) => a.equals(b)) && same(this.borders, other.borders, (a, b) => a.equals(b));
|
|
205
|
+
}
|
|
206
|
+
/** The parts a level records, as a mask: Number Format, alignment, Font, Fill, borders. */
|
|
207
|
+
get mask() {
|
|
208
|
+
return (this.numberFormat === void 0 ? 0 : 1) | (this.alignment === void 0 ? 0 : 2) | (this.font === void 0 ? 0 : 4) | (this.fill === void 0 ? 0 : 8) | (this.borders === void 0 ? 0 : 16);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
function same(a, b, equal) {
|
|
212
|
+
return a === void 0 || b === void 0 ? a === b : equal(a, b);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/invariant.ts
|
|
216
|
+
function grouped(value) {
|
|
217
|
+
const digits = String(Math.abs(Math.trunc(value)));
|
|
218
|
+
const groups = [];
|
|
219
|
+
for (let end = digits.length; end > 0; end -= 3) groups.unshift(digits.slice(Math.max(0, end - 3), end));
|
|
220
|
+
return (value < 0 ? "-" : "") + groups.join(",");
|
|
221
|
+
}
|
|
222
|
+
function roundTrip(value) {
|
|
223
|
+
if (Number.isNaN(value)) return "NaN";
|
|
224
|
+
if (!Number.isFinite(value)) return value > 0 ? "Infinity" : "-Infinity";
|
|
225
|
+
if (value === 0) return Object.is(value, -0) ? "-0" : "0";
|
|
226
|
+
const match = /^(\d)(?:\.(\d+))?e([+-]\d+)$/.exec(Math.abs(value).toExponential());
|
|
227
|
+
if (match === null) throw new Error(`Internal: ${String(value)} is not written in exponent form.`);
|
|
228
|
+
const [, first = "", rest = "", exponent = ""] = match;
|
|
229
|
+
const digits = first + rest;
|
|
230
|
+
const point = Number(exponent) + 1;
|
|
231
|
+
const sign = value < 0 ? "-" : "";
|
|
232
|
+
if (point > 17 || point < -3) {
|
|
233
|
+
const e = point - 1;
|
|
234
|
+
const mantissa = digits.length > 1 ? `${digits.slice(0, 1)}.${digits.slice(1)}` : digits;
|
|
235
|
+
return `${sign}${mantissa}E${e < 0 ? "-" : "+"}${String(Math.abs(e)).padStart(2, "0")}`;
|
|
236
|
+
}
|
|
237
|
+
if (point <= 0) return `${sign}0.${"0".repeat(-point)}${digits}`;
|
|
238
|
+
if (point >= digits.length) return sign + digits + "0".repeat(point - digits.length);
|
|
239
|
+
return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
240
|
+
}
|
|
241
|
+
function parseDouble(text) {
|
|
242
|
+
const trimmed = text.replace(/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g, "");
|
|
243
|
+
if (/^[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/.test(trimmed)) return Number(trimmed);
|
|
244
|
+
const lower2 = trimmed.toLowerCase();
|
|
245
|
+
if (lower2 === "infinity" || lower2 === "+infinity") return Number.POSITIVE_INFINITY;
|
|
246
|
+
if (lower2 === "-infinity") return Number.NEGATIVE_INFINITY;
|
|
247
|
+
if (lower2 === "nan" || lower2 === "+nan" || lower2 === "-nan") return Number.NaN;
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/iso-date.ts
|
|
252
|
+
import { serialFromDate } from "@ex-grid/sheet-engine";
|
|
253
|
+
var shape = /^(?:(?<y>[0-9]{4})-(?<mo>[0-9]{2})-(?<d>[0-9]{2}))?(?<t>T)?(?:(?<h>[0-9]{2}):(?<mi>[0-9]{2})(?::(?<s>[0-9]{2})(?<f>\.[0-9]+)?)?)?(?<z>Z|[+-][0-9]{2}(?::?[0-9]{2})?)?$/;
|
|
254
|
+
function isoDateSerial(text) {
|
|
255
|
+
const groups = shape.exec(text)?.groups;
|
|
256
|
+
if (groups === void 0) return void 0;
|
|
257
|
+
const { y, mo, d, t, h, mi, s, f } = groups;
|
|
258
|
+
const hasDate = y !== void 0;
|
|
259
|
+
const hasTime = h !== void 0;
|
|
260
|
+
if (!hasDate && !hasTime) return void 0;
|
|
261
|
+
if (t !== void 0 !== (hasDate && hasTime)) return void 0;
|
|
262
|
+
let time = 0;
|
|
263
|
+
if (hasTime) {
|
|
264
|
+
const hours = Number(h);
|
|
265
|
+
const minutes = Number(mi);
|
|
266
|
+
const seconds = s === void 0 ? 0 : Number(s);
|
|
267
|
+
const fraction = f === void 0 ? 0 : Number(`0${f}`);
|
|
268
|
+
if (hours > 23 || minutes > 59 || seconds > 59) return void 0;
|
|
269
|
+
time = (hours * 3600 + minutes * 60 + seconds + fraction) / 86400;
|
|
270
|
+
}
|
|
271
|
+
if (!hasDate) return time;
|
|
272
|
+
const serial = serialFromDate(Number(y), Number(mo), Number(d));
|
|
273
|
+
return serial === void 0 ? void 0 : serial + time;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/must.ts
|
|
277
|
+
function elementAt(list, index) {
|
|
278
|
+
const value = list[index];
|
|
279
|
+
if (value === void 0) {
|
|
280
|
+
throw new Error(`Internal: index ${String(index)} is outside a list of ${String(list.length)}.`);
|
|
281
|
+
}
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
function defined(value, what) {
|
|
285
|
+
if (value === void 0) {
|
|
286
|
+
throw new Error(`Internal: ${what} is missing.`);
|
|
287
|
+
}
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
function unreachable(value) {
|
|
291
|
+
throw new Error(`Internal: ${String(value)} is not a value this switch takes.`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/open-error.ts
|
|
295
|
+
import { XlsxError } from "@ex-grid/data-xlsx";
|
|
296
|
+
var XlsxOpenError = class _XlsxOpenError extends Error {
|
|
297
|
+
/** Why the workbook was refused. */
|
|
298
|
+
reason;
|
|
299
|
+
/** The name of the Sheet the refusal met, or `undefined` when it is not about one Sheet. */
|
|
300
|
+
sheet;
|
|
301
|
+
/** The cell the refusal met, as `B7`, or `undefined` when it is not about one cell. */
|
|
302
|
+
cell;
|
|
303
|
+
/** What the cell held — a Formula's text with its `=`, or a constant — or `undefined` when the refusal is not about one
|
|
304
|
+
* cell. */
|
|
305
|
+
held;
|
|
306
|
+
/**
|
|
307
|
+
* A refusal naming what it met.
|
|
308
|
+
* @param reason - Why the workbook is refused.
|
|
309
|
+
* @param message - What was wrong, as a sentence that follows the Sheet and the cell.
|
|
310
|
+
* @param where - The Sheet, the cell and what the cell held, as far as it is about one.
|
|
311
|
+
* @param options - What caused it, when it was another error.
|
|
312
|
+
*/
|
|
313
|
+
constructor(reason, message, where = {}, options) {
|
|
314
|
+
super(formatted(where.sheet, where.cell, where.held, message), options);
|
|
315
|
+
this.name = "XlsxOpenError";
|
|
316
|
+
this.reason = reason;
|
|
317
|
+
this.sheet = where.sheet;
|
|
318
|
+
this.cell = where.cell;
|
|
319
|
+
this.held = where.held;
|
|
320
|
+
}
|
|
321
|
+
/** Why the file layer refused the file, when `reason` is `file`; otherwise `undefined`. */
|
|
322
|
+
get fileRefusal() {
|
|
323
|
+
return this.reason === "file" && this.cause instanceof XlsxError ? this.cause.reason : void 0;
|
|
324
|
+
}
|
|
325
|
+
/** The file layer's refusal, with its Sheet and cell. @internal */
|
|
326
|
+
static fromFile(e) {
|
|
327
|
+
const refusal = new _XlsxOpenError("file", e.message, { sheet: e.sheet, cell: e.cell }, { cause: e });
|
|
328
|
+
refusal.message = e.message;
|
|
329
|
+
return refusal;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
function formatted(sheet, cell, held2, message) {
|
|
333
|
+
const at = held2 === void 0 ? cell : `${cell === void 0 ? "" : cell} (${held2})`;
|
|
334
|
+
if (sheet === void 0) return message;
|
|
335
|
+
return at === void 0 ? `Sheet '${sheet}': ${message}` : `Sheet '${sheet}', cell ${at}: ${message}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/open-report.ts
|
|
339
|
+
var OpenReport = class {
|
|
340
|
+
/**
|
|
341
|
+
* What the file held that changes how a Sheet looks, or what else the file held, and that ExSheet did not carry
|
|
342
|
+
* (ADR-0330 §4's "Not carried"), in the workbook's order: what the workbook holds first, then each Sheet's in turn. Saving
|
|
343
|
+
* the Workbook again will not keep any of it.
|
|
344
|
+
*/
|
|
345
|
+
notCarried;
|
|
346
|
+
/**
|
|
347
|
+
* The cells whose Formula names a function outside ExSheet's declared set (`DeclaredFunction`), in Sheet order and then
|
|
348
|
+
* row by row: each is carried, and shows `#NAME?` (ADR-0047, ADR-0330 §4).
|
|
349
|
+
*/
|
|
350
|
+
unsupportedFunctions;
|
|
351
|
+
/** @internal */
|
|
352
|
+
constructor(notCarried, unsupportedFunctions) {
|
|
353
|
+
this.notCarried = Object.freeze(notCarried.map((n) => Object.freeze({ ...n })));
|
|
354
|
+
this.unsupportedFunctions = Object.freeze(unsupportedFunctions.map((u) => Object.freeze({ ...u, functions: Object.freeze([...u.functions]) })));
|
|
355
|
+
Object.freeze(this);
|
|
356
|
+
}
|
|
357
|
+
/** Whether the file was carried whole: nothing left behind, and every function it names supported. */
|
|
358
|
+
get isEmpty() {
|
|
359
|
+
return this.notCarried.length === 0 && this.unsupportedFunctions.length === 0;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/sheet-format-plan.ts
|
|
364
|
+
import { Sheet as Sheet2 } from "@ex-grid/sheet-engine";
|
|
365
|
+
var allParts = 31;
|
|
366
|
+
var SheetFormatPlan = class {
|
|
367
|
+
#sheet;
|
|
368
|
+
#columnLevels = /* @__PURE__ */ new Map();
|
|
369
|
+
/** Each formatted column's parts other than the default's, as a mask. */
|
|
370
|
+
#columnParts = /* @__PURE__ */ new Map();
|
|
371
|
+
/** The formatted column runs, in order, each with the parts it records other than the default's, as a mask. */
|
|
372
|
+
#columnRuns = [];
|
|
373
|
+
/** The formatted row runs, in order, each with the parts it leaves to its columns, as a mask. */
|
|
374
|
+
#rowRuns = [];
|
|
375
|
+
/** For each mask of parts a row leaves open, the column runs it crosses and how many columns they hold. */
|
|
376
|
+
#crossed = /* @__PURE__ */ new Map();
|
|
377
|
+
/** The column ranges, in order, each with its width and its format's index; set by `build`. */
|
|
378
|
+
columns = [];
|
|
379
|
+
/** The runs of formatted rows, in order; set by `build`. */
|
|
380
|
+
rows = [];
|
|
381
|
+
/** The plan for `sheet`, its levels read; nothing is written to a styles part yet. */
|
|
382
|
+
constructor(sheet) {
|
|
383
|
+
this.#sheet = sheet;
|
|
384
|
+
for (const run of sheet.columns.toSorted((a, b) => a.first - b.first)) {
|
|
385
|
+
const level = partsOf(run);
|
|
386
|
+
const parts = FormatParts.differing(level.over(defaultFormat), defaultFormat).mask;
|
|
387
|
+
for (let c = run.first; c <= run.last; c++) {
|
|
388
|
+
this.#columnLevels.set(c, level);
|
|
389
|
+
this.#columnParts.set(c, parts);
|
|
390
|
+
}
|
|
391
|
+
this.#columnRuns.push({ first: run.first, last: run.last, level, parts });
|
|
392
|
+
}
|
|
393
|
+
for (const run of sheet.rows.toSorted((a, b) => a.first - b.first)) {
|
|
394
|
+
const level = partsOf(run);
|
|
395
|
+
this.#rowRuns.push({ first: run.first, last: run.last, level, open: ~level.mask & allParts });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/** The rows the row formats write, each a `<row>` element whether or not it holds a cell. */
|
|
399
|
+
get formattedRows() {
|
|
400
|
+
let rows = 0;
|
|
401
|
+
for (const run of this.#rowRuns) rows += run.last - run.first + 1;
|
|
402
|
+
return rows;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* How many cells of no value the crossings write: every place a formatted row crosses a formatted column that records a
|
|
406
|
+
* part the row leaves open, less those of `recorded`, which are written as the Sheet's own cells. Counted from the runs,
|
|
407
|
+
* never cell by cell.
|
|
408
|
+
* @param recorded - The cells the Sheet writes of its own, each once.
|
|
409
|
+
*/
|
|
410
|
+
crossings(recorded) {
|
|
411
|
+
let count2 = 0;
|
|
412
|
+
for (const run of this.#rowRuns) count2 += (run.last - run.first + 1) * this.#crossedBy(run.open).columns;
|
|
413
|
+
if (count2 === 0) return 0;
|
|
414
|
+
for (const at of recorded) {
|
|
415
|
+
const r = this.#rowRunAt(at.row);
|
|
416
|
+
const parts = this.#columnParts.get(at.column);
|
|
417
|
+
if (r !== void 0 && parts !== void 0 && (parts & elementAt(this.#rowRuns, r).open) !== 0) count2--;
|
|
418
|
+
}
|
|
419
|
+
return count2;
|
|
420
|
+
}
|
|
421
|
+
/** Finds every format the columns, the rows and the crossings write, adding each to `styles`. */
|
|
422
|
+
build(styles) {
|
|
423
|
+
const rows = [];
|
|
424
|
+
for (const run of this.#rowRuns) {
|
|
425
|
+
const crossings = [];
|
|
426
|
+
for (const index of this.#crossedBy(run.open).runs) {
|
|
427
|
+
const column = elementAt(this.#columnRuns, index);
|
|
428
|
+
const style = styles.index(run.level.over(column.level.over(defaultFormat)));
|
|
429
|
+
const last = crossings.at(-1);
|
|
430
|
+
if (last !== void 0 && last.last === column.first - 1 && last.style === style) crossings[crossings.length - 1] = { ...last, last: column.last };
|
|
431
|
+
else crossings.push({ first: column.first, last: column.last, style });
|
|
432
|
+
}
|
|
433
|
+
rows.push({ first: run.first, last: run.last, style: styles.index(run.level.over(defaultFormat)), crossings });
|
|
434
|
+
}
|
|
435
|
+
this.rows = rows;
|
|
436
|
+
this.columns = this.#columnRanges(styles);
|
|
437
|
+
}
|
|
438
|
+
/** The index of the format a cell element at `at` is written with: what the Sheet shows there, its own level over its row's
|
|
439
|
+
* and its column's. */
|
|
440
|
+
style(styles, own, at) {
|
|
441
|
+
const column = this.#columnLevels.get(at.column);
|
|
442
|
+
let under = column === void 0 ? defaultFormat : column.over(defaultFormat);
|
|
443
|
+
const r = this.#rowRunAt(at.row);
|
|
444
|
+
if (r !== void 0) under = elementAt(this.#rowRuns, r).level.over(under);
|
|
445
|
+
return styles.index(own.over(under));
|
|
446
|
+
}
|
|
447
|
+
/** The column runs a row that leaves `open` to its columns crosses: those that record one of those parts other than the
|
|
448
|
+
* default's. */
|
|
449
|
+
#crossedBy(open) {
|
|
450
|
+
const known = this.#crossed.get(open);
|
|
451
|
+
if (known !== void 0) return known;
|
|
452
|
+
const runs = [];
|
|
453
|
+
let columns = 0;
|
|
454
|
+
this.#columnRuns.forEach((run, i) => {
|
|
455
|
+
if ((run.parts & open) === 0) return;
|
|
456
|
+
runs.push(i);
|
|
457
|
+
columns += run.last - run.first + 1;
|
|
458
|
+
});
|
|
459
|
+
const crossed = { runs, columns };
|
|
460
|
+
this.#crossed.set(open, crossed);
|
|
461
|
+
return crossed;
|
|
462
|
+
}
|
|
463
|
+
/** The index of the formatted row run that holds `row`, or `undefined`. */
|
|
464
|
+
#rowRunAt(row) {
|
|
465
|
+
let low = 0;
|
|
466
|
+
let high = this.#rowRuns.length - 1;
|
|
467
|
+
while (low <= high) {
|
|
468
|
+
const mid = Math.floor((low + high) / 2);
|
|
469
|
+
const run = elementAt(this.#rowRuns, mid);
|
|
470
|
+
if (row < run.first) high = mid - 1;
|
|
471
|
+
else if (row > run.last) low = mid + 1;
|
|
472
|
+
else return mid;
|
|
473
|
+
}
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
/** The columns' widths and formats as ranges: a range wherever both stay the same. */
|
|
477
|
+
#columnRanges(styles) {
|
|
478
|
+
const widths2 = /* @__PURE__ */ new Map();
|
|
479
|
+
for (const run of this.#sheet.columnWidths) {
|
|
480
|
+
for (let c = run.first; c <= run.last; c++) widths2.set(c, run);
|
|
481
|
+
}
|
|
482
|
+
const ranges = [];
|
|
483
|
+
for (const column of [.../* @__PURE__ */ new Set([...widths2.keys(), ...this.#columnLevels.keys()])].toSorted((a, b) => a - b)) {
|
|
484
|
+
const w = widths2.get(column);
|
|
485
|
+
const width = widthToFile(w === void 0 ? Sheet2.defaultColumnWidth : w.width);
|
|
486
|
+
const bestFit = w !== void 0 && w.kind === "widenedByEntry";
|
|
487
|
+
const level = this.#columnLevels.get(column);
|
|
488
|
+
const styleIndex = level === void 0 ? 0 : styles.index(level.over(defaultFormat));
|
|
489
|
+
const last = ranges.at(-1);
|
|
490
|
+
if (last !== void 0 && last.last === column - 1 && last.width === width && last.styleIndex === styleIndex && last.bestFit === bestFit && last.customWidth === (w !== void 0)) {
|
|
491
|
+
last.last = column;
|
|
492
|
+
} else {
|
|
493
|
+
ranges.push({ first: column, last: column, width, styleIndex, bestFit, customWidth: w !== void 0 });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return ranges;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
function partsOf(run) {
|
|
500
|
+
return new FormatParts(run.numberFormat, run.alignment, run.font, run.fill, run.borders);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/sheet-elements.ts
|
|
504
|
+
var defaultMargins = [["left", 0.7], ["right", 0.7], ["top", 0.75], ["bottom", 0.75], ["header", 0.3], ["footer", 0.3]];
|
|
505
|
+
function elementNotCarried(element) {
|
|
506
|
+
const kinds = [];
|
|
507
|
+
const add2 = (kind, detail) => {
|
|
508
|
+
kinds.push({ kind, detail });
|
|
509
|
+
};
|
|
510
|
+
const attribute = (name) => element.attributes.get(name);
|
|
511
|
+
switch (element.name) {
|
|
512
|
+
// Nothing a Sheet shows or prints: the used range, the sheet's default row (judged below), phonetic settings,
|
|
513
|
+
// dismissed error marks, calculation flags.
|
|
514
|
+
case "dimension":
|
|
515
|
+
case "phoneticPr":
|
|
516
|
+
case "ignoredErrors":
|
|
517
|
+
case "sheetCalcPr":
|
|
518
|
+
break;
|
|
519
|
+
case "sheetFormatPr": {
|
|
520
|
+
if (isTrue(attribute("zeroHeight"))) add2("hiddenRows");
|
|
521
|
+
if (isTrue(attribute("customHeight"))) add2("rowHeights");
|
|
522
|
+
const base = attribute("baseColWidth");
|
|
523
|
+
if (attribute("defaultColWidth") !== void 0 || base !== void 0 && base !== "8") add2("defaultColumnWidth");
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case "sheetViews":
|
|
527
|
+
if (element.children.some(viewSetsAnything)) add2("view");
|
|
528
|
+
break;
|
|
529
|
+
case "sheetPr":
|
|
530
|
+
if (element.children.some((c) => c.name === "tabColor")) add2("sheetProperties");
|
|
531
|
+
if (element.children.some((c) => c.name === "pageSetUpPr" && isTrue(c.attributes.get("fitToPage")))) add2("printSettings");
|
|
532
|
+
break;
|
|
533
|
+
case "pageMargins":
|
|
534
|
+
if (defaultMargins.some(([name, inches]) => {
|
|
535
|
+
const value = attribute(name);
|
|
536
|
+
return value !== void 0 && marginInches(value) !== inches;
|
|
537
|
+
})) {
|
|
538
|
+
add2("printSettings");
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case "printOptions":
|
|
542
|
+
if (["horizontalCentered", "verticalCentered", "headings", "gridLines"].some((a) => isTrue(attribute(a)))) add2("printSettings");
|
|
543
|
+
break;
|
|
544
|
+
case "pageSetup":
|
|
545
|
+
if (pageSetupSetsAnything(element)) add2("printSettings");
|
|
546
|
+
break;
|
|
547
|
+
case "headerFooter":
|
|
548
|
+
if (element.children.some((c) => c.text !== null && trimWhiteSpace(c.text) !== "")) add2("printSettings");
|
|
549
|
+
break;
|
|
550
|
+
case "rowBreaks":
|
|
551
|
+
case "colBreaks":
|
|
552
|
+
if (element.children.some((c) => c.name === "brk") || element.childrenCut) add2("printSettings");
|
|
553
|
+
break;
|
|
554
|
+
case "extLst":
|
|
555
|
+
for (const extension of element.children.filter((e) => e.name === "ext")) {
|
|
556
|
+
for (const held2 of extension.children) kinds.push(...elementNotCarried(held2));
|
|
557
|
+
}
|
|
558
|
+
break;
|
|
559
|
+
case "mergeCells":
|
|
560
|
+
add2("mergedCells");
|
|
561
|
+
break;
|
|
562
|
+
case "conditionalFormatting":
|
|
563
|
+
case "conditionalFormattings":
|
|
564
|
+
add2("conditionalFormatting");
|
|
565
|
+
break;
|
|
566
|
+
case "dataValidations":
|
|
567
|
+
add2("dataValidation");
|
|
568
|
+
break;
|
|
569
|
+
case "legacyDrawing":
|
|
570
|
+
add2("notesAndComments");
|
|
571
|
+
break;
|
|
572
|
+
case "hyperlinks":
|
|
573
|
+
add2("hyperlinks");
|
|
574
|
+
break;
|
|
575
|
+
case "drawing":
|
|
576
|
+
case "picture":
|
|
577
|
+
case "oleObjects":
|
|
578
|
+
case "controls":
|
|
579
|
+
case "legacyDrawingHF":
|
|
580
|
+
case "sparklineGroups":
|
|
581
|
+
add2("picturesAndCharts");
|
|
582
|
+
break;
|
|
583
|
+
case "sheetProtection":
|
|
584
|
+
case "protectedRanges":
|
|
585
|
+
add2("protection");
|
|
586
|
+
break;
|
|
587
|
+
case "autoFilter":
|
|
588
|
+
case "sortState":
|
|
589
|
+
add2("autoFilter");
|
|
590
|
+
break;
|
|
591
|
+
case "tableParts":
|
|
592
|
+
add2("excelTable");
|
|
593
|
+
break;
|
|
594
|
+
case "slicerList":
|
|
595
|
+
case "timelineRefs":
|
|
596
|
+
add2("slicers");
|
|
597
|
+
break;
|
|
598
|
+
default:
|
|
599
|
+
add2("other", element.name);
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
return kinds;
|
|
603
|
+
}
|
|
604
|
+
function viewSetsAnything(view) {
|
|
605
|
+
if (view.name !== "sheetView") return false;
|
|
606
|
+
const a = (name) => view.attributes.get(name);
|
|
607
|
+
const zoom = a("zoomScale");
|
|
608
|
+
const layout = a("view");
|
|
609
|
+
return isFalse(a("showGridLines")) || isFalse(a("showRowColHeaders")) || isFalse(a("showZeros")) || isTrue(a("showFormulas")) || isTrue(a("rightToLeft")) || zoom !== void 0 && zoom !== "100" || layout !== void 0 && layout !== "normal";
|
|
610
|
+
}
|
|
611
|
+
function pageSetupSetsAnything(element) {
|
|
612
|
+
for (const [name, value] of element.attributes) {
|
|
613
|
+
let sets;
|
|
614
|
+
switch (name) {
|
|
615
|
+
case "horizontalDpi":
|
|
616
|
+
case "verticalDpi":
|
|
617
|
+
case "id":
|
|
618
|
+
case "usePrinterDefaults":
|
|
619
|
+
sets = false;
|
|
620
|
+
break;
|
|
621
|
+
case "paperSize":
|
|
622
|
+
sets = value !== "1" && value !== "9";
|
|
623
|
+
break;
|
|
624
|
+
case "orientation":
|
|
625
|
+
sets = value !== "default" && value !== "portrait";
|
|
626
|
+
break;
|
|
627
|
+
case "scale":
|
|
628
|
+
sets = value !== "100";
|
|
629
|
+
break;
|
|
630
|
+
case "fitToWidth":
|
|
631
|
+
case "fitToHeight":
|
|
632
|
+
case "firstPageNumber":
|
|
633
|
+
case "copies":
|
|
634
|
+
sets = value !== "1";
|
|
635
|
+
break;
|
|
636
|
+
case "pageOrder":
|
|
637
|
+
sets = value !== "downThenOver";
|
|
638
|
+
break;
|
|
639
|
+
case "cellComments":
|
|
640
|
+
sets = value !== "none";
|
|
641
|
+
break;
|
|
642
|
+
case "errors":
|
|
643
|
+
sets = value !== "displayed";
|
|
644
|
+
break;
|
|
645
|
+
case "blackAndWhite":
|
|
646
|
+
case "draft":
|
|
647
|
+
case "useFirstPageNumber":
|
|
648
|
+
sets = isTrue(value);
|
|
649
|
+
break;
|
|
650
|
+
default:
|
|
651
|
+
sets = true;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
if (sets) return true;
|
|
655
|
+
}
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
function isTrue(value) {
|
|
659
|
+
return value === "1" || value === "true";
|
|
660
|
+
}
|
|
661
|
+
function isFalse(value) {
|
|
662
|
+
return value === "0" || value === "false";
|
|
663
|
+
}
|
|
664
|
+
function marginInches(value) {
|
|
665
|
+
const inches = parseDouble(value);
|
|
666
|
+
return inches === void 0 ? Number.NaN : inches;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/stored-formula.ts
|
|
670
|
+
var prefixes = ["_xlfn.", "_xlws.", "_xlpm."];
|
|
671
|
+
function readStoredFormula(stored) {
|
|
672
|
+
const refusal = refusalOf(stored);
|
|
673
|
+
if (refusal !== "none") return { read: `=${stored}`, problem: refusal };
|
|
674
|
+
const anchored = anchorArrays(stripPrefixes(stored));
|
|
675
|
+
return { read: `=${anchored.text}`, problem: anchored.read ? "none" : "spillRange" };
|
|
676
|
+
}
|
|
677
|
+
function stripPrefixes(stored) {
|
|
678
|
+
let text = "";
|
|
679
|
+
let i = 0;
|
|
680
|
+
while (i < stored.length) {
|
|
681
|
+
const c = stored.charAt(i);
|
|
682
|
+
if (c === '"' || c === "'") {
|
|
683
|
+
const end = skipQuoted(stored, i);
|
|
684
|
+
text += stored.slice(i, end);
|
|
685
|
+
i = end;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (c === "_" && (text.length === 0 || !isNameChar(text.charAt(text.length - 1)))) {
|
|
689
|
+
const prefix = prefixes.find((p) => startsWithIgnoreCase(stored, i, p));
|
|
690
|
+
if (prefix !== void 0) {
|
|
691
|
+
i += prefix.length;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
text += c;
|
|
696
|
+
i++;
|
|
697
|
+
}
|
|
698
|
+
return text;
|
|
699
|
+
}
|
|
700
|
+
function refusalOf(stored) {
|
|
701
|
+
for (let i = 0; i < stored.length; i++) {
|
|
702
|
+
const c = stored.charAt(i);
|
|
703
|
+
if (c === '"') {
|
|
704
|
+
i = skipQuoted(stored, i) - 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (c === "'") {
|
|
708
|
+
if (i + 1 < stored.length && stored.charAt(i + 1) === "[") return "otherWorkbook";
|
|
709
|
+
i = skipQuoted(stored, i) - 1;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (c === "[") {
|
|
713
|
+
const before = i > 0 ? stored.charAt(i - 1) : "\0";
|
|
714
|
+
if (isNameChar(before)) return "excelTable";
|
|
715
|
+
return /^\[[0-9]+\]/.test(stored.slice(i)) ? "otherWorkbook" : "excelTable";
|
|
716
|
+
}
|
|
717
|
+
if (c === "@") return "implicitIntersection";
|
|
718
|
+
if (c === "_" && (i === 0 || !isNameChar(stored.charAt(i - 1))) && startsWithIgnoreCase(stored, i, "_xlfn.SINGLE(")) return "implicitIntersection";
|
|
719
|
+
}
|
|
720
|
+
return "none";
|
|
721
|
+
}
|
|
722
|
+
var cellPattern = /^(?:'(?:[^']|'')+'!|[A-Za-z0-9_.\\]+!)?\$?[A-Za-z]{1,3}\$?[0-9]+$/;
|
|
723
|
+
function anchorArrays(text) {
|
|
724
|
+
const name = "ANCHORARRAY(";
|
|
725
|
+
let written = "";
|
|
726
|
+
let anchored = false;
|
|
727
|
+
let at = 0;
|
|
728
|
+
for (let i = 0; i < text.length; i++) {
|
|
729
|
+
const c = text.charAt(i);
|
|
730
|
+
if (c === '"' || c === "'") {
|
|
731
|
+
i = skipQuoted(text, i) - 1;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (c !== "A" && c !== "a" || i > 0 && isNameChar(text.charAt(i - 1)) || !startsWithIgnoreCase(text, i, name)) continue;
|
|
735
|
+
const open = i + name.length;
|
|
736
|
+
let close = open;
|
|
737
|
+
while (close < text.length && text.charAt(close) !== ")") {
|
|
738
|
+
close = text.charAt(close) === '"' || text.charAt(close) === "'" ? skipQuoted(text, close) : close + 1;
|
|
739
|
+
}
|
|
740
|
+
const inner = close < text.length ? trimWhiteSpace(text.slice(open, close)) : "";
|
|
741
|
+
if (!cellPattern.test(inner)) return { text, read: false };
|
|
742
|
+
written += text.slice(at, i) + inner + "#";
|
|
743
|
+
anchored = true;
|
|
744
|
+
at = close + 1;
|
|
745
|
+
i = close;
|
|
746
|
+
}
|
|
747
|
+
return anchored ? { text: written + text.slice(at), read: true } : { text, read: true };
|
|
748
|
+
}
|
|
749
|
+
function skipQuoted(text, start) {
|
|
750
|
+
const quote = text.charAt(start);
|
|
751
|
+
let i = start + 1;
|
|
752
|
+
while (i < text.length) {
|
|
753
|
+
if (text.charAt(i) === quote) {
|
|
754
|
+
if (i + 1 < text.length && text.charAt(i + 1) === quote) {
|
|
755
|
+
i += 2;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
return i + 1;
|
|
759
|
+
}
|
|
760
|
+
i++;
|
|
761
|
+
}
|
|
762
|
+
return text.length;
|
|
763
|
+
}
|
|
764
|
+
function isNameChar(c) {
|
|
765
|
+
return /^[\p{L}\p{Nd}_.\\?]$/u.test(c);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/style-reading.ts
|
|
769
|
+
import {
|
|
770
|
+
BorderLine,
|
|
771
|
+
CellBorders as CellBorders2,
|
|
772
|
+
CellColour,
|
|
773
|
+
CellFill as CellFill2,
|
|
774
|
+
CellFont as CellFont2,
|
|
775
|
+
CellFormat as CellFormat2,
|
|
776
|
+
NumberFormat as NumberFormat3
|
|
777
|
+
} from "@ex-grid/sheet-engine";
|
|
778
|
+
import {
|
|
779
|
+
defaultAlignment
|
|
780
|
+
} from "@ex-grid/data-xlsx";
|
|
781
|
+
|
|
782
|
+
// src/number-formats.ts
|
|
783
|
+
import { NumberFormat as NumberFormat2 } from "@ex-grid/sheet-engine";
|
|
784
|
+
import { firstCustomNumberFormat } from "@ex-grid/data-xlsx";
|
|
785
|
+
var builtIns = /* @__PURE__ */ new Map([
|
|
786
|
+
[0, "General"],
|
|
787
|
+
[1, "0"],
|
|
788
|
+
[2, "0.00"],
|
|
789
|
+
[3, "#,##0"],
|
|
790
|
+
[4, "#,##0.00"],
|
|
791
|
+
[9, "0%"],
|
|
792
|
+
[10, "0.00%"],
|
|
793
|
+
[11, "0.00E+00"],
|
|
794
|
+
[12, "# ?/?"],
|
|
795
|
+
[13, "# ??/??"],
|
|
796
|
+
[14, "m/d/yyyy"],
|
|
797
|
+
[15, "d-mmm-yy"],
|
|
798
|
+
[16, "d-mmm"],
|
|
799
|
+
[17, "mmm-yy"],
|
|
800
|
+
[18, "h:mm AM/PM"],
|
|
801
|
+
[19, "h:mm:ss AM/PM"],
|
|
802
|
+
[20, "h:mm"],
|
|
803
|
+
[21, "h:mm:ss"],
|
|
804
|
+
[22, "m/d/yyyy h:mm"],
|
|
805
|
+
[37, "#,##0 ;(#,##0)"],
|
|
806
|
+
[38, "#,##0 ;[Red](#,##0)"],
|
|
807
|
+
[39, "#,##0.00;(#,##0.00)"],
|
|
808
|
+
[40, "#,##0.00;[Red](#,##0.00)"],
|
|
809
|
+
[45, "mm:ss"],
|
|
810
|
+
[46, "[h]:mm:ss"],
|
|
811
|
+
[47, "mmss.0"],
|
|
812
|
+
[48, "##0.0E+0"],
|
|
813
|
+
[49, "@"]
|
|
814
|
+
]);
|
|
815
|
+
var localised = /* @__PURE__ */ new Set([14, 15, 16, 20, 22]);
|
|
816
|
+
var builtInCurrencies = ["$#,##0.00_);[Red]($#,##0.00)", "$#,##0_);[Red]($#,##0)"];
|
|
817
|
+
function writtenCode(format, culture) {
|
|
818
|
+
return builtInCurrencies.some((c) => equalsIgnoreCase(c, format.code)) ? format.localCode(culture) : format.code;
|
|
819
|
+
}
|
|
820
|
+
function readNumberFormat(id, code) {
|
|
821
|
+
const builtIn = builtIns.get(id);
|
|
822
|
+
if (builtIn !== void 0) {
|
|
823
|
+
const reading2 = NumberFormat2.tryParse(builtIn);
|
|
824
|
+
if (reading2.format !== void 0) return { format: reading2.format };
|
|
825
|
+
return { reason: `Excel's built-in Number Format ${String(id)} (${builtIn}) is not one the engine reads: ${reading2.reason}`, held: builtIn };
|
|
826
|
+
}
|
|
827
|
+
if (code === void 0) {
|
|
828
|
+
return {
|
|
829
|
+
held: `Number Format ${String(id)}`,
|
|
830
|
+
reason: id < firstCustomNumberFormat ? `The cell takes Excel's built-in Number Format ${String(id)}, whose code follows the computer's locale, and the file does not spell it out.` : `The cell takes the Number Format ${String(id)}, which the file does not hold.`
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
const reading = NumberFormat2.tryParseAsWritten(code);
|
|
834
|
+
if (reading.format !== void 0) return { format: reading.format };
|
|
835
|
+
return { reason: `The Number Format '${code}' is not one the engine reads: ${reading.reason}`, held: code };
|
|
836
|
+
}
|
|
837
|
+
function builtInId(format) {
|
|
838
|
+
for (const [id, code] of builtIns) {
|
|
839
|
+
const same2 = localised.has(id) ? equalsIgnoreCase(code, format.code) : code === format.code;
|
|
840
|
+
if (same2) return id;
|
|
841
|
+
}
|
|
842
|
+
return format.isGeneral ? 0 : void 0;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/style-reading.ts
|
|
846
|
+
var StyleReading = class {
|
|
847
|
+
#styles;
|
|
848
|
+
#theme;
|
|
849
|
+
#read = /* @__PURE__ */ new Map();
|
|
850
|
+
#workbookFont;
|
|
851
|
+
constructor(styles, theme) {
|
|
852
|
+
this.#styles = styles;
|
|
853
|
+
this.#theme = theme;
|
|
854
|
+
this.#workbookFont = elementAt(styles.fonts, 0);
|
|
855
|
+
}
|
|
856
|
+
/** The cell format at `index` of the styles' cell formats, read once. */
|
|
857
|
+
at(index) {
|
|
858
|
+
let format = this.#read.get(index);
|
|
859
|
+
if (format === void 0) {
|
|
860
|
+
format = this.#readFormat(elementAt(this.#styles.cellFormats, index));
|
|
861
|
+
this.#read.set(index, format);
|
|
862
|
+
}
|
|
863
|
+
return format;
|
|
864
|
+
}
|
|
865
|
+
#readFormat(xf) {
|
|
866
|
+
const styles = this.#styles;
|
|
867
|
+
const notCarried = [];
|
|
868
|
+
let namesTheme = false;
|
|
869
|
+
const note = (kind) => {
|
|
870
|
+
if (!notCarried.includes(kind)) notCarried.push(kind);
|
|
871
|
+
};
|
|
872
|
+
const colour = (color2) => {
|
|
873
|
+
if (color2 === null) return CellColour.automatic;
|
|
874
|
+
const argb = this.#theme.resolve(color2, styles.indexedColors);
|
|
875
|
+
if (argb === void 0) return CellColour.automatic;
|
|
876
|
+
namesTheme ||= color2.kind === "theme";
|
|
877
|
+
return CellColour.fromRgb(argb & 16777215);
|
|
878
|
+
};
|
|
879
|
+
let refusal;
|
|
880
|
+
let held2;
|
|
881
|
+
let numberFormat = NumberFormat3.general;
|
|
882
|
+
const read = readNumberFormat(xf.numberFormatId, styles.numberFormats.get(xf.numberFormatId));
|
|
883
|
+
if (read.format !== void 0) {
|
|
884
|
+
numberFormat = read.format;
|
|
885
|
+
} else {
|
|
886
|
+
refusal = read.reason;
|
|
887
|
+
held2 = read.held;
|
|
888
|
+
}
|
|
889
|
+
const a = xf.alignment === null ? defaultAlignment : xf.alignment;
|
|
890
|
+
let alignment;
|
|
891
|
+
switch (a.horizontal) {
|
|
892
|
+
case "general":
|
|
893
|
+
case "left":
|
|
894
|
+
case "center":
|
|
895
|
+
case "right":
|
|
896
|
+
alignment = a.horizontal;
|
|
897
|
+
break;
|
|
898
|
+
case "fill":
|
|
899
|
+
case "justify":
|
|
900
|
+
case "centerContinuous":
|
|
901
|
+
case "distributed":
|
|
902
|
+
note("horizontalAlignment");
|
|
903
|
+
break;
|
|
904
|
+
default:
|
|
905
|
+
return unreachableAlignment(a.horizontal);
|
|
906
|
+
}
|
|
907
|
+
if (a.vertical !== "bottom") note("verticalAlignment");
|
|
908
|
+
if (a.wrapText || a.indent !== 0 || a.textRotation !== 0 || a.shrinkToFit) note("textLayout");
|
|
909
|
+
const f = elementAt(styles.fonts, xf.fontId);
|
|
910
|
+
if (!sameFace(f, this.#workbookFont)) note("fontNamesAndSizes");
|
|
911
|
+
if (f.underline !== "none" && f.underline !== "single") note("underline");
|
|
912
|
+
const font = new CellFont2({ colour: this.#fontColour(f.color, colour), bold: f.bold, italic: f.italic, underline: f.underline !== "none", strikethrough: f.strike });
|
|
913
|
+
let fill = CellFill2.none;
|
|
914
|
+
const x = elementAt(styles.fills, xf.fillId);
|
|
915
|
+
if (x.isGradient || x.pattern !== "none" && x.pattern !== "solid") {
|
|
916
|
+
note("patternFills");
|
|
917
|
+
} else if (x.pattern === "solid") {
|
|
918
|
+
const solid = colour(x.foreground);
|
|
919
|
+
if (solid.isAutomatic) note("patternFills");
|
|
920
|
+
else fill = CellFill2.solid(solid);
|
|
921
|
+
}
|
|
922
|
+
const b = elementAt(styles.borders, xf.borderId);
|
|
923
|
+
if ((b.diagonalUp || b.diagonalDown) && b.diagonal.style !== "none") note("diagonalBorders");
|
|
924
|
+
const line = (edge2) => edge2.style === "none" ? BorderLine.none : new BorderLine(lineStyle(edge2.style), colour(edge2.color));
|
|
925
|
+
const borders = new CellBorders2({ top: line(b.top), bottom: line(b.bottom), left: line(b.left), right: line(b.right) });
|
|
926
|
+
return {
|
|
927
|
+
format: new CellFormat2(numberFormat, alignment === void 0 ? "general" : alignment, font, fill, borders),
|
|
928
|
+
refusal,
|
|
929
|
+
held: held2,
|
|
930
|
+
notCarried,
|
|
931
|
+
namesTheme
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* A Font's colour: the workbook font's own colour, where it paints black, is Automatic, the Ink, as Excel's own default
|
|
936
|
+
* text (Text 1 of the Office theme) is; any other is the RGB it resolves to.
|
|
937
|
+
*/
|
|
938
|
+
#fontColour(color2, colour) {
|
|
939
|
+
const own = this.#workbookFont.color;
|
|
940
|
+
if (color2 !== null && own !== null && sameColor(color2, own)) {
|
|
941
|
+
const argb = this.#theme.resolve(color2, this.#styles.indexedColors);
|
|
942
|
+
if (argb === void 0 || argb >>> 0 === 4278190080 || argb === 0) return CellColour.automatic;
|
|
943
|
+
}
|
|
944
|
+
return colour(color2);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
function unreachableAlignment(value) {
|
|
948
|
+
throw new Error(`Internal: ${String(value)} is not a horizontal alignment.`);
|
|
949
|
+
}
|
|
950
|
+
function sameColor(a, b) {
|
|
951
|
+
return a.kind === b.kind && a.value === b.value && a.tint === b.tint;
|
|
952
|
+
}
|
|
953
|
+
function sameFace(font, workbook) {
|
|
954
|
+
return (font.name === null || workbook.name !== null && equalsIgnoreCase(font.name, workbook.name)) && (font.size === null || font.size === workbook.size);
|
|
955
|
+
}
|
|
956
|
+
var fromFile = /* @__PURE__ */ new Map([
|
|
957
|
+
["thin", "thin"],
|
|
958
|
+
["medium", "medium"],
|
|
959
|
+
["dashed", "dashed"],
|
|
960
|
+
["dotted", "dotted"],
|
|
961
|
+
["thick", "thick"],
|
|
962
|
+
["double", "double"],
|
|
963
|
+
["hair", "hair"],
|
|
964
|
+
["mediumDashed", "mediumDashed"],
|
|
965
|
+
["dashDot", "dashDot"],
|
|
966
|
+
["mediumDashDot", "mediumDashDot"],
|
|
967
|
+
["dashDotDot", "dashDotDot"],
|
|
968
|
+
["mediumDashDotDot", "mediumDashDotDot"],
|
|
969
|
+
["slantDashDot", "slantedDashDot"]
|
|
970
|
+
]);
|
|
971
|
+
function lineStyle(style) {
|
|
972
|
+
const engine = fromFile.get(style);
|
|
973
|
+
return engine === void 0 ? "none" : engine;
|
|
974
|
+
}
|
|
975
|
+
function fileLineStyle(style) {
|
|
976
|
+
for (const [file, engine] of fromFile) {
|
|
977
|
+
if (engine === style) return file;
|
|
978
|
+
}
|
|
979
|
+
return "none";
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/reader.ts
|
|
983
|
+
async function openXlsxWorkbook(file, culture, options) {
|
|
984
|
+
const workbookCulture = typeof culture === "string" ? Culture.of(culture) : culture;
|
|
985
|
+
let reader;
|
|
986
|
+
try {
|
|
987
|
+
reader = await openXlsx(file, options);
|
|
988
|
+
} catch (e) {
|
|
989
|
+
if (e instanceof XlsxError2) throw XlsxOpenError.fromFile(e);
|
|
990
|
+
throw e;
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
return await new Mapping(reader, workbookCulture, options?.signal).read();
|
|
994
|
+
} catch (e) {
|
|
995
|
+
if (e instanceof XlsxError2) throw XlsxOpenError.fromFile(e);
|
|
996
|
+
throw e;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
var days1904 = 1462;
|
|
1000
|
+
var filterDatabase = "_xlnm._FilterDatabase";
|
|
1001
|
+
var printArea = "_xlnm.Print_Area";
|
|
1002
|
+
var printTitles = "_xlnm.Print_Titles";
|
|
1003
|
+
var Mapping = class {
|
|
1004
|
+
#reader;
|
|
1005
|
+
#culture;
|
|
1006
|
+
#signal;
|
|
1007
|
+
#notCarried = [];
|
|
1008
|
+
#unsupported = [];
|
|
1009
|
+
#definedNames;
|
|
1010
|
+
#hasExcelTables;
|
|
1011
|
+
#dateStyles = /* @__PURE__ */ new Map();
|
|
1012
|
+
#styles;
|
|
1013
|
+
#namesTheme = false;
|
|
1014
|
+
constructor(reader, culture, signal) {
|
|
1015
|
+
this.#reader = reader;
|
|
1016
|
+
this.#culture = culture;
|
|
1017
|
+
this.#signal = signal;
|
|
1018
|
+
this.#definedNames = new Set(reader.definedNames.map((n) => foldCase(n.name)));
|
|
1019
|
+
this.#hasExcelTables = reader.parts.some((p) => startsWithIgnoreCase(p, 0, "xl/tables/"));
|
|
1020
|
+
this.#styles = new StyleReading(reader.styles, reader.theme);
|
|
1021
|
+
}
|
|
1022
|
+
async read() {
|
|
1023
|
+
const reader = this.#reader;
|
|
1024
|
+
const worksheets = [];
|
|
1025
|
+
const names = /* @__PURE__ */ new Map();
|
|
1026
|
+
for (let i = 0; i < reader.sheets.length; i++) {
|
|
1027
|
+
const info = elementAt(reader.sheets, i);
|
|
1028
|
+
if (info.kind !== "worksheet" || info.part === null) {
|
|
1029
|
+
this.#notCarried.push({
|
|
1030
|
+
kind: "chartSheet",
|
|
1031
|
+
sheet: info.name,
|
|
1032
|
+
detail: null,
|
|
1033
|
+
description: `The sheet '${info.name}' is not a worksheet (a chart sheet, or another that holds no cells), and is not opened.`
|
|
1034
|
+
});
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
const why = Sheet3.whyNotAName(info.name);
|
|
1038
|
+
if (why !== void 0) throw new XlsxOpenError("sheets", `The worksheet's name is not one a Sheet can have: ${why}`, { sheet: info.name });
|
|
1039
|
+
const same2 = names.get(foldCase(info.name));
|
|
1040
|
+
if (same2 !== void 0) {
|
|
1041
|
+
throw new XlsxOpenError(
|
|
1042
|
+
"sheets",
|
|
1043
|
+
`The worksheets '${same2}' and '${info.name}' have names that differ only in case, which two Sheets of one Workbook cannot have.`,
|
|
1044
|
+
{ sheet: info.name }
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
names.set(foldCase(info.name), info.name);
|
|
1048
|
+
worksheets.push(i);
|
|
1049
|
+
}
|
|
1050
|
+
if (worksheets.length === 0) throw new XlsxOpenError("sheets", "The workbook holds no worksheet, so it holds no cells to open.");
|
|
1051
|
+
this.#workbookParts();
|
|
1052
|
+
for (const name of reader.definedNames) {
|
|
1053
|
+
const local = name.localSheet;
|
|
1054
|
+
if (local !== null && worksheets.includes(local)) continue;
|
|
1055
|
+
const shown = builtInName(name);
|
|
1056
|
+
if (shown === void 0) continue;
|
|
1057
|
+
const sheet = local !== null && local >= 0 && local < reader.sheets.length ? elementAt(reader.sheets, local).name : null;
|
|
1058
|
+
this.#notCarried.push({ kind: "definedName", sheet, detail: shown, description: describeName(name, shown, sheet) });
|
|
1059
|
+
}
|
|
1060
|
+
const workbookEntries = this.#notCarried.length;
|
|
1061
|
+
const sheets = [];
|
|
1062
|
+
for (const index of worksheets) {
|
|
1063
|
+
const info = elementAt(reader.sheets, index);
|
|
1064
|
+
if (info.visibility !== "visible") {
|
|
1065
|
+
this.#notCarried.push({ kind: "hiddenSheet", sheet: info.name, detail: null, description: `The Sheet '${info.name}' is hidden in the file, and opens shown.` });
|
|
1066
|
+
}
|
|
1067
|
+
let printNames = false;
|
|
1068
|
+
for (const name of reader.definedNames) {
|
|
1069
|
+
if (name.localSheet !== index) continue;
|
|
1070
|
+
if (equalsIgnoreCase(name.name, printArea) || equalsIgnoreCase(name.name, printTitles)) {
|
|
1071
|
+
printNames = true;
|
|
1072
|
+
} else {
|
|
1073
|
+
const shown = builtInName(name);
|
|
1074
|
+
if (shown !== void 0) {
|
|
1075
|
+
this.#notCarried.push({ kind: "definedName", sheet: info.name, detail: shown, description: describeName(name, shown, info.name) });
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
sheets.push(await this.#readSheet(index, printNames));
|
|
1080
|
+
}
|
|
1081
|
+
if (this.#namesTheme) {
|
|
1082
|
+
this.#notCarried.splice(workbookEntries, 0, {
|
|
1083
|
+
kind: "themeColours",
|
|
1084
|
+
sheet: null,
|
|
1085
|
+
detail: null,
|
|
1086
|
+
description: "Colours of the workbook name its theme: each is carried as the RGB value the file's theme gives it, and the link to the theme is not, so another theme will not recolour them."
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
const shownIndex = worksheets.includes(reader.activeSheet) ? reader.activeSheet : elementAt(worksheets, 0);
|
|
1090
|
+
const document = WorkbookDocument.of(this.#culture, elementAt(reader.sheets, shownIndex).name, sheets);
|
|
1091
|
+
return { document, report: new OpenReport(this.#notCarried, this.#unsupported) };
|
|
1092
|
+
}
|
|
1093
|
+
/** What the workbook as a whole holds, beside its sheets, as its parts say. */
|
|
1094
|
+
#workbookParts() {
|
|
1095
|
+
const parts = this.#reader.parts;
|
|
1096
|
+
const has = (folder) => parts.some((p) => startsWithIgnoreCase(p, 0, folder));
|
|
1097
|
+
const add2 = (kind, description) => {
|
|
1098
|
+
this.#notCarried.push({ kind, sheet: null, detail: null, description });
|
|
1099
|
+
};
|
|
1100
|
+
if (has("xl/pivotTables/")) add2("pivotTables", "The workbook holds PivotTables: the cells they show are carried as constants, the PivotTables are not.");
|
|
1101
|
+
if (has("xl/externalLinks/")) add2("externalLinks", "The workbook holds links to other workbooks, which no Formula carried reads; they are not carried.");
|
|
1102
|
+
if (has("xl/connections.xml") || has("xl/queryTables/")) {
|
|
1103
|
+
add2("dataConnections", "The workbook holds connections to outside data, which are not carried; the cells they filled are carried as constants.");
|
|
1104
|
+
}
|
|
1105
|
+
if (has("xl/slicers/") || has("xl/slicerCaches/") || has("xl/timelines/") || has("xl/timelineCaches/")) {
|
|
1106
|
+
add2("slicers", "The workbook holds slicers or timelines, which are not carried.");
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
async #readSheet(index, printNames) {
|
|
1110
|
+
const sheet = await this.#reader.openSheet(index);
|
|
1111
|
+
try {
|
|
1112
|
+
const name = sheet.sheet.name;
|
|
1113
|
+
const cells = [];
|
|
1114
|
+
const shared = /* @__PURE__ */ new Map();
|
|
1115
|
+
let spills = [];
|
|
1116
|
+
let spillRow = -1;
|
|
1117
|
+
let richCells = 0;
|
|
1118
|
+
let firstRich;
|
|
1119
|
+
const formats = [];
|
|
1120
|
+
let columns;
|
|
1121
|
+
const rowRuns = [];
|
|
1122
|
+
const rowLevels = /* @__PURE__ */ new Map();
|
|
1123
|
+
let lastRow;
|
|
1124
|
+
while (await sheet.read()) {
|
|
1125
|
+
this.#signal?.throwIfAborted();
|
|
1126
|
+
columns ??= this.#readColumns(name, sheet.columns, formats);
|
|
1127
|
+
rowLevels.clear();
|
|
1128
|
+
if (lastRow !== void 0) rowLevels.set(lastRow.row, lastRow.level);
|
|
1129
|
+
for (const row of sheet.rows) {
|
|
1130
|
+
const level = this.#readRow(name, row, columns, formats);
|
|
1131
|
+
rowLevels.set(row.row, level);
|
|
1132
|
+
lastRow = { row: row.row, level };
|
|
1133
|
+
addRun(rowRuns, row.row, level);
|
|
1134
|
+
}
|
|
1135
|
+
for (const cell of sheet.cells) {
|
|
1136
|
+
const at = formatCell(cell.row, cell.column);
|
|
1137
|
+
if (cell.isRichText) {
|
|
1138
|
+
richCells++;
|
|
1139
|
+
firstRich ??= at;
|
|
1140
|
+
}
|
|
1141
|
+
if (cell.row !== spillRow) {
|
|
1142
|
+
spillRow = cell.row;
|
|
1143
|
+
if (spills.length > 0) spills = spills.filter((s) => s.last.row >= spillRow);
|
|
1144
|
+
}
|
|
1145
|
+
const address = new CellAddress(cell.row, cell.column);
|
|
1146
|
+
const entry = cell.formula !== null ? this.#formulaEntry(name, cell, at, cell.formula, shared, spills) : insideSpill(spills, address) ? void 0 : this.#constant(name, cell, at);
|
|
1147
|
+
const rowLevel = rowLevels.get(cell.row);
|
|
1148
|
+
const own = this.#readCell(name, cell, at, columns, rowLevel === void 0 ? FormatParts.empty : rowLevel, formats);
|
|
1149
|
+
if (entry !== void 0 || !own.isEmpty) {
|
|
1150
|
+
cells.push(new SheetDocumentCell(address, entry, own.numberFormat, own.alignment, own.font, own.fill, own.borders));
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
columns ??= this.#readColumns(name, sheet.columns, formats);
|
|
1155
|
+
this.#sheetElements(name, sheet, richCells, firstRich, printNames, formats);
|
|
1156
|
+
return SheetDocument.of(this.#culture, name, cells, { columns: columns.runs, rows: rowRuns, columnWidths: widths(sheet.columns) });
|
|
1157
|
+
} finally {
|
|
1158
|
+
await sheet.close();
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
/** The cell format at `style`, refusing the file where a level records a Number Format that cannot cross. */
|
|
1162
|
+
#format(sheet, style, place, formats) {
|
|
1163
|
+
const format = this.#styles.at(style);
|
|
1164
|
+
if (format.refusal !== void 0) throw new XlsxOpenError("numberFormat", format.refusal, { sheet, cell: place, held: format.held });
|
|
1165
|
+
this.#namesTheme ||= format.namesTheme;
|
|
1166
|
+
for (const kind of format.notCarried) {
|
|
1167
|
+
if (!formats.some((f) => f.kind === kind)) formats.push({ kind, detail: place });
|
|
1168
|
+
}
|
|
1169
|
+
return format;
|
|
1170
|
+
}
|
|
1171
|
+
#readColumns(sheet, columns, formats) {
|
|
1172
|
+
const byColumn = /* @__PURE__ */ new Map();
|
|
1173
|
+
const runs = [];
|
|
1174
|
+
for (const range of byFirst(columns)) {
|
|
1175
|
+
if (range.styleIndex === 0) continue;
|
|
1176
|
+
const place = `${CellAddress.columnName(range.first)}:${CellAddress.columnName(range.last)}`;
|
|
1177
|
+
const level = FormatParts.differing(this.#format(sheet, range.styleIndex, place, formats).format, defaultFormat);
|
|
1178
|
+
if (level.isEmpty) continue;
|
|
1179
|
+
for (let c = range.first; c <= range.last; c++) byColumn.set(c, level);
|
|
1180
|
+
const last = runs.at(-1);
|
|
1181
|
+
if (last !== void 0 && last.last === range.first - 1 && partsOf(last).equals(level)) {
|
|
1182
|
+
runs[runs.length - 1] = axisFormat(last.first, range.last, level);
|
|
1183
|
+
} else {
|
|
1184
|
+
runs.push(axisFormat(range.first, range.last, level));
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const recorded = new FormatParts(
|
|
1188
|
+
runs.some((r) => r.numberFormat !== void 0) ? NumberFormat4.general : void 0,
|
|
1189
|
+
runs.some((r) => r.alignment !== void 0) ? "general" : void 0,
|
|
1190
|
+
runs.some((r) => r.font !== void 0) ? CellFont3.default : void 0,
|
|
1191
|
+
runs.some((r) => r.fill !== void 0) ? CellFill3.none : void 0,
|
|
1192
|
+
runs.some((r) => r.borders !== void 0) ? CellBorders3.none : void 0
|
|
1193
|
+
);
|
|
1194
|
+
return { byColumn, runs, recorded };
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* A formatted row's level: each part that differs from the default, and each part a column records, so that the row
|
|
1198
|
+
* stands in place of its columns as in the file.
|
|
1199
|
+
*/
|
|
1200
|
+
#readRow(sheet, row, columns, formats) {
|
|
1201
|
+
const number = String(row.row + 1);
|
|
1202
|
+
const format = this.#format(sheet, row.styleIndex, `${number}:${number}`, formats).format;
|
|
1203
|
+
const differing = FormatParts.differing(format, defaultFormat);
|
|
1204
|
+
const recorded = columns.recorded;
|
|
1205
|
+
return new FormatParts(
|
|
1206
|
+
differing.numberFormat ?? (recorded.numberFormat === void 0 ? void 0 : format.numberFormat),
|
|
1207
|
+
differing.alignment ?? (recorded.alignment === void 0 ? void 0 : format.alignment),
|
|
1208
|
+
differing.font ?? (recorded.font === void 0 ? void 0 : format.font),
|
|
1209
|
+
differing.fill ?? (recorded.fill === void 0 ? void 0 : format.fill),
|
|
1210
|
+
differing.borders ?? (recorded.borders === void 0 ? void 0 : format.borders)
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
/** A cell's own level: the parts of its format that differ from what its row and column give it. In the file a cell's format stands alone. */
|
|
1214
|
+
#readCell(sheet, cell, at, columns, row, formats) {
|
|
1215
|
+
const format = this.#format(sheet, cell.styleIndex, at, formats).format;
|
|
1216
|
+
const level = columns.byColumn.get(cell.column);
|
|
1217
|
+
const column = level === void 0 ? FormatParts.empty : level;
|
|
1218
|
+
return FormatParts.differing(format, row.over(column.over(defaultFormat)));
|
|
1219
|
+
}
|
|
1220
|
+
#formulaEntry(sheet, cell, at, formula, shared, spills) {
|
|
1221
|
+
switch (formula.kind) {
|
|
1222
|
+
case "dataTable":
|
|
1223
|
+
throw new XlsxOpenError(
|
|
1224
|
+
"unreadableFormula",
|
|
1225
|
+
"The cell is part of a What-If data table, which the engine does not compute.",
|
|
1226
|
+
{ sheet, cell: at, held: held(formula.text) }
|
|
1227
|
+
);
|
|
1228
|
+
case "array":
|
|
1229
|
+
if (!formula.isDynamicArray) {
|
|
1230
|
+
throw new XlsxOpenError(
|
|
1231
|
+
"legacyArrayFormula",
|
|
1232
|
+
"The cell holds a legacy array formula (entered with Ctrl+Shift+Enter), which Excel computes as one over its cells and ExSheet would spill instead.",
|
|
1233
|
+
{ sheet, cell: at, held: `{${held(formula.text)}}` }
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
break;
|
|
1237
|
+
case "shared":
|
|
1238
|
+
if (formula.text.length === 0) {
|
|
1239
|
+
const master = formula.sharedIndex === null ? void 0 : shared.get(formula.sharedIndex);
|
|
1240
|
+
if (master === void 0) {
|
|
1241
|
+
const index = formula.sharedIndex === null ? "" : String(formula.sharedIndex);
|
|
1242
|
+
throw new XlsxOpenError("unreadableFormula", `The cell takes the shared formula ${index}, whose first cell does not come before it.`, { sheet, cell: at });
|
|
1243
|
+
}
|
|
1244
|
+
const moved = master.entry.movedBy(cell.row - master.row, cell.column - master.column);
|
|
1245
|
+
this.#report(sheet, at, master.unsupported);
|
|
1246
|
+
return moved;
|
|
1247
|
+
}
|
|
1248
|
+
break;
|
|
1249
|
+
case "normal":
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
const { entry, unsupported } = this.#readFormula(sheet, at, formula.text);
|
|
1253
|
+
this.#report(sheet, at, unsupported);
|
|
1254
|
+
if (formula.kind === "shared" && formula.sharedIndex !== null) {
|
|
1255
|
+
shared.set(formula.sharedIndex, { entry, row: cell.row, column: cell.column, unsupported });
|
|
1256
|
+
}
|
|
1257
|
+
if (formula.kind === "array" && formula.reference !== null) {
|
|
1258
|
+
const spill = rangeOf(formula.reference);
|
|
1259
|
+
if (spill !== void 0) spills.push(spill);
|
|
1260
|
+
}
|
|
1261
|
+
return entry;
|
|
1262
|
+
}
|
|
1263
|
+
/** Reads a Formula's stored text into its Entry, refusing what would read wrongly, with the functions it names that ExSheet does not support. */
|
|
1264
|
+
#readFormula(sheet, at, stored) {
|
|
1265
|
+
const { read: text, problem } = readStoredFormula(stored);
|
|
1266
|
+
const where = { sheet, cell: at, held: text };
|
|
1267
|
+
switch (problem) {
|
|
1268
|
+
case "otherWorkbook":
|
|
1269
|
+
throw new XlsxOpenError("otherWorkbook", "The Formula reads another workbook, which ExSheet cannot reach.", where);
|
|
1270
|
+
case "excelTable":
|
|
1271
|
+
throw new XlsxOpenError("excelTable", "The Formula reads an Excel Table, which ExSheet does not read.", where);
|
|
1272
|
+
case "implicitIntersection":
|
|
1273
|
+
throw new XlsxOpenError("unreadableFormula", "The Formula uses the implicit intersection operator (@), which the engine does not read.", where);
|
|
1274
|
+
case "spillRange":
|
|
1275
|
+
throw new XlsxOpenError("unreadableFormula", "The Formula reads a Spill Range in a form the engine does not read.", where);
|
|
1276
|
+
case "none":
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
let entry;
|
|
1280
|
+
try {
|
|
1281
|
+
entry = defined(Entry.parse(text, Culture.invariant), "the Entry of a Formula");
|
|
1282
|
+
} catch (e) {
|
|
1283
|
+
if (e instanceof FormulaSyntaxError) throw new XlsxOpenError("unreadableFormula", `The engine cannot read the Formula: ${e.message}`, where, { cause: e });
|
|
1284
|
+
throw e;
|
|
1285
|
+
}
|
|
1286
|
+
for (const name of entry.unboundNames) {
|
|
1287
|
+
if (this.#definedNames.has(foldCase(name))) {
|
|
1288
|
+
throw new XlsxOpenError("definedName", `The Formula reads the defined name '${name}', which ExSheet does not define.`, where);
|
|
1289
|
+
}
|
|
1290
|
+
if (this.#hasExcelTables) {
|
|
1291
|
+
throw new XlsxOpenError(
|
|
1292
|
+
"excelTable",
|
|
1293
|
+
`The Formula reads the name '${name}', which the workbook does not define; it holds Excel Tables, which a name can read, and ExSheet does not read them.`,
|
|
1294
|
+
where
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const functions = entry.undeclaredFunctions;
|
|
1299
|
+
const lambda = functions.find((f) => this.#definedNames.has(foldCase(f)));
|
|
1300
|
+
if (lambda !== void 0) throw new XlsxOpenError("definedName", `The Formula calls the defined name '${lambda}', which ExSheet does not define.`, where);
|
|
1301
|
+
return { entry, unsupported: functions };
|
|
1302
|
+
}
|
|
1303
|
+
#report(sheet, cell, functions) {
|
|
1304
|
+
if (functions.length > 0) this.#unsupported.push({ sheet, cell, functions });
|
|
1305
|
+
}
|
|
1306
|
+
/** A constant's Entry, or `undefined` for a cell that holds none (a cell with a style only). */
|
|
1307
|
+
#constant(sheet, cell, at) {
|
|
1308
|
+
const value = cell.value;
|
|
1309
|
+
switch (cell.type) {
|
|
1310
|
+
case "sharedString":
|
|
1311
|
+
case "inlineString":
|
|
1312
|
+
return Entry.fromValue(Value.fromText(cell.text === null ? "" : cell.text));
|
|
1313
|
+
case "formulaString":
|
|
1314
|
+
return value === null ? void 0 : Entry.fromValue(Value.fromText(value));
|
|
1315
|
+
case "number": {
|
|
1316
|
+
if (value === null) return void 0;
|
|
1317
|
+
let number = parseDouble(value);
|
|
1318
|
+
if (number === void 0 || !Number.isFinite(number)) {
|
|
1319
|
+
throw new XlsxOpenError("unreadableConstant", "The cell's number cannot be read.", { sheet, cell: at, held: value });
|
|
1320
|
+
}
|
|
1321
|
+
if (this.#reader.date1904 && this.#showsDate(cell.styleIndex)) number += days1904;
|
|
1322
|
+
return Entry.fromValue(Value.fromNumber(number));
|
|
1323
|
+
}
|
|
1324
|
+
case "boolean":
|
|
1325
|
+
switch (value) {
|
|
1326
|
+
case "1":
|
|
1327
|
+
case "true":
|
|
1328
|
+
return Entry.fromValue(Value.fromBoolean(true));
|
|
1329
|
+
case "0":
|
|
1330
|
+
case "false":
|
|
1331
|
+
return Entry.fromValue(Value.fromBoolean(false));
|
|
1332
|
+
case null:
|
|
1333
|
+
return void 0;
|
|
1334
|
+
default:
|
|
1335
|
+
throw new XlsxOpenError("unreadableConstant", "The cell's boolean cannot be read.", { sheet, cell: at, held: value });
|
|
1336
|
+
}
|
|
1337
|
+
case "error": {
|
|
1338
|
+
if (value === null) return void 0;
|
|
1339
|
+
const error = Entry.fromErrorText(value);
|
|
1340
|
+
if (error !== void 0) return error;
|
|
1341
|
+
throw new XlsxOpenError("unreadableConstant", "The cell holds an Error Value a cell of ExSheet cannot hold.", { sheet, cell: at, held: value });
|
|
1342
|
+
}
|
|
1343
|
+
case "date": {
|
|
1344
|
+
if (value === null) return void 0;
|
|
1345
|
+
const serial = isoDateSerial(value);
|
|
1346
|
+
if (serial === void 0) {
|
|
1347
|
+
throw new XlsxOpenError(
|
|
1348
|
+
"unreadableConstant",
|
|
1349
|
+
"The cell's date is not an ISO 8601 date or time, or lies outside the dates a serial counts.",
|
|
1350
|
+
{ sheet, cell: at, held: value }
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
return Entry.fromValue(Value.fromNumber(serial));
|
|
1354
|
+
}
|
|
1355
|
+
default:
|
|
1356
|
+
return unreachable(cell.type);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
/** Whether the cell format at `style` shows a date (its serial moves from 1904's to 1900's). */
|
|
1360
|
+
#showsDate(style) {
|
|
1361
|
+
const known = this.#dateStyles.get(style);
|
|
1362
|
+
if (known !== void 0) return known;
|
|
1363
|
+
const styles = this.#reader.styles;
|
|
1364
|
+
const id = elementAt(styles.cellFormats, style).numberFormatId;
|
|
1365
|
+
const shows = showsDate(id, styles.numberFormats.get(id));
|
|
1366
|
+
this.#dateStyles.set(style, shows);
|
|
1367
|
+
return shows;
|
|
1368
|
+
}
|
|
1369
|
+
/** What else a worksheet held, as the file layer reports it, said once per Sheet and kind. */
|
|
1370
|
+
#sheetElements(sheet, read, richCells, firstRich, printNames, formats) {
|
|
1371
|
+
const kinds = [];
|
|
1372
|
+
const add2 = (kind, detail) => {
|
|
1373
|
+
if (!kinds.some((k) => k.kind === kind && k.detail === detail)) kinds.push({ kind, detail });
|
|
1374
|
+
};
|
|
1375
|
+
if (read.customRowHeights) add2("rowHeights");
|
|
1376
|
+
if (read.hiddenRows) add2("hiddenRows");
|
|
1377
|
+
if (read.columns.some((c) => c.hidden || c.width !== null && c.width <= 0)) add2("hiddenColumns");
|
|
1378
|
+
if (read.frozenPanes) add2("frozenPanes");
|
|
1379
|
+
for (const element of read.otherElements) {
|
|
1380
|
+
for (const { kind, detail } of elementNotCarried(element)) add2(kind, detail);
|
|
1381
|
+
}
|
|
1382
|
+
if (printNames) add2("printSettings");
|
|
1383
|
+
if (richCells > 0) add2("richText", firstRich);
|
|
1384
|
+
for (const { kind, detail } of formats) add2(kind, detail);
|
|
1385
|
+
for (const { kind, detail } of kinds) {
|
|
1386
|
+
this.#notCarried.push({ kind, sheet, detail: detail === void 0 ? null : detail, description: describe(kind, sheet, detail, richCells) });
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
function builtInName(name) {
|
|
1391
|
+
return equalsIgnoreCase(name.name, filterDatabase) ? void 0 : name.name;
|
|
1392
|
+
}
|
|
1393
|
+
function describeName(name, shown, sheet) {
|
|
1394
|
+
return sheet === null ? `The defined name '${shown}' (${name.text}) is not carried; no Formula carried reads it.` : `The defined name '${shown}' of the sheet '${sheet}' (${name.text}) is not carried; no Formula carried reads it.`;
|
|
1395
|
+
}
|
|
1396
|
+
function byFirst(columns) {
|
|
1397
|
+
return columns.toSorted((a, b) => a.first - b.first);
|
|
1398
|
+
}
|
|
1399
|
+
function axisFormat(first, last, level) {
|
|
1400
|
+
return new SheetDocumentAxisFormat(first, last, level.numberFormat, level.alignment, level.font, level.fill, level.borders);
|
|
1401
|
+
}
|
|
1402
|
+
function addRun(runs, row, level) {
|
|
1403
|
+
if (level.isEmpty) return;
|
|
1404
|
+
const last = runs.at(-1);
|
|
1405
|
+
if (last !== void 0 && last.last === row - 1 && partsOf(last).equals(level)) {
|
|
1406
|
+
runs[runs.length - 1] = axisFormat(last.first, row, level);
|
|
1407
|
+
} else {
|
|
1408
|
+
runs.push(axisFormat(row, row, level));
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function widths(columns) {
|
|
1412
|
+
const runs = [];
|
|
1413
|
+
for (const range of byFirst(columns)) {
|
|
1414
|
+
if (range.width === null) continue;
|
|
1415
|
+
const characters = readWidth(range.width);
|
|
1416
|
+
if (characters === void 0) continue;
|
|
1417
|
+
const kind = range.bestFit ? "widenedByEntry" : "setByUser";
|
|
1418
|
+
const last = runs.at(-1);
|
|
1419
|
+
if (last !== void 0 && last.last === range.first - 1 && last.width === characters && last.kind === kind) {
|
|
1420
|
+
runs[runs.length - 1] = new SheetDocumentColumnWidth(last.first, range.last, characters, kind);
|
|
1421
|
+
} else {
|
|
1422
|
+
runs.push(new SheetDocumentColumnWidth(range.first, range.last, characters, kind));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return runs;
|
|
1426
|
+
}
|
|
1427
|
+
function held(stored) {
|
|
1428
|
+
return `=${stripPrefixes(stored)}`;
|
|
1429
|
+
}
|
|
1430
|
+
function insideSpill(spills, cell) {
|
|
1431
|
+
return spills.some((s) => s.contains(cell));
|
|
1432
|
+
}
|
|
1433
|
+
function rangeOf(text) {
|
|
1434
|
+
const colon = text.indexOf(":");
|
|
1435
|
+
const first = parseCell(colon < 0 ? text : text.slice(0, colon));
|
|
1436
|
+
const last = parseCell(colon < 0 ? text : text.slice(colon + 1));
|
|
1437
|
+
if (first === void 0 || last === void 0) return void 0;
|
|
1438
|
+
return new CellRange(new CellAddress(first.row, first.column), new CellAddress(last.row, last.column));
|
|
1439
|
+
}
|
|
1440
|
+
function describe(kind, sheet, detail, richCells) {
|
|
1441
|
+
const at = detail === void 0 ? "" : detail;
|
|
1442
|
+
switch (kind) {
|
|
1443
|
+
case "rowHeights":
|
|
1444
|
+
return `Rows of '${sheet}' have heights of their own, which are not carried.`;
|
|
1445
|
+
case "hiddenRows":
|
|
1446
|
+
return `Rows of '${sheet}' are hidden in the file, and open shown.`;
|
|
1447
|
+
case "hiddenColumns":
|
|
1448
|
+
return `Columns of '${sheet}' are hidden in the file, and open shown.`;
|
|
1449
|
+
case "frozenPanes":
|
|
1450
|
+
return `'${sheet}' has frozen panes, which are not carried.`;
|
|
1451
|
+
case "mergedCells":
|
|
1452
|
+
return `'${sheet}' has merged cells; each cell is carried on its own.`;
|
|
1453
|
+
case "conditionalFormatting":
|
|
1454
|
+
return `'${sheet}' has conditional formatting, which is not carried.`;
|
|
1455
|
+
case "dataValidation":
|
|
1456
|
+
return `'${sheet}' has data validation, which is not carried.`;
|
|
1457
|
+
case "notesAndComments":
|
|
1458
|
+
return `'${sheet}' has notes or comments, which are not carried.`;
|
|
1459
|
+
case "hyperlinks":
|
|
1460
|
+
return `'${sheet}' has hyperlinks; their cells' text is carried, the links are not.`;
|
|
1461
|
+
case "picturesAndCharts":
|
|
1462
|
+
return `'${sheet}' has pictures, charts, shapes or controls, which are not carried.`;
|
|
1463
|
+
case "protection":
|
|
1464
|
+
return `'${sheet}' is protected in the file; the protection is not carried.`;
|
|
1465
|
+
case "printSettings":
|
|
1466
|
+
return `'${sheet}' has print settings beyond Excel's defaults (margins, a header or footer, page breaks, a print area, a scale or the like), which are not carried.`;
|
|
1467
|
+
case "view":
|
|
1468
|
+
return `'${sheet}' has view settings (hidden gridlines, headings or zeros, Formulas shown, right to left, a zoom or a page layout), which are not carried.`;
|
|
1469
|
+
case "autoFilter":
|
|
1470
|
+
return `'${sheet}' has an AutoFilter, which is not carried; every row opens shown.`;
|
|
1471
|
+
case "excelTable":
|
|
1472
|
+
return `'${sheet}' has Excel Tables; their cells are carried, the Tables are not.`;
|
|
1473
|
+
case "slicers":
|
|
1474
|
+
return `'${sheet}' has slicers or timelines, which are not carried.`;
|
|
1475
|
+
case "sheetProperties":
|
|
1476
|
+
return `The tab colour of '${sheet}' is not carried.`;
|
|
1477
|
+
case "richText":
|
|
1478
|
+
return `${String(richCells)} cell(s) of '${sheet}', the first ${at}, hold rich text: the text is carried, the runs' own formatting is not.`;
|
|
1479
|
+
case "defaultColumnWidth":
|
|
1480
|
+
return `'${sheet}' has a default column width of its own, which is not carried; its columns without a width of their own open at the default width.`;
|
|
1481
|
+
case "fontNamesAndSizes":
|
|
1482
|
+
return `Cells of '${sheet}', the first at ${at}, use a typeface or a size other than the workbook's own, which is not carried; they open in the Sheet's font.`;
|
|
1483
|
+
case "verticalAlignment":
|
|
1484
|
+
return `Cells of '${sheet}', the first at ${at}, are aligned vertically, which is not carried.`;
|
|
1485
|
+
case "textLayout":
|
|
1486
|
+
return `Cells of '${sheet}', the first at ${at}, wrap, indent, rotate or shrink their text, which is not carried.`;
|
|
1487
|
+
case "horizontalAlignment":
|
|
1488
|
+
return `Cells of '${sheet}', the first at ${at}, are aligned to fill, justify, distribute or centre across the selection, which is not carried; they open aligned as General.`;
|
|
1489
|
+
case "underline":
|
|
1490
|
+
return `Cells of '${sheet}', the first at ${at}, are underlined twice or for accounting; they open with a single underline.`;
|
|
1491
|
+
case "patternFills":
|
|
1492
|
+
return `Cells of '${sheet}', the first at ${at}, have a pattern or gradient fill, or a solid fill of no colour, which is not carried; they open with no Fill.`;
|
|
1493
|
+
case "diagonalBorders":
|
|
1494
|
+
return `Cells of '${sheet}', the first at ${at}, have diagonal borders, which are not carried; their four sides are.`;
|
|
1495
|
+
// Said of the workbook, never of a Sheet's elements; an element the file layer names is said by its name.
|
|
1496
|
+
case "chartSheet":
|
|
1497
|
+
case "dataConnections":
|
|
1498
|
+
case "definedName":
|
|
1499
|
+
case "externalLinks":
|
|
1500
|
+
case "hiddenSheet":
|
|
1501
|
+
case "other":
|
|
1502
|
+
case "pivotTables":
|
|
1503
|
+
case "themeColours":
|
|
1504
|
+
default:
|
|
1505
|
+
return `'${sheet}' holds <${at}>, which is not carried.`;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// src/writer.ts
|
|
1510
|
+
import {
|
|
1511
|
+
CellAddress as CellAddress3,
|
|
1512
|
+
Sheet as Sheet5
|
|
1513
|
+
} from "@ex-grid/sheet-engine";
|
|
1514
|
+
import {
|
|
1515
|
+
createXlsxWriter,
|
|
1516
|
+
defaultMaxCells,
|
|
1517
|
+
defaultMaxUncompressedBytes,
|
|
1518
|
+
formatCell as formatCell2,
|
|
1519
|
+
isValidTableName,
|
|
1520
|
+
maxColumns,
|
|
1521
|
+
maxRows,
|
|
1522
|
+
XlsxError as XlsxError3
|
|
1523
|
+
} from "@ex-grid/data-xlsx";
|
|
1524
|
+
|
|
1525
|
+
// src/save-error.ts
|
|
1526
|
+
var XlsxSaveError = class extends Error {
|
|
1527
|
+
/** Why the Workbook was not written. */
|
|
1528
|
+
reason;
|
|
1529
|
+
/** The name of the Sheet the refusal met, or `undefined` when it is not about one Sheet. */
|
|
1530
|
+
sheet;
|
|
1531
|
+
/** The cell the refusal met, as `B7`, or `undefined` when it is not about one cell. */
|
|
1532
|
+
cell;
|
|
1533
|
+
/** What the cell held — a Formula's text with its `=` — or `undefined`. */
|
|
1534
|
+
held;
|
|
1535
|
+
/**
|
|
1536
|
+
* A refusal naming what it met.
|
|
1537
|
+
* @param reason - Why the Workbook is not written.
|
|
1538
|
+
* @param message - What was wrong, as a sentence that follows the Sheet and the cell.
|
|
1539
|
+
* @param where - The Sheet, the cell and what the cell held, as far as it is about one.
|
|
1540
|
+
*/
|
|
1541
|
+
constructor(reason, message, where = {}) {
|
|
1542
|
+
super(formatted(where.sheet, where.cell, where.held, message));
|
|
1543
|
+
this.name = "XlsxSaveError";
|
|
1544
|
+
this.reason = reason;
|
|
1545
|
+
this.sheet = where.sheet;
|
|
1546
|
+
this.cell = where.cell;
|
|
1547
|
+
this.held = where.held;
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
// src/save-pacer.ts
|
|
1552
|
+
var batch = 256;
|
|
1553
|
+
function macrotask() {
|
|
1554
|
+
return new Promise((resolve) => {
|
|
1555
|
+
setTimeout(resolve, 0);
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
var SavePacer = class {
|
|
1559
|
+
#budget;
|
|
1560
|
+
#yield;
|
|
1561
|
+
#onProgress;
|
|
1562
|
+
#signal;
|
|
1563
|
+
#sliceStart = performance.now();
|
|
1564
|
+
#sinceLook = 0;
|
|
1565
|
+
/** The worksheets the file holds. */
|
|
1566
|
+
sheets = 0;
|
|
1567
|
+
/** The elements the file writes; 0 until the snapshot is planned. */
|
|
1568
|
+
total = 0;
|
|
1569
|
+
/** The worksheet being written; -1 while the snapshot is planned. */
|
|
1570
|
+
sheet = -1;
|
|
1571
|
+
/** The elements written so far. */
|
|
1572
|
+
written = 0;
|
|
1573
|
+
constructor(options) {
|
|
1574
|
+
const budget = options.sliceBudgetMs === void 0 ? 30 : options.sliceBudgetMs;
|
|
1575
|
+
if (typeof budget !== "number" || Number.isNaN(budget)) throw new TypeError(`The slice budget ${String(budget)} is not a number of milliseconds.`);
|
|
1576
|
+
this.#budget = Math.max(0, budget);
|
|
1577
|
+
this.#yield = options.yield === void 0 ? macrotask : options.yield;
|
|
1578
|
+
this.#onProgress = options.onProgress;
|
|
1579
|
+
this.#signal = options.signal;
|
|
1580
|
+
}
|
|
1581
|
+
/** Throws the signal's reason when the write is cancelled. */
|
|
1582
|
+
cancelled() {
|
|
1583
|
+
this.#signal?.throwIfAborted();
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* One step of work, which wrote `elements` (none while the snapshot is planned); every batch of steps answers the
|
|
1587
|
+
* checkpoint to await, and every other step nothing, so a step costs no turn of the event loop.
|
|
1588
|
+
*/
|
|
1589
|
+
step(elements = 1) {
|
|
1590
|
+
this.written += elements;
|
|
1591
|
+
if (++this.#sinceLook < batch) return void 0;
|
|
1592
|
+
this.#sinceLook = 0;
|
|
1593
|
+
return this.checkpoint();
|
|
1594
|
+
}
|
|
1595
|
+
/** Looks at the signal and, past the budget, reports the progress and yields. */
|
|
1596
|
+
async checkpoint() {
|
|
1597
|
+
this.cancelled();
|
|
1598
|
+
if (performance.now() < this.#sliceStart + this.#budget) return;
|
|
1599
|
+
this.report();
|
|
1600
|
+
await this.#yield();
|
|
1601
|
+
this.cancelled();
|
|
1602
|
+
this.#sliceStart = performance.now();
|
|
1603
|
+
}
|
|
1604
|
+
/** Tells the progress as it stands. */
|
|
1605
|
+
report() {
|
|
1606
|
+
this.#onProgress?.({
|
|
1607
|
+
sheet: this.sheet,
|
|
1608
|
+
sheets: this.sheets,
|
|
1609
|
+
elements: this.sheet < 0 ? 0 : this.written,
|
|
1610
|
+
totalElements: this.sheet < 0 ? 0 : this.total
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
// src/style-table.ts
|
|
1616
|
+
import {
|
|
1617
|
+
defaultAlignment as defaultAlignment2,
|
|
1618
|
+
firstCustomNumberFormat as firstCustomNumberFormat2,
|
|
1619
|
+
gray125Fill,
|
|
1620
|
+
noBorder,
|
|
1621
|
+
noBorderEdge,
|
|
1622
|
+
noFill,
|
|
1623
|
+
rgbColor,
|
|
1624
|
+
solidFill
|
|
1625
|
+
} from "@ex-grid/data-xlsx";
|
|
1626
|
+
var workbookFont = Object.freeze({ name: "Calibri", size: 11, bold: false, italic: false, underline: "none", strike: false, color: null });
|
|
1627
|
+
var StyleTable = class {
|
|
1628
|
+
#formats = /* @__PURE__ */ new Map();
|
|
1629
|
+
#cellFormats = [];
|
|
1630
|
+
#codes = /* @__PURE__ */ new Map();
|
|
1631
|
+
#fonts = /* @__PURE__ */ new Map();
|
|
1632
|
+
#fills = /* @__PURE__ */ new Map();
|
|
1633
|
+
#borders = /* @__PURE__ */ new Map();
|
|
1634
|
+
#culture;
|
|
1635
|
+
/** A table for a Workbook of `culture`, holding the default Cell Format. */
|
|
1636
|
+
constructor(culture) {
|
|
1637
|
+
this.#culture = culture;
|
|
1638
|
+
this.#font(workbookFont);
|
|
1639
|
+
this.#fill(noFill);
|
|
1640
|
+
this.#fill(gray125Fill);
|
|
1641
|
+
this.#border(noBorder);
|
|
1642
|
+
this.index(defaultFormat);
|
|
1643
|
+
}
|
|
1644
|
+
/** The index of the cell format that writes `format`, added when it is new. */
|
|
1645
|
+
index(format) {
|
|
1646
|
+
const known = this.#formats.get(format.key);
|
|
1647
|
+
if (known !== void 0) return known;
|
|
1648
|
+
const id = builtInId(format.numberFormat);
|
|
1649
|
+
const numberFormat = id === void 0 ? this.#code(writtenCode(format.numberFormat, this.#culture)) : id;
|
|
1650
|
+
const f = format.font;
|
|
1651
|
+
const font = this.#font({
|
|
1652
|
+
...workbookFont,
|
|
1653
|
+
bold: f.bold,
|
|
1654
|
+
italic: f.italic,
|
|
1655
|
+
underline: f.underline ? "single" : "none",
|
|
1656
|
+
strike: f.strikethrough,
|
|
1657
|
+
color: color(f.colour)
|
|
1658
|
+
});
|
|
1659
|
+
const fill = format.fill.colour === void 0 ? 0 : this.#fill(solidFill(solidColor(format.fill.colour)));
|
|
1660
|
+
const b = format.borders;
|
|
1661
|
+
const border = this.#border({
|
|
1662
|
+
left: edge(b.left),
|
|
1663
|
+
right: edge(b.right),
|
|
1664
|
+
top: edge(b.top),
|
|
1665
|
+
bottom: edge(b.bottom),
|
|
1666
|
+
diagonal: noBorderEdge,
|
|
1667
|
+
diagonalUp: false,
|
|
1668
|
+
diagonalDown: false
|
|
1669
|
+
});
|
|
1670
|
+
let alignment;
|
|
1671
|
+
switch (format.alignment) {
|
|
1672
|
+
case "left":
|
|
1673
|
+
case "center":
|
|
1674
|
+
case "right":
|
|
1675
|
+
alignment = { ...defaultAlignment2, horizontal: format.alignment };
|
|
1676
|
+
break;
|
|
1677
|
+
case "general":
|
|
1678
|
+
alignment = null;
|
|
1679
|
+
break;
|
|
1680
|
+
default:
|
|
1681
|
+
return unreachableAlignment2(format.alignment);
|
|
1682
|
+
}
|
|
1683
|
+
const index = this.#cellFormats.length;
|
|
1684
|
+
this.#cellFormats.push({ numberFormatId: numberFormat, fontId: font, fillId: fill, borderId: border, alignment });
|
|
1685
|
+
this.#formats.set(format.key, index);
|
|
1686
|
+
return index;
|
|
1687
|
+
}
|
|
1688
|
+
/** The styles part, holding every format met so far. */
|
|
1689
|
+
build() {
|
|
1690
|
+
return {
|
|
1691
|
+
numberFormats: new Map([...this.#codes].map(([code, id]) => [id, code])),
|
|
1692
|
+
fonts: [...this.#fonts.values()].map(([font]) => font),
|
|
1693
|
+
fills: [...this.#fills.values()].map(([fill]) => fill),
|
|
1694
|
+
borders: [...this.#borders.values()].map(([border]) => border),
|
|
1695
|
+
cellFormats: [...this.#cellFormats],
|
|
1696
|
+
indexedColors: null
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
#code(code) {
|
|
1700
|
+
let id = this.#codes.get(code);
|
|
1701
|
+
if (id === void 0) {
|
|
1702
|
+
id = firstCustomNumberFormat2 + this.#codes.size;
|
|
1703
|
+
this.#codes.set(code, id);
|
|
1704
|
+
}
|
|
1705
|
+
return id;
|
|
1706
|
+
}
|
|
1707
|
+
#font(font) {
|
|
1708
|
+
return add(this.#fonts, `${String(font.name)}|${String(font.size)}|${String(font.bold)}|${String(font.italic)}|${font.underline}|${String(font.strike)}|${colorKey(font.color)}`, font);
|
|
1709
|
+
}
|
|
1710
|
+
#fill(fill) {
|
|
1711
|
+
return add(this.#fills, `${String(fill.pattern)}|${colorKey(fill.foreground)}|${colorKey(fill.background)}|${String(fill.isGradient)}`, fill);
|
|
1712
|
+
}
|
|
1713
|
+
#border(border) {
|
|
1714
|
+
return add(this.#borders, `${edgeKey(border.left)}|${edgeKey(border.right)}|${edgeKey(border.top)}|${edgeKey(border.bottom)}|${edgeKey(border.diagonal)}|${String(border.diagonalUp)}|${String(border.diagonalDown)}`, border);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
function add(table, key, item) {
|
|
1718
|
+
const known = table.get(key);
|
|
1719
|
+
if (known !== void 0) return known[1];
|
|
1720
|
+
const index = table.size;
|
|
1721
|
+
table.set(key, [item, index]);
|
|
1722
|
+
return index;
|
|
1723
|
+
}
|
|
1724
|
+
function colorKey(c) {
|
|
1725
|
+
return c === null ? "-" : `${c.kind}:${String(c.value)}:${String(c.tint)}`;
|
|
1726
|
+
}
|
|
1727
|
+
function edgeKey(side) {
|
|
1728
|
+
return `${side.style}:${colorKey(side.color)}`;
|
|
1729
|
+
}
|
|
1730
|
+
function color(colour) {
|
|
1731
|
+
return colour.isAutomatic ? null : rgbColor((4278190080 | colour.rgb) >>> 0);
|
|
1732
|
+
}
|
|
1733
|
+
function solidColor(colour) {
|
|
1734
|
+
const written = color(colour);
|
|
1735
|
+
if (written === null) throw new Error("Internal: a solid Fill has no colour.");
|
|
1736
|
+
return written;
|
|
1737
|
+
}
|
|
1738
|
+
function edge(line) {
|
|
1739
|
+
return line.isNone ? noBorderEdge : { style: fileLineStyle(line.style), color: color(line.colour) };
|
|
1740
|
+
}
|
|
1741
|
+
function unreachableAlignment2(value) {
|
|
1742
|
+
throw new Error(`Internal: ${String(value)} is not a horizontal alignment.`);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/written-formula.ts
|
|
1746
|
+
import { CellAddress as CellAddress2, DeclaredFunction, Sheet as Sheet4 } from "@ex-grid/sheet-engine";
|
|
1747
|
+
var newerFunctions = Object.freeze([
|
|
1748
|
+
"ACOT",
|
|
1749
|
+
"ACOTH",
|
|
1750
|
+
"AGGREGATE",
|
|
1751
|
+
"ARABIC",
|
|
1752
|
+
"ARRAYTOTEXT",
|
|
1753
|
+
"BASE",
|
|
1754
|
+
"BETA.DIST",
|
|
1755
|
+
"BETA.INV",
|
|
1756
|
+
"BINOM.DIST",
|
|
1757
|
+
"BINOM.DIST.RANGE",
|
|
1758
|
+
"BINOM.INV",
|
|
1759
|
+
"BITAND",
|
|
1760
|
+
"BITLSHIFT",
|
|
1761
|
+
"BITOR",
|
|
1762
|
+
"BITRSHIFT",
|
|
1763
|
+
"BITXOR",
|
|
1764
|
+
"BYCOL",
|
|
1765
|
+
"BYROW",
|
|
1766
|
+
"CEILING.MATH",
|
|
1767
|
+
"CEILING.PRECISE",
|
|
1768
|
+
"CHISQ.DIST",
|
|
1769
|
+
"CHISQ.DIST.RT",
|
|
1770
|
+
"CHISQ.INV",
|
|
1771
|
+
"CHISQ.INV.RT",
|
|
1772
|
+
"CHISQ.TEST",
|
|
1773
|
+
"CHOOSECOLS",
|
|
1774
|
+
"CHOOSEROWS",
|
|
1775
|
+
"COMBINA",
|
|
1776
|
+
"CONCAT",
|
|
1777
|
+
"CONFIDENCE.NORM",
|
|
1778
|
+
"CONFIDENCE.T",
|
|
1779
|
+
"COPILOT",
|
|
1780
|
+
"COT",
|
|
1781
|
+
"COTH",
|
|
1782
|
+
"COVARIANCE.P",
|
|
1783
|
+
"COVARIANCE.S",
|
|
1784
|
+
"CSC",
|
|
1785
|
+
"CSCH",
|
|
1786
|
+
"DAYS",
|
|
1787
|
+
"DECIMAL",
|
|
1788
|
+
"DETECTLANGUAGE",
|
|
1789
|
+
"DROP",
|
|
1790
|
+
"ECMA.CEILING",
|
|
1791
|
+
"ENCODEURL",
|
|
1792
|
+
"ERF.PRECISE",
|
|
1793
|
+
"ERFC.PRECISE",
|
|
1794
|
+
"EXPAND",
|
|
1795
|
+
"EXPON.DIST",
|
|
1796
|
+
"F.DIST",
|
|
1797
|
+
"F.DIST.RT",
|
|
1798
|
+
"F.INV",
|
|
1799
|
+
"F.INV.RT",
|
|
1800
|
+
"F.TEST",
|
|
1801
|
+
"FIELDVALUE",
|
|
1802
|
+
"FILTER",
|
|
1803
|
+
"FILTERXML",
|
|
1804
|
+
"FLOOR.MATH",
|
|
1805
|
+
"FLOOR.PRECISE",
|
|
1806
|
+
"FORECAST.ETS",
|
|
1807
|
+
"FORECAST.ETS.CONFINT",
|
|
1808
|
+
"FORECAST.ETS.SEASONALITY",
|
|
1809
|
+
"FORECAST.ETS.STAT",
|
|
1810
|
+
"FORECAST.LINEAR",
|
|
1811
|
+
"FORMULATEXT",
|
|
1812
|
+
"GAMMA",
|
|
1813
|
+
"GAMMA.DIST",
|
|
1814
|
+
"GAMMA.INV",
|
|
1815
|
+
"GAMMALN.PRECISE",
|
|
1816
|
+
"GAUSS",
|
|
1817
|
+
"GROUPBY",
|
|
1818
|
+
"HSTACK",
|
|
1819
|
+
"HYPGEOM.DIST",
|
|
1820
|
+
"IFNA",
|
|
1821
|
+
"IFS",
|
|
1822
|
+
"IMAGE",
|
|
1823
|
+
"IMCOSH",
|
|
1824
|
+
"IMCOT",
|
|
1825
|
+
"IMCSC",
|
|
1826
|
+
"IMCSCH",
|
|
1827
|
+
"IMSEC",
|
|
1828
|
+
"IMSECH",
|
|
1829
|
+
"IMSINH",
|
|
1830
|
+
"IMTAN",
|
|
1831
|
+
"ISFORMULA",
|
|
1832
|
+
"ISOMITTED",
|
|
1833
|
+
"ISOWEEKNUM",
|
|
1834
|
+
"LAMBDA",
|
|
1835
|
+
"LET",
|
|
1836
|
+
"LOGNORM.DIST",
|
|
1837
|
+
"LOGNORM.INV",
|
|
1838
|
+
"MAKEARRAY",
|
|
1839
|
+
"MAP",
|
|
1840
|
+
"MAXIFS",
|
|
1841
|
+
"MINIFS",
|
|
1842
|
+
"MODE.MULT",
|
|
1843
|
+
"MODE.SNGL",
|
|
1844
|
+
"MUNIT",
|
|
1845
|
+
"NEGBINOM.DIST",
|
|
1846
|
+
"NETWORKDAYS.INTL",
|
|
1847
|
+
"NORM.DIST",
|
|
1848
|
+
"NORM.INV",
|
|
1849
|
+
"NORM.S.DIST",
|
|
1850
|
+
"NORM.S.INV",
|
|
1851
|
+
"NUMBERVALUE",
|
|
1852
|
+
"PDURATION",
|
|
1853
|
+
"PERCENTILE.EXC",
|
|
1854
|
+
"PERCENTILE.INC",
|
|
1855
|
+
"PERCENTOF",
|
|
1856
|
+
"PERCENTRANK.EXC",
|
|
1857
|
+
"PERCENTRANK.INC",
|
|
1858
|
+
"PERMUTATIONA",
|
|
1859
|
+
"PHI",
|
|
1860
|
+
"PIVOTBY",
|
|
1861
|
+
"POISSON.DIST",
|
|
1862
|
+
"QUARTILE.EXC",
|
|
1863
|
+
"QUARTILE.INC",
|
|
1864
|
+
"RANDARRAY",
|
|
1865
|
+
"RANK.AVG",
|
|
1866
|
+
"RANK.EQ",
|
|
1867
|
+
"REDUCE",
|
|
1868
|
+
"REGEXEXTRACT",
|
|
1869
|
+
"REGEXREPLACE",
|
|
1870
|
+
"REGEXTEST",
|
|
1871
|
+
"RRI",
|
|
1872
|
+
"SCAN",
|
|
1873
|
+
"SEC",
|
|
1874
|
+
"SECH",
|
|
1875
|
+
"SEQUENCE",
|
|
1876
|
+
"SHEET",
|
|
1877
|
+
"SHEETS",
|
|
1878
|
+
"SKEW.P",
|
|
1879
|
+
"SORT",
|
|
1880
|
+
"SORTBY",
|
|
1881
|
+
"STDEV.P",
|
|
1882
|
+
"STDEV.S",
|
|
1883
|
+
"STOCKHISTORY",
|
|
1884
|
+
"SWITCH",
|
|
1885
|
+
"T.DIST",
|
|
1886
|
+
"T.DIST.2T",
|
|
1887
|
+
"T.DIST.RT",
|
|
1888
|
+
"T.INV",
|
|
1889
|
+
"T.INV.2T",
|
|
1890
|
+
"T.TEST",
|
|
1891
|
+
"TAKE",
|
|
1892
|
+
"TEXTAFTER",
|
|
1893
|
+
"TEXTBEFORE",
|
|
1894
|
+
"TEXTJOIN",
|
|
1895
|
+
"TEXTSPLIT",
|
|
1896
|
+
"TOCOL",
|
|
1897
|
+
"TOROW",
|
|
1898
|
+
"TRANSLATE",
|
|
1899
|
+
"TRIMRANGE",
|
|
1900
|
+
"UNICHAR",
|
|
1901
|
+
"UNICODE",
|
|
1902
|
+
"UNIQUE",
|
|
1903
|
+
"VALUETOTEXT",
|
|
1904
|
+
"VAR.P",
|
|
1905
|
+
"VAR.S",
|
|
1906
|
+
"VSTACK",
|
|
1907
|
+
"WEBSERVICE",
|
|
1908
|
+
"WEIBULL.DIST",
|
|
1909
|
+
"WORKDAY.INTL",
|
|
1910
|
+
"WRAPCOLS",
|
|
1911
|
+
"WRAPROWS",
|
|
1912
|
+
"XLOOKUP",
|
|
1913
|
+
"XMATCH",
|
|
1914
|
+
"XOR",
|
|
1915
|
+
"Z.TEST"
|
|
1916
|
+
]);
|
|
1917
|
+
var newer = new Set(newerFunctions.map(foldCase));
|
|
1918
|
+
function isNewerFunction(name) {
|
|
1919
|
+
return newer.has(foldCase(name));
|
|
1920
|
+
}
|
|
1921
|
+
var worksheetOnly = /* @__PURE__ */ new Set(["FILTER", "SORT"]);
|
|
1922
|
+
var errors = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A"];
|
|
1923
|
+
var maxText = 255;
|
|
1924
|
+
var maxDepth = 64;
|
|
1925
|
+
var maxArguments = 255;
|
|
1926
|
+
var unseenInColumns = /[@,:."{}$^&*+=\-<>/\t\n\r]/;
|
|
1927
|
+
var Token = class {
|
|
1928
|
+
kind;
|
|
1929
|
+
text;
|
|
1930
|
+
/** A Reference written with `#` after it: the Spill Range of the Formula in that cell. */
|
|
1931
|
+
spilled;
|
|
1932
|
+
/** The Sheet a Reference names, unquoted, or `undefined` for one of its own Sheet; for a 3D Reference, the Sheet its span
|
|
1933
|
+
* begins on. */
|
|
1934
|
+
sheet;
|
|
1935
|
+
/** For a 3D Reference (`Sheet1:Sheet3!B2`), the Sheet its span ends on, unquoted. */
|
|
1936
|
+
lastSheet;
|
|
1937
|
+
/** A name that a `(` follows: a function's. */
|
|
1938
|
+
called;
|
|
1939
|
+
/** A name a `LET` binds or a `LAMBDA` takes, or a use of one where it is bound. */
|
|
1940
|
+
bound = false;
|
|
1941
|
+
constructor(kind, text, more = {}) {
|
|
1942
|
+
this.kind = kind;
|
|
1943
|
+
this.text = text;
|
|
1944
|
+
this.spilled = more.spilled === true;
|
|
1945
|
+
this.sheet = more.sheet;
|
|
1946
|
+
this.lastSheet = more.lastSheet;
|
|
1947
|
+
this.called = more.called === true;
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
function writeFormula(entry, hasSheet, columnsOf) {
|
|
1951
|
+
const formula = entry.formula;
|
|
1952
|
+
if (formula === void 0) throw new Error("Internal: only a Formula Entry is written as a Formula.");
|
|
1953
|
+
const refused = (problem) => ({ text: formula.slice(1), problem });
|
|
1954
|
+
const split = splitFormula(formula, columnsOf);
|
|
1955
|
+
if (split.problem !== "none") return refused(split.problem);
|
|
1956
|
+
const tokens = split.tokens;
|
|
1957
|
+
if (hasSheet !== void 0 && tokens.some((t) => t.sheet !== void 0 && !hasSheet(t.sheet) || t.lastSheet !== void 0 && !hasSheet(t.lastSheet))) {
|
|
1958
|
+
return refused("missingSheet");
|
|
1959
|
+
}
|
|
1960
|
+
const bound = bind(tokens);
|
|
1961
|
+
if (bound !== "none") return refused(bound);
|
|
1962
|
+
if (columnsOf !== void 0 && tokens.some((t) => t.kind === "name" && !t.called && !t.bound && columnsOf(t.text) !== void 0)) return refused("tableName");
|
|
1963
|
+
if (tokens.some((t) => t.kind === "text" && t.text.length - 2 - count(t.text, '""') > maxText)) return refused("longText");
|
|
1964
|
+
if (!sameUnbound(tokens, entry.unboundNames)) return refused("unreadable");
|
|
1965
|
+
let text = "";
|
|
1966
|
+
for (const token of tokens) {
|
|
1967
|
+
if (token.kind === "reference" && token.spilled) text += `_xlfn.ANCHORARRAY(${token.text})`;
|
|
1968
|
+
else if (token.kind === "name" && token.bound) text += `_xlpm.${token.text}`;
|
|
1969
|
+
else if (token.kind === "name" && token.called && isNewerFunction(token.text)) text += (worksheetOnly.has(foldCase(token.text)) ? "_xlfn._xlws." : "_xlfn.") + token.text;
|
|
1970
|
+
else text += token.text;
|
|
1971
|
+
}
|
|
1972
|
+
return { text, problem: "none" };
|
|
1973
|
+
}
|
|
1974
|
+
function count(text, of) {
|
|
1975
|
+
let n = 0;
|
|
1976
|
+
for (let at = text.indexOf(of); at >= 0; at = text.indexOf(of, at + of.length)) n++;
|
|
1977
|
+
return n;
|
|
1978
|
+
}
|
|
1979
|
+
var referencePattern = new RegExp(
|
|
1980
|
+
String.raw`(?:(?<sheet>'(?:[^']|'')+'|[A-Za-z_][A-Za-z0-9_.]*)!)?` + String.raw`(?:(?<c1>\$?[A-Za-z]{1,3})(?<r1>\$?[0-9]{1,7})(?::(?<c2>\$?[A-Za-z]{1,3})(?<r2>\$?[0-9]{1,7}))?` + String.raw`|(?<cc1>\$?[A-Za-z]{1,3}):(?<cc2>\$?[A-Za-z]{1,3})` + String.raw`|(?<rr1>\$?[0-9]{1,7}):(?<rr2>\$?[0-9]{1,7}))` + String.raw`(?![A-Za-z0-9_.(\[!:$])`,
|
|
1981
|
+
"y"
|
|
1982
|
+
);
|
|
1983
|
+
var qualifiedRefError = /(?<sheet>'(?:[^']|'')+'|[A-Za-z_][A-Za-z0-9_.]*)!#REF!/iy;
|
|
1984
|
+
var sheetSpanPattern = /(?:'(?<quotedFirst>(?:[^':]|'')+):(?<quotedLast>(?:[^':]|'')+)'|(?<first>[A-Za-z_][A-Za-z0-9_.]*):(?<last>[A-Za-z_][A-Za-z0-9_.]*))!/y;
|
|
1985
|
+
var numberPattern = /(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?/y;
|
|
1986
|
+
var namePattern = /(?=[\0-])[\p{L}_\\](?:(?=[\0-])[\p{L}0-9_.])*/uy;
|
|
1987
|
+
function matchAt(pattern, text, at) {
|
|
1988
|
+
pattern.lastIndex = at;
|
|
1989
|
+
return pattern.exec(text);
|
|
1990
|
+
}
|
|
1991
|
+
var operators = ["<>", "<=", ">=", "=", "<", ">", "+", "-", "*", "/", "^", "&", "%"];
|
|
1992
|
+
function splitFormula(formula, columnsOf) {
|
|
1993
|
+
const tokens = [];
|
|
1994
|
+
const unreadable = { tokens: [], problem: "unreadable" };
|
|
1995
|
+
let i = 1;
|
|
1996
|
+
while (i < formula.length) {
|
|
1997
|
+
const c = formula.charAt(i);
|
|
1998
|
+
if (c === " " || c === "\n" || c === "\r") {
|
|
1999
|
+
const start = i;
|
|
2000
|
+
while (i < formula.length && (formula.charAt(i) === " " || formula.charAt(i) === "\n" || formula.charAt(i) === "\r")) i++;
|
|
2001
|
+
tokens.push(new Token("space", formula.slice(start, i)));
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
if (c === '"') {
|
|
2005
|
+
let j = i + 1;
|
|
2006
|
+
while (j < formula.length && !(formula.charAt(j) === '"' && (j + 1 >= formula.length || formula.charAt(j + 1) !== '"'))) j += formula.charAt(j) === '"' ? 2 : 1;
|
|
2007
|
+
if (j >= formula.length) return unreadable;
|
|
2008
|
+
tokens.push(new Token("text", formula.slice(i, j + 1)));
|
|
2009
|
+
i = j + 1;
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
if (c === "#") {
|
|
2013
|
+
const error = errors.find((e) => startsWithIgnoreCase(formula, i, e));
|
|
2014
|
+
if (error === void 0) return unreadable;
|
|
2015
|
+
tokens.push(new Token("error", formula.slice(i, i + error.length)));
|
|
2016
|
+
i += error.length;
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (c === "(" || c === ")" || c === ",") {
|
|
2020
|
+
tokens.push(new Token(c === "(" ? "open" : c === ")" ? "close" : "comma", c));
|
|
2021
|
+
i++;
|
|
2022
|
+
continue;
|
|
2023
|
+
}
|
|
2024
|
+
const qualifiedError = matchAt(qualifiedRefError, formula, i);
|
|
2025
|
+
if (qualifiedError !== null) {
|
|
2026
|
+
tokens.push(new Token("error", qualifiedError[0], { sheet: sheetOf(qualifiedError) }));
|
|
2027
|
+
i += qualifiedError[0].length;
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
const span = matchAt(sheetSpanPattern, formula, i);
|
|
2031
|
+
if (span !== null) {
|
|
2032
|
+
const place = matchAt(referencePattern, formula, i + span[0].length);
|
|
2033
|
+
if (place?.groups !== void 0 && place.groups["sheet"] === void 0 && isReference(place)) {
|
|
2034
|
+
const end = i + span[0].length + place[0].length;
|
|
2035
|
+
const spannedSpill = place.groups["c1"] !== void 0 && place.groups["c2"] === void 0 && end < formula.length && formula.charAt(end) === "#";
|
|
2036
|
+
const g = span.groups;
|
|
2037
|
+
const first = g?.["quotedFirst"] ?? g?.["first"];
|
|
2038
|
+
const last = g?.["quotedLast"] ?? g?.["last"];
|
|
2039
|
+
if (first === void 0 || last === void 0) throw new Error("Internal: a span names no Sheets.");
|
|
2040
|
+
tokens.push(new Token("reference", span[0] + place[0], { spilled: spannedSpill, sheet: first.replaceAll("''", "'"), lastSheet: last.replaceAll("''", "'") }));
|
|
2041
|
+
i = end + (spannedSpill ? 1 : 0);
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
const reference = matchAt(referencePattern, formula, i);
|
|
2046
|
+
if (reference?.groups !== void 0 && isReference(reference)) {
|
|
2047
|
+
const end = i + reference[0].length;
|
|
2048
|
+
const spilled = reference.groups["c1"] !== void 0 && reference.groups["c2"] === void 0 && end < formula.length && formula.charAt(end) === "#";
|
|
2049
|
+
tokens.push(new Token("reference", reference[0], { spilled, sheet: sheetOf(reference) }));
|
|
2050
|
+
i = end + (spilled ? 1 : 0);
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
if (c >= "0" && c <= "9" || c === ".") {
|
|
2054
|
+
const number = matchAt(numberPattern, formula, i);
|
|
2055
|
+
if (number === null) return unreadable;
|
|
2056
|
+
tokens.push(new Token("number", number[0]));
|
|
2057
|
+
i += number[0].length;
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
const name = matchAt(namePattern, formula, i);
|
|
2061
|
+
if (name !== null) {
|
|
2062
|
+
const end = i + name[0].length;
|
|
2063
|
+
if (end < formula.length && formula.charAt(end) === "[") {
|
|
2064
|
+
const column = structuredColumn(formula, end);
|
|
2065
|
+
if (column.name === void 0) {
|
|
2066
|
+
if (!column.at) return unreadable;
|
|
2067
|
+
return { tokens, problem: "columnName" };
|
|
2068
|
+
}
|
|
2069
|
+
const declared = columnsOf?.(name[0]);
|
|
2070
|
+
const columnName = column.name;
|
|
2071
|
+
if (declared === void 0 || !declared.some((d) => equalsIgnoreCase(d, columnName))) return { tokens, problem: "structuredReference" };
|
|
2072
|
+
if (columnName.startsWith(" ") || columnName.endsWith(" ") || unseenInColumns.test(columnName)) {
|
|
2073
|
+
return { tokens, problem: "columnName" };
|
|
2074
|
+
}
|
|
2075
|
+
tokens.push(new Token("structuredReference", name[0] + storedColumn(columnName)));
|
|
2076
|
+
i = column.after;
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
tokens.push(new Token("name", name[0], { called: end < formula.length && formula.charAt(end) === "(" }));
|
|
2080
|
+
i = end;
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
const op = operators.find((o) => formula.startsWith(o, i));
|
|
2084
|
+
if (op !== void 0) {
|
|
2085
|
+
tokens.push(new Token("operator", op));
|
|
2086
|
+
i += op.length;
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
return unreadable;
|
|
2090
|
+
}
|
|
2091
|
+
return { tokens, problem: "none" };
|
|
2092
|
+
}
|
|
2093
|
+
function structuredColumn(formula, open) {
|
|
2094
|
+
const none = (at) => ({ name: void 0, after: open, at });
|
|
2095
|
+
const doubled = open + 1 < formula.length && formula.charAt(open + 1) === "[";
|
|
2096
|
+
let i = open + (doubled ? 2 : 1);
|
|
2097
|
+
let column = "";
|
|
2098
|
+
for (; ; ) {
|
|
2099
|
+
if (i >= formula.length) return none(false);
|
|
2100
|
+
const c = formula.charAt(i);
|
|
2101
|
+
if (c === "'" && i + 1 < formula.length) {
|
|
2102
|
+
column += formula.charAt(i + 1);
|
|
2103
|
+
i += 2;
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (c === "[" || c === "#" || c === "@") return none(c === "@");
|
|
2107
|
+
if (c === "]") break;
|
|
2108
|
+
column += c;
|
|
2109
|
+
i++;
|
|
2110
|
+
}
|
|
2111
|
+
if (doubled) {
|
|
2112
|
+
if (i + 1 >= formula.length || formula.charAt(i + 1) !== "]") return none(false);
|
|
2113
|
+
i++;
|
|
2114
|
+
}
|
|
2115
|
+
if (column.length === 0) return none(false);
|
|
2116
|
+
return { name: column, after: i + 1, at: false };
|
|
2117
|
+
}
|
|
2118
|
+
function storedColumn(column) {
|
|
2119
|
+
let text = "[";
|
|
2120
|
+
for (const c of column) {
|
|
2121
|
+
if (c === "[" || c === "]" || c === "#" || c === "'") text += "'";
|
|
2122
|
+
text += c;
|
|
2123
|
+
}
|
|
2124
|
+
return `${text}]`;
|
|
2125
|
+
}
|
|
2126
|
+
function sheetOf(match) {
|
|
2127
|
+
const raw = match.groups?.["sheet"];
|
|
2128
|
+
if (raw === void 0) return void 0;
|
|
2129
|
+
return raw.startsWith("'") ? raw.slice(1, -1).replaceAll("''", "'") : raw;
|
|
2130
|
+
}
|
|
2131
|
+
function bind(tokens) {
|
|
2132
|
+
const bindings = [];
|
|
2133
|
+
const frames = [{ function: void 0, arguments: [[]] }];
|
|
2134
|
+
const top = () => elementAt(frames, frames.length - 1);
|
|
2135
|
+
const lastArgument = (frame) => elementAt(frame.arguments, frame.arguments.length - 1);
|
|
2136
|
+
let calls = 0;
|
|
2137
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2138
|
+
const token = elementAt(tokens, i);
|
|
2139
|
+
switch (token.kind) {
|
|
2140
|
+
case "open": {
|
|
2141
|
+
lastArgument(top()).push(i);
|
|
2142
|
+
const previous = i > 0 ? elementAt(tokens, i - 1) : void 0;
|
|
2143
|
+
const called = previous !== void 0 && previous.kind === "name" && previous.called ? previous : void 0;
|
|
2144
|
+
if (called !== void 0 && ++calls > maxDepth) return "tooDeep";
|
|
2145
|
+
frames.push({ function: called, arguments: [[]] });
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
case "comma":
|
|
2149
|
+
if (frames.length > 1) {
|
|
2150
|
+
top().arguments.push([]);
|
|
2151
|
+
if (top().function !== void 0 && top().arguments.length > maxArguments) return "tooManyArguments";
|
|
2152
|
+
} else {
|
|
2153
|
+
lastArgument(top()).push(i);
|
|
2154
|
+
}
|
|
2155
|
+
break;
|
|
2156
|
+
case "close":
|
|
2157
|
+
if (frames.length > 1) {
|
|
2158
|
+
const frame = elementAt(frames, frames.length - 1);
|
|
2159
|
+
frames.pop();
|
|
2160
|
+
if (frame.function !== void 0) calls--;
|
|
2161
|
+
lastArgument(top()).push(i);
|
|
2162
|
+
binds(frame, tokens, i, bindings);
|
|
2163
|
+
} else {
|
|
2164
|
+
lastArgument(top()).push(i);
|
|
2165
|
+
}
|
|
2166
|
+
break;
|
|
2167
|
+
case "space":
|
|
2168
|
+
break;
|
|
2169
|
+
case "text":
|
|
2170
|
+
case "error":
|
|
2171
|
+
case "reference":
|
|
2172
|
+
case "number":
|
|
2173
|
+
case "name":
|
|
2174
|
+
case "operator":
|
|
2175
|
+
case "structuredReference":
|
|
2176
|
+
lastArgument(top()).push(i);
|
|
2177
|
+
break;
|
|
2178
|
+
default:
|
|
2179
|
+
return unreachableKind(token.kind);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2183
|
+
const name = elementAt(tokens, i);
|
|
2184
|
+
if (name.kind !== "name" || !bindings.some((b) => b.first <= i && i <= b.last && equalsIgnoreCase(b.name, name.text))) continue;
|
|
2185
|
+
if (!name.called) name.bound = true;
|
|
2186
|
+
else if (DeclaredFunction.find(name.text) === void 0) return "boundCall";
|
|
2187
|
+
}
|
|
2188
|
+
return "none";
|
|
2189
|
+
}
|
|
2190
|
+
function unreachableKind(kind) {
|
|
2191
|
+
throw new Error(`Internal: ${String(kind)} is not a token's kind.`);
|
|
2192
|
+
}
|
|
2193
|
+
function binds(frame, tokens, close, bindings) {
|
|
2194
|
+
const fn = frame.function;
|
|
2195
|
+
if (fn === void 0) return;
|
|
2196
|
+
const args = frame.arguments;
|
|
2197
|
+
const loneName = (argument) => {
|
|
2198
|
+
if (argument.length !== 1) return void 0;
|
|
2199
|
+
const at = elementAt(argument, 0);
|
|
2200
|
+
const token = elementAt(tokens, at);
|
|
2201
|
+
return token.kind === "name" && !token.called ? [at, token] : void 0;
|
|
2202
|
+
};
|
|
2203
|
+
if (equalsIgnoreCase(fn.text, "LET")) {
|
|
2204
|
+
for (let k = 0; k + 1 < args.length; k += 2) {
|
|
2205
|
+
const named = loneName(elementAt(args, k));
|
|
2206
|
+
if (named !== void 0) bindings.push({ name: named[1].text, first: named[0], last: close });
|
|
2207
|
+
}
|
|
2208
|
+
} else if (equalsIgnoreCase(fn.text, "LAMBDA") && args.length > 0 && elementAt(args, args.length - 1).length > 0) {
|
|
2209
|
+
const body = elementAt(args, args.length - 1);
|
|
2210
|
+
for (let k = 0; k + 1 < args.length; k++) {
|
|
2211
|
+
const named = loneName(elementAt(args, k));
|
|
2212
|
+
if (named !== void 0) {
|
|
2213
|
+
bindings.push({ name: named[1].text, first: named[0], last: named[0] });
|
|
2214
|
+
bindings.push({ name: named[1].text, first: elementAt(body, 0), last: elementAt(body, body.length - 1) });
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
function sameUnbound(tokens, engine) {
|
|
2220
|
+
const unbound = new Set(tokens.filter((t) => t.kind === "name" && !t.called && !t.bound && !isBoolean(t.text) && isAscii(t.text)).map((t) => foldCase(t.text)));
|
|
2221
|
+
const found = new Set(engine.map(foldCase));
|
|
2222
|
+
return unbound.size === found.size && [...found].every((name) => unbound.has(name));
|
|
2223
|
+
}
|
|
2224
|
+
function isAscii(text) {
|
|
2225
|
+
for (let i = 0; i < text.length; i++) {
|
|
2226
|
+
if (text.charCodeAt(i) >= 128) return false;
|
|
2227
|
+
}
|
|
2228
|
+
return true;
|
|
2229
|
+
}
|
|
2230
|
+
function isBoolean(name) {
|
|
2231
|
+
return equalsIgnoreCase(name, "TRUE") || equalsIgnoreCase(name, "FALSE");
|
|
2232
|
+
}
|
|
2233
|
+
function readsColumn(value) {
|
|
2234
|
+
return value === void 0 || CellAddress2.tryParseColumn(trimDollars(value)) !== void 0;
|
|
2235
|
+
}
|
|
2236
|
+
function readsRow(value) {
|
|
2237
|
+
if (value === void 0) return true;
|
|
2238
|
+
const digits = trimDollars(value);
|
|
2239
|
+
return !digits.startsWith("0") && /^[0-9]+$/.test(digits) && Number(digits) <= Sheet4.rowCount;
|
|
2240
|
+
}
|
|
2241
|
+
function isReference(match) {
|
|
2242
|
+
const g = match.groups;
|
|
2243
|
+
if (g === void 0) return false;
|
|
2244
|
+
return readsColumn(g["c1"]) && readsColumn(g["c2"]) && readsColumn(g["cc1"]) && readsColumn(g["cc2"]) && readsRow(g["r1"]) && readsRow(g["r2"]) && readsRow(g["rr1"]) && readsRow(g["rr2"]);
|
|
2245
|
+
}
|
|
2246
|
+
function trimDollars(text) {
|
|
2247
|
+
let i = 0;
|
|
2248
|
+
while (i < text.length && text.charAt(i) === "$") i++;
|
|
2249
|
+
return text.slice(i);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// src/writer.ts
|
|
2253
|
+
var maxFormulaLength = 8192;
|
|
2254
|
+
var maxTextLength = 32767;
|
|
2255
|
+
var maxLinkedTableRows = maxRows - 1;
|
|
2256
|
+
async function saveXlsxWorkbook(workbook, options = {}) {
|
|
2257
|
+
options.signal?.throwIfAborted();
|
|
2258
|
+
const maxBytes = options.maxUncompressedBytes === void 0 ? defaultMaxUncompressedBytes : options.maxUncompressedBytes;
|
|
2259
|
+
if (!(maxBytes >= 1 && maxBytes <= defaultMaxUncompressedBytes)) {
|
|
2260
|
+
throw new RangeError(`MaxUncompressedBytes is 1 to ${grouped(defaultMaxUncompressedBytes)}.`);
|
|
2261
|
+
}
|
|
2262
|
+
const maxCells = options.maxCells === void 0 ? defaultMaxCells : options.maxCells;
|
|
2263
|
+
if (typeof maxCells !== "number" || Number.isNaN(maxCells)) throw new RangeError(`MaxCells is a number of elements, not ${String(maxCells)}.`);
|
|
2264
|
+
const snapshot = snapshotOf(workbook);
|
|
2265
|
+
const pacer = new SavePacer(options);
|
|
2266
|
+
let writer;
|
|
2267
|
+
try {
|
|
2268
|
+
writer = await build(snapshot, maxCells, maxBytes, pacer);
|
|
2269
|
+
const file = await writer.complete();
|
|
2270
|
+
options.signal?.throwIfAborted();
|
|
2271
|
+
return file;
|
|
2272
|
+
} catch (e) {
|
|
2273
|
+
await writer?.abandon();
|
|
2274
|
+
if (e instanceof XlsxError3 && e.reason === "tooLarge") throw tooLarge(e, maxBytes);
|
|
2275
|
+
throw e;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
function tooLarge(e, maxBytes) {
|
|
2279
|
+
const part = e.part === void 0 ? "" : e.part;
|
|
2280
|
+
return new XlsxSaveError(
|
|
2281
|
+
"tooLarge",
|
|
2282
|
+
`The workbook would hold more than the ${grouped(maxBytes)} uncompressed bytes a file is written with, and read with by default; the part '${part}' went past it.`,
|
|
2283
|
+
{ sheet: e.sheet }
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
function snapshotOf(workbook) {
|
|
2287
|
+
const document = workbook.toDocument();
|
|
2288
|
+
const sheets = [];
|
|
2289
|
+
const names = /* @__PURE__ */ new Map();
|
|
2290
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
2291
|
+
const values = /* @__PURE__ */ new Map();
|
|
2292
|
+
const spilledFrom = /* @__PURE__ */ new Map();
|
|
2293
|
+
for (const at of sheet.valueAddresses) {
|
|
2294
|
+
const value = sheet.getValue(at);
|
|
2295
|
+
if (value !== void 0) values.set(at.key, value);
|
|
2296
|
+
const anchor = sheet.spilledFrom(at);
|
|
2297
|
+
if (anchor !== void 0) spilledFrom.set(at.key, { at, anchor });
|
|
2298
|
+
}
|
|
2299
|
+
sheets.push({ name: sheet.name, document: elementAt(document.sheets, i), values, spilledFrom });
|
|
2300
|
+
if (!names.has(foldCase(sheet.name))) names.set(foldCase(sheet.name), sheet.name);
|
|
2301
|
+
});
|
|
2302
|
+
const tables = workbook.linkedTables.map((table) => ({ table, columns: table.isWaiting ? void 0 : workbook.linkedTableColumns(table.name) }));
|
|
2303
|
+
return { culture: workbook.culture, shown: indexOf(workbook, workbook.shown), sheets, sheetNames: names, tables };
|
|
2304
|
+
}
|
|
2305
|
+
function indexOf(workbook, sheet) {
|
|
2306
|
+
const sheets = workbook.sheets;
|
|
2307
|
+
for (let i = 0; i < sheets.length; i++) {
|
|
2308
|
+
if (sheets[i] === sheet) return i;
|
|
2309
|
+
}
|
|
2310
|
+
return 0;
|
|
2311
|
+
}
|
|
2312
|
+
async function build(snapshot, maxCells, maxBytes, pacer) {
|
|
2313
|
+
pacer.sheets = snapshot.sheets.length + snapshot.tables.length;
|
|
2314
|
+
const tables = await tablesOf(snapshot, pacer);
|
|
2315
|
+
const planned = [];
|
|
2316
|
+
let cells = 0;
|
|
2317
|
+
for (const sheet of snapshot.sheets) {
|
|
2318
|
+
if (!carries(sheet.name, true)) {
|
|
2319
|
+
throw new XlsxSaveError(
|
|
2320
|
+
"text",
|
|
2321
|
+
"The Sheet's name holds a character a file cannot carry in a worksheet's name: a control character below U+0020, U+FFFE or U+FFFF, or half of a surrogate pair.",
|
|
2322
|
+
{ sheet: sheet.name }
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
const own = ownCells(sheet);
|
|
2326
|
+
const plan = new SheetFormatPlan(sheet.document);
|
|
2327
|
+
const crossings = plan.crossings(own.map((c) => c.at));
|
|
2328
|
+
const rows = plan.formattedRows;
|
|
2329
|
+
cells += own.length + crossings + rows;
|
|
2330
|
+
if (cells > maxCells) {
|
|
2331
|
+
throw new XlsxSaveError(
|
|
2332
|
+
"tooManyCells",
|
|
2333
|
+
`The workbook would write more than the ${grouped(maxCells)} elements it may (XlsxSaveOptions.MaxCells, which counts the cells written and each row a row format writes; by default the cells a file is read with): this Sheet writes ${grouped(own.length)} cells of its own, ${grouped(crossings)} where its formatted rows cross its formatted columns, and ${grouped(rows)} rows its row formats write.`,
|
|
2334
|
+
{ sheet: sheet.name }
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
planned.push({ sheet, plan, cells: own });
|
|
2338
|
+
await pacer.checkpoint();
|
|
2339
|
+
}
|
|
2340
|
+
for (const { table, columns } of tables) {
|
|
2341
|
+
cells += table.columns.length;
|
|
2342
|
+
for (const column of columns) {
|
|
2343
|
+
for (const value of column) if (value !== void 0) cells++;
|
|
2344
|
+
}
|
|
2345
|
+
if (cells > maxCells) {
|
|
2346
|
+
throw new XlsxSaveError(
|
|
2347
|
+
"tooManyCells",
|
|
2348
|
+
`The workbook would write more than the ${grouped(maxCells)} elements it may (XlsxSaveOptions.MaxCells, which counts the cells written and each row a row format writes; by default the cells a file is read with): the Linked Table '${table.name}' writes ${grouped(table.rowCount)} rows of ${grouped(table.columns.length)} columns under its header.`,
|
|
2349
|
+
{ sheet: table.name }
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
const styles = new StyleTable(snapshot.culture);
|
|
2354
|
+
const sheets = [];
|
|
2355
|
+
for (const { sheet, plan, cells: own } of planned) {
|
|
2356
|
+
plan.build(styles);
|
|
2357
|
+
const recorded = [];
|
|
2358
|
+
for (const { at, entry, own: parts } of own) {
|
|
2359
|
+
recorded.push({ at, entry, style: plan.style(styles, parts, at) });
|
|
2360
|
+
const step = pacer.step(0);
|
|
2361
|
+
if (step !== void 0) await step;
|
|
2362
|
+
}
|
|
2363
|
+
sheets.push({ sheet, plan, cells: recorded, spills: spillsOf(sheet) });
|
|
2364
|
+
}
|
|
2365
|
+
pacer.total = cells;
|
|
2366
|
+
pacer.sheet = 0;
|
|
2367
|
+
const writer = createXlsxWriter({
|
|
2368
|
+
styles: styles.build(),
|
|
2369
|
+
activeSheet: snapshot.shown,
|
|
2370
|
+
fullCalculationOnLoad: true,
|
|
2371
|
+
maxUncompressedBytes: maxBytes
|
|
2372
|
+
});
|
|
2373
|
+
const declared = /* @__PURE__ */ new Map();
|
|
2374
|
+
for (const { table } of snapshot.tables) {
|
|
2375
|
+
if (!declared.has(foldCase(table.name))) declared.set(foldCase(table.name), table.columns);
|
|
2376
|
+
}
|
|
2377
|
+
const columnsOf = (name) => declared.get(foldCase(name));
|
|
2378
|
+
const hasSheet = (name) => snapshot.sheetNames.has(foldCase(name));
|
|
2379
|
+
try {
|
|
2380
|
+
for (const { sheet, plan, cells: recorded, spills } of sheets) {
|
|
2381
|
+
const out = new Paced(writer.addSheet(sheet.name, plan.columns), pacer);
|
|
2382
|
+
await writeSheet(out, sheet, plan, recorded, spills, hasSheet, columnsOf);
|
|
2383
|
+
pacer.sheet++;
|
|
2384
|
+
}
|
|
2385
|
+
for (const table of tables) {
|
|
2386
|
+
await writeTable(new Paced(writer.addSheet(table.table.name), pacer), table);
|
|
2387
|
+
pacer.sheet++;
|
|
2388
|
+
}
|
|
2389
|
+
pacer.sheet = Math.max(0, pacer.sheets - 1);
|
|
2390
|
+
pacer.report();
|
|
2391
|
+
return writer;
|
|
2392
|
+
} catch (e) {
|
|
2393
|
+
await writer.abandon();
|
|
2394
|
+
throw e;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
var flushEvery = 4096;
|
|
2398
|
+
var Paced = class {
|
|
2399
|
+
sheet;
|
|
2400
|
+
#pacer;
|
|
2401
|
+
#since = 0;
|
|
2402
|
+
constructor(sheet, pacer) {
|
|
2403
|
+
this.sheet = sheet;
|
|
2404
|
+
this.#pacer = pacer;
|
|
2405
|
+
}
|
|
2406
|
+
async row(row, style) {
|
|
2407
|
+
this.sheet.writeRow({ row, styleIndex: style });
|
|
2408
|
+
await this.#step();
|
|
2409
|
+
}
|
|
2410
|
+
async cell(cell) {
|
|
2411
|
+
this.sheet.write(cell);
|
|
2412
|
+
await this.#step();
|
|
2413
|
+
}
|
|
2414
|
+
async #step() {
|
|
2415
|
+
if (++this.#since >= flushEvery) {
|
|
2416
|
+
this.#since = 0;
|
|
2417
|
+
await this.sheet.flush();
|
|
2418
|
+
}
|
|
2419
|
+
const step = this.#pacer.step();
|
|
2420
|
+
if (step !== void 0) await step;
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
async function tablesOf(snapshot, pacer) {
|
|
2424
|
+
const tables = [];
|
|
2425
|
+
for (const { table, columns } of snapshot.tables) {
|
|
2426
|
+
const name = table.name;
|
|
2427
|
+
const where = { sheet: name };
|
|
2428
|
+
if (table.isWaiting || columns === void 0) {
|
|
2429
|
+
throw new XlsxSaveError(
|
|
2430
|
+
"linkedTable",
|
|
2431
|
+
`The Linked Table '${name}' has not answered: it holds no rows yet, or its last snapshot was refused, and every Formula that reads it shows #GETTING_DATA. Its rows are written as they stand, and it has none to write.`,
|
|
2432
|
+
where
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
const sheet = snapshot.sheetNames.get(foldCase(name));
|
|
2436
|
+
if (sheet !== void 0) {
|
|
2437
|
+
throw new XlsxSaveError("linkedTable", `The Linked Table '${name}' is written on a worksheet of its name, and the Sheet '${sheet}' already has it.`, where);
|
|
2438
|
+
}
|
|
2439
|
+
if (!isValidTableName(name)) {
|
|
2440
|
+
throw new XlsxSaveError(
|
|
2441
|
+
"linkedTable",
|
|
2442
|
+
`The Linked Table '${name}' is written as an Excel Table of its name, and Excel gives a Table no such name: it reads as a cell's address, or holds a character a Table's name does not.`,
|
|
2443
|
+
where
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
const why = Sheet5.whyNotAName(name);
|
|
2447
|
+
if (why !== void 0) {
|
|
2448
|
+
throw new XlsxSaveError("linkedTable", `The Linked Table '${name}' is written on a worksheet of its name, and a worksheet cannot have it: ${why}`, where);
|
|
2449
|
+
}
|
|
2450
|
+
if (table.columns.length > maxColumns) {
|
|
2451
|
+
throw new XlsxSaveError(
|
|
2452
|
+
"linkedTable",
|
|
2453
|
+
`The Linked Table '${name}' has ${grouped(table.columns.length)} columns, and a worksheet of Excel holds ${grouped(maxColumns)}.`,
|
|
2454
|
+
where
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
if (table.rowCount === 0) {
|
|
2458
|
+
throw new XlsxSaveError(
|
|
2459
|
+
"linkedTable",
|
|
2460
|
+
`The Linked Table '${name}' holds no rows, and an Excel Table holds at least one: written with one empty row, its Formulas would answer otherwise in Excel (ROWS gives 1, COUNTBLANK 1, the column itself 0 where ExSheet shows #VALUE!).`,
|
|
2461
|
+
where
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
if (table.rowCount > maxLinkedTableRows) {
|
|
2465
|
+
throw new XlsxSaveError(
|
|
2466
|
+
"linkedTable",
|
|
2467
|
+
`The Linked Table '${name}' holds ${grouped(table.rowCount)} rows, and a worksheet of Excel holds ${grouped(maxRows)}, its header's included.`,
|
|
2468
|
+
where
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
for (let c = 0; c < columns.length; c++) {
|
|
2472
|
+
const columnName = elementAt(table.columns, c);
|
|
2473
|
+
checkTableText(name, columnName, 0, c, "as a column's name");
|
|
2474
|
+
const column = elementAt(columns, c);
|
|
2475
|
+
for (let r = 0; r < column.length; r++) {
|
|
2476
|
+
const value = column[r];
|
|
2477
|
+
if (value !== void 0 && value.kind === "text") {
|
|
2478
|
+
checkTableText(name, value.text, r + 1, c, `in its column '${columnName}', row ${grouped(r + 1)},`);
|
|
2479
|
+
}
|
|
2480
|
+
const step = pacer.step(0);
|
|
2481
|
+
if (step !== void 0) await step;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
tables.push({ table, columns });
|
|
2485
|
+
}
|
|
2486
|
+
return tables;
|
|
2487
|
+
}
|
|
2488
|
+
function checkTableText(table, text, row, column, at) {
|
|
2489
|
+
const cell = formatCell2(row, column);
|
|
2490
|
+
if (text.length > maxTextLength) {
|
|
2491
|
+
throw new XlsxSaveError(
|
|
2492
|
+
"text",
|
|
2493
|
+
`The Linked Table '${table}' holds, ${at} text ${grouped(text.length)} characters long, and a cell of Excel holds at most ${grouped(maxTextLength)}.`,
|
|
2494
|
+
{ sheet: table, cell }
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
if (!carries(text, false)) {
|
|
2498
|
+
throw new XlsxSaveError("text", `The Linked Table '${table}' holds, ${at} text with half of a surrogate pair, which a file cannot carry.`, { sheet: table, cell });
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
async function writeTable(out, { table, columns }) {
|
|
2502
|
+
out.sheet.addTable({ name: table.name, headerRow: 0, firstColumn: 0, columns: table.columns, dataRows: table.rowCount });
|
|
2503
|
+
for (let c = 0; c < table.columns.length; c++) {
|
|
2504
|
+
await out.cell({ row: 0, column: c, type: "sharedString", text: elementAt(table.columns, c) });
|
|
2505
|
+
}
|
|
2506
|
+
for (let r = 0; r < table.rowCount; r++) {
|
|
2507
|
+
for (let c = 0; c < columns.length; c++) {
|
|
2508
|
+
const value = elementAt(columns, c)[r];
|
|
2509
|
+
if (value === void 0) continue;
|
|
2510
|
+
const row = r + 1;
|
|
2511
|
+
switch (value.kind) {
|
|
2512
|
+
case "number":
|
|
2513
|
+
await out.cell({ row, column: c, type: "number", value: roundTrip(value.number) });
|
|
2514
|
+
break;
|
|
2515
|
+
case "text":
|
|
2516
|
+
await out.cell({ row, column: c, type: "sharedString", text: value.text });
|
|
2517
|
+
break;
|
|
2518
|
+
case "boolean":
|
|
2519
|
+
await out.cell({ row, column: c, type: "boolean", value: value.boolean ? "1" : "0" });
|
|
2520
|
+
break;
|
|
2521
|
+
case "error":
|
|
2522
|
+
await out.cell({ row, column: c, type: "error", value: value.toString() });
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
function ownCells(sheet) {
|
|
2529
|
+
const cells = /* @__PURE__ */ new Map();
|
|
2530
|
+
for (const cell of sheet.document.cells) {
|
|
2531
|
+
cells.set(cell.address.key, {
|
|
2532
|
+
at: cell.address,
|
|
2533
|
+
entry: cell.entry,
|
|
2534
|
+
own: new FormatParts(cell.numberFormat, cell.alignment, cell.font, cell.fill, cell.borders)
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
for (const [key, { at }] of sheet.spilledFrom) {
|
|
2538
|
+
if (!cells.has(key)) cells.set(key, { at, entry: void 0, own: FormatParts.empty });
|
|
2539
|
+
}
|
|
2540
|
+
return [...cells.values()].toSorted((a, b) => a.at.compareTo(b.at));
|
|
2541
|
+
}
|
|
2542
|
+
function spillsOf(sheet) {
|
|
2543
|
+
const spills = /* @__PURE__ */ new Map();
|
|
2544
|
+
for (const { at, anchor } of sheet.spilledFrom.values()) {
|
|
2545
|
+
const last = spills.get(anchor.key);
|
|
2546
|
+
spills.set(anchor.key, last === void 0 ? at : new CellAddress3(Math.max(last.row, at.row), Math.max(last.column, at.column)));
|
|
2547
|
+
}
|
|
2548
|
+
return spills;
|
|
2549
|
+
}
|
|
2550
|
+
async function writeSheet(out, sheet, plan, cells, spills, hasSheet, columnsOf) {
|
|
2551
|
+
const write = (cell) => out.cell(cellOf(sheet, cell, spills, hasSheet, columnsOf));
|
|
2552
|
+
const runs = plan.rows;
|
|
2553
|
+
let run = 0;
|
|
2554
|
+
let runRow = runs.length > 0 ? elementAt(runs, 0).first : Number.MAX_SAFE_INTEGER;
|
|
2555
|
+
let next = 0;
|
|
2556
|
+
while (next < cells.length || run < runs.length) {
|
|
2557
|
+
const cellRow = next < cells.length ? elementAt(cells, next).at.row : Number.MAX_SAFE_INTEGER;
|
|
2558
|
+
const row = Math.min(cellRow, runRow);
|
|
2559
|
+
let end = next;
|
|
2560
|
+
while (end < cells.length && elementAt(cells, end).at.row === row) end++;
|
|
2561
|
+
if (row === runRow) {
|
|
2562
|
+
const formatted2 = elementAt(runs, run);
|
|
2563
|
+
await out.row(row, formatted2.style);
|
|
2564
|
+
let at = next;
|
|
2565
|
+
for (const crossing of formatted2.crossings) {
|
|
2566
|
+
for (let column = crossing.first; column <= crossing.last; column++) {
|
|
2567
|
+
while (at < end && elementAt(cells, at).at.column < column) await write(elementAt(cells, at++));
|
|
2568
|
+
if (at < end && elementAt(cells, at).at.column === column) continue;
|
|
2569
|
+
await out.cell({ row, column, type: "number", value: null, styleIndex: crossing.style });
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
while (at < end) await write(elementAt(cells, at++));
|
|
2573
|
+
runRow++;
|
|
2574
|
+
if (runRow > formatted2.last) {
|
|
2575
|
+
run++;
|
|
2576
|
+
runRow = run < runs.length ? elementAt(runs, run).first : Number.MAX_SAFE_INTEGER;
|
|
2577
|
+
}
|
|
2578
|
+
} else {
|
|
2579
|
+
for (let at = next; at < end; at++) await write(elementAt(cells, at));
|
|
2580
|
+
}
|
|
2581
|
+
next = end;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
function cellOf(sheet, cell, spills, hasSheet, columnsOf) {
|
|
2585
|
+
const at = cell.at;
|
|
2586
|
+
const empty = { row: at.row, column: at.column, type: "number", value: null, styleIndex: cell.style };
|
|
2587
|
+
const entry = cell.entry;
|
|
2588
|
+
const constant = entry?.constant;
|
|
2589
|
+
if (constant !== void 0) {
|
|
2590
|
+
if (constant.kind !== "text") return valued(sheet, at, constant, empty, void 0);
|
|
2591
|
+
checkText(sheet, at, constant.text, void 0);
|
|
2592
|
+
return { ...empty, type: "sharedString", text: constant.text };
|
|
2593
|
+
}
|
|
2594
|
+
const value = sheet.values.get(at.key);
|
|
2595
|
+
const formula = entry?.formula;
|
|
2596
|
+
if (entry !== void 0 && formula !== void 0) {
|
|
2597
|
+
const where = { sheet: sheet.name, cell: at.toString(), held: formula };
|
|
2598
|
+
if (formula.length - 1 > maxFormulaLength) {
|
|
2599
|
+
throw new XlsxSaveError(
|
|
2600
|
+
"formula",
|
|
2601
|
+
`The Formula is ${grouped(formula.length - 1)} characters long, and Excel holds at most ${grouped(maxFormulaLength)}.`,
|
|
2602
|
+
{ sheet: sheet.name, cell: at.toString() }
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
if (!carries(formula, false)) throw new XlsxSaveError("formula", "The Formula holds half of a surrogate pair, which a file cannot carry.", where);
|
|
2606
|
+
const { text: stored, problem } = writeFormula(entry, hasSheet, columnsOf);
|
|
2607
|
+
switch (problem) {
|
|
2608
|
+
case "boundCall":
|
|
2609
|
+
throw new XlsxSaveError("formula", "The Formula calls a name a LET binds or a LAMBDA takes, which ExSheet reads as an unknown function and Excel would call.", where);
|
|
2610
|
+
case "longText":
|
|
2611
|
+
throw new XlsxSaveError("formula", `The Formula holds a text constant longer than the ${String(maxText)} characters Excel takes in one.`, where);
|
|
2612
|
+
case "tooDeep":
|
|
2613
|
+
throw new XlsxSaveError("formula", `The Formula nests its calls deeper than the ${String(maxDepth)} levels Excel takes.`, where);
|
|
2614
|
+
case "tooManyArguments":
|
|
2615
|
+
throw new XlsxSaveError("formula", `The Formula makes a call with more than the ${String(maxArguments)} arguments Excel takes.`, where);
|
|
2616
|
+
case "missingSheet":
|
|
2617
|
+
throw new XlsxSaveError("formula", "The Formula reads a Sheet the Workbook does not have, which Excel would read as another workbook's.", where);
|
|
2618
|
+
case "structuredReference":
|
|
2619
|
+
throw new XlsxSaveError(
|
|
2620
|
+
"formula",
|
|
2621
|
+
"The Formula reads, by a structured reference, a Linked Table or a column the Workbook does not declare; the file would hold no Excel Table of it for Excel to read.",
|
|
2622
|
+
where
|
|
2623
|
+
);
|
|
2624
|
+
case "columnName":
|
|
2625
|
+
throw new XlsxSaveError(
|
|
2626
|
+
"formula",
|
|
2627
|
+
`The Formula reads a Linked Table's column whose name begins or ends with a space, which a structured reference in Excel does not keep, or holds @ or one of , : . " { } $ ^ & * + = - < > / or a tab or line break, whose form in a structured reference Excel has not been seen to write.`,
|
|
2628
|
+
where
|
|
2629
|
+
);
|
|
2630
|
+
case "tableName":
|
|
2631
|
+
throw new XlsxSaveError(
|
|
2632
|
+
"formula",
|
|
2633
|
+
"The Formula reads a Linked Table's name as a name of its own, which ExSheet shows as #NAME? and Excel would read as the Excel Table written.",
|
|
2634
|
+
where
|
|
2635
|
+
);
|
|
2636
|
+
case "unreadable":
|
|
2637
|
+
throw new XlsxSaveError("formula", "The Formula cannot be written as Excel stores it: its names do not bind as the engine binds them.", where);
|
|
2638
|
+
case "none":
|
|
2639
|
+
break;
|
|
2640
|
+
}
|
|
2641
|
+
const last = spills.get(at.key);
|
|
2642
|
+
const range = last === void 0 ? at.toString() : `${at.toString()}:${last.toString()}`;
|
|
2643
|
+
const written = { text: stored, kind: "array", reference: range, isDynamicArray: true };
|
|
2644
|
+
return { ...value === void 0 ? empty : valued(sheet, at, value, empty, formula), formula: written };
|
|
2645
|
+
}
|
|
2646
|
+
return value === void 0 ? empty : valued(sheet, at, value, empty, void 0);
|
|
2647
|
+
}
|
|
2648
|
+
function valued(sheet, at, value, cell, formula) {
|
|
2649
|
+
switch (value.kind) {
|
|
2650
|
+
case "number":
|
|
2651
|
+
return { ...cell, value: roundTrip(value.number) };
|
|
2652
|
+
case "boolean":
|
|
2653
|
+
return { ...cell, type: "boolean", value: value.boolean ? "1" : "0" };
|
|
2654
|
+
case "text":
|
|
2655
|
+
checkText(sheet, at, value.text, formula);
|
|
2656
|
+
return { ...cell, type: "formulaString", value: value.text };
|
|
2657
|
+
case "error":
|
|
2658
|
+
break;
|
|
2659
|
+
}
|
|
2660
|
+
const where = { sheet: sheet.name, cell: at.toString(), held: formula };
|
|
2661
|
+
if (value.error === "#CIRC!") {
|
|
2662
|
+
throw new XlsxSaveError(
|
|
2663
|
+
"circularReference",
|
|
2664
|
+
"The cell shows #CIRC!: it is part of a circular reference, or depends on one. A file holds no such Error Value, and Excel shows 0 there; no Value it can hold is the one shown.",
|
|
2665
|
+
where
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
if (value.error === "#GETTING_DATA") {
|
|
2669
|
+
throw new XlsxSaveError("linkedTable", "The cell shows #GETTING_DATA: it waits on a Linked Table that has not answered.", where);
|
|
2670
|
+
}
|
|
2671
|
+
return { ...cell, type: "error", value: value.toString() };
|
|
2672
|
+
}
|
|
2673
|
+
function isHighSurrogate(c) {
|
|
2674
|
+
return c >= 55296 && c <= 56319;
|
|
2675
|
+
}
|
|
2676
|
+
function isLowSurrogate(c) {
|
|
2677
|
+
return c >= 56320 && c <= 57343;
|
|
2678
|
+
}
|
|
2679
|
+
function carries(text, inName) {
|
|
2680
|
+
for (let i = 0; i < text.length; i++) {
|
|
2681
|
+
const c = text.charCodeAt(i);
|
|
2682
|
+
if (isHighSurrogate(c) && i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
2683
|
+
i++;
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2686
|
+
if (isHighSurrogate(c) || isLowSurrogate(c)) return false;
|
|
2687
|
+
if (inName && (c < 32 || c === 65534 || c === 65535)) return false;
|
|
2688
|
+
}
|
|
2689
|
+
return true;
|
|
2690
|
+
}
|
|
2691
|
+
function checkText(sheet, at, text, formula) {
|
|
2692
|
+
const where = { sheet: sheet.name, cell: at.toString(), held: formula };
|
|
2693
|
+
if (text.length > maxTextLength) {
|
|
2694
|
+
throw new XlsxSaveError("text", `The cell's text is ${grouped(text.length)} characters long, and a cell of Excel holds at most ${grouped(maxTextLength)}.`, where);
|
|
2695
|
+
}
|
|
2696
|
+
if (!carries(text, false)) throw new XlsxSaveError("text", "The cell's text holds half of a surrogate pair, which a file cannot carry.", where);
|
|
2697
|
+
}
|
|
2698
|
+
export {
|
|
2699
|
+
OpenReport,
|
|
2700
|
+
XlsxOpenError,
|
|
2701
|
+
XlsxSaveError,
|
|
2702
|
+
maxFormulaLength,
|
|
2703
|
+
maxLinkedTableRows,
|
|
2704
|
+
maxTextLength,
|
|
2705
|
+
openXlsxWorkbook,
|
|
2706
|
+
saveXlsxWorkbook
|
|
2707
|
+
};
|
|
2708
|
+
//# sourceMappingURL=index.js.map
|