sheetra 1.0.0 → 1.0.2
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 +71 -2
- package/dist/index.esm.js +312 -96
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +312 -96
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +10 -5
- package/dist/types/writers/excel-writer.d.ts +10 -10
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/sheetra)
|
|
6
6
|
[](LICENSE)
|
|
7
|
+
[](https://github.com/opencorex-org/sheetra/actions)
|
|
7
8
|
[](https://www.npmjs.com/package/sheetra)
|
|
8
9
|
[](https://github.com/opencorex-org/sheetra)
|
|
9
10
|
[](https://github.com/opencorex-org/sheetra/issues)
|
|
10
|
-
[](https://github.com/opencorex-org/sheetra/releases)
|
|
11
11
|
|
|
12
12
|
Sheetra is a powerful, zero-dependency library for exporting data to Excel (XLSX), CSV, and JSON formats. It provides a clean, fluent API for creating complex spreadsheets with styles, outlines, and sections.
|
|
13
13
|
|
|
14
|
+
---
|
|
15
|
+
|
|
14
16
|
## Features
|
|
15
17
|
|
|
16
18
|
- **Zero Dependencies** – Pure TypeScript/JavaScript implementation
|
|
@@ -21,12 +23,79 @@ Sheetra is a powerful, zero-dependency library for exporting data to Excel (XLSX
|
|
|
21
23
|
- **TypeScript** – Full type support
|
|
22
24
|
- **Browser & Node** – Works in both environments
|
|
23
25
|
|
|
26
|
+
---
|
|
27
|
+
|
|
24
28
|
## Installation
|
|
25
29
|
|
|
30
|
+
Install Sheetra using your favorite package manager:
|
|
31
|
+
|
|
26
32
|
```bash
|
|
27
33
|
npm install sheetra
|
|
28
34
|
# or
|
|
29
35
|
yarn add sheetra
|
|
30
36
|
# or
|
|
31
37
|
pnpm add sheetra
|
|
32
|
-
```
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Basic Example
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { ExportBuilder } from 'sheetra';
|
|
48
|
+
|
|
49
|
+
const builder = ExportBuilder.create('Users')
|
|
50
|
+
.addHeaderRow(['Name', 'Age'])
|
|
51
|
+
.addDataRows([
|
|
52
|
+
['John', 30],
|
|
53
|
+
['Jane', 25]
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
builder.download({ filename: 'users.xlsx', format: 'xlsx' });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Advanced Example
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { ExportBuilder } from 'sheetra';
|
|
63
|
+
|
|
64
|
+
const parts = [
|
|
65
|
+
{ part_number: 'P001', part_name: 'Widget', current_stock: 100 },
|
|
66
|
+
{ part_number: 'P002', part_name: 'Gadget', current_stock: 50 },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const instances = [
|
|
70
|
+
{ serial_number: 'S001', part_number: 'P001', status: 'Active', location: 'A1' },
|
|
71
|
+
{ serial_number: 'S002', part_number: 'P001', status: 'Inactive', location: 'A2' },
|
|
72
|
+
{ serial_number: 'S003', part_number: 'P002', status: 'Active', location: 'B1' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
ExportBuilder.create('Inventory')
|
|
76
|
+
.addSection({ name: 'Parts' })
|
|
77
|
+
.addHeaderRow(['Part Number', 'Part Name', 'Current Stock'])
|
|
78
|
+
.addDataRows(parts.map(p => [p.part_number, p.part_name, p.current_stock]))
|
|
79
|
+
.addSection({ name: 'Instances' })
|
|
80
|
+
.addHeaderRow(['Serial Number', 'Part Number', 'Status', 'Location'])
|
|
81
|
+
.addDataRows(instances.map(i => [i.serial_number, i.part_number, i.status, i.location]))
|
|
82
|
+
.download({ filename: 'inventory.xlsx', format: 'xlsx' });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Documentation
|
|
88
|
+
|
|
89
|
+
For detailed documentation, visit the [API Reference](docs/API.md) and [Getting Started Guide](docs/GETTING_STARTED.md).
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Contributing
|
|
94
|
+
|
|
95
|
+
We welcome contributions! Please read the [Contributing Guide](docs/CONTRIBUTING.md) to learn how you can help improve Sheetra.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
This project is licensed under the [Apache 2.0 License](LICENSE).
|
package/dist/index.esm.js
CHANGED
|
@@ -402,6 +402,318 @@ class Worksheet {
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Minimal ZIP file creator for XLSX generation (no external dependencies)
|
|
407
|
+
* Uses STORE method (no compression) for simplicity and compatibility
|
|
408
|
+
*/
|
|
409
|
+
class ZipWriter {
|
|
410
|
+
constructor() {
|
|
411
|
+
this.files = [];
|
|
412
|
+
this.encoder = new TextEncoder();
|
|
413
|
+
}
|
|
414
|
+
addFile(name, content) {
|
|
415
|
+
this.files.push({
|
|
416
|
+
name,
|
|
417
|
+
data: this.encoder.encode(content)
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
generate() {
|
|
421
|
+
const localFiles = [];
|
|
422
|
+
const centralDirectory = [];
|
|
423
|
+
let offset = 0;
|
|
424
|
+
for (const file of this.files) {
|
|
425
|
+
const localHeader = this.createLocalFileHeader(file.name, file.data);
|
|
426
|
+
localFiles.push(localHeader);
|
|
427
|
+
localFiles.push(file.data);
|
|
428
|
+
const centralHeader = this.createCentralDirectoryHeader(file.name, file.data, offset);
|
|
429
|
+
centralDirectory.push(centralHeader);
|
|
430
|
+
offset += localHeader.length + file.data.length;
|
|
431
|
+
}
|
|
432
|
+
const centralDirStart = offset;
|
|
433
|
+
let centralDirSize = 0;
|
|
434
|
+
for (const header of centralDirectory) {
|
|
435
|
+
centralDirSize += header.length;
|
|
436
|
+
}
|
|
437
|
+
const endOfCentralDir = this.createEndOfCentralDirectory(this.files.length, centralDirSize, centralDirStart);
|
|
438
|
+
// Combine all parts
|
|
439
|
+
const totalSize = offset + centralDirSize + endOfCentralDir.length;
|
|
440
|
+
const result = new Uint8Array(totalSize);
|
|
441
|
+
let pos = 0;
|
|
442
|
+
for (const local of localFiles) {
|
|
443
|
+
result.set(local, pos);
|
|
444
|
+
pos += local.length;
|
|
445
|
+
}
|
|
446
|
+
for (const central of centralDirectory) {
|
|
447
|
+
result.set(central, pos);
|
|
448
|
+
pos += central.length;
|
|
449
|
+
}
|
|
450
|
+
result.set(endOfCentralDir, pos);
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
453
|
+
createLocalFileHeader(name, data) {
|
|
454
|
+
const nameBytes = this.encoder.encode(name);
|
|
455
|
+
const crc = this.crc32(data);
|
|
456
|
+
const header = new Uint8Array(30 + nameBytes.length);
|
|
457
|
+
const view = new DataView(header.buffer);
|
|
458
|
+
view.setUint32(0, 0x04034b50, true); // Local file header signature
|
|
459
|
+
view.setUint16(4, 20, true); // Version needed to extract
|
|
460
|
+
view.setUint16(6, 0, true); // General purpose bit flag
|
|
461
|
+
view.setUint16(8, 0, true); // Compression method (STORE)
|
|
462
|
+
view.setUint16(10, 0, true); // File last modification time
|
|
463
|
+
view.setUint16(12, 0, true); // File last modification date
|
|
464
|
+
view.setUint32(14, crc, true); // CRC-32
|
|
465
|
+
view.setUint32(18, data.length, true); // Compressed size
|
|
466
|
+
view.setUint32(22, data.length, true); // Uncompressed size
|
|
467
|
+
view.setUint16(26, nameBytes.length, true); // File name length
|
|
468
|
+
view.setUint16(28, 0, true); // Extra field length
|
|
469
|
+
header.set(nameBytes, 30);
|
|
470
|
+
return header;
|
|
471
|
+
}
|
|
472
|
+
createCentralDirectoryHeader(name, data, localHeaderOffset) {
|
|
473
|
+
const nameBytes = this.encoder.encode(name);
|
|
474
|
+
const crc = this.crc32(data);
|
|
475
|
+
const header = new Uint8Array(46 + nameBytes.length);
|
|
476
|
+
const view = new DataView(header.buffer);
|
|
477
|
+
view.setUint32(0, 0x02014b50, true); // Central directory file header signature
|
|
478
|
+
view.setUint16(4, 20, true); // Version made by
|
|
479
|
+
view.setUint16(6, 20, true); // Version needed to extract
|
|
480
|
+
view.setUint16(8, 0, true); // General purpose bit flag
|
|
481
|
+
view.setUint16(10, 0, true); // Compression method (STORE)
|
|
482
|
+
view.setUint16(12, 0, true); // File last modification time
|
|
483
|
+
view.setUint16(14, 0, true); // File last modification date
|
|
484
|
+
view.setUint32(16, crc, true); // CRC-32
|
|
485
|
+
view.setUint32(20, data.length, true); // Compressed size
|
|
486
|
+
view.setUint32(24, data.length, true); // Uncompressed size
|
|
487
|
+
view.setUint16(28, nameBytes.length, true); // File name length
|
|
488
|
+
view.setUint16(30, 0, true); // Extra field length
|
|
489
|
+
view.setUint16(32, 0, true); // File comment length
|
|
490
|
+
view.setUint16(34, 0, true); // Disk number start
|
|
491
|
+
view.setUint16(36, 0, true); // Internal file attributes
|
|
492
|
+
view.setUint32(38, 0, true); // External file attributes
|
|
493
|
+
view.setUint32(42, localHeaderOffset, true); // Relative offset of local header
|
|
494
|
+
header.set(nameBytes, 46);
|
|
495
|
+
return header;
|
|
496
|
+
}
|
|
497
|
+
createEndOfCentralDirectory(numFiles, centralDirSize, centralDirOffset) {
|
|
498
|
+
const header = new Uint8Array(22);
|
|
499
|
+
const view = new DataView(header.buffer);
|
|
500
|
+
view.setUint32(0, 0x06054b50, true); // End of central directory signature
|
|
501
|
+
view.setUint16(4, 0, true); // Number of this disk
|
|
502
|
+
view.setUint16(6, 0, true); // Disk where central directory starts
|
|
503
|
+
view.setUint16(8, numFiles, true); // Number of central directory records on this disk
|
|
504
|
+
view.setUint16(10, numFiles, true); // Total number of central directory records
|
|
505
|
+
view.setUint32(12, centralDirSize, true); // Size of central directory
|
|
506
|
+
view.setUint32(16, centralDirOffset, true); // Offset of start of central directory
|
|
507
|
+
view.setUint16(20, 0, true); // Comment length
|
|
508
|
+
return header;
|
|
509
|
+
}
|
|
510
|
+
crc32(data) {
|
|
511
|
+
let crc = 0xFFFFFFFF;
|
|
512
|
+
for (let i = 0; i < data.length; i++) {
|
|
513
|
+
crc ^= data[i];
|
|
514
|
+
for (let j = 0; j < 8; j++) {
|
|
515
|
+
crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
class ExcelWriter {
|
|
522
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
523
|
+
static async write(workbook, _options) {
|
|
524
|
+
const zip = new ZipWriter();
|
|
525
|
+
const sheets = workbook['sheets'];
|
|
526
|
+
const sheetNames = sheets.map((sheet, index) => sheet.getName() || `Sheet${index + 1}`);
|
|
527
|
+
// Add required XLSX files
|
|
528
|
+
zip.addFile('[Content_Types].xml', this.generateContentTypes(sheetNames));
|
|
529
|
+
zip.addFile('_rels/.rels', this.generateRels());
|
|
530
|
+
zip.addFile('xl/workbook.xml', this.generateWorkbook(sheetNames));
|
|
531
|
+
zip.addFile('xl/_rels/workbook.xml.rels', this.generateWorkbookRels(sheetNames));
|
|
532
|
+
zip.addFile('xl/styles.xml', this.generateStyles());
|
|
533
|
+
zip.addFile('xl/sharedStrings.xml', this.generateSharedStrings(sheets));
|
|
534
|
+
// Add each worksheet
|
|
535
|
+
sheets.forEach((sheet, index) => {
|
|
536
|
+
zip.addFile(`xl/worksheets/sheet${index + 1}.xml`, this.generateWorksheet(sheet));
|
|
537
|
+
});
|
|
538
|
+
const buffer = zip.generate();
|
|
539
|
+
return new Blob([buffer.buffer], {
|
|
540
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
static escapeXml(str) {
|
|
544
|
+
if (typeof str !== 'string')
|
|
545
|
+
return String(str !== null && str !== void 0 ? str : '');
|
|
546
|
+
return str
|
|
547
|
+
.replace(/&/g, '&')
|
|
548
|
+
.replace(/</g, '<')
|
|
549
|
+
.replace(/>/g, '>')
|
|
550
|
+
.replace(/"/g, '"')
|
|
551
|
+
.replace(/'/g, ''');
|
|
552
|
+
}
|
|
553
|
+
static generateContentTypes(sheetNames) {
|
|
554
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
555
|
+
xml += '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
|
|
556
|
+
xml += '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
|
|
557
|
+
xml += '<Default Extension="xml" ContentType="application/xml"/>';
|
|
558
|
+
xml += '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
|
|
559
|
+
xml += '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
|
|
560
|
+
xml += '<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>';
|
|
561
|
+
sheetNames.forEach((_, index) => {
|
|
562
|
+
xml += `<Override PartName="/xl/worksheets/sheet${index + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
|
563
|
+
});
|
|
564
|
+
xml += '</Types>';
|
|
565
|
+
return xml;
|
|
566
|
+
}
|
|
567
|
+
static generateRels() {
|
|
568
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
569
|
+
xml += '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
|
|
570
|
+
xml += '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>';
|
|
571
|
+
xml += '</Relationships>';
|
|
572
|
+
return xml;
|
|
573
|
+
}
|
|
574
|
+
static generateWorkbook(sheetNames) {
|
|
575
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
576
|
+
xml += '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
|
|
577
|
+
xml += '<sheets>';
|
|
578
|
+
sheetNames.forEach((name, index) => {
|
|
579
|
+
xml += `<sheet name="${this.escapeXml(name)}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`;
|
|
580
|
+
});
|
|
581
|
+
xml += '</sheets>';
|
|
582
|
+
xml += '</workbook>';
|
|
583
|
+
return xml;
|
|
584
|
+
}
|
|
585
|
+
static generateWorkbookRels(sheetNames) {
|
|
586
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
587
|
+
xml += '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
|
|
588
|
+
sheetNames.forEach((_, index) => {
|
|
589
|
+
xml += `<Relationship Id="rId${index + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${index + 1}.xml"/>`;
|
|
590
|
+
});
|
|
591
|
+
xml += `<Relationship Id="rId${sheetNames.length + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
592
|
+
xml += `<Relationship Id="rId${sheetNames.length + 2}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`;
|
|
593
|
+
xml += '</Relationships>';
|
|
594
|
+
return xml;
|
|
595
|
+
}
|
|
596
|
+
static generateStyles() {
|
|
597
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
598
|
+
xml += '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
|
|
599
|
+
xml += '<fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts>';
|
|
600
|
+
xml += '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>';
|
|
601
|
+
xml += '<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>';
|
|
602
|
+
xml += '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>';
|
|
603
|
+
xml += '<cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs>';
|
|
604
|
+
xml += '</styleSheet>';
|
|
605
|
+
return xml;
|
|
606
|
+
}
|
|
607
|
+
static generateSharedStrings(sheets) {
|
|
608
|
+
const strings = [];
|
|
609
|
+
sheets.forEach((sheet) => {
|
|
610
|
+
const rows = sheet.getRows();
|
|
611
|
+
rows.forEach((row) => {
|
|
612
|
+
row.getCells().forEach((cell) => {
|
|
613
|
+
const cellData = cell.toData();
|
|
614
|
+
if (typeof cellData.value === 'string' && !cellData.formula) {
|
|
615
|
+
if (!strings.includes(cellData.value)) {
|
|
616
|
+
strings.push(cellData.value);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
623
|
+
xml += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${strings.length}" uniqueCount="${strings.length}">`;
|
|
624
|
+
strings.forEach(str => {
|
|
625
|
+
xml += `<si><t>${this.escapeXml(str)}</t></si>`;
|
|
626
|
+
});
|
|
627
|
+
xml += '</sst>';
|
|
628
|
+
return xml;
|
|
629
|
+
}
|
|
630
|
+
static generateWorksheet(sheet) {
|
|
631
|
+
const rows = sheet.getRows();
|
|
632
|
+
const allStrings = this.collectAllStrings(sheet);
|
|
633
|
+
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
634
|
+
xml += '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
|
|
635
|
+
// Add sheet dimension
|
|
636
|
+
const dimension = this.getSheetDimension(rows);
|
|
637
|
+
xml += `<dimension ref="${dimension}"/>`;
|
|
638
|
+
xml += '<sheetData>';
|
|
639
|
+
rows.forEach((row, rowIndex) => {
|
|
640
|
+
const cells = row.getCells();
|
|
641
|
+
if (cells.length > 0) {
|
|
642
|
+
xml += `<row r="${rowIndex + 1}">`;
|
|
643
|
+
cells.forEach((cell, colIndex) => {
|
|
644
|
+
const cellData = cell.toData();
|
|
645
|
+
const cellRef = this.columnToLetter(colIndex + 1) + (rowIndex + 1);
|
|
646
|
+
if (cellData.formula) {
|
|
647
|
+
xml += `<c r="${cellRef}"><f>${this.escapeXml(cellData.formula)}</f></c>`;
|
|
648
|
+
}
|
|
649
|
+
else if (cellData.value !== null && cellData.value !== undefined && cellData.value !== '') {
|
|
650
|
+
if (typeof cellData.value === 'number') {
|
|
651
|
+
xml += `<c r="${cellRef}"><v>${cellData.value}</v></c>`;
|
|
652
|
+
}
|
|
653
|
+
else if (typeof cellData.value === 'boolean') {
|
|
654
|
+
xml += `<c r="${cellRef}" t="b"><v>${cellData.value ? 1 : 0}</v></c>`;
|
|
655
|
+
}
|
|
656
|
+
else if (cellData.type === 'date') {
|
|
657
|
+
const excelDate = DateFormatter.toExcelDate(cellData.value);
|
|
658
|
+
xml += `<c r="${cellRef}"><v>${excelDate}</v></c>`;
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
// String value - use shared string index
|
|
662
|
+
const stringIndex = allStrings.indexOf(String(cellData.value));
|
|
663
|
+
if (stringIndex >= 0) {
|
|
664
|
+
xml += `<c r="${cellRef}" t="s"><v>${stringIndex}</v></c>`;
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// Inline string as fallback
|
|
668
|
+
xml += `<c r="${cellRef}" t="inlineStr"><is><t>${this.escapeXml(String(cellData.value))}</t></is></c>`;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
xml += '</row>';
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
xml += '</sheetData>';
|
|
677
|
+
xml += '</worksheet>';
|
|
678
|
+
return xml;
|
|
679
|
+
}
|
|
680
|
+
static collectAllStrings(sheet) {
|
|
681
|
+
const strings = [];
|
|
682
|
+
const rows = sheet.getRows();
|
|
683
|
+
rows.forEach((row) => {
|
|
684
|
+
row.getCells().forEach((cell) => {
|
|
685
|
+
const cellData = cell.toData();
|
|
686
|
+
if (typeof cellData.value === 'string' && !cellData.formula) {
|
|
687
|
+
if (!strings.includes(cellData.value)) {
|
|
688
|
+
strings.push(cellData.value);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
return strings;
|
|
694
|
+
}
|
|
695
|
+
static getSheetDimension(rows) {
|
|
696
|
+
if (rows.length === 0)
|
|
697
|
+
return 'A1';
|
|
698
|
+
let maxCol = 1;
|
|
699
|
+
rows.forEach(row => {
|
|
700
|
+
const cellCount = row.getCells().length;
|
|
701
|
+
if (cellCount > maxCol)
|
|
702
|
+
maxCol = cellCount;
|
|
703
|
+
});
|
|
704
|
+
return `A1:${this.columnToLetter(maxCol)}${rows.length}`;
|
|
705
|
+
}
|
|
706
|
+
static columnToLetter(column) {
|
|
707
|
+
let letters = '';
|
|
708
|
+
while (column > 0) {
|
|
709
|
+
const temp = (column - 1) % 26;
|
|
710
|
+
letters = String.fromCharCode(temp + 65) + letters;
|
|
711
|
+
column = Math.floor((column - temp - 1) / 26);
|
|
712
|
+
}
|
|
713
|
+
return letters || 'A';
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
405
717
|
class CSVWriter {
|
|
406
718
|
static async write(workbook, _options) {
|
|
407
719
|
const sheets = workbook['sheets'];
|
|
@@ -473,102 +785,6 @@ class JSONWriter {
|
|
|
473
785
|
}
|
|
474
786
|
}
|
|
475
787
|
|
|
476
|
-
class ExcelWriter {
|
|
477
|
-
static async write(workbook, _options) {
|
|
478
|
-
const data = this.generateExcelData(workbook);
|
|
479
|
-
const buffer = this.createExcelFile(data);
|
|
480
|
-
const uint8Buffer = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
481
|
-
const arrayBuffer = uint8Buffer.slice().buffer;
|
|
482
|
-
return new Blob([arrayBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
483
|
-
}
|
|
484
|
-
static generateExcelData(workbook) {
|
|
485
|
-
const sheets = workbook['sheets'];
|
|
486
|
-
return {
|
|
487
|
-
Sheets: sheets.reduce((acc, sheet, index) => {
|
|
488
|
-
const sheetName = sheet.getName() || `Sheet${index + 1}`;
|
|
489
|
-
acc[sheetName] = this.generateSheetData(sheet);
|
|
490
|
-
return acc;
|
|
491
|
-
}, {}),
|
|
492
|
-
SheetNames: sheets.map((sheet, index) => sheet.getName() || `Sheet${index + 1}`)
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
static generateSheetData(sheet) {
|
|
496
|
-
const rows = sheet.getRows();
|
|
497
|
-
const data = [];
|
|
498
|
-
rows.forEach((row) => {
|
|
499
|
-
const rowData = [];
|
|
500
|
-
row.getCells().forEach((cell) => {
|
|
501
|
-
const cellData = cell.toData();
|
|
502
|
-
if (cellData.formula) {
|
|
503
|
-
rowData.push({ f: cellData.formula });
|
|
504
|
-
}
|
|
505
|
-
else if (cellData.type === 'date' &&
|
|
506
|
-
(typeof cellData.value === 'string' ||
|
|
507
|
-
typeof cellData.value === 'number' ||
|
|
508
|
-
cellData.value instanceof Date)) {
|
|
509
|
-
rowData.push(DateFormatter.toExcelDate(cellData.value));
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
rowData.push(cellData.value);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
data.push(rowData);
|
|
516
|
-
});
|
|
517
|
-
return {
|
|
518
|
-
'!ref': this.getSheetRange(data),
|
|
519
|
-
'!rows': rows.map(row => ({
|
|
520
|
-
hpt: row['height'],
|
|
521
|
-
hidden: row['hidden'],
|
|
522
|
-
outlineLevel: row['outlineLevel'],
|
|
523
|
-
collapsed: row['collapsed']
|
|
524
|
-
})),
|
|
525
|
-
'!cols': sheet.getColumns().map(col => ({
|
|
526
|
-
wch: col['width'],
|
|
527
|
-
hidden: col['hidden'],
|
|
528
|
-
outlineLevel: col['outlineLevel'],
|
|
529
|
-
collapsed: col['collapsed']
|
|
530
|
-
})),
|
|
531
|
-
'!merges': sheet['mergeCells'],
|
|
532
|
-
'!freeze': sheet['freezePane']
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
static getSheetRange(data) {
|
|
536
|
-
if (data.length === 0)
|
|
537
|
-
return 'A1:A1';
|
|
538
|
-
const lastRow = data.length;
|
|
539
|
-
const lastCol = Math.max(...data.map(row => row.length));
|
|
540
|
-
return `A1:${this.columnToLetter(lastCol)}${lastRow}`;
|
|
541
|
-
}
|
|
542
|
-
static columnToLetter(column) {
|
|
543
|
-
let letters = '';
|
|
544
|
-
while (column > 0) {
|
|
545
|
-
const temp = (column - 1) % 26;
|
|
546
|
-
letters = String.fromCharCode(temp + 65) + letters;
|
|
547
|
-
column = (column - temp - 1) / 26;
|
|
548
|
-
}
|
|
549
|
-
return letters || 'A';
|
|
550
|
-
}
|
|
551
|
-
static createExcelFile(data) {
|
|
552
|
-
// This is a simplified Excel file generator
|
|
553
|
-
// In a real implementation, you'd generate proper XLSX binary format
|
|
554
|
-
// For now, we'll return a simple XML-based structure
|
|
555
|
-
const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
556
|
-
const workbook = this.generateWorkbookXML(data);
|
|
557
|
-
const encoder = new TextEncoder();
|
|
558
|
-
return encoder.encode(xmlHeader + workbook);
|
|
559
|
-
}
|
|
560
|
-
static generateWorkbookXML(data) {
|
|
561
|
-
let xml = '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
|
|
562
|
-
xml += '<sheets>';
|
|
563
|
-
data.SheetNames.forEach((name, index) => {
|
|
564
|
-
xml += `<sheet name="${name}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`;
|
|
565
|
-
});
|
|
566
|
-
xml += '</sheets>';
|
|
567
|
-
xml += '</workbook>';
|
|
568
|
-
return xml;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
788
|
class Workbook {
|
|
573
789
|
constructor(data) {
|
|
574
790
|
this.sheets = [];
|