sheetra 1.0.2 → 1.0.4
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 +81 -9
- package/dist/index.esm.js +390 -21
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +390 -21
- package/dist/index.js.map +1 -1
- package/dist/types/builders/export-builder.d.ts +33 -1
- package/dist/types/builders/sheet-builder.d.ts +12 -0
- package/dist/types/index.d.ts +63 -1
- package/dist/types/writers/excel-writer.d.ts +18 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -526,22 +526,129 @@ class ExcelWriter {
|
|
|
526
526
|
const zip = new ZipWriter();
|
|
527
527
|
const sheets = workbook['sheets'];
|
|
528
528
|
const sheetNames = sheets.map((sheet, index) => sheet.getName() || `Sheet${index + 1}`);
|
|
529
|
+
// Collect all styles from sheets
|
|
530
|
+
const styleRegistry = this.collectStyles(sheets);
|
|
529
531
|
// Add required XLSX files
|
|
530
532
|
zip.addFile('[Content_Types].xml', this.generateContentTypes(sheetNames));
|
|
531
533
|
zip.addFile('_rels/.rels', this.generateRels());
|
|
532
534
|
zip.addFile('xl/workbook.xml', this.generateWorkbook(sheetNames));
|
|
533
535
|
zip.addFile('xl/_rels/workbook.xml.rels', this.generateWorkbookRels(sheetNames));
|
|
534
|
-
zip.addFile('xl/styles.xml', this.generateStyles());
|
|
536
|
+
zip.addFile('xl/styles.xml', this.generateStyles(styleRegistry));
|
|
535
537
|
zip.addFile('xl/sharedStrings.xml', this.generateSharedStrings(sheets));
|
|
536
538
|
// Add each worksheet
|
|
537
539
|
sheets.forEach((sheet, index) => {
|
|
538
|
-
zip.addFile(`xl/worksheets/sheet${index + 1}.xml`, this.generateWorksheet(sheet));
|
|
540
|
+
zip.addFile(`xl/worksheets/sheet${index + 1}.xml`, this.generateWorksheet(sheet, styleRegistry));
|
|
539
541
|
});
|
|
540
542
|
const buffer = zip.generate();
|
|
541
543
|
return new Blob([buffer.buffer], {
|
|
542
544
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
543
545
|
});
|
|
544
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Collect all unique styles from sheets
|
|
549
|
+
*/
|
|
550
|
+
static collectStyles(sheets) {
|
|
551
|
+
const registry = {
|
|
552
|
+
fonts: new Map(),
|
|
553
|
+
fills: new Map(),
|
|
554
|
+
borders: new Map(),
|
|
555
|
+
cellXfs: new Map()
|
|
556
|
+
};
|
|
557
|
+
// Add default font (Calibri 11)
|
|
558
|
+
const defaultFont = this.serializeFont({});
|
|
559
|
+
registry.fonts.set(defaultFont, 0);
|
|
560
|
+
// Add required fills (none and gray125)
|
|
561
|
+
registry.fills.set('none', 0);
|
|
562
|
+
registry.fills.set('gray125', 1);
|
|
563
|
+
// Add default border
|
|
564
|
+
registry.borders.set('none', 0);
|
|
565
|
+
// Add default cellXf (no style)
|
|
566
|
+
registry.cellXfs.set('default', 0);
|
|
567
|
+
// Collect styles from all cells
|
|
568
|
+
sheets.forEach((sheet) => {
|
|
569
|
+
const rows = sheet.getRows();
|
|
570
|
+
rows.forEach((row) => {
|
|
571
|
+
row.getCells().forEach((cell) => {
|
|
572
|
+
const cellData = cell.toData();
|
|
573
|
+
if (cellData.style) {
|
|
574
|
+
this.registerStyle(cellData.style, registry);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
return registry;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Register a style and get its cellXf index
|
|
583
|
+
*/
|
|
584
|
+
static registerStyle(style, registry) {
|
|
585
|
+
var _a, _b, _c, _d;
|
|
586
|
+
// Serialize and register font
|
|
587
|
+
const fontKey = this.serializeFont(style);
|
|
588
|
+
if (!registry.fonts.has(fontKey)) {
|
|
589
|
+
registry.fonts.set(fontKey, registry.fonts.size);
|
|
590
|
+
}
|
|
591
|
+
// Serialize and register fill
|
|
592
|
+
const fillKey = this.serializeFill(style);
|
|
593
|
+
if (fillKey !== 'none' && !registry.fills.has(fillKey)) {
|
|
594
|
+
registry.fills.set(fillKey, registry.fills.size);
|
|
595
|
+
}
|
|
596
|
+
// Serialize and register border
|
|
597
|
+
const borderKey = this.serializeBorder(style);
|
|
598
|
+
if (borderKey !== 'none' && !registry.borders.has(borderKey)) {
|
|
599
|
+
registry.borders.set(borderKey, registry.borders.size);
|
|
600
|
+
}
|
|
601
|
+
// Create cellXf key
|
|
602
|
+
const fontIndex = (_a = registry.fonts.get(fontKey)) !== null && _a !== void 0 ? _a : 0;
|
|
603
|
+
const fillIndex = (_b = registry.fills.get(fillKey)) !== null && _b !== void 0 ? _b : 0;
|
|
604
|
+
const borderIndex = (_c = registry.borders.get(borderKey)) !== null && _c !== void 0 ? _c : 0;
|
|
605
|
+
let alignment = this.serializeAlignment(style);
|
|
606
|
+
if (!alignment)
|
|
607
|
+
alignment = '{}';
|
|
608
|
+
const xfKey = `f${fontIndex}:l${fillIndex}:b${borderIndex}:a${alignment}`;
|
|
609
|
+
if (!registry.cellXfs.has(xfKey)) {
|
|
610
|
+
registry.cellXfs.set(xfKey, registry.cellXfs.size);
|
|
611
|
+
}
|
|
612
|
+
return (_d = registry.cellXfs.get(xfKey)) !== null && _d !== void 0 ? _d : 0;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Get style index for a cell style
|
|
616
|
+
*/
|
|
617
|
+
static getStyleIndex(style, registry) {
|
|
618
|
+
if (!style)
|
|
619
|
+
return 0;
|
|
620
|
+
return this.registerStyle(style, registry);
|
|
621
|
+
}
|
|
622
|
+
static serializeFont(style) {
|
|
623
|
+
var _a, _b, _c, _d, _e, _f;
|
|
624
|
+
const bold = style.bold || ((_a = style.font) === null || _a === void 0 ? void 0 : _a.bold);
|
|
625
|
+
const italic = style.italic || ((_b = style.font) === null || _b === void 0 ? void 0 : _b.italic);
|
|
626
|
+
const underline = style.underline || ((_c = style.font) === null || _c === void 0 ? void 0 : _c.underline);
|
|
627
|
+
const color = style.color || ((_d = style.font) === null || _d === void 0 ? void 0 : _d.color);
|
|
628
|
+
const size = style.fontSize || ((_e = style.font) === null || _e === void 0 ? void 0 : _e.size) || 11;
|
|
629
|
+
const name = style.fontFamily || ((_f = style.font) === null || _f === void 0 ? void 0 : _f.name) || 'Calibri';
|
|
630
|
+
return JSON.stringify({ bold, italic, underline, color, size, name });
|
|
631
|
+
}
|
|
632
|
+
static serializeFill(style) {
|
|
633
|
+
var _a;
|
|
634
|
+
const bgColor = style.backgroundColor || ((_a = style.fill) === null || _a === void 0 ? void 0 : _a.fgColor);
|
|
635
|
+
if (!bgColor)
|
|
636
|
+
return 'none';
|
|
637
|
+
return JSON.stringify({ bgColor });
|
|
638
|
+
}
|
|
639
|
+
static serializeBorder(style) {
|
|
640
|
+
if (!style.border && !style.borderAll)
|
|
641
|
+
return 'none';
|
|
642
|
+
return JSON.stringify({ border: style.border, borderAll: style.borderAll });
|
|
643
|
+
}
|
|
644
|
+
static serializeAlignment(style) {
|
|
645
|
+
const h = style.alignment;
|
|
646
|
+
const v = style.verticalAlignment;
|
|
647
|
+
const wrap = style.wrapText;
|
|
648
|
+
if (!h && !v && !wrap)
|
|
649
|
+
return '';
|
|
650
|
+
return JSON.stringify({ h, v, wrap });
|
|
651
|
+
}
|
|
545
652
|
static escapeXml(str) {
|
|
546
653
|
if (typeof str !== 'string')
|
|
547
654
|
return String(str !== null && str !== void 0 ? str : '');
|
|
@@ -595,17 +702,150 @@ class ExcelWriter {
|
|
|
595
702
|
xml += '</Relationships>';
|
|
596
703
|
return xml;
|
|
597
704
|
}
|
|
598
|
-
static generateStyles() {
|
|
705
|
+
static generateStyles(registry) {
|
|
599
706
|
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
600
707
|
xml += '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
|
|
601
|
-
|
|
602
|
-
xml +=
|
|
603
|
-
|
|
708
|
+
// Generate fonts
|
|
709
|
+
xml += `<fonts count="${registry.fonts.size}">`;
|
|
710
|
+
const fontEntries = Array.from(registry.fonts.entries()).sort((a, b) => a[1] - b[1]);
|
|
711
|
+
for (const [fontKey] of fontEntries) {
|
|
712
|
+
const font = JSON.parse(fontKey);
|
|
713
|
+
xml += '<font>';
|
|
714
|
+
if (font.bold)
|
|
715
|
+
xml += '<b/>';
|
|
716
|
+
if (font.italic)
|
|
717
|
+
xml += '<i/>';
|
|
718
|
+
if (font.underline)
|
|
719
|
+
xml += '<u/>';
|
|
720
|
+
xml += `<sz val="${font.size || 11}"/>`;
|
|
721
|
+
if (font.color) {
|
|
722
|
+
const colorHex = this.colorToARGB(font.color);
|
|
723
|
+
xml += `<color rgb="${colorHex}"/>`;
|
|
724
|
+
}
|
|
725
|
+
xml += `<name val="${font.name || 'Calibri'}"/>`;
|
|
726
|
+
xml += '</font>';
|
|
727
|
+
}
|
|
728
|
+
xml += '</fonts>';
|
|
729
|
+
// Generate fills
|
|
730
|
+
xml += `<fills count="${registry.fills.size}">`;
|
|
731
|
+
const fillEntries = Array.from(registry.fills.entries()).sort((a, b) => a[1] - b[1]);
|
|
732
|
+
for (const [fillKey] of fillEntries) {
|
|
733
|
+
if (fillKey === 'none') {
|
|
734
|
+
xml += '<fill><patternFill patternType="none"/></fill>';
|
|
735
|
+
}
|
|
736
|
+
else if (fillKey === 'gray125') {
|
|
737
|
+
xml += '<fill><patternFill patternType="gray125"/></fill>';
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
const fill = JSON.parse(fillKey);
|
|
741
|
+
const colorHex = this.colorToARGB(fill.bgColor);
|
|
742
|
+
xml += `<fill><patternFill patternType="solid"><fgColor rgb="${colorHex}"/><bgColor indexed="64"/></patternFill></fill>`;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
xml += '</fills>';
|
|
746
|
+
// Generate borders
|
|
747
|
+
xml += `<borders count="${registry.borders.size}">`;
|
|
748
|
+
const borderEntries = Array.from(registry.borders.entries()).sort((a, b) => a[1] - b[1]);
|
|
749
|
+
for (const [borderKey] of borderEntries) {
|
|
750
|
+
if (borderKey === 'none') {
|
|
751
|
+
xml += '<border><left/><right/><top/><bottom/><diagonal/></border>';
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
const borderData = JSON.parse(borderKey);
|
|
755
|
+
xml += '<border>';
|
|
756
|
+
if (borderData.borderAll) {
|
|
757
|
+
const style = borderData.borderAll.style || 'thin';
|
|
758
|
+
const color = borderData.borderAll.color ? this.colorToARGB(borderData.borderAll.color) : 'FF000000';
|
|
759
|
+
xml += `<left style="${style}"><color rgb="${color}"/></left>`;
|
|
760
|
+
xml += `<right style="${style}"><color rgb="${color}"/></right>`;
|
|
761
|
+
xml += `<top style="${style}"><color rgb="${color}"/></top>`;
|
|
762
|
+
xml += `<bottom style="${style}"><color rgb="${color}"/></bottom>`;
|
|
763
|
+
}
|
|
764
|
+
else if (borderData.border) {
|
|
765
|
+
const b = borderData.border;
|
|
766
|
+
xml += this.generateBorderEdge('left', b.left);
|
|
767
|
+
xml += this.generateBorderEdge('right', b.right);
|
|
768
|
+
xml += this.generateBorderEdge('top', b.top);
|
|
769
|
+
xml += this.generateBorderEdge('bottom', b.bottom);
|
|
770
|
+
}
|
|
771
|
+
xml += '<diagonal/></border>';
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
xml += '</borders>';
|
|
775
|
+
// Generate cellStyleXfs (base styles)
|
|
604
776
|
xml += '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>';
|
|
605
|
-
|
|
777
|
+
// Generate cellXfs (cell formats)
|
|
778
|
+
xml += `<cellXfs count="${registry.cellXfs.size}">`;
|
|
779
|
+
const xfEntries = Array.from(registry.cellXfs.entries()).sort((a, b) => a[1] - b[1]);
|
|
780
|
+
for (const [xfKey] of xfEntries) {
|
|
781
|
+
if (xfKey === 'default') {
|
|
782
|
+
xml += '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>';
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
// Parse xf key: f{fontIndex}:l{fillIndex}:b{borderIndex}:a{alignment}
|
|
786
|
+
const parts = xfKey.split(':');
|
|
787
|
+
const fontId = parseInt(parts[0].substring(1)) || 0;
|
|
788
|
+
const fillId = parseInt(parts[1].substring(1)) || 0;
|
|
789
|
+
const borderId = parseInt(parts[2].substring(1)) || 0;
|
|
790
|
+
const alignmentJson = parts[3].substring(1);
|
|
791
|
+
let xf = `<xf numFmtId="0" fontId="${fontId}" fillId="${fillId}" borderId="${borderId}" xfId="0"`;
|
|
792
|
+
if (fontId > 0)
|
|
793
|
+
xf += ' applyFont="1"';
|
|
794
|
+
if (fillId > 0)
|
|
795
|
+
xf += ' applyFill="1"';
|
|
796
|
+
if (borderId > 0)
|
|
797
|
+
xf += ' applyBorder="1"';
|
|
798
|
+
let align = {};
|
|
799
|
+
if (alignmentJson && alignmentJson.trim().startsWith('{')) {
|
|
800
|
+
try {
|
|
801
|
+
align = JSON.parse(alignmentJson);
|
|
802
|
+
}
|
|
803
|
+
catch (e) {
|
|
804
|
+
align = {};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (Object.keys(align).length > 0) {
|
|
808
|
+
xf += ' applyAlignment="1">';
|
|
809
|
+
xf += '<alignment';
|
|
810
|
+
if (align.h)
|
|
811
|
+
xf += ` horizontal="${align.h}"`;
|
|
812
|
+
if (align.v)
|
|
813
|
+
xf += ` vertical="${align.v === 'middle' ? 'center' : align.v}"`;
|
|
814
|
+
if (align.wrap)
|
|
815
|
+
xf += ' wrapText="1"';
|
|
816
|
+
xf += '/></xf>';
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
xf += '/>';
|
|
820
|
+
}
|
|
821
|
+
xml += xf;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
xml += '</cellXfs>';
|
|
606
825
|
xml += '</styleSheet>';
|
|
607
826
|
return xml;
|
|
608
827
|
}
|
|
828
|
+
static generateBorderEdge(side, edge) {
|
|
829
|
+
if (!edge || !edge.style)
|
|
830
|
+
return `<${side}/>`;
|
|
831
|
+
const color = edge.color ? this.colorToARGB(edge.color) : 'FF000000';
|
|
832
|
+
return `<${side} style="${edge.style}"><color rgb="${color}"/></${side}>`;
|
|
833
|
+
}
|
|
834
|
+
static colorToARGB(color) {
|
|
835
|
+
if (!color)
|
|
836
|
+
return 'FF000000';
|
|
837
|
+
// Remove # if present
|
|
838
|
+
let hex = color.replace('#', '').toUpperCase();
|
|
839
|
+
// Add alpha if not present (6 chars -> 8 chars)
|
|
840
|
+
if (hex.length === 6) {
|
|
841
|
+
hex = 'FF' + hex;
|
|
842
|
+
}
|
|
843
|
+
// Handle 3 char hex
|
|
844
|
+
if (hex.length === 3) {
|
|
845
|
+
hex = 'FF' + hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
846
|
+
}
|
|
847
|
+
return hex;
|
|
848
|
+
}
|
|
609
849
|
static generateSharedStrings(sheets) {
|
|
610
850
|
const strings = [];
|
|
611
851
|
sheets.forEach((sheet) => {
|
|
@@ -629,14 +869,32 @@ class ExcelWriter {
|
|
|
629
869
|
xml += '</sst>';
|
|
630
870
|
return xml;
|
|
631
871
|
}
|
|
632
|
-
static generateWorksheet(sheet) {
|
|
872
|
+
static generateWorksheet(sheet, styleRegistry) {
|
|
633
873
|
const rows = sheet.getRows();
|
|
874
|
+
const columns = sheet.getColumns();
|
|
634
875
|
const allStrings = this.collectAllStrings(sheet);
|
|
635
876
|
let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
636
877
|
xml += '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
|
|
637
878
|
// Add sheet dimension
|
|
638
879
|
const dimension = this.getSheetDimension(rows);
|
|
639
880
|
xml += `<dimension ref="${dimension}"/>`;
|
|
881
|
+
// Add column widths if defined
|
|
882
|
+
if (columns.length > 0) {
|
|
883
|
+
xml += '<cols>';
|
|
884
|
+
columns.forEach((col, index) => {
|
|
885
|
+
const colData = col.toData();
|
|
886
|
+
if (colData.width !== undefined && colData.width > 0) {
|
|
887
|
+
// Excel width is in character units, convert from pixels: width / 7
|
|
888
|
+
const excelWidth = colData.width / 7;
|
|
889
|
+
xml += `<col min="${index + 1}" max="${index + 1}" width="${excelWidth}" customWidth="1"`;
|
|
890
|
+
if (colData.hidden) {
|
|
891
|
+
xml += ' hidden="1"';
|
|
892
|
+
}
|
|
893
|
+
xml += '/>';
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
xml += '</cols>';
|
|
897
|
+
}
|
|
640
898
|
xml += '<sheetData>';
|
|
641
899
|
rows.forEach((row, rowIndex) => {
|
|
642
900
|
const cells = row.getCells();
|
|
@@ -645,37 +903,54 @@ class ExcelWriter {
|
|
|
645
903
|
cells.forEach((cell, colIndex) => {
|
|
646
904
|
const cellData = cell.toData();
|
|
647
905
|
const cellRef = this.columnToLetter(colIndex + 1) + (rowIndex + 1);
|
|
906
|
+
const styleIndex = this.getStyleIndex(cellData.style, styleRegistry);
|
|
907
|
+
const styleAttr = styleIndex > 0 ? ` s="${styleIndex}"` : '';
|
|
648
908
|
if (cellData.formula) {
|
|
649
|
-
xml += `<c r="${cellRef}"><f>${this.escapeXml(cellData.formula)}</f></c>`;
|
|
909
|
+
xml += `<c r="${cellRef}"${styleAttr}><f>${this.escapeXml(cellData.formula)}</f></c>`;
|
|
650
910
|
}
|
|
651
911
|
else if (cellData.value !== null && cellData.value !== undefined && cellData.value !== '') {
|
|
652
912
|
if (typeof cellData.value === 'number') {
|
|
653
|
-
xml += `<c r="${cellRef}"><v>${cellData.value}</v></c>`;
|
|
913
|
+
xml += `<c r="${cellRef}"${styleAttr}><v>${cellData.value}</v></c>`;
|
|
654
914
|
}
|
|
655
915
|
else if (typeof cellData.value === 'boolean') {
|
|
656
|
-
xml += `<c r="${cellRef}" t="b"><v>${cellData.value ? 1 : 0}</v></c>`;
|
|
916
|
+
xml += `<c r="${cellRef}"${styleAttr} t="b"><v>${cellData.value ? 1 : 0}</v></c>`;
|
|
657
917
|
}
|
|
658
918
|
else if (cellData.type === 'date') {
|
|
659
919
|
const excelDate = DateFormatter.toExcelDate(cellData.value);
|
|
660
|
-
xml += `<c r="${cellRef}"><v>${excelDate}</v></c>`;
|
|
920
|
+
xml += `<c r="${cellRef}"${styleAttr}><v>${excelDate}</v></c>`;
|
|
661
921
|
}
|
|
662
922
|
else {
|
|
663
923
|
// String value - use shared string index
|
|
664
924
|
const stringIndex = allStrings.indexOf(String(cellData.value));
|
|
665
925
|
if (stringIndex >= 0) {
|
|
666
|
-
xml += `<c r="${cellRef}" t="s"><v>${stringIndex}</v></c>`;
|
|
926
|
+
xml += `<c r="${cellRef}"${styleAttr} t="s"><v>${stringIndex}</v></c>`;
|
|
667
927
|
}
|
|
668
928
|
else {
|
|
669
929
|
// Inline string as fallback
|
|
670
|
-
xml += `<c r="${cellRef}" t="inlineStr"><is><t>${this.escapeXml(String(cellData.value))}</t></is></c>`;
|
|
930
|
+
xml += `<c r="${cellRef}"${styleAttr} t="inlineStr"><is><t>${this.escapeXml(String(cellData.value))}</t></is></c>`;
|
|
671
931
|
}
|
|
672
932
|
}
|
|
673
933
|
}
|
|
934
|
+
else if (styleIndex > 0) {
|
|
935
|
+
// Empty cell with style
|
|
936
|
+
xml += `<c r="${cellRef}"${styleAttr}/>`;
|
|
937
|
+
}
|
|
674
938
|
});
|
|
675
939
|
xml += '</row>';
|
|
676
940
|
}
|
|
677
941
|
});
|
|
678
942
|
xml += '</sheetData>';
|
|
943
|
+
// Add merged cells if any
|
|
944
|
+
const sheetData = sheet.toData();
|
|
945
|
+
if (sheetData.mergeCells && sheetData.mergeCells.length > 0) {
|
|
946
|
+
xml += '<mergeCells>';
|
|
947
|
+
sheetData.mergeCells.forEach(merge => {
|
|
948
|
+
const startRef = this.columnToLetter(merge.startCol + 1) + (merge.startRow + 1);
|
|
949
|
+
const endRef = this.columnToLetter(merge.endCol + 1) + (merge.endRow + 1);
|
|
950
|
+
xml += `<mergeCell ref="${startRef}:${endRef}"/>`;
|
|
951
|
+
});
|
|
952
|
+
xml += '</mergeCells>';
|
|
953
|
+
}
|
|
679
954
|
xml += '</worksheet>';
|
|
680
955
|
return xml;
|
|
681
956
|
}
|
|
@@ -969,23 +1244,46 @@ class ExportBuilder {
|
|
|
969
1244
|
});
|
|
970
1245
|
return this;
|
|
971
1246
|
}
|
|
972
|
-
|
|
973
|
-
|
|
1247
|
+
/**
|
|
1248
|
+
* Add multiple data rows to the sheet
|
|
1249
|
+
* @param data Array of row data
|
|
1250
|
+
* @param fields Optional array of field names (for object data)
|
|
1251
|
+
* @param styles Optional array of styles per row or per cell
|
|
1252
|
+
*/
|
|
1253
|
+
addDataRows(data, fields, styles) {
|
|
1254
|
+
data.forEach((item, rowIdx) => {
|
|
974
1255
|
const row = this.currentSheet.createRow();
|
|
1256
|
+
let rowStyle = undefined;
|
|
1257
|
+
let cellStyles = undefined;
|
|
1258
|
+
if (styles && styles[rowIdx]) {
|
|
1259
|
+
if (Array.isArray(styles[rowIdx])) {
|
|
1260
|
+
cellStyles = styles[rowIdx];
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
rowStyle = styles[rowIdx];
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
975
1266
|
if (fields && fields.length > 0) {
|
|
976
|
-
fields.forEach(field => {
|
|
1267
|
+
fields.forEach((field, colIdx) => {
|
|
977
1268
|
const value = this.getNestedValue(item, field);
|
|
978
|
-
|
|
1269
|
+
const style = cellStyles ? cellStyles[colIdx] : rowStyle;
|
|
1270
|
+
row.createCell(value, style);
|
|
979
1271
|
});
|
|
980
1272
|
}
|
|
981
1273
|
else if (Array.isArray(item)) {
|
|
982
|
-
item.forEach(value =>
|
|
1274
|
+
item.forEach((value, colIdx) => {
|
|
1275
|
+
const style = cellStyles ? cellStyles[colIdx] : rowStyle;
|
|
1276
|
+
row.createCell(value, style);
|
|
1277
|
+
});
|
|
983
1278
|
}
|
|
984
1279
|
else if (typeof item === 'object') {
|
|
985
|
-
Object.values(item).forEach(value =>
|
|
1280
|
+
Object.values(item).forEach((value, colIdx) => {
|
|
1281
|
+
const style = cellStyles ? cellStyles[colIdx] : rowStyle;
|
|
1282
|
+
row.createCell(value, style);
|
|
1283
|
+
});
|
|
986
1284
|
}
|
|
987
1285
|
else {
|
|
988
|
-
row.createCell(item);
|
|
1286
|
+
row.createCell(item, rowStyle);
|
|
989
1287
|
}
|
|
990
1288
|
});
|
|
991
1289
|
return this;
|
|
@@ -1044,6 +1342,55 @@ class ExportBuilder {
|
|
|
1044
1342
|
});
|
|
1045
1343
|
return this;
|
|
1046
1344
|
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Merge cells in the current worksheet
|
|
1347
|
+
* @param startRow Start row index (0-based)
|
|
1348
|
+
* @param startCol Start column index (0-based)
|
|
1349
|
+
* @param endRow End row index (0-based)
|
|
1350
|
+
* @param endCol End column index (0-based)
|
|
1351
|
+
*/
|
|
1352
|
+
mergeCells(startRow, startCol, endRow, endCol) {
|
|
1353
|
+
this.currentSheet.mergeCells(startRow, startCol, endRow, endCol);
|
|
1354
|
+
return this;
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Set alignment for a specific cell
|
|
1358
|
+
* @param row Row index (0-based)
|
|
1359
|
+
* @param col Column index (0-based)
|
|
1360
|
+
* @param horizontal Horizontal alignment
|
|
1361
|
+
* @param vertical Vertical alignment (optional)
|
|
1362
|
+
*/
|
|
1363
|
+
setAlignment(row, col, horizontal, vertical) {
|
|
1364
|
+
const rowObj = this.currentSheet.getRow(row);
|
|
1365
|
+
if (rowObj) {
|
|
1366
|
+
const cells = rowObj.getCells();
|
|
1367
|
+
if (cells[col]) {
|
|
1368
|
+
const style = StyleBuilder.create().align(horizontal);
|
|
1369
|
+
if (vertical) {
|
|
1370
|
+
style.verticalAlign(vertical);
|
|
1371
|
+
}
|
|
1372
|
+
cells[col].setStyle(style.build());
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return this;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Set alignment for a range of cells
|
|
1379
|
+
* @param startRow Start row index (0-based)
|
|
1380
|
+
* @param startCol Start column index (0-based)
|
|
1381
|
+
* @param endRow End row index (0-based)
|
|
1382
|
+
* @param endCol End column index (0-based)
|
|
1383
|
+
* @param horizontal Horizontal alignment
|
|
1384
|
+
* @param vertical Vertical alignment (optional)
|
|
1385
|
+
*/
|
|
1386
|
+
setRangeAlignment(startRow, startCol, endRow, endCol, horizontal, vertical) {
|
|
1387
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
1388
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
1389
|
+
this.setAlignment(r, c, horizontal, vertical);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return this;
|
|
1393
|
+
}
|
|
1047
1394
|
autoSizeColumns() {
|
|
1048
1395
|
const rows = this.currentSheet.getRows();
|
|
1049
1396
|
const maxLengths = [];
|
|
@@ -1168,6 +1515,28 @@ class SheetBuilder {
|
|
|
1168
1515
|
});
|
|
1169
1516
|
return this;
|
|
1170
1517
|
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Set the width of a specific column
|
|
1520
|
+
* @param colIndex Column index
|
|
1521
|
+
* @param width Width to set
|
|
1522
|
+
*/
|
|
1523
|
+
setColumnWidth(colIndex, width) {
|
|
1524
|
+
const column = this.getOrCreateColumn(colIndex);
|
|
1525
|
+
column.setWidth(width);
|
|
1526
|
+
return this;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Set the height of a specific row
|
|
1530
|
+
* @param rowIndex Row index
|
|
1531
|
+
* @param height Height to set
|
|
1532
|
+
*/
|
|
1533
|
+
setRowHeight(rowIndex, height) {
|
|
1534
|
+
const row = this.worksheet.getRow(rowIndex);
|
|
1535
|
+
if (row) {
|
|
1536
|
+
row.setHeight(height);
|
|
1537
|
+
}
|
|
1538
|
+
return this;
|
|
1539
|
+
}
|
|
1171
1540
|
/**
|
|
1172
1541
|
* Add a title row
|
|
1173
1542
|
* @param title Title text
|