@justybase/spreadsheet-tasks 2.2.0 → 3.1.0
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/CHANGELOG.md +90 -0
- package/README.md +11 -0
- package/browser/browser-spreadsheet.js +163 -60
- package/browser/justybase-spreadsheet.min.js +13 -13
- package/dist/cjs/index.d.ts +134 -7
- package/dist/cjs/index.js +1381 -464
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1364 -464
- package/dist/esm/index.js.map +1 -1
- package/docs/API.md +66 -1
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.1.0] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- XLSX output omits the `r` attribute on cells that directly follow the previous
|
|
15
|
+
cell (`<c><v>1</v></c>`). A cell keeps `r` after a gap, at the start of a row that
|
|
16
|
+
does not begin in column A, and in the header row only when needed. Excel and the
|
|
17
|
+
readers in this package resolve the position from the order of cells.
|
|
18
|
+
- XLSX and XLSB writers accept `{ compressionLevel }` (integer 0–9, default 6). The
|
|
19
|
+
archiver default was level 1, which compresses less.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Raw CRLF and lone CR in XML text (shared strings, inline strings, `str` cells) are
|
|
24
|
+
normalized to LF on read, as XML 1.0 requires. ` ` still reads as CR.
|
|
25
|
+
- `escapeXmlText` writes CR as ` `, so a CR written by the library survives a
|
|
26
|
+
round trip instead of being normalized away. The XLSX writer now does the same in
|
|
27
|
+
shared strings, where a raw CR was previously written and read back as LF by Excel.
|
|
28
|
+
- Text containing `_xHHHH_` is escaped as `_x005F_xHHHH_` when written, and
|
|
29
|
+
`_xHHHH_` written by Excel (for example `_x000D_`) is decoded on read (ECMA-376
|
|
30
|
+
ST_Xstring). Attribute values are not affected.
|
|
31
|
+
- `XlsxUpdater` infers the column of a cell without `r` when it reads existing styles,
|
|
32
|
+
so style inheritance works for files produced by this library and by xlspy.
|
|
33
|
+
|
|
34
|
+
## [3.0.0] - 2026-10-23
|
|
35
|
+
|
|
36
|
+
### Breaking
|
|
37
|
+
|
|
38
|
+
- `XlsxReader` / `XlsbReader` now return an empty row (`[]`, `getValue()` → `null`)
|
|
39
|
+
for every missing row position, including leading gaps, based on the row `r`
|
|
40
|
+
attribute (XLSX) and `BrtRowHdr` (XLSB). Like xlspy, gap positions are yielded;
|
|
41
|
+
unlike xlspy's padded `[None] * cols`, this reader yields a zero-length row.
|
|
42
|
+
Code that counts rows must account for gap rows.
|
|
43
|
+
|
|
44
|
+
### Documentation
|
|
45
|
+
|
|
46
|
+
- Date conversion is UTC-based on both write and read: a `Date` is stored by its
|
|
47
|
+
UTC instant (`getTime()`), so `new Date(2024, 5, 30)` in UTC+2 appears as
|
|
48
|
+
2024-06-29 22:00 in Excel. Use `Date.UTC(...)` for date-only values.
|
|
49
|
+
|
|
10
50
|
### Added
|
|
11
51
|
|
|
52
|
+
- `selectSheet(nameOrIndex)` and `nextResult()` on `ExcelReaderAbstract`,
|
|
53
|
+
implemented by `XlsxReader` and `XlsbReader`. Default remains sheet 0; state
|
|
54
|
+
(row/EOF, `fieldCount`) resets on selection.
|
|
55
|
+
- Excel limits (`MAX_ROWS=1_048_576`, `MAX_COLS=16_384`, `MAX_CELL_CHARS=32_767`
|
|
56
|
+
UTF-16 units) enforced in `XlsxWriter`, `XlsbWriter`, `BrowserXlsxWriter` and
|
|
57
|
+
`BrowserXlsbWriter`. Overlong text, oversized dimensions and rows/headers
|
|
58
|
+
longer than `columnCount` throw instead of writing corrupt files (xlspy parity).
|
|
59
|
+
- Shared XML helpers: `parseTagAttributes()`, `resolveRelTarget()` and
|
|
60
|
+
single-pass `unescapeXml()` in `src/xmlUtils.ts`, exported from the package
|
|
61
|
+
entry point.
|
|
12
62
|
- `convertXlsbToXlsx` and `convertXlsxToXlsb` — Windows/Excel COM conversions
|
|
13
63
|
with protected destinations and optional explicit overwriting.
|
|
14
64
|
- `replaceSheetDataStream()` and `saveStreaming()` for XLSX/XLSB updaters,
|
|
@@ -19,6 +69,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
69
|
|
|
20
70
|
### Fixed
|
|
21
71
|
|
|
72
|
+
- `BrowserXlsxWriter` / `BrowserXlsbWriter` skip a `FormattedCell` whose value is
|
|
73
|
+
`null` or `undefined`, matching the Node writers (previously a `TypeError`).
|
|
74
|
+
- Browser sources in `src/browser/` are type-checked under `strict` by
|
|
75
|
+
`tsconfig.browser.json`, which `npm run typecheck` runs. The `@ts-nocheck` pragmas
|
|
76
|
+
were removed.
|
|
77
|
+
- Column letters ≥702 (`AAA`…`XFD`): `XlsxWriter` and `BrowserXlsxWriter` now
|
|
78
|
+
delegate to `columnIndexToLetter()` instead of falling back to `'A'`.
|
|
79
|
+
- XLSB shared-string table: `BrtBeginSst` now writes `total` then `unique`
|
|
80
|
+
(was swapped); `BiffReaderWriter` and updaters already expected that order.
|
|
81
|
+
- Writer finalization is atomic: output goes to a same-directory temp file,
|
|
82
|
+
output/archive errors reject the promise, streams are destroyed and the temp
|
|
83
|
+
file removed; only a complete workbook is renamed into place (xlspy parity).
|
|
84
|
+
`writeAdmZipStreaming()` now observes output errors before/during the append loop.
|
|
85
|
+
- Workbook relationships and sheet paths: absolute `Target="/xl/…"` resolved
|
|
86
|
+
correctly, attribute order no longer matters (`parseTagAttributes` used in
|
|
87
|
+
both readers and both updaters). Verified against openpyxl output.
|
|
88
|
+
- `unescapeXml()` single-pass: `&lt;` stays `<` instead of becoming `<`.
|
|
89
|
+
- Shared strings: `<rPh>` phonetics skipped, `<si/>` yields `""`, rich-text
|
|
90
|
+
`inlineStr` concatenates all `<t>` runs.
|
|
91
|
+
- XLSX cell types: `t="str"` → text, `t="e"` → error text, `t="d"` → `Date`.
|
|
92
|
+
- Self-closing `<row …/>` no longer swallows the next row.
|
|
93
|
+
- 1904 date system: `workbookPr date1904` (XLSX) and `BrtWbProp` (XLSB) set the
|
|
94
|
+
reader/updater epoch (`1904-01-01` vs `1899-12-30`).
|
|
95
|
+
- XLSB dimension: updaters patch `BrtWsDim` (`0x94`, `rwLast@4`/`colLast@12`)
|
|
96
|
+
instead of `BrtSel` (`0x98`).
|
|
97
|
+
- Stale parts after `replaceSheetData`: `xl/calcChain.xml` (`.bin` for XLSB)
|
|
98
|
+
removed with its `Content_Types`/rels entries; XLSX `xl/tables/*.xml` refs
|
|
99
|
+
(`<table>` + `<autoFilter>`) updated to the new dimension when the table's
|
|
100
|
+
width is unchanged, without expanding over other tables. XLSB tables are not
|
|
101
|
+
patched (documented limitation).
|
|
22
102
|
- Destination replacement during Excel conversion now preserves files created
|
|
23
103
|
concurrently and rolls back safely when the final rename fails.
|
|
24
104
|
- Shared-string `count` values now reflect the replaced worksheet rather than
|
|
@@ -26,6 +106,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
26
106
|
committed only once.
|
|
27
107
|
- Updater saves now install the destination atomically through a same-directory
|
|
28
108
|
temporary file.
|
|
109
|
+
- 1900 date system leap-day gap: Excel counts a fictitious 1900-02-29, so
|
|
110
|
+
serials 1-59 denote 1900-01-01..1900-02-28. `oaDateToTime()` /
|
|
111
|
+
`timeToOaDate()` in `writerHelpers.ts` apply the one-day shift (both
|
|
112
|
+
directions, all readers/writers/updaters incl. browser builds); the 1904
|
|
113
|
+
system is unaffected. Previously serial 1 read as 1899-12-31, disagreeing
|
|
114
|
+
with Excel, xlspy and openpyxl.
|
|
115
|
+
- `XlsbUpdater` date fallback now recognises custom `BrtFmt` date formats
|
|
116
|
+
(e.g. xlspy's `yyyy-mm-dd`) via `BiffReaderWriter.readStyles()` instead of
|
|
117
|
+
only built-in numFmt ranges, so replacement `Date` cells keep a date style
|
|
118
|
+
instead of degrading to raw serials.
|
|
29
119
|
|
|
30
120
|
## [2.1.0] - 2026-08-07
|
|
31
121
|
|
package/README.md
CHANGED
|
@@ -179,6 +179,17 @@ await writer.finalize();
|
|
|
179
179
|
- Generate data on-the-fly from databases, APIs, or other sources
|
|
180
180
|
- Same performance as batch mode
|
|
181
181
|
|
|
182
|
+
### Compression Level
|
|
183
|
+
|
|
184
|
+
XLSX and XLSB writers compress the package with zlib. The default is level 6; pass a
|
|
185
|
+
level from 0 (store) to 9 (smallest, slowest) to trade size for speed:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const writer = new XlsxWriter('fast.xlsx', { compressionLevel: 1 });
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Out-of-range values throw a `RangeError`.
|
|
192
|
+
|
|
182
193
|
### Reading Excel Files
|
|
183
194
|
|
|
184
195
|
```typescript
|
|
@@ -181,6 +181,10 @@ var BrowserBigBuffer = class {
|
|
|
181
181
|
var BrowserZip = class _BrowserZip {
|
|
182
182
|
constructor() {
|
|
183
183
|
this.files = [];
|
|
184
|
+
this.files = [];
|
|
185
|
+
}
|
|
186
|
+
static {
|
|
187
|
+
this._crc32Table = null;
|
|
184
188
|
}
|
|
185
189
|
/**
|
|
186
190
|
* Add a file entry (string or Uint8Array).
|
|
@@ -298,7 +302,6 @@ var BrowserZip = class _BrowserZip {
|
|
|
298
302
|
return (crc ^ 4294967295) >>> 0;
|
|
299
303
|
}
|
|
300
304
|
};
|
|
301
|
-
BrowserZip._crc32Table = null;
|
|
302
305
|
|
|
303
306
|
// src/Formats.ts
|
|
304
307
|
var F = {
|
|
@@ -343,15 +346,64 @@ function getFormat(val) {
|
|
|
343
346
|
return null;
|
|
344
347
|
}
|
|
345
348
|
|
|
349
|
+
// src/xmlUtils.ts
|
|
350
|
+
function columnIndexToLetter(colIndex) {
|
|
351
|
+
let n = colIndex + 1;
|
|
352
|
+
let result = "";
|
|
353
|
+
while (n > 0) {
|
|
354
|
+
const rem = (n - 1) % 26;
|
|
355
|
+
result = String.fromCharCode(65 + rem) + result;
|
|
356
|
+
n = Math.floor((n - 1) / 26);
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/writerHelpers.ts
|
|
362
|
+
var OA_EPOCH_1900 = Date.UTC(1899, 11, 30);
|
|
363
|
+
var MARCH_1_1900 = Date.UTC(1900, 2, 1);
|
|
364
|
+
function timeToOaDate(timeMs, epochMs) {
|
|
365
|
+
let serial = (timeMs - epochMs) / 864e5;
|
|
366
|
+
if (epochMs === OA_EPOCH_1900 && timeMs < MARCH_1_1900) {
|
|
367
|
+
serial -= 1;
|
|
368
|
+
}
|
|
369
|
+
return serial;
|
|
370
|
+
}
|
|
371
|
+
var MAX_ROWS = 1048576;
|
|
372
|
+
var MAX_COLS = 16384;
|
|
373
|
+
var MAX_CELL_CHARS = 32767;
|
|
374
|
+
function assertCellTextLength(text) {
|
|
375
|
+
if (text.length > MAX_CELL_CHARS) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Excel cell text exceeds ${MAX_CELL_CHARS} UTF-16 code units (got ${text.length}).`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function assertColumnCount(columnCount) {
|
|
382
|
+
if (!Number.isInteger(columnCount) || columnCount < 1 || columnCount > MAX_COLS) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Excel column count must be 1..${MAX_COLS} (got ${columnCount}).`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function assertRowFitsColumns(kind, length, columnCount) {
|
|
389
|
+
if (length > columnCount) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`Excel ${kind} length ${length} exceeds columnCount ${columnCount}.`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function assertRowCount(totalRows) {
|
|
396
|
+
if (totalRows > MAX_ROWS) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`Excel row count exceeds ${MAX_ROWS} (got ${totalRows}).`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function cellRefAttr(col, prevCol, colRef, rowNum) {
|
|
403
|
+
return col === prevCol + 1 ? "" : ` r="${colRef}${rowNum}"`;
|
|
404
|
+
}
|
|
405
|
+
|
|
346
406
|
// src/browser/BrowserXlsxWriter.ts
|
|
347
|
-
var COL_LETTERS = (() => {
|
|
348
|
-
const l = [];
|
|
349
|
-
for (let i = 65; i < 91; i++) l.push(String.fromCharCode(i));
|
|
350
|
-
const t = [];
|
|
351
|
-
for (const p of l) for (let i = 65; i < 91; i++) t.push(p + String.fromCharCode(i));
|
|
352
|
-
l.push(...t);
|
|
353
|
-
return l;
|
|
354
|
-
})();
|
|
355
407
|
var BrowserXlsxWriter = class {
|
|
356
408
|
constructor() {
|
|
357
409
|
this._zip = new BrowserZip();
|
|
@@ -376,7 +428,7 @@ var BrowserXlsxWriter = class {
|
|
|
376
428
|
this._nextXf = 6;
|
|
377
429
|
}
|
|
378
430
|
_cl(i) {
|
|
379
|
-
return i
|
|
431
|
+
return columnIndexToLetter(i);
|
|
380
432
|
}
|
|
381
433
|
_san(n) {
|
|
382
434
|
if (!n || typeof n !== "string") return `Sheet${this._sc + 1}`;
|
|
@@ -389,17 +441,17 @@ var BrowserXlsxWriter = class {
|
|
|
389
441
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
390
442
|
}
|
|
391
443
|
_esc(s) {
|
|
392
|
-
|
|
444
|
+
s = s.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_");
|
|
393
445
|
let need = false;
|
|
394
446
|
for (let i = 0; i < s.length; i++) {
|
|
395
447
|
const c = s.charCodeAt(i);
|
|
396
|
-
if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
|
|
448
|
+
if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c === 13 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
|
|
397
449
|
need = true;
|
|
398
450
|
break;
|
|
399
451
|
}
|
|
400
452
|
}
|
|
401
453
|
if (!need) return s;
|
|
402
|
-
const escaped = s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
454
|
+
const escaped = s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r/g, " ");
|
|
403
455
|
let out = "";
|
|
404
456
|
for (let i = 0; i < escaped.length; i++) {
|
|
405
457
|
const c = escaped.charCodeAt(i);
|
|
@@ -416,7 +468,9 @@ var BrowserXlsxWriter = class {
|
|
|
416
468
|
this._sc++;
|
|
417
469
|
this._sl.push({ name: sn, path: `xl/worksheets/sheet${this._sc}.xml`, hidden, fn: `sheet${this._sc}.xml`, id: this._sc, rId: `rId${this._sc}`, fhr: null });
|
|
418
470
|
}
|
|
419
|
-
|
|
471
|
+
/** `ref` is the cell's `r` attribute from `cellRefAttr`, or '' when omitted. */
|
|
472
|
+
_wsc(bb, val, ref, so) {
|
|
473
|
+
assertCellTextLength(val);
|
|
420
474
|
let idx = this._sstM.get(val);
|
|
421
475
|
if (idx === void 0) {
|
|
422
476
|
idx = this._sstA.length;
|
|
@@ -425,9 +479,10 @@ var BrowserXlsxWriter = class {
|
|
|
425
479
|
}
|
|
426
480
|
this._sstCnt++;
|
|
427
481
|
const sa = so !== void 0 ? ` s="${so}"` : "";
|
|
428
|
-
bb.writeString(`<c
|
|
482
|
+
bb.writeString(`<c${ref} t="s"${sa}><v>${idx}</v></c>`);
|
|
429
483
|
}
|
|
430
|
-
_wscStyle(bb, val,
|
|
484
|
+
_wscStyle(bb, val, ref, styleId) {
|
|
485
|
+
assertCellTextLength(val);
|
|
431
486
|
let idx = this._sstM.get(val);
|
|
432
487
|
if (idx === void 0) {
|
|
433
488
|
idx = this._sstA.length;
|
|
@@ -435,35 +490,45 @@ var BrowserXlsxWriter = class {
|
|
|
435
490
|
this._sstM.set(val, idx);
|
|
436
491
|
}
|
|
437
492
|
this._sstCnt++;
|
|
438
|
-
bb.writeString(`<c
|
|
493
|
+
bb.writeString(`<c${ref} t="s" s="${styleId}"><v>${idx}</v></c>`);
|
|
439
494
|
}
|
|
440
495
|
_oa(d) {
|
|
441
|
-
return (d.getTime()
|
|
496
|
+
return timeToOaDate(d.getTime(), this._oaE);
|
|
442
497
|
}
|
|
443
498
|
_regFmt(fs) {
|
|
444
|
-
|
|
499
|
+
const existing = this._fmtXf.get(fs);
|
|
500
|
+
if (existing !== void 0) return existing;
|
|
445
501
|
const nid = this._nextNfmt++;
|
|
446
502
|
this._fmtReg.set(fs, nid);
|
|
447
503
|
const xf = this._nextXf++;
|
|
448
504
|
this._fmtXf.set(fs, xf);
|
|
449
505
|
return xf;
|
|
450
506
|
}
|
|
451
|
-
|
|
452
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Write one cell of row `rn` at column `c` (letter `cr`). Returns the column
|
|
509
|
+
* of the last cell written, which the caller passes as `prevCol` for the next
|
|
510
|
+
* cell; a null cell writes nothing and leaves `prevCol` unchanged.
|
|
511
|
+
*/
|
|
512
|
+
_wcv(bb, raw, c, cr, rn, prevCol) {
|
|
513
|
+
if (raw === null || raw === void 0) return prevCol;
|
|
453
514
|
const fs = getFormat(raw);
|
|
454
|
-
const v =
|
|
515
|
+
const v = unwrapCell(raw);
|
|
455
516
|
const xf = fs !== null ? this._regFmt(fs) : -1;
|
|
456
517
|
const sa = xf >= 0 ? ` s="${xf}"` : "";
|
|
518
|
+
if (v === null || v === void 0) return prevCol;
|
|
519
|
+
const ref = cellRefAttr(c, prevCol, cr, rn);
|
|
520
|
+
const so = xf >= 0 ? xf : void 0;
|
|
457
521
|
if (typeof v === "number") {
|
|
458
|
-
if (Number.isFinite(v)) bb.writeString(`<c
|
|
459
|
-
else this._wsc(bb, v.toString(),
|
|
460
|
-
} else if (typeof v === "bigint") this._wsc(bb, v.toString(),
|
|
461
|
-
else if (typeof v === "boolean") bb.writeString(`<c
|
|
522
|
+
if (Number.isFinite(v)) bb.writeString(`<c${ref}${sa}><v>${v}</v></c>`);
|
|
523
|
+
else this._wsc(bb, v.toString(), ref, so);
|
|
524
|
+
} else if (typeof v === "bigint") this._wsc(bb, v.toString(), ref, so);
|
|
525
|
+
else if (typeof v === "boolean") bb.writeString(`<c${ref} t="b"${sa}><v>${v ? 1 : 0}</v></c>`);
|
|
462
526
|
else if (v instanceof Date) {
|
|
463
527
|
const o = this._oa(v);
|
|
464
|
-
if (Number.isFinite(o)) bb.writeString(`<c
|
|
465
|
-
else this._wsc(bb, v.toString(),
|
|
466
|
-
} else this._wsc(bb, v.toString(),
|
|
528
|
+
if (Number.isFinite(o)) bb.writeString(`<c${ref} s="${xf >= 0 ? xf : 1}"><v>${o}</v></c>`);
|
|
529
|
+
else this._wsc(bb, v.toString(), ref, so);
|
|
530
|
+
} else this._wsc(bb, v.toString(), ref, so);
|
|
531
|
+
return c;
|
|
467
532
|
}
|
|
468
533
|
_sheetHead(bb, cc, isFirst, hasAF) {
|
|
469
534
|
bb.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
|
|
@@ -480,13 +545,19 @@ var BrowserXlsxWriter = class {
|
|
|
480
545
|
}
|
|
481
546
|
startSheet(name, colCount, headers, options = {}) {
|
|
482
547
|
if (this._isS) throw new Error("Already streaming");
|
|
548
|
+
assertColumnCount(colCount);
|
|
549
|
+
if (headers !== void 0) {
|
|
550
|
+
assertRowFitsColumns("header", headers.length, colCount);
|
|
551
|
+
for (const h of headers) assertCellTextLength(h);
|
|
552
|
+
}
|
|
483
553
|
const { hidden = false, doAutofilter = true, headerStyle = "bold" } = options;
|
|
484
554
|
let hs = 3;
|
|
485
555
|
if (headerStyle === "fill") hs = 4;
|
|
486
556
|
else if (headerStyle === "bold+fill") hs = 5;
|
|
487
557
|
this.addSheet(name, hidden);
|
|
488
558
|
this._isS = true;
|
|
489
|
-
|
|
559
|
+
const csb = new BrowserBigBuffer();
|
|
560
|
+
this._csb = csb;
|
|
490
561
|
this._csrn = 0;
|
|
491
562
|
this._csSC = 0;
|
|
492
563
|
this._csEC = colCount;
|
|
@@ -500,33 +571,38 @@ var BrowserXlsxWriter = class {
|
|
|
500
571
|
if (w > 80) w = 80;
|
|
501
572
|
if (this._cw[i] < w) this._cw[i] = w;
|
|
502
573
|
}
|
|
503
|
-
this._sheetHead(
|
|
574
|
+
this._sheetHead(csb, colCount, this._sc === 1, this._csAF);
|
|
504
575
|
if (headers) {
|
|
505
576
|
this._csrn++;
|
|
506
|
-
|
|
507
|
-
for (let c = 0; c < headers.length; c++) this._wscStyle(
|
|
508
|
-
|
|
577
|
+
csb.writeString(`<row r="${this._csrn}">`);
|
|
578
|
+
for (let c = 0; c < headers.length; c++) this._wscStyle(csb, headers[c], "", hs);
|
|
579
|
+
csb.writeString("</row>");
|
|
509
580
|
}
|
|
510
581
|
}
|
|
511
582
|
writeRow(row) {
|
|
512
|
-
if (!this._isS) throw new Error("Not streaming");
|
|
583
|
+
if (!this._isS || this._csb === null) throw new Error("Not streaming");
|
|
584
|
+
if (row.length !== this._csEC) throw new Error(`Row length mismatch. Expected ${this._csEC} columns, got ${row.length}`);
|
|
585
|
+
const csb = this._csb;
|
|
513
586
|
this._csrn++;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
587
|
+
assertRowCount(this._csrn);
|
|
588
|
+
csb.writeString(`<row r="${this._csrn}">`);
|
|
589
|
+
let prev = -1;
|
|
590
|
+
for (let c = 0; c < row.length; c++) prev = this._wcv(csb, row[c], c, this._csCL[c], this._csrn, prev);
|
|
591
|
+
csb.writeString("</row>");
|
|
517
592
|
}
|
|
518
593
|
endSheet() {
|
|
519
|
-
if (!this._isS) throw new Error("Not streaming");
|
|
520
|
-
this._csb
|
|
594
|
+
if (!this._isS || this._csb === null) throw new Error("Not streaming");
|
|
595
|
+
const csb = this._csb;
|
|
596
|
+
csb.writeString("</sheetData>");
|
|
521
597
|
if (this._csAF && this._csEC > 0) {
|
|
522
598
|
this._afOn = true;
|
|
523
599
|
const fr = `A1:${this._csCL[this._csEC - 1]}${this._csrn}`;
|
|
524
|
-
|
|
600
|
+
csb.writeString(`<autoFilter ref="${fr}"/>`);
|
|
525
601
|
const sh = this._sl[this._sc - 1];
|
|
526
602
|
sh.fhr = `${this._fmn(sh.name)}!$A$1:$${this._csCL[this._csEC - 1]}$${this._csrn}`;
|
|
527
603
|
}
|
|
528
|
-
|
|
529
|
-
this._zip.addFile(this._sl[this._sc - 1].path,
|
|
604
|
+
csb.writeString("</worksheet>");
|
|
605
|
+
this._zip.addFile(this._sl[this._sc - 1].path, csb.toUint8Array());
|
|
530
606
|
this._isS = false;
|
|
531
607
|
this._csb = null;
|
|
532
608
|
}
|
|
@@ -538,6 +614,14 @@ var BrowserXlsxWriter = class {
|
|
|
538
614
|
else if (headerStyle === "bold+fill") hs = 5;
|
|
539
615
|
const bb = new BrowserBigBuffer();
|
|
540
616
|
const cc = rows.length > 0 ? rows[0].length : headers ? headers.length : 0;
|
|
617
|
+
if (cc > 0) assertColumnCount(cc);
|
|
618
|
+
const tr = rows.length + (headers ? 1 : 0);
|
|
619
|
+
assertRowCount(tr);
|
|
620
|
+
if (headers) {
|
|
621
|
+
assertRowFitsColumns("header", headers.length, cc);
|
|
622
|
+
for (const h of headers) assertCellTextLength(h);
|
|
623
|
+
}
|
|
624
|
+
for (let r = 0; r < rows.length; r++) assertRowFitsColumns("row", rows[r].length, cc);
|
|
541
625
|
this._cw = new Array(cc).fill(-1);
|
|
542
626
|
const cl = new Array(cc);
|
|
543
627
|
for (let i = 0; i < cc; i++) cl[i] = this._cl(i);
|
|
@@ -550,23 +634,24 @@ var BrowserXlsxWriter = class {
|
|
|
550
634
|
const raw = rows[r][c];
|
|
551
635
|
if (raw == null) continue;
|
|
552
636
|
const v = isFormattedCell(raw) ? raw.value : raw;
|
|
637
|
+
if (v === null || v === void 0) continue;
|
|
553
638
|
let w = 1.3 * (v instanceof Date ? 10 : v.toString().length) + 3;
|
|
554
639
|
if (w > 80) w = 80;
|
|
555
640
|
if (this._cw[c] < w) this._cw[c] = w;
|
|
556
641
|
}
|
|
557
|
-
const tr = rows.length + (headers ? 1 : 0);
|
|
558
642
|
this._sheetHead(bb, cc, this._sc === 1, doAutofilter && !!headers);
|
|
559
643
|
let rn = 0;
|
|
560
644
|
if (headers) {
|
|
561
645
|
rn++;
|
|
562
646
|
bb.writeString(`<row r="${rn}">`);
|
|
563
|
-
for (let c = 0; c < headers.length; c++) this._wscStyle(bb, headers[c],
|
|
647
|
+
for (let c = 0; c < headers.length; c++) this._wscStyle(bb, headers[c], "", hs);
|
|
564
648
|
bb.writeString("</row>");
|
|
565
649
|
}
|
|
566
650
|
for (let r = 0; r < rows.length; r++) {
|
|
567
651
|
rn++;
|
|
568
652
|
bb.writeString(`<row r="${rn}">`);
|
|
569
|
-
|
|
653
|
+
let prev = -1;
|
|
654
|
+
for (let c = 0; c < rows[r].length; c++) prev = this._wcv(bb, rows[r][c], c, cl[c], rn, prev);
|
|
570
655
|
bb.writeString("</row>");
|
|
571
656
|
}
|
|
572
657
|
bb.writeString("</sheetData>");
|
|
@@ -2468,7 +2553,8 @@ var BrowserXlsbWriter = class {
|
|
|
2468
2553
|
return s;
|
|
2469
2554
|
}
|
|
2470
2555
|
_registerFormat(fmtString) {
|
|
2471
|
-
|
|
2556
|
+
const existing = this._fmtMap.get(fmtString);
|
|
2557
|
+
if (existing !== void 0) return existing.xf;
|
|
2472
2558
|
const nid = this._nextNid++;
|
|
2473
2559
|
const xf = this._nextXf++;
|
|
2474
2560
|
this._fmtMap.set(fmtString, { nid, xf });
|
|
@@ -2592,6 +2678,11 @@ var BrowserXlsbWriter = class {
|
|
|
2592
2678
|
}
|
|
2593
2679
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
2594
2680
|
if (this._isStreaming) throw new Error("Already streaming");
|
|
2681
|
+
assertColumnCount(columnCount);
|
|
2682
|
+
if (headers !== void 0) {
|
|
2683
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
2684
|
+
for (const h of headers) assertCellTextLength(h);
|
|
2685
|
+
}
|
|
2595
2686
|
const { hidden = false, doAutofilter = true, headerStyle = "bold" } = options;
|
|
2596
2687
|
let hs = 3;
|
|
2597
2688
|
if (headerStyle === "fill") hs = 4;
|
|
@@ -2620,15 +2711,19 @@ var BrowserXlsbWriter = class {
|
|
|
2620
2711
|
}
|
|
2621
2712
|
}
|
|
2622
2713
|
writeRow(row) {
|
|
2623
|
-
if (!this._isStreaming) throw new Error("Not streaming");
|
|
2624
2714
|
const bb = this._currentSheetBuffer;
|
|
2715
|
+
if (!this._isStreaming || bb === null) throw new Error("Not streaming");
|
|
2716
|
+
if (row.length !== this._currentSheetEndCol - this._currentSheetStartCol) {
|
|
2717
|
+
throw new Error(`Row length mismatch. Expected ${this._currentSheetEndCol - this._currentSheetStartCol} columns, got ${row.length}`);
|
|
2718
|
+
}
|
|
2719
|
+
assertRowCount(this._currentSheetRowNum + 1);
|
|
2625
2720
|
this.createRowHeader(bb, this._currentSheetRowNum, this._currentSheetStartCol, this._currentSheetEndCol);
|
|
2626
2721
|
for (let c = 0; c < row.length; c++) this._writeCell(bb, row[c], c);
|
|
2627
2722
|
this._currentSheetRowNum++;
|
|
2628
2723
|
}
|
|
2629
2724
|
endSheet() {
|
|
2630
|
-
if (!this._isStreaming) throw new Error("Not streaming");
|
|
2631
2725
|
const bb = this._currentSheetBuffer;
|
|
2726
|
+
if (!this._isStreaming || bb === null) throw new Error("Not streaming");
|
|
2632
2727
|
bb.write(this._sheet1Bytes.subarray(218, 290));
|
|
2633
2728
|
if (this._currentSheetDoAutofilter) {
|
|
2634
2729
|
this._autofilterIsOn = true;
|
|
@@ -2664,6 +2759,14 @@ var BrowserXlsbWriter = class {
|
|
|
2664
2759
|
else if (headerStyle === "bold+fill") hs = 5;
|
|
2665
2760
|
const bb = new BrowserBigBuffer();
|
|
2666
2761
|
const colCount = rows.length > 0 ? rows[0].length : headers ? headers.length : 0;
|
|
2762
|
+
if (colCount > 0) assertColumnCount(colCount);
|
|
2763
|
+
const totalBr = rows.length + (headers ? 1 : 0);
|
|
2764
|
+
assertRowCount(totalBr);
|
|
2765
|
+
if (headers) {
|
|
2766
|
+
assertRowFitsColumns("header", headers.length, colCount);
|
|
2767
|
+
for (const h of headers) assertCellTextLength(h);
|
|
2768
|
+
}
|
|
2769
|
+
for (let r = 0; r < rows.length; r++) assertRowFitsColumns("row", rows[r].length, colCount);
|
|
2667
2770
|
this._colWidths = new Array(colCount).fill(-1);
|
|
2668
2771
|
if (headers) {
|
|
2669
2772
|
for (let i = 0; i < colCount; i++) {
|
|
@@ -2678,6 +2781,7 @@ var BrowserXlsbWriter = class {
|
|
|
2678
2781
|
const raw = rows[r][c];
|
|
2679
2782
|
if (raw == null) continue;
|
|
2680
2783
|
const v = isFormattedCell(raw) ? raw.value : raw;
|
|
2784
|
+
if (v === null || v === void 0) continue;
|
|
2681
2785
|
const len = v instanceof Date ? 10 : v.toString().length;
|
|
2682
2786
|
let w = 1.3 * len + 3;
|
|
2683
2787
|
if (w > 80) w = 80;
|
|
@@ -2755,7 +2859,7 @@ var BrowserXlsbWriter = class {
|
|
|
2755
2859
|
_writeCell(bb, raw, c) {
|
|
2756
2860
|
if (raw === null || raw === void 0) return;
|
|
2757
2861
|
const fmtString = getFormat(raw);
|
|
2758
|
-
const val =
|
|
2862
|
+
const val = unwrapCell(raw);
|
|
2759
2863
|
const styleNum = fmtString !== null ? this._registerFormat(fmtString) : 0;
|
|
2760
2864
|
if (typeof val === "number") {
|
|
2761
2865
|
if (Number.isInteger(val) && val >= this._rRkIntegerLowerLimit && val <= this._rRkIntegerUpperLimit) {
|
|
@@ -2768,9 +2872,9 @@ var BrowserXlsbWriter = class {
|
|
|
2768
2872
|
} else if (typeof val === "boolean") {
|
|
2769
2873
|
this.writeBool(bb, val, c);
|
|
2770
2874
|
} else if (val instanceof Date) {
|
|
2771
|
-
const oaDate = (val.getTime()
|
|
2875
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
2772
2876
|
this.writeDouble(bb, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
2773
|
-
} else {
|
|
2877
|
+
} else if (val !== null && val !== void 0) {
|
|
2774
2878
|
this.writeString(bb, val.toString(), c, 0, fmtString !== null ? styleNum : void 0);
|
|
2775
2879
|
}
|
|
2776
2880
|
}
|
|
@@ -2823,13 +2927,12 @@ var BrowserXlsbWriter = class {
|
|
|
2823
2927
|
bb.writeUnsafeByte(val ? 1 : 0);
|
|
2824
2928
|
}
|
|
2825
2929
|
writeDateTime(bb, date, colNum) {
|
|
2826
|
-
this.writeDouble(bb, (date.getTime()
|
|
2930
|
+
this.writeDouble(bb, timeToOaDate(date.getTime(), this._oaEpoch), colNum, 1);
|
|
2827
2931
|
}
|
|
2828
|
-
writeString(bb, val, colNum, styleIndex = 0, styleOverride
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
} else {
|
|
2932
|
+
writeString(bb, val, colNum, styleIndex = 0, styleOverride) {
|
|
2933
|
+
assertCellTextLength(val);
|
|
2934
|
+
let index = this._sstDic.get(val);
|
|
2935
|
+
if (index === void 0) {
|
|
2833
2936
|
index = this._sstCntUnique++;
|
|
2834
2937
|
this._sstDic.set(val, index);
|
|
2835
2938
|
}
|
|
@@ -2850,8 +2953,8 @@ var BrowserXlsbWriter = class {
|
|
|
2850
2953
|
bb.writeByte(159);
|
|
2851
2954
|
bb.writeByte(1);
|
|
2852
2955
|
bb.writeByte(8);
|
|
2853
|
-
bb.writeInt32LE(this._sstCntUnique);
|
|
2854
2956
|
bb.writeInt32LE(this._sstCntAll);
|
|
2957
|
+
bb.writeInt32LE(this._sstCntUnique);
|
|
2855
2958
|
for (const [txt] of this._sstDic) {
|
|
2856
2959
|
const tl = txt.length;
|
|
2857
2960
|
bb.writeByte(19);
|
|
@@ -2927,7 +3030,7 @@ var BrowserXlsbWriter = class {
|
|
|
2927
3030
|
}
|
|
2928
3031
|
wbBuffers.push(this._workbookBinMiddle);
|
|
2929
3032
|
if (this._autofilterIsOn) {
|
|
2930
|
-
const filtered = this._sheetList.filter((s) => s.filterData);
|
|
3033
|
+
const filtered = this._sheetList.filter((s) => s.filterData !== void 0);
|
|
2931
3034
|
if (filtered.length > 0) {
|
|
2932
3035
|
wbBuffers.push(this._magicFilterExcel2016Fix0);
|
|
2933
3036
|
const cnt = filtered.length;
|