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 CHANGED
@@ -4,13 +4,15 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/sheetra.svg)](https://www.npmjs.com/package/sheetra)
6
6
  [![License](https://img.shields.io/github/license/opencorex-org/sheetra)](LICENSE)
7
+ [![Build Status](https://github.com/opencorex-org/sheetra/actions/workflows/build.yml/badge.svg)](https://github.com/opencorex-org/sheetra/actions)
7
8
  [![npm downloads](https://img.shields.io/npm/dm/sheetra.svg)](https://www.npmjs.com/package/sheetra)
8
9
  [![GitHub stars](https://img.shields.io/github/stars/opencorex-org/sheetra?style=social)](https://github.com/opencorex-org/sheetra)
9
10
  [![GitHub issues](https://img.shields.io/github/issues/opencorex-org/sheetra)](https://github.com/opencorex-org/sheetra/issues)
10
- [![Version](https://img.shields.io/badge/version-1.0.0-blue)](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, '&amp;')
548
+ .replace(/</g, '&lt;')
549
+ .replace(/>/g, '&gt;')
550
+ .replace(/"/g, '&quot;')
551
+ .replace(/'/g, '&apos;');
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 = [];