@justybase/spreadsheet-tasks 1.1.1 → 1.3.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/README.md +36 -0
- package/dist/Formats.d.ts +34 -0
- package/dist/Formats.d.ts.map +1 -0
- package/dist/Formats.js +48 -0
- package/dist/Formats.js.map +1 -0
- package/dist/XlsbWriter.d.ts +10 -1
- package/dist/XlsbWriter.d.ts.map +1 -1
- package/dist/XlsbWriter.js +157 -19
- package/dist/XlsbWriter.js.map +1 -1
- package/dist/XlsxWriter.d.ts +5 -0
- package/dist/XlsxWriter.d.ts.map +1 -1
- package/dist/XlsxWriter.js +77 -28
- package/dist/XlsxWriter.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/examples/formats-demo.ts +225 -0
- package/examples/formats-verify.ts +74 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -30,6 +30,42 @@ A high-performance TypeScript library for reading and writing Excel files in XLS
|
|
|
30
30
|
npm install @justybase/spreadsheet-tasks
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## 🌐 Browser Support & CDN (Zero Dependencies)
|
|
34
|
+
|
|
35
|
+
This library includes browser-compatible modules to generate XLSB and XLSX files directly on the client side without relying on Node.js APIs or external dependencies.
|
|
36
|
+
|
|
37
|
+
To bundle the browser version into a single minified file (e.g. for deployment to a CDN), run the following command in the project root:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx esbuild browser/browser-spreadsheet.js --bundle --minify --format=esm --outfile=browser/justybase-spreadsheet.min.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You can then import this single file via a script tag in any web application:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<script type="module">
|
|
47
|
+
import { downloadXlsb, downloadXlsx } from 'https://your-cdn.com/justybase-spreadsheet.min.js';
|
|
48
|
+
|
|
49
|
+
const headers = ["ID", "Name", "Score"];
|
|
50
|
+
const rows = [
|
|
51
|
+
[1, "Alice", 99.5],
|
|
52
|
+
[2, "Bob", 88.0]
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
// Generates the file and triggers the browser download automatically
|
|
56
|
+
document.getElementById('downloadBtn').addEventListener('click', () => {
|
|
57
|
+
downloadXlsb('report.xlsb', rows, headers, { headerStyle: 'bold+fill' });
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Features included in the browser build:**
|
|
63
|
+
- Built-in Zip archiving (no external libraries needed)
|
|
64
|
+
- Memory-efficient `Uint8Array` buffer handling
|
|
65
|
+
- Auto-fitted column widths
|
|
66
|
+
- Frozen and styled headers (styles: `bold`, `fill`, `bold+fill`)
|
|
67
|
+
- Auto-filters enabled by default
|
|
68
|
+
|
|
33
69
|
## 🔧 Quick Start
|
|
34
70
|
|
|
35
71
|
### Writing Excel Files
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface FormattedCell {
|
|
2
|
+
value: any;
|
|
3
|
+
format: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const F: {
|
|
6
|
+
THOUSANDS_SEP: string;
|
|
7
|
+
CURRENCY_PLN: string;
|
|
8
|
+
CURRENCY_EUR: string;
|
|
9
|
+
PERCENTAGE: string;
|
|
10
|
+
SCIENTIFIC: string;
|
|
11
|
+
TWO_DECIMALS: string;
|
|
12
|
+
TEXT: string;
|
|
13
|
+
LEADING_ZEROS: string;
|
|
14
|
+
DATE_SHORT: string;
|
|
15
|
+
DATE_LONG: string;
|
|
16
|
+
DATE_DAY_MONTH_YEAR: string;
|
|
17
|
+
DATE_ISO: string;
|
|
18
|
+
DATE_MONTH_YEAR: string;
|
|
19
|
+
DATE_WEEKDAY: string;
|
|
20
|
+
DATE_DAY_MONTH: string;
|
|
21
|
+
DATE_YEAR_ONLY: string;
|
|
22
|
+
DATETIME_SHORT: string;
|
|
23
|
+
DATETIME_LONG: string;
|
|
24
|
+
TIME_HH_MM: string;
|
|
25
|
+
TIME_HH_MM_SS: string;
|
|
26
|
+
TIME_12H: string;
|
|
27
|
+
DATETIME_24H: string;
|
|
28
|
+
DATETIME_ISO: string;
|
|
29
|
+
TIME_MS: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function isFormattedCell(val: any): val is FormattedCell;
|
|
32
|
+
export declare function unwrapCell(val: any): any;
|
|
33
|
+
export declare function getFormat(val: any): string | null;
|
|
34
|
+
//# sourceMappingURL=Formats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Formats.d.ts","sourceRoot":"","sources":["../src/Formats.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,GAAG,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CA2Bb,CAAC;AAEF,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,aAAa,CAE9D;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAKxC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAKjD"}
|
package/dist/Formats.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.F = void 0;
|
|
4
|
+
exports.isFormattedCell = isFormattedCell;
|
|
5
|
+
exports.unwrapCell = unwrapCell;
|
|
6
|
+
exports.getFormat = getFormat;
|
|
7
|
+
exports.F = {
|
|
8
|
+
THOUSANDS_SEP: '#,##0',
|
|
9
|
+
CURRENCY_PLN: '#,##0.00 "z\u0142"',
|
|
10
|
+
CURRENCY_EUR: '#,##0.00 \u20AC',
|
|
11
|
+
PERCENTAGE: '0%',
|
|
12
|
+
SCIENTIFIC: '0.00E+00',
|
|
13
|
+
TWO_DECIMALS: '#,##0.00',
|
|
14
|
+
TEXT: '@',
|
|
15
|
+
LEADING_ZEROS: '000000000',
|
|
16
|
+
DATE_SHORT: 'dd.mm.yyyy',
|
|
17
|
+
DATE_LONG: 'd mmmm yyyy',
|
|
18
|
+
DATE_DAY_MONTH_YEAR: 'dd-mm-yyyy',
|
|
19
|
+
DATE_ISO: 'yyyy-mm-dd',
|
|
20
|
+
DATE_MONTH_YEAR: 'mmmm yyyy',
|
|
21
|
+
DATE_WEEKDAY: 'dddd, d mmmm yyyy',
|
|
22
|
+
DATE_DAY_MONTH: 'd mmmm',
|
|
23
|
+
DATE_YEAR_ONLY: 'yyyy',
|
|
24
|
+
DATETIME_SHORT: 'dd.mm.yyyy hh:mm',
|
|
25
|
+
DATETIME_LONG: 'd mmmm yyyy hh:mm:ss',
|
|
26
|
+
TIME_HH_MM: 'hh:mm',
|
|
27
|
+
TIME_HH_MM_SS: 'hh:mm:ss',
|
|
28
|
+
TIME_12H: 'h:mm AM/PM',
|
|
29
|
+
DATETIME_24H: 'dd.mm.yyyy hh:mm:ss',
|
|
30
|
+
DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
|
|
31
|
+
TIME_MS: 'hh:mm:ss.000',
|
|
32
|
+
};
|
|
33
|
+
function isFormattedCell(val) {
|
|
34
|
+
return val !== null && val !== undefined && typeof val === 'object' && 'format' in val && typeof val.format === 'string';
|
|
35
|
+
}
|
|
36
|
+
function unwrapCell(val) {
|
|
37
|
+
if (isFormattedCell(val)) {
|
|
38
|
+
return val.value;
|
|
39
|
+
}
|
|
40
|
+
return val;
|
|
41
|
+
}
|
|
42
|
+
function getFormat(val) {
|
|
43
|
+
if (isFormattedCell(val)) {
|
|
44
|
+
return val.format;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=Formats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Formats.js","sourceRoot":"","sources":["../src/Formats.ts"],"names":[],"mappings":";;;AAkCA,0CAEC;AAED,gCAKC;AAED,8BAKC;AA7CY,QAAA,CAAC,GAAG;IACb,aAAa,EAAE,OAAO;IACtB,YAAY,EAAE,oBAAoB;IAClC,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,UAAU;IACtB,YAAY,EAAE,UAAU;IACxB,IAAI,EAAE,GAAG;IACT,aAAa,EAAE,WAAW;IAE1B,UAAU,EAAE,YAAY;IACxB,SAAS,EAAE,aAAa;IACxB,mBAAmB,EAAE,YAAY;IACjC,QAAQ,EAAE,YAAY;IACtB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,mBAAmB;IACjC,cAAc,EAAE,QAAQ;IACxB,cAAc,EAAE,MAAM;IAEtB,cAAc,EAAE,kBAAkB;IAClC,aAAa,EAAE,sBAAsB;IACrC,UAAU,EAAE,OAAO;IACnB,aAAa,EAAE,UAAU;IACzB,QAAQ,EAAE,YAAY;IACtB,YAAY,EAAE,qBAAqB;IACnC,YAAY,EAAE,uBAAuB;IACrC,OAAO,EAAE,cAAc;CAC1B,CAAC;AAEF,SAAgB,eAAe,CAAC,GAAQ;IACpC,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,QAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC;AAC7H,CAAC;AAED,SAAgB,UAAU,CAAC,GAAQ;IAC/B,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,KAAK,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAgB,SAAS,CAAC,GAAQ;IAC9B,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,MAAM,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
package/dist/XlsbWriter.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare class XlsbWriter {
|
|
|
10
10
|
private colWidths;
|
|
11
11
|
private _autofilterIsOn;
|
|
12
12
|
private _oaEpoch;
|
|
13
|
+
private _nextFmtId;
|
|
14
|
+
private _nextXfIdx;
|
|
15
|
+
private _stFmtRecords;
|
|
13
16
|
private _sheet1Bytes;
|
|
14
17
|
private _workbookBinStart;
|
|
15
18
|
private _workbookBinMiddle;
|
|
@@ -32,6 +35,12 @@ export declare class XlsbWriter {
|
|
|
32
35
|
private isStreaming;
|
|
33
36
|
constructor(filePath: string);
|
|
34
37
|
private _sanitizeSheetName;
|
|
38
|
+
private _registerFormat;
|
|
39
|
+
private static _buildBrtRecord;
|
|
40
|
+
private static _encodeVlq;
|
|
41
|
+
private static _buildStFmtRecord;
|
|
42
|
+
private static _buildXfRecord;
|
|
43
|
+
private _buildStylesBin;
|
|
35
44
|
addSheet(sheetName: string, hidden?: boolean): void;
|
|
36
45
|
/**
|
|
37
46
|
* Start a new sheet in streaming mode. Call writeRow() for each row, then endSheet() when done.
|
|
@@ -60,7 +69,7 @@ export declare class XlsbWriter {
|
|
|
60
69
|
writeDouble(bigBuf: BigBuffer, val: number, colNum: number, styleNum?: number): void;
|
|
61
70
|
writeBool(bigBuf: BigBuffer, val: boolean, colNum: number): void;
|
|
62
71
|
writeDateTime(bigBuf: BigBuffer, date: Date, colNum: number): void;
|
|
63
|
-
writeString(bigBuf: BigBuffer, val: string, colNum: number, bolded?: boolean): void;
|
|
72
|
+
writeString(bigBuf: BigBuffer, val: string, colNum: number, bolded?: boolean, styleOverride?: number): void;
|
|
64
73
|
private saveSst;
|
|
65
74
|
private _writeFilterDefinedName;
|
|
66
75
|
finalize(): Promise<void>;
|
package/dist/XlsbWriter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"XlsbWriter.d.ts","sourceRoot":"","sources":["../src/XlsbWriter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"XlsbWriter.d.ts","sourceRoot":"","sources":["../src/XlsbWriter.ts"],"names":[],"mappings":"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"}
|
package/dist/XlsbWriter.js
CHANGED
|
@@ -41,6 +41,7 @@ const fs = __importStar(require("fs"));
|
|
|
41
41
|
const archiver_1 = __importDefault(require("archiver"));
|
|
42
42
|
const stream_1 = require("stream");
|
|
43
43
|
const BigBuffer_1 = require("./BigBuffer");
|
|
44
|
+
const Formats_1 = require("./Formats");
|
|
44
45
|
const INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
|
|
45
46
|
class XlsbWriter {
|
|
46
47
|
constructor(filePath) {
|
|
@@ -51,6 +52,9 @@ class XlsbWriter {
|
|
|
51
52
|
this.sstCntAll = 0;
|
|
52
53
|
this.colWidths = [];
|
|
53
54
|
this._autofilterIsOn = false;
|
|
55
|
+
this._nextFmtId = 167;
|
|
56
|
+
this._nextXfIdx = 4;
|
|
57
|
+
this._stFmtRecords = new Map();
|
|
54
58
|
// Streaming state
|
|
55
59
|
this.currentSheetBuffer = null;
|
|
56
60
|
this.currentSheetRowNum = 0;
|
|
@@ -229,6 +233,128 @@ class XlsbWriter {
|
|
|
229
233
|
}
|
|
230
234
|
return sanitized;
|
|
231
235
|
}
|
|
236
|
+
_registerFormat(fmtString) {
|
|
237
|
+
if (this._stFmtRecords.has(fmtString)) {
|
|
238
|
+
return this._stFmtRecords.get(fmtString).xfIndex;
|
|
239
|
+
}
|
|
240
|
+
const numfmtId = this._nextFmtId++;
|
|
241
|
+
const xfIndex = this._nextXfIdx++;
|
|
242
|
+
this._stFmtRecords.set(fmtString, { numfmtId, xfIndex });
|
|
243
|
+
return xfIndex;
|
|
244
|
+
}
|
|
245
|
+
static _buildBrtRecord(type, data) {
|
|
246
|
+
const header = XlsbWriter._encodeVlq(type);
|
|
247
|
+
header.push(...XlsbWriter._encodeVlq(data.length));
|
|
248
|
+
return Buffer.concat([Buffer.from(header), data]);
|
|
249
|
+
}
|
|
250
|
+
static _encodeVlq(value) {
|
|
251
|
+
const result = [];
|
|
252
|
+
while (value >= 0x80) {
|
|
253
|
+
result.push((value & 0x7F) | 0x80);
|
|
254
|
+
value >>>= 7;
|
|
255
|
+
}
|
|
256
|
+
result.push(value & 0x7F);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
static _buildStFmtRecord(ifmt, fmtString) {
|
|
260
|
+
const cch = fmtString.length;
|
|
261
|
+
const remaining = 2 + 4 + cch * 2;
|
|
262
|
+
const data = Buffer.alloc(2 + remaining);
|
|
263
|
+
data[0] = 0x2C;
|
|
264
|
+
data[1] = remaining;
|
|
265
|
+
data.writeUInt16LE(ifmt, 2);
|
|
266
|
+
data.writeUInt32LE(cch, 4);
|
|
267
|
+
data.write(fmtString, 8, cch * 2, 'utf16le');
|
|
268
|
+
return data;
|
|
269
|
+
}
|
|
270
|
+
static _buildXfRecord(fontId, ifmt, flags, byte6Extra = 0) {
|
|
271
|
+
const data = Buffer.alloc(18);
|
|
272
|
+
data[0] = 0x2F;
|
|
273
|
+
data[1] = 16;
|
|
274
|
+
data.writeUInt16LE(fontId, 2);
|
|
275
|
+
data.writeUInt16LE(ifmt, 4);
|
|
276
|
+
data[6] = byte6Extra;
|
|
277
|
+
data[14] = 0x10;
|
|
278
|
+
data[15] = 0x10;
|
|
279
|
+
data.writeUInt16LE(flags, 16);
|
|
280
|
+
return data;
|
|
281
|
+
}
|
|
282
|
+
_buildStylesBin() {
|
|
283
|
+
if (this._stFmtRecords.size === 0) {
|
|
284
|
+
return this._stylesBin;
|
|
285
|
+
}
|
|
286
|
+
const base = this._stylesBin;
|
|
287
|
+
// Find BrtEndFonts (0xE8, 0x04, 0x00)
|
|
288
|
+
let fontEndOff = -1;
|
|
289
|
+
for (let i = 3; i < base.length - 3; i++) {
|
|
290
|
+
if (base[i] === 0xE8 && base[i + 1] === 0x04 && base[i + 2] === 0x00) {
|
|
291
|
+
fontEndOff = i;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Find BrtBeginCellXFs (0xE9, 0x04)
|
|
296
|
+
let xfBeginOff = -1;
|
|
297
|
+
for (let i = fontEndOff + 3; i < base.length - 2; i++) {
|
|
298
|
+
if (base[i] === 0xE9 && base[i + 1] === 0x04) {
|
|
299
|
+
xfBeginOff = i;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Find BrtEndCellXFs (0xEA, 0x04, 0x00)
|
|
304
|
+
let xfEndOff = -1;
|
|
305
|
+
for (let i = xfBeginOff; i < base.length - 3; i++) {
|
|
306
|
+
if (base[i] === 0xEA && base[i + 1] === 0x04 && base[i + 2] === 0x00) {
|
|
307
|
+
xfEndOff = i;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Find BrtBeginFills (0xE3, 0x04) — start after BrtEndFonts
|
|
312
|
+
let fillBeginOff = -1;
|
|
313
|
+
for (let i = fontEndOff + 3; i < base.length - 2; i++) {
|
|
314
|
+
if (base[i] === 0xE3 && base[i + 1] === 0x04) {
|
|
315
|
+
fillBeginOff = i;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const totalFmt = 2 + this._stFmtRecords.size;
|
|
320
|
+
const totalXf = 4 + this._stFmtRecords.size;
|
|
321
|
+
const parts = [];
|
|
322
|
+
// 1. BrtBeginStyleSheet
|
|
323
|
+
parts.push(base.subarray(0, 3));
|
|
324
|
+
// 2. NEW BrtFmt: VLQ(0x0267) + VLQ(4) + count(2 LE) + pad(2)
|
|
325
|
+
const fmtHeader = Buffer.alloc(4);
|
|
326
|
+
fmtHeader.writeUInt16LE(totalFmt, 0);
|
|
327
|
+
parts.push(XlsbWriter._buildBrtRecord(0x0267, fmtHeader));
|
|
328
|
+
// 3. BrtNumFmt records: 2 base + custom
|
|
329
|
+
parts.push(XlsbWriter._buildStFmtRecord(164, 'yyyy\\-mm\\-dd\\ hh:mm:ss'));
|
|
330
|
+
parts.push(XlsbWriter._buildStFmtRecord(166, 'yyyy\\-mm\\-dd'));
|
|
331
|
+
for (const [fmtString, st] of this._stFmtRecords) {
|
|
332
|
+
parts.push(XlsbWriter._buildStFmtRecord(st.numfmtId, fmtString));
|
|
333
|
+
}
|
|
334
|
+
// 4. BrtEndFonts
|
|
335
|
+
parts.push(XlsbWriter._buildBrtRecord(0x0268, Buffer.alloc(0)));
|
|
336
|
+
// 5. Copy fills+borders+BrtFont+cellStyleXFs from template
|
|
337
|
+
parts.push(base.subarray(fillBeginOff, xfBeginOff));
|
|
338
|
+
// 7. NEW BrtBeginCellXFs
|
|
339
|
+
const xfHeader = Buffer.alloc(4);
|
|
340
|
+
xfHeader.writeUInt16LE(totalXf, 0);
|
|
341
|
+
parts.push(XlsbWriter._buildBrtRecord(0x0269, xfHeader));
|
|
342
|
+
// 8. Base XF records
|
|
343
|
+
parts.push(XlsbWriter._buildXfRecord(0, 0, 0x0000));
|
|
344
|
+
parts.push(XlsbWriter._buildXfRecord(0, 164, 0x0001));
|
|
345
|
+
parts.push(XlsbWriter._buildXfRecord(0, 166, 0x0001));
|
|
346
|
+
parts.push(XlsbWriter._buildXfRecord(1, 0, 0x0000, 1));
|
|
347
|
+
// 9. Custom XF records
|
|
348
|
+
const sortedXf = [...this._stFmtRecords.entries()].sort((a, b) => a[1].xfIndex - b[1].xfIndex);
|
|
349
|
+
for (const [, st] of sortedXf) {
|
|
350
|
+
parts.push(XlsbWriter._buildXfRecord(0, st.numfmtId, 0x0001));
|
|
351
|
+
}
|
|
352
|
+
// 10. BrtEndCellXFs
|
|
353
|
+
parts.push(XlsbWriter._buildBrtRecord(0x026A, Buffer.alloc(0)));
|
|
354
|
+
// 11. Copy remaining template
|
|
355
|
+
parts.push(base.subarray(xfEndOff + 3));
|
|
356
|
+
return Buffer.concat(parts);
|
|
357
|
+
}
|
|
232
358
|
addSheet(sheetName, hidden = false) {
|
|
233
359
|
const sanitizedName = this._sanitizeSheetName(sheetName);
|
|
234
360
|
this.sheetCount++;
|
|
@@ -339,20 +465,23 @@ class XlsbWriter {
|
|
|
339
465
|
this.createRowHeader(bigBuf, this.currentSheetRowNum, this.currentSheetStartCol, this.currentSheetEndCol);
|
|
340
466
|
// Write each cell
|
|
341
467
|
for (let c = 0; c < row.length; c++) {
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
468
|
+
const raw = row[c];
|
|
469
|
+
if (raw === null || raw === undefined)
|
|
344
470
|
continue;
|
|
471
|
+
const fmtString = (0, Formats_1.getFormat)(raw);
|
|
472
|
+
const val = fmtString !== null ? (0, Formats_1.unwrapCell)(raw) : raw;
|
|
473
|
+
const styleNum = fmtString !== null ? this._registerFormat(fmtString) : 0;
|
|
345
474
|
if (typeof val === 'number') {
|
|
346
475
|
if (Number.isInteger(val)) {
|
|
347
476
|
if (val >= this._rRkIntegerLowerLimit && val <= this._rRkIntegerUpperLimit) {
|
|
348
|
-
this.writeRkNumberInteger(bigBuf, val, c);
|
|
477
|
+
this.writeRkNumberInteger(bigBuf, val, c, styleNum);
|
|
349
478
|
}
|
|
350
479
|
else {
|
|
351
|
-
this.writeDouble(bigBuf, val, c);
|
|
480
|
+
this.writeDouble(bigBuf, val, c, styleNum);
|
|
352
481
|
}
|
|
353
482
|
}
|
|
354
483
|
else {
|
|
355
|
-
this.writeDouble(bigBuf, val, c);
|
|
484
|
+
this.writeDouble(bigBuf, val, c, styleNum);
|
|
356
485
|
}
|
|
357
486
|
}
|
|
358
487
|
else if (typeof val === 'bigint') {
|
|
@@ -362,10 +491,11 @@ class XlsbWriter {
|
|
|
362
491
|
this.writeBool(bigBuf, val, c);
|
|
363
492
|
}
|
|
364
493
|
else if (val instanceof Date) {
|
|
365
|
-
|
|
494
|
+
const oaDate = (val.getTime() - this._oaEpoch) / 86400000;
|
|
495
|
+
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
366
496
|
}
|
|
367
497
|
else {
|
|
368
|
-
this.writeString(bigBuf, val.toString(), c);
|
|
498
|
+
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : undefined);
|
|
369
499
|
}
|
|
370
500
|
}
|
|
371
501
|
this.currentSheetRowNum++;
|
|
@@ -442,8 +572,11 @@ class XlsbWriter {
|
|
|
442
572
|
for (let r = 0; r < Math.min(rows.length, 100); r++) {
|
|
443
573
|
const row = rows[r];
|
|
444
574
|
for (let c = 0; c < row.length; c++) {
|
|
445
|
-
const
|
|
446
|
-
|
|
575
|
+
const raw = row[c];
|
|
576
|
+
if (raw === null || raw === undefined)
|
|
577
|
+
continue;
|
|
578
|
+
const val = (0, Formats_1.isFormattedCell)(raw) ? raw.value : raw;
|
|
579
|
+
const len = val instanceof Date ? 10 : val.toString().length;
|
|
447
580
|
let width = 1.25 * len + 2;
|
|
448
581
|
if (width > 80)
|
|
449
582
|
width = 80;
|
|
@@ -501,20 +634,23 @@ class XlsbWriter {
|
|
|
501
634
|
this.createRowHeader(bigBuf, rowNum, startCol, endCol);
|
|
502
635
|
const row = rows[r];
|
|
503
636
|
for (let c = 0; c < row.length; c++) {
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
637
|
+
const raw = row[c];
|
|
638
|
+
if (raw === null || raw === undefined)
|
|
506
639
|
continue;
|
|
640
|
+
const fmtString = (0, Formats_1.getFormat)(raw);
|
|
641
|
+
const val = fmtString !== null ? (0, Formats_1.unwrapCell)(raw) : raw;
|
|
642
|
+
const styleNum = fmtString !== null ? this._registerFormat(fmtString) : 0;
|
|
507
643
|
if (typeof val === 'number') {
|
|
508
644
|
if (Number.isInteger(val)) {
|
|
509
645
|
if (val >= this._rRkIntegerLowerLimit && val <= this._rRkIntegerUpperLimit) {
|
|
510
|
-
this.writeRkNumberInteger(bigBuf, val, c);
|
|
646
|
+
this.writeRkNumberInteger(bigBuf, val, c, styleNum);
|
|
511
647
|
}
|
|
512
648
|
else {
|
|
513
|
-
this.writeDouble(bigBuf, val, c);
|
|
649
|
+
this.writeDouble(bigBuf, val, c, styleNum);
|
|
514
650
|
}
|
|
515
651
|
}
|
|
516
652
|
else {
|
|
517
|
-
this.writeDouble(bigBuf, val, c);
|
|
653
|
+
this.writeDouble(bigBuf, val, c, styleNum);
|
|
518
654
|
}
|
|
519
655
|
}
|
|
520
656
|
else if (typeof val === 'bigint') {
|
|
@@ -524,10 +660,11 @@ class XlsbWriter {
|
|
|
524
660
|
this.writeBool(bigBuf, val, c);
|
|
525
661
|
}
|
|
526
662
|
else if (val instanceof Date) {
|
|
527
|
-
|
|
663
|
+
const oaDate = (val.getTime() - this._oaEpoch) / 86400000;
|
|
664
|
+
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
528
665
|
}
|
|
529
666
|
else {
|
|
530
|
-
this.writeString(bigBuf, val.toString(), c);
|
|
667
|
+
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : undefined);
|
|
531
668
|
}
|
|
532
669
|
}
|
|
533
670
|
rowNum++;
|
|
@@ -611,7 +748,7 @@ class XlsbWriter {
|
|
|
611
748
|
const oaDate = (date.getTime() - this._oaEpoch) / 86400000;
|
|
612
749
|
this.writeDouble(bigBuf, oaDate, colNum, 1);
|
|
613
750
|
}
|
|
614
|
-
writeString(bigBuf, val, colNum, bolded = false) {
|
|
751
|
+
writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
|
|
615
752
|
let index;
|
|
616
753
|
if (this.sstDic.has(val)) {
|
|
617
754
|
index = this.sstDic.get(val);
|
|
@@ -622,11 +759,12 @@ class XlsbWriter {
|
|
|
622
759
|
this.sstCntUnique++;
|
|
623
760
|
}
|
|
624
761
|
this.sstCntAll++;
|
|
762
|
+
const styleNum = styleOverride !== undefined ? styleOverride : (bolded ? 3 : 0);
|
|
625
763
|
bigBuf.ensureCapacity(17);
|
|
626
764
|
bigBuf.writeUnsafeByte(7);
|
|
627
765
|
bigBuf.writeUnsafeByte(12);
|
|
628
766
|
bigBuf.writeUnsafeInt32LE(colNum);
|
|
629
|
-
bigBuf.writeUnsafeByte(
|
|
767
|
+
bigBuf.writeUnsafeByte(styleNum);
|
|
630
768
|
bigBuf.writeUnsafeByte(0);
|
|
631
769
|
bigBuf.writeUnsafeByte(0);
|
|
632
770
|
bigBuf.writeUnsafeByte(0);
|
|
@@ -692,7 +830,7 @@ class XlsbWriter {
|
|
|
692
830
|
return new Promise((resolve, reject) => {
|
|
693
831
|
try {
|
|
694
832
|
this.saveSst();
|
|
695
|
-
this.archive.append(this.
|
|
833
|
+
this.archive.append(this._buildStylesBin(), { name: 'xl/styles.bin' });
|
|
696
834
|
const wbBuffers = [];
|
|
697
835
|
wbBuffers.push(this._workbookBinStart);
|
|
698
836
|
for (const sheet of this.sheetList) {
|