@justybase/spreadsheet-tasks 2.2.0 → 3.0.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 +64 -0
- package/browser/browser-spreadsheet.js +131 -43
- package/browser/justybase-spreadsheet.min.js +13 -13
- package/dist/cjs/index.d.ts +107 -5
- package/dist/cjs/index.js +1299 -431
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1284 -431
- 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,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Breaking
|
|
11
|
+
|
|
12
|
+
- `XlsxReader` / `XlsbReader` now return an empty row (`[]`, `getValue()` → `null`)
|
|
13
|
+
for every missing row position, including leading gaps, based on the row `r`
|
|
14
|
+
attribute (XLSX) and `BrtRowHdr` (XLSB). Like xlspy, gap positions are yielded;
|
|
15
|
+
unlike xlspy's padded `[None] * cols`, this reader yields a zero-length row.
|
|
16
|
+
Code that counts rows must account for gap rows.
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
- Date conversion is UTC-based on both write and read: a `Date` is stored by its
|
|
21
|
+
UTC instant (`getTime()`), so `new Date(2024, 5, 30)` in UTC+2 appears as
|
|
22
|
+
2024-06-29 22:00 in Excel. Use `Date.UTC(...)` for date-only values.
|
|
23
|
+
|
|
10
24
|
### Added
|
|
11
25
|
|
|
26
|
+
- `selectSheet(nameOrIndex)` and `nextResult()` on `ExcelReaderAbstract`,
|
|
27
|
+
implemented by `XlsxReader` and `XlsbReader`. Default remains sheet 0; state
|
|
28
|
+
(row/EOF, `fieldCount`) resets on selection.
|
|
29
|
+
- Excel limits (`MAX_ROWS=1_048_576`, `MAX_COLS=16_384`, `MAX_CELL_CHARS=32_767`
|
|
30
|
+
UTF-16 units) enforced in `XlsxWriter`, `XlsbWriter`, `BrowserXlsxWriter` and
|
|
31
|
+
`BrowserXlsbWriter`. Overlong text, oversized dimensions and rows/headers
|
|
32
|
+
longer than `columnCount` throw instead of writing corrupt files (xlspy parity).
|
|
33
|
+
- Shared XML helpers: `parseTagAttributes()`, `resolveRelTarget()` and
|
|
34
|
+
single-pass `unescapeXml()` in `src/xmlUtils.ts`, exported from the package
|
|
35
|
+
entry point.
|
|
12
36
|
- `convertXlsbToXlsx` and `convertXlsxToXlsb` — Windows/Excel COM conversions
|
|
13
37
|
with protected destinations and optional explicit overwriting.
|
|
14
38
|
- `replaceSheetDataStream()` and `saveStreaming()` for XLSX/XLSB updaters,
|
|
@@ -19,6 +43,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
43
|
|
|
20
44
|
### Fixed
|
|
21
45
|
|
|
46
|
+
- `BrowserXlsxWriter` / `BrowserXlsbWriter` skip a `FormattedCell` whose value is
|
|
47
|
+
`null` or `undefined`, matching the Node writers (previously a `TypeError`).
|
|
48
|
+
- Browser sources in `src/browser/` are type-checked under `strict` by
|
|
49
|
+
`tsconfig.browser.json`, which `npm run typecheck` runs. The `@ts-nocheck` pragmas
|
|
50
|
+
were removed.
|
|
51
|
+
- Column letters ≥702 (`AAA`…`XFD`): `XlsxWriter` and `BrowserXlsxWriter` now
|
|
52
|
+
delegate to `columnIndexToLetter()` instead of falling back to `'A'`.
|
|
53
|
+
- XLSB shared-string table: `BrtBeginSst` now writes `total` then `unique`
|
|
54
|
+
(was swapped); `BiffReaderWriter` and updaters already expected that order.
|
|
55
|
+
- Writer finalization is atomic: output goes to a same-directory temp file,
|
|
56
|
+
output/archive errors reject the promise, streams are destroyed and the temp
|
|
57
|
+
file removed; only a complete workbook is renamed into place (xlspy parity).
|
|
58
|
+
`writeAdmZipStreaming()` now observes output errors before/during the append loop.
|
|
59
|
+
- Workbook relationships and sheet paths: absolute `Target="/xl/…"` resolved
|
|
60
|
+
correctly, attribute order no longer matters (`parseTagAttributes` used in
|
|
61
|
+
both readers and both updaters). Verified against openpyxl output.
|
|
62
|
+
- `unescapeXml()` single-pass: `&lt;` stays `<` instead of becoming `<`.
|
|
63
|
+
- Shared strings: `<rPh>` phonetics skipped, `<si/>` yields `""`, rich-text
|
|
64
|
+
`inlineStr` concatenates all `<t>` runs.
|
|
65
|
+
- XLSX cell types: `t="str"` → text, `t="e"` → error text, `t="d"` → `Date`.
|
|
66
|
+
- Self-closing `<row …/>` no longer swallows the next row.
|
|
67
|
+
- 1904 date system: `workbookPr date1904` (XLSX) and `BrtWbProp` (XLSB) set the
|
|
68
|
+
reader/updater epoch (`1904-01-01` vs `1899-12-30`).
|
|
69
|
+
- XLSB dimension: updaters patch `BrtWsDim` (`0x94`, `rwLast@4`/`colLast@12`)
|
|
70
|
+
instead of `BrtSel` (`0x98`).
|
|
71
|
+
- Stale parts after `replaceSheetData`: `xl/calcChain.xml` (`.bin` for XLSB)
|
|
72
|
+
removed with its `Content_Types`/rels entries; XLSX `xl/tables/*.xml` refs
|
|
73
|
+
(`<table>` + `<autoFilter>`) updated to the new dimension when the table's
|
|
74
|
+
width is unchanged, without expanding over other tables. XLSB tables are not
|
|
75
|
+
patched (documented limitation).
|
|
22
76
|
- Destination replacement during Excel conversion now preserves files created
|
|
23
77
|
concurrently and rolls back safely when the final rename fails.
|
|
24
78
|
- Shared-string `count` values now reflect the replaced worksheet rather than
|
|
@@ -26,6 +80,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
26
80
|
committed only once.
|
|
27
81
|
- Updater saves now install the destination atomically through a same-directory
|
|
28
82
|
temporary file.
|
|
83
|
+
- 1900 date system leap-day gap: Excel counts a fictitious 1900-02-29, so
|
|
84
|
+
serials 1-59 denote 1900-01-01..1900-02-28. `oaDateToTime()` /
|
|
85
|
+
`timeToOaDate()` in `writerHelpers.ts` apply the one-day shift (both
|
|
86
|
+
directions, all readers/writers/updaters incl. browser builds); the 1904
|
|
87
|
+
system is unaffected. Previously serial 1 read as 1899-12-31, disagreeing
|
|
88
|
+
with Excel, xlspy and openpyxl.
|
|
89
|
+
- `XlsbUpdater` date fallback now recognises custom `BrtFmt` date formats
|
|
90
|
+
(e.g. xlspy's `yyyy-mm-dd`) via `BiffReaderWriter.readStyles()` instead of
|
|
91
|
+
only built-in numFmt ranges, so replacement `Date` cells keep a date style
|
|
92
|
+
instead of degrading to raw serials.
|
|
29
93
|
|
|
30
94
|
## [2.1.0] - 2026-08-07
|
|
31
95
|
|
|
@@ -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,61 @@ 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
|
+
|
|
346
403
|
// 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
404
|
var BrowserXlsxWriter = class {
|
|
356
405
|
constructor() {
|
|
357
406
|
this._zip = new BrowserZip();
|
|
@@ -376,7 +425,7 @@ var BrowserXlsxWriter = class {
|
|
|
376
425
|
this._nextXf = 6;
|
|
377
426
|
}
|
|
378
427
|
_cl(i) {
|
|
379
|
-
return i
|
|
428
|
+
return columnIndexToLetter(i);
|
|
380
429
|
}
|
|
381
430
|
_san(n) {
|
|
382
431
|
if (!n || typeof n !== "string") return `Sheet${this._sc + 1}`;
|
|
@@ -389,7 +438,6 @@ var BrowserXlsxWriter = class {
|
|
|
389
438
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
390
439
|
}
|
|
391
440
|
_esc(s) {
|
|
392
|
-
if (typeof s !== "string") return s;
|
|
393
441
|
let need = false;
|
|
394
442
|
for (let i = 0; i < s.length; i++) {
|
|
395
443
|
const c = s.charCodeAt(i);
|
|
@@ -417,6 +465,7 @@ var BrowserXlsxWriter = class {
|
|
|
417
465
|
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
466
|
}
|
|
419
467
|
_wsc(bb, val, cr, rn, so) {
|
|
468
|
+
assertCellTextLength(val);
|
|
420
469
|
let idx = this._sstM.get(val);
|
|
421
470
|
if (idx === void 0) {
|
|
422
471
|
idx = this._sstA.length;
|
|
@@ -428,6 +477,7 @@ var BrowserXlsxWriter = class {
|
|
|
428
477
|
bb.writeString(`<c r="${cr}${rn}" t="s"${sa}><v>${idx}</v></c>`);
|
|
429
478
|
}
|
|
430
479
|
_wscStyle(bb, val, cr, rn, styleId) {
|
|
480
|
+
assertCellTextLength(val);
|
|
431
481
|
let idx = this._sstM.get(val);
|
|
432
482
|
if (idx === void 0) {
|
|
433
483
|
idx = this._sstA.length;
|
|
@@ -438,10 +488,11 @@ var BrowserXlsxWriter = class {
|
|
|
438
488
|
bb.writeString(`<c r="${cr}${rn}" t="s" s="${styleId}"><v>${idx}</v></c>`);
|
|
439
489
|
}
|
|
440
490
|
_oa(d) {
|
|
441
|
-
return (d.getTime()
|
|
491
|
+
return timeToOaDate(d.getTime(), this._oaE);
|
|
442
492
|
}
|
|
443
493
|
_regFmt(fs) {
|
|
444
|
-
|
|
494
|
+
const existing = this._fmtXf.get(fs);
|
|
495
|
+
if (existing !== void 0) return existing;
|
|
445
496
|
const nid = this._nextNfmt++;
|
|
446
497
|
this._fmtReg.set(fs, nid);
|
|
447
498
|
const xf = this._nextXf++;
|
|
@@ -451,9 +502,10 @@ var BrowserXlsxWriter = class {
|
|
|
451
502
|
_wcv(bb, raw, cr, rn) {
|
|
452
503
|
if (raw === null || raw === void 0) return;
|
|
453
504
|
const fs = getFormat(raw);
|
|
454
|
-
const v =
|
|
505
|
+
const v = unwrapCell(raw);
|
|
455
506
|
const xf = fs !== null ? this._regFmt(fs) : -1;
|
|
456
507
|
const sa = xf >= 0 ? ` s="${xf}"` : "";
|
|
508
|
+
if (v === null || v === void 0) return;
|
|
457
509
|
if (typeof v === "number") {
|
|
458
510
|
if (Number.isFinite(v)) bb.writeString(`<c r="${cr}${rn}"${sa}><v>${v}</v></c>`);
|
|
459
511
|
else this._wsc(bb, v.toString(), cr, rn, xf >= 0 ? xf : void 0);
|
|
@@ -480,13 +532,19 @@ var BrowserXlsxWriter = class {
|
|
|
480
532
|
}
|
|
481
533
|
startSheet(name, colCount, headers, options = {}) {
|
|
482
534
|
if (this._isS) throw new Error("Already streaming");
|
|
535
|
+
assertColumnCount(colCount);
|
|
536
|
+
if (headers !== void 0) {
|
|
537
|
+
assertRowFitsColumns("header", headers.length, colCount);
|
|
538
|
+
for (const h of headers) assertCellTextLength(h);
|
|
539
|
+
}
|
|
483
540
|
const { hidden = false, doAutofilter = true, headerStyle = "bold" } = options;
|
|
484
541
|
let hs = 3;
|
|
485
542
|
if (headerStyle === "fill") hs = 4;
|
|
486
543
|
else if (headerStyle === "bold+fill") hs = 5;
|
|
487
544
|
this.addSheet(name, hidden);
|
|
488
545
|
this._isS = true;
|
|
489
|
-
|
|
546
|
+
const csb = new BrowserBigBuffer();
|
|
547
|
+
this._csb = csb;
|
|
490
548
|
this._csrn = 0;
|
|
491
549
|
this._csSC = 0;
|
|
492
550
|
this._csEC = colCount;
|
|
@@ -500,33 +558,37 @@ var BrowserXlsxWriter = class {
|
|
|
500
558
|
if (w > 80) w = 80;
|
|
501
559
|
if (this._cw[i] < w) this._cw[i] = w;
|
|
502
560
|
}
|
|
503
|
-
this._sheetHead(
|
|
561
|
+
this._sheetHead(csb, colCount, this._sc === 1, this._csAF);
|
|
504
562
|
if (headers) {
|
|
505
563
|
this._csrn++;
|
|
506
|
-
|
|
507
|
-
for (let c = 0; c < headers.length; c++) this._wscStyle(
|
|
508
|
-
|
|
564
|
+
csb.writeString(`<row r="${this._csrn}">`);
|
|
565
|
+
for (let c = 0; c < headers.length; c++) this._wscStyle(csb, headers[c], cl[c], this._csrn, hs);
|
|
566
|
+
csb.writeString("</row>");
|
|
509
567
|
}
|
|
510
568
|
}
|
|
511
569
|
writeRow(row) {
|
|
512
|
-
if (!this._isS) throw new Error("Not streaming");
|
|
570
|
+
if (!this._isS || this._csb === null) throw new Error("Not streaming");
|
|
571
|
+
if (row.length !== this._csEC) throw new Error(`Row length mismatch. Expected ${this._csEC} columns, got ${row.length}`);
|
|
572
|
+
const csb = this._csb;
|
|
513
573
|
this._csrn++;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
this.
|
|
574
|
+
assertRowCount(this._csrn);
|
|
575
|
+
csb.writeString(`<row r="${this._csrn}">`);
|
|
576
|
+
for (let c = 0; c < row.length; c++) this._wcv(csb, row[c], this._csCL[c], this._csrn);
|
|
577
|
+
csb.writeString("</row>");
|
|
517
578
|
}
|
|
518
579
|
endSheet() {
|
|
519
|
-
if (!this._isS) throw new Error("Not streaming");
|
|
520
|
-
this._csb
|
|
580
|
+
if (!this._isS || this._csb === null) throw new Error("Not streaming");
|
|
581
|
+
const csb = this._csb;
|
|
582
|
+
csb.writeString("</sheetData>");
|
|
521
583
|
if (this._csAF && this._csEC > 0) {
|
|
522
584
|
this._afOn = true;
|
|
523
585
|
const fr = `A1:${this._csCL[this._csEC - 1]}${this._csrn}`;
|
|
524
|
-
|
|
586
|
+
csb.writeString(`<autoFilter ref="${fr}"/>`);
|
|
525
587
|
const sh = this._sl[this._sc - 1];
|
|
526
588
|
sh.fhr = `${this._fmn(sh.name)}!$A$1:$${this._csCL[this._csEC - 1]}$${this._csrn}`;
|
|
527
589
|
}
|
|
528
|
-
|
|
529
|
-
this._zip.addFile(this._sl[this._sc - 1].path,
|
|
590
|
+
csb.writeString("</worksheet>");
|
|
591
|
+
this._zip.addFile(this._sl[this._sc - 1].path, csb.toUint8Array());
|
|
530
592
|
this._isS = false;
|
|
531
593
|
this._csb = null;
|
|
532
594
|
}
|
|
@@ -538,6 +600,14 @@ var BrowserXlsxWriter = class {
|
|
|
538
600
|
else if (headerStyle === "bold+fill") hs = 5;
|
|
539
601
|
const bb = new BrowserBigBuffer();
|
|
540
602
|
const cc = rows.length > 0 ? rows[0].length : headers ? headers.length : 0;
|
|
603
|
+
if (cc > 0) assertColumnCount(cc);
|
|
604
|
+
const tr = rows.length + (headers ? 1 : 0);
|
|
605
|
+
assertRowCount(tr);
|
|
606
|
+
if (headers) {
|
|
607
|
+
assertRowFitsColumns("header", headers.length, cc);
|
|
608
|
+
for (const h of headers) assertCellTextLength(h);
|
|
609
|
+
}
|
|
610
|
+
for (let r = 0; r < rows.length; r++) assertRowFitsColumns("row", rows[r].length, cc);
|
|
541
611
|
this._cw = new Array(cc).fill(-1);
|
|
542
612
|
const cl = new Array(cc);
|
|
543
613
|
for (let i = 0; i < cc; i++) cl[i] = this._cl(i);
|
|
@@ -550,11 +620,11 @@ var BrowserXlsxWriter = class {
|
|
|
550
620
|
const raw = rows[r][c];
|
|
551
621
|
if (raw == null) continue;
|
|
552
622
|
const v = isFormattedCell(raw) ? raw.value : raw;
|
|
623
|
+
if (v === null || v === void 0) continue;
|
|
553
624
|
let w = 1.3 * (v instanceof Date ? 10 : v.toString().length) + 3;
|
|
554
625
|
if (w > 80) w = 80;
|
|
555
626
|
if (this._cw[c] < w) this._cw[c] = w;
|
|
556
627
|
}
|
|
557
|
-
const tr = rows.length + (headers ? 1 : 0);
|
|
558
628
|
this._sheetHead(bb, cc, this._sc === 1, doAutofilter && !!headers);
|
|
559
629
|
let rn = 0;
|
|
560
630
|
if (headers) {
|
|
@@ -2468,7 +2538,8 @@ var BrowserXlsbWriter = class {
|
|
|
2468
2538
|
return s;
|
|
2469
2539
|
}
|
|
2470
2540
|
_registerFormat(fmtString) {
|
|
2471
|
-
|
|
2541
|
+
const existing = this._fmtMap.get(fmtString);
|
|
2542
|
+
if (existing !== void 0) return existing.xf;
|
|
2472
2543
|
const nid = this._nextNid++;
|
|
2473
2544
|
const xf = this._nextXf++;
|
|
2474
2545
|
this._fmtMap.set(fmtString, { nid, xf });
|
|
@@ -2592,6 +2663,11 @@ var BrowserXlsbWriter = class {
|
|
|
2592
2663
|
}
|
|
2593
2664
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
2594
2665
|
if (this._isStreaming) throw new Error("Already streaming");
|
|
2666
|
+
assertColumnCount(columnCount);
|
|
2667
|
+
if (headers !== void 0) {
|
|
2668
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
2669
|
+
for (const h of headers) assertCellTextLength(h);
|
|
2670
|
+
}
|
|
2595
2671
|
const { hidden = false, doAutofilter = true, headerStyle = "bold" } = options;
|
|
2596
2672
|
let hs = 3;
|
|
2597
2673
|
if (headerStyle === "fill") hs = 4;
|
|
@@ -2620,15 +2696,19 @@ var BrowserXlsbWriter = class {
|
|
|
2620
2696
|
}
|
|
2621
2697
|
}
|
|
2622
2698
|
writeRow(row) {
|
|
2623
|
-
if (!this._isStreaming) throw new Error("Not streaming");
|
|
2624
2699
|
const bb = this._currentSheetBuffer;
|
|
2700
|
+
if (!this._isStreaming || bb === null) throw new Error("Not streaming");
|
|
2701
|
+
if (row.length !== this._currentSheetEndCol - this._currentSheetStartCol) {
|
|
2702
|
+
throw new Error(`Row length mismatch. Expected ${this._currentSheetEndCol - this._currentSheetStartCol} columns, got ${row.length}`);
|
|
2703
|
+
}
|
|
2704
|
+
assertRowCount(this._currentSheetRowNum + 1);
|
|
2625
2705
|
this.createRowHeader(bb, this._currentSheetRowNum, this._currentSheetStartCol, this._currentSheetEndCol);
|
|
2626
2706
|
for (let c = 0; c < row.length; c++) this._writeCell(bb, row[c], c);
|
|
2627
2707
|
this._currentSheetRowNum++;
|
|
2628
2708
|
}
|
|
2629
2709
|
endSheet() {
|
|
2630
|
-
if (!this._isStreaming) throw new Error("Not streaming");
|
|
2631
2710
|
const bb = this._currentSheetBuffer;
|
|
2711
|
+
if (!this._isStreaming || bb === null) throw new Error("Not streaming");
|
|
2632
2712
|
bb.write(this._sheet1Bytes.subarray(218, 290));
|
|
2633
2713
|
if (this._currentSheetDoAutofilter) {
|
|
2634
2714
|
this._autofilterIsOn = true;
|
|
@@ -2664,6 +2744,14 @@ var BrowserXlsbWriter = class {
|
|
|
2664
2744
|
else if (headerStyle === "bold+fill") hs = 5;
|
|
2665
2745
|
const bb = new BrowserBigBuffer();
|
|
2666
2746
|
const colCount = rows.length > 0 ? rows[0].length : headers ? headers.length : 0;
|
|
2747
|
+
if (colCount > 0) assertColumnCount(colCount);
|
|
2748
|
+
const totalBr = rows.length + (headers ? 1 : 0);
|
|
2749
|
+
assertRowCount(totalBr);
|
|
2750
|
+
if (headers) {
|
|
2751
|
+
assertRowFitsColumns("header", headers.length, colCount);
|
|
2752
|
+
for (const h of headers) assertCellTextLength(h);
|
|
2753
|
+
}
|
|
2754
|
+
for (let r = 0; r < rows.length; r++) assertRowFitsColumns("row", rows[r].length, colCount);
|
|
2667
2755
|
this._colWidths = new Array(colCount).fill(-1);
|
|
2668
2756
|
if (headers) {
|
|
2669
2757
|
for (let i = 0; i < colCount; i++) {
|
|
@@ -2678,6 +2766,7 @@ var BrowserXlsbWriter = class {
|
|
|
2678
2766
|
const raw = rows[r][c];
|
|
2679
2767
|
if (raw == null) continue;
|
|
2680
2768
|
const v = isFormattedCell(raw) ? raw.value : raw;
|
|
2769
|
+
if (v === null || v === void 0) continue;
|
|
2681
2770
|
const len = v instanceof Date ? 10 : v.toString().length;
|
|
2682
2771
|
let w = 1.3 * len + 3;
|
|
2683
2772
|
if (w > 80) w = 80;
|
|
@@ -2755,7 +2844,7 @@ var BrowserXlsbWriter = class {
|
|
|
2755
2844
|
_writeCell(bb, raw, c) {
|
|
2756
2845
|
if (raw === null || raw === void 0) return;
|
|
2757
2846
|
const fmtString = getFormat(raw);
|
|
2758
|
-
const val =
|
|
2847
|
+
const val = unwrapCell(raw);
|
|
2759
2848
|
const styleNum = fmtString !== null ? this._registerFormat(fmtString) : 0;
|
|
2760
2849
|
if (typeof val === "number") {
|
|
2761
2850
|
if (Number.isInteger(val) && val >= this._rRkIntegerLowerLimit && val <= this._rRkIntegerUpperLimit) {
|
|
@@ -2768,9 +2857,9 @@ var BrowserXlsbWriter = class {
|
|
|
2768
2857
|
} else if (typeof val === "boolean") {
|
|
2769
2858
|
this.writeBool(bb, val, c);
|
|
2770
2859
|
} else if (val instanceof Date) {
|
|
2771
|
-
const oaDate = (val.getTime()
|
|
2860
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
2772
2861
|
this.writeDouble(bb, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
2773
|
-
} else {
|
|
2862
|
+
} else if (val !== null && val !== void 0) {
|
|
2774
2863
|
this.writeString(bb, val.toString(), c, 0, fmtString !== null ? styleNum : void 0);
|
|
2775
2864
|
}
|
|
2776
2865
|
}
|
|
@@ -2823,13 +2912,12 @@ var BrowserXlsbWriter = class {
|
|
|
2823
2912
|
bb.writeUnsafeByte(val ? 1 : 0);
|
|
2824
2913
|
}
|
|
2825
2914
|
writeDateTime(bb, date, colNum) {
|
|
2826
|
-
this.writeDouble(bb, (date.getTime()
|
|
2915
|
+
this.writeDouble(bb, timeToOaDate(date.getTime(), this._oaEpoch), colNum, 1);
|
|
2827
2916
|
}
|
|
2828
|
-
writeString(bb, val, colNum, styleIndex = 0, styleOverride
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
} else {
|
|
2917
|
+
writeString(bb, val, colNum, styleIndex = 0, styleOverride) {
|
|
2918
|
+
assertCellTextLength(val);
|
|
2919
|
+
let index = this._sstDic.get(val);
|
|
2920
|
+
if (index === void 0) {
|
|
2833
2921
|
index = this._sstCntUnique++;
|
|
2834
2922
|
this._sstDic.set(val, index);
|
|
2835
2923
|
}
|
|
@@ -2850,8 +2938,8 @@ var BrowserXlsbWriter = class {
|
|
|
2850
2938
|
bb.writeByte(159);
|
|
2851
2939
|
bb.writeByte(1);
|
|
2852
2940
|
bb.writeByte(8);
|
|
2853
|
-
bb.writeInt32LE(this._sstCntUnique);
|
|
2854
2941
|
bb.writeInt32LE(this._sstCntAll);
|
|
2942
|
+
bb.writeInt32LE(this._sstCntUnique);
|
|
2855
2943
|
for (const [txt] of this._sstDic) {
|
|
2856
2944
|
const tl = txt.length;
|
|
2857
2945
|
bb.writeByte(19);
|
|
@@ -2927,7 +3015,7 @@ var BrowserXlsbWriter = class {
|
|
|
2927
3015
|
}
|
|
2928
3016
|
wbBuffers.push(this._workbookBinMiddle);
|
|
2929
3017
|
if (this._autofilterIsOn) {
|
|
2930
|
-
const filtered = this._sheetList.filter((s) => s.filterData);
|
|
3018
|
+
const filtered = this._sheetList.filter((s) => s.filterData !== void 0);
|
|
2931
3019
|
if (filtered.length > 0) {
|
|
2932
3020
|
wbBuffers.push(this._magicFilterExcel2016Fix0);
|
|
2933
3021
|
const cnt = filtered.length;
|