@niicojs/excel 0.2.4 → 0.2.5
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/LICENSE +20 -20
- package/README.md +8 -232
- package/dist/index.cjs +54 -421
- package/dist/index.d.cts +3 -116
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -116
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -418
- package/package.json +1 -1
- package/src/index.ts +1 -15
- package/src/pivot-cache.ts +30 -17
- package/src/pivot-table.ts +12 -122
- package/src/range.ts +2 -15
- package/src/styles.ts +1 -60
- package/src/types.ts +0 -23
- package/src/utils/address.ts +1 -4
- package/src/utils/xml.ts +7 -0
- package/src/workbook.ts +0 -18
- package/src/worksheet.ts +7 -235
package/dist/index.js
CHANGED
|
@@ -38,10 +38,8 @@ import { unzip, strFromU8, zip, strToU8 } from 'fflate';
|
|
|
38
38
|
if (!match) {
|
|
39
39
|
throw new Error(`Invalid cell address: ${address}`);
|
|
40
40
|
}
|
|
41
|
-
const rowNumber = +match[2];
|
|
42
|
-
if (rowNumber <= 0) throw new Error(`Invalid cell address: ${address}`);
|
|
43
41
|
const col = letterToCol(match[1].toUpperCase());
|
|
44
|
-
const row =
|
|
42
|
+
const row = parseInt(match[2], 10) - 1; // Convert to 0-based
|
|
45
43
|
return {
|
|
46
44
|
row,
|
|
47
45
|
col
|
|
@@ -103,12 +101,6 @@ import { unzip, strFromU8, zip, strToU8 } from 'fflate';
|
|
|
103
101
|
}
|
|
104
102
|
};
|
|
105
103
|
};
|
|
106
|
-
/**
|
|
107
|
-
* Checks if an address is within a range
|
|
108
|
-
*/ const isInRange = (addr, range)=>{
|
|
109
|
-
const norm = normalizeRange(range);
|
|
110
|
-
return addr.row >= norm.start.row && addr.row <= norm.end.row && addr.col >= norm.start.col && addr.col <= norm.end.col;
|
|
111
|
-
};
|
|
112
104
|
|
|
113
105
|
// Excel epoch: December 30, 1899 (accounting for the 1900 leap year bug)
|
|
114
106
|
const EXCEL_EPOCH = new Date(Date.UTC(1899, 11, 30));
|
|
@@ -441,23 +433,12 @@ _computedKey = Symbol.iterator;
|
|
|
441
433
|
/**
|
|
442
434
|
* Get all values in the range as a 2D array
|
|
443
435
|
*/ get values() {
|
|
444
|
-
return this.getValues();
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Get all values in the range as a 2D array with options
|
|
448
|
-
*/ getValues(options = {}) {
|
|
449
|
-
const { createMissing = true } = options;
|
|
450
436
|
const result = [];
|
|
451
437
|
for(let r = this._range.start.row; r <= this._range.end.row; r++){
|
|
452
438
|
const row = [];
|
|
453
439
|
for(let c = this._range.start.col; c <= this._range.end.col; c++){
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
row.push(cell.value);
|
|
457
|
-
} else {
|
|
458
|
-
const cell = this._worksheet.getCellIfExists(r, c);
|
|
459
|
-
row.push(cell?.value ?? null);
|
|
460
|
-
}
|
|
440
|
+
const cell = this._worksheet.cell(r, c);
|
|
441
|
+
row.push(cell.value);
|
|
461
442
|
}
|
|
462
443
|
result.push(row);
|
|
463
444
|
}
|
|
@@ -630,11 +611,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
630
611
|
this._dirty = false;
|
|
631
612
|
this._mergedCells = new Set();
|
|
632
613
|
this._sheetData = [];
|
|
633
|
-
this._columnWidths = new Map();
|
|
634
|
-
this._rowHeights = new Map();
|
|
635
|
-
this._frozenPane = null;
|
|
636
|
-
this._dataBoundsCache = null;
|
|
637
|
-
this._boundsDirty = true;
|
|
638
614
|
this._workbook = workbook;
|
|
639
615
|
this._name = name;
|
|
640
616
|
}
|
|
@@ -661,49 +637,12 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
661
637
|
const worksheet = findElement(this._xmlNodes, 'worksheet');
|
|
662
638
|
if (!worksheet) return;
|
|
663
639
|
const worksheetChildren = getChildren(worksheet, 'worksheet');
|
|
664
|
-
// Parse sheet views (freeze panes)
|
|
665
|
-
const sheetViews = findElement(worksheetChildren, 'sheetViews');
|
|
666
|
-
if (sheetViews) {
|
|
667
|
-
const viewChildren = getChildren(sheetViews, 'sheetViews');
|
|
668
|
-
const sheetView = findElement(viewChildren, 'sheetView');
|
|
669
|
-
if (sheetView) {
|
|
670
|
-
const sheetViewChildren = getChildren(sheetView, 'sheetView');
|
|
671
|
-
const pane = findElement(sheetViewChildren, 'pane');
|
|
672
|
-
if (pane && getAttr(pane, 'state') === 'frozen') {
|
|
673
|
-
const xSplit = parseInt(getAttr(pane, 'xSplit') || '0', 10);
|
|
674
|
-
const ySplit = parseInt(getAttr(pane, 'ySplit') || '0', 10);
|
|
675
|
-
if (xSplit > 0 || ySplit > 0) {
|
|
676
|
-
this._frozenPane = {
|
|
677
|
-
row: ySplit,
|
|
678
|
-
col: xSplit
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
640
|
// Parse sheet data (cells)
|
|
685
641
|
const sheetData = findElement(worksheetChildren, 'sheetData');
|
|
686
642
|
if (sheetData) {
|
|
687
643
|
this._sheetData = getChildren(sheetData, 'sheetData');
|
|
688
644
|
this._parseSheetData(this._sheetData);
|
|
689
645
|
}
|
|
690
|
-
// Parse column widths
|
|
691
|
-
const cols = findElement(worksheetChildren, 'cols');
|
|
692
|
-
if (cols) {
|
|
693
|
-
const colChildren = getChildren(cols, 'cols');
|
|
694
|
-
for (const col of colChildren){
|
|
695
|
-
if (!('col' in col)) continue;
|
|
696
|
-
const min = parseInt(getAttr(col, 'min') || '0', 10);
|
|
697
|
-
const max = parseInt(getAttr(col, 'max') || '0', 10);
|
|
698
|
-
const width = parseFloat(getAttr(col, 'width') || '0');
|
|
699
|
-
if (!Number.isFinite(width) || width <= 0) continue;
|
|
700
|
-
if (min > 0 && max > 0) {
|
|
701
|
-
for(let idx = min; idx <= max; idx++){
|
|
702
|
-
this._columnWidths.set(idx - 1, width);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
646
|
// Parse merged cells
|
|
708
647
|
const mergeCells = findElement(worksheetChildren, 'mergeCells');
|
|
709
648
|
if (mergeCells) {
|
|
@@ -723,11 +662,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
723
662
|
*/ _parseSheetData(rows) {
|
|
724
663
|
for (const rowNode of rows){
|
|
725
664
|
if (!('row' in rowNode)) continue;
|
|
726
|
-
const rowIndex = parseInt(getAttr(rowNode, 'r') || '0', 10) - 1;
|
|
727
|
-
const rowHeight = parseFloat(getAttr(rowNode, 'ht') || '0');
|
|
728
|
-
if (rowIndex >= 0 && Number.isFinite(rowHeight) && rowHeight > 0) {
|
|
729
|
-
this._rowHeights.set(rowIndex, rowHeight);
|
|
730
|
-
}
|
|
731
665
|
const rowChildren = getChildren(rowNode, 'row');
|
|
732
666
|
for (const cellNode of rowChildren){
|
|
733
667
|
if (!('c' in cellNode)) continue;
|
|
@@ -739,7 +673,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
739
673
|
this._cells.set(ref, cell);
|
|
740
674
|
}
|
|
741
675
|
}
|
|
742
|
-
this._boundsDirty = true;
|
|
743
676
|
}
|
|
744
677
|
/**
|
|
745
678
|
* Parse a cell XML node to CellData
|
|
@@ -826,17 +759,9 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
826
759
|
if (!cell) {
|
|
827
760
|
cell = new Cell(this, row, c);
|
|
828
761
|
this._cells.set(address, cell);
|
|
829
|
-
this._boundsDirty = true;
|
|
830
762
|
}
|
|
831
763
|
return cell;
|
|
832
764
|
}
|
|
833
|
-
/**
|
|
834
|
-
* Get an existing cell without creating it.
|
|
835
|
-
*/ getCellIfExists(rowOrAddress, col) {
|
|
836
|
-
const { row, col: c } = parseCellRef(rowOrAddress, col);
|
|
837
|
-
const address = toAddress(row, c);
|
|
838
|
-
return this._cells.get(address);
|
|
839
|
-
}
|
|
840
765
|
range(startRowOrRange, startCol, endRow, endCol) {
|
|
841
766
|
let rangeAddr;
|
|
842
767
|
if (typeof startRowOrRange === 'string') {
|
|
@@ -896,65 +821,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
896
821
|
return this._cells;
|
|
897
822
|
}
|
|
898
823
|
/**
|
|
899
|
-
* Set a column width (0-based index or column letter)
|
|
900
|
-
*/ setColumnWidth(col, width) {
|
|
901
|
-
if (!Number.isFinite(width) || width <= 0) {
|
|
902
|
-
throw new Error('Column width must be a positive number');
|
|
903
|
-
}
|
|
904
|
-
const colIndex = typeof col === 'number' ? col : letterToCol(col);
|
|
905
|
-
if (colIndex < 0) {
|
|
906
|
-
throw new Error(`Invalid column: ${col}`);
|
|
907
|
-
}
|
|
908
|
-
this._columnWidths.set(colIndex, width);
|
|
909
|
-
this._dirty = true;
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Get a column width if set
|
|
913
|
-
*/ getColumnWidth(col) {
|
|
914
|
-
const colIndex = typeof col === 'number' ? col : letterToCol(col);
|
|
915
|
-
return this._columnWidths.get(colIndex);
|
|
916
|
-
}
|
|
917
|
-
/**
|
|
918
|
-
* Set a row height (0-based index)
|
|
919
|
-
*/ setRowHeight(row, height) {
|
|
920
|
-
if (!Number.isFinite(height) || height <= 0) {
|
|
921
|
-
throw new Error('Row height must be a positive number');
|
|
922
|
-
}
|
|
923
|
-
if (row < 0) {
|
|
924
|
-
throw new Error('Row index must be >= 0');
|
|
925
|
-
}
|
|
926
|
-
this._rowHeights.set(row, height);
|
|
927
|
-
this._dirty = true;
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* Get a row height if set
|
|
931
|
-
*/ getRowHeight(row) {
|
|
932
|
-
return this._rowHeights.get(row);
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Freeze panes at a given row/column split (counts from top-left)
|
|
936
|
-
*/ freezePane(rowSplit, colSplit) {
|
|
937
|
-
if (rowSplit < 0 || colSplit < 0) {
|
|
938
|
-
throw new Error('Freeze pane splits must be >= 0');
|
|
939
|
-
}
|
|
940
|
-
if (rowSplit === 0 && colSplit === 0) {
|
|
941
|
-
this._frozenPane = null;
|
|
942
|
-
} else {
|
|
943
|
-
this._frozenPane = {
|
|
944
|
-
row: rowSplit,
|
|
945
|
-
col: colSplit
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
this._dirty = true;
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Get current frozen pane configuration
|
|
952
|
-
*/ getFrozenPane() {
|
|
953
|
-
return this._frozenPane ? {
|
|
954
|
-
...this._frozenPane
|
|
955
|
-
} : null;
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
824
|
* Convert sheet data to an array of JSON objects.
|
|
959
825
|
*
|
|
960
826
|
* @param config - Configuration options
|
|
@@ -972,7 +838,7 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
972
838
|
* const data = sheet.toJson({ startRow: 2, startCol: 1 });
|
|
973
839
|
* ```
|
|
974
840
|
*/ toJson(config = {}) {
|
|
975
|
-
const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true
|
|
841
|
+
const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true } = config;
|
|
976
842
|
// Get the bounds of data in the sheet
|
|
977
843
|
const bounds = this._getDataBounds();
|
|
978
844
|
if (!bounds) {
|
|
@@ -1005,10 +871,7 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1005
871
|
for(let colOffset = 0; colOffset < fieldNames.length; colOffset++){
|
|
1006
872
|
const col = startCol + colOffset;
|
|
1007
873
|
const cell = this._cells.get(toAddress(row, col));
|
|
1008
|
-
|
|
1009
|
-
if (value instanceof Date) {
|
|
1010
|
-
value = this._serializeDate(value, dateHandling, cell);
|
|
1011
|
-
}
|
|
874
|
+
const value = cell?.value ?? null;
|
|
1012
875
|
if (value !== null) {
|
|
1013
876
|
hasData = true;
|
|
1014
877
|
}
|
|
@@ -1025,24 +888,10 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1025
888
|
}
|
|
1026
889
|
return result;
|
|
1027
890
|
}
|
|
1028
|
-
_serializeDate(value, dateHandling, cell) {
|
|
1029
|
-
if (dateHandling === 'excelSerial') {
|
|
1030
|
-
return cell?._jsDateToExcel(value) ?? value;
|
|
1031
|
-
}
|
|
1032
|
-
if (dateHandling === 'isoString') {
|
|
1033
|
-
return value.toISOString();
|
|
1034
|
-
}
|
|
1035
|
-
return value;
|
|
1036
|
-
}
|
|
1037
891
|
/**
|
|
1038
892
|
* Get the bounds of data in the sheet (min/max row and column with data)
|
|
1039
893
|
*/ _getDataBounds() {
|
|
1040
|
-
if (!this._boundsDirty && this._dataBoundsCache) {
|
|
1041
|
-
return this._dataBoundsCache;
|
|
1042
|
-
}
|
|
1043
894
|
if (this._cells.size === 0) {
|
|
1044
|
-
this._dataBoundsCache = null;
|
|
1045
|
-
this._boundsDirty = false;
|
|
1046
895
|
return null;
|
|
1047
896
|
}
|
|
1048
897
|
let minRow = Infinity;
|
|
@@ -1058,18 +907,14 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1058
907
|
}
|
|
1059
908
|
}
|
|
1060
909
|
if (minRow === Infinity) {
|
|
1061
|
-
this._dataBoundsCache = null;
|
|
1062
|
-
this._boundsDirty = false;
|
|
1063
910
|
return null;
|
|
1064
911
|
}
|
|
1065
|
-
|
|
912
|
+
return {
|
|
1066
913
|
minRow,
|
|
1067
914
|
maxRow,
|
|
1068
915
|
minCol,
|
|
1069
916
|
maxCol
|
|
1070
917
|
};
|
|
1071
|
-
this._boundsDirty = false;
|
|
1072
|
-
return this._dataBoundsCache;
|
|
1073
918
|
}
|
|
1074
919
|
/**
|
|
1075
920
|
* Generate XML for this worksheet
|
|
@@ -1083,11 +928,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1083
928
|
}
|
|
1084
929
|
rowMap.get(row).push(cell);
|
|
1085
930
|
}
|
|
1086
|
-
for (const rowIdx of this._rowHeights.keys()){
|
|
1087
|
-
if (!rowMap.has(rowIdx)) {
|
|
1088
|
-
rowMap.set(rowIdx, []);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
931
|
// Sort rows and cells
|
|
1092
932
|
const sortedRows = Array.from(rowMap.entries()).sort((a, b)=>a[0] - b[0]);
|
|
1093
933
|
const rowNodes = [];
|
|
@@ -1098,71 +938,16 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1098
938
|
const cellNode = this._buildCellNode(cell);
|
|
1099
939
|
cellNodes.push(cellNode);
|
|
1100
940
|
}
|
|
1101
|
-
const
|
|
941
|
+
const rowNode = createElement('row', {
|
|
1102
942
|
r: String(rowIdx + 1)
|
|
1103
|
-
};
|
|
1104
|
-
const rowHeight = this._rowHeights.get(rowIdx);
|
|
1105
|
-
if (rowHeight !== undefined) {
|
|
1106
|
-
rowAttrs.ht = String(rowHeight);
|
|
1107
|
-
rowAttrs.customHeight = '1';
|
|
1108
|
-
}
|
|
1109
|
-
const rowNode = createElement('row', rowAttrs, cellNodes);
|
|
943
|
+
}, cellNodes);
|
|
1110
944
|
rowNodes.push(rowNode);
|
|
1111
945
|
}
|
|
1112
946
|
const sheetDataNode = createElement('sheetData', {}, rowNodes);
|
|
1113
947
|
// Build worksheet structure
|
|
1114
|
-
const worksheetChildren = [
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const paneAttrs = {
|
|
1118
|
-
state: 'frozen'
|
|
1119
|
-
};
|
|
1120
|
-
const topLeftCell = toAddress(this._frozenPane.row, this._frozenPane.col);
|
|
1121
|
-
paneAttrs.topLeftCell = topLeftCell;
|
|
1122
|
-
if (this._frozenPane.col > 0) {
|
|
1123
|
-
paneAttrs.xSplit = String(this._frozenPane.col);
|
|
1124
|
-
}
|
|
1125
|
-
if (this._frozenPane.row > 0) {
|
|
1126
|
-
paneAttrs.ySplit = String(this._frozenPane.row);
|
|
1127
|
-
}
|
|
1128
|
-
let activePane = 'bottomRight';
|
|
1129
|
-
if (this._frozenPane.row > 0 && this._frozenPane.col === 0) {
|
|
1130
|
-
activePane = 'bottomLeft';
|
|
1131
|
-
} else if (this._frozenPane.row === 0 && this._frozenPane.col > 0) {
|
|
1132
|
-
activePane = 'topRight';
|
|
1133
|
-
}
|
|
1134
|
-
paneAttrs.activePane = activePane;
|
|
1135
|
-
const paneNode = createElement('pane', paneAttrs, []);
|
|
1136
|
-
const selectionNode = createElement('selection', {
|
|
1137
|
-
pane: activePane,
|
|
1138
|
-
activeCell: topLeftCell,
|
|
1139
|
-
sqref: topLeftCell
|
|
1140
|
-
}, []);
|
|
1141
|
-
const sheetViewNode = createElement('sheetView', {
|
|
1142
|
-
workbookViewId: '0'
|
|
1143
|
-
}, [
|
|
1144
|
-
paneNode,
|
|
1145
|
-
selectionNode
|
|
1146
|
-
]);
|
|
1147
|
-
worksheetChildren.push(createElement('sheetViews', {}, [
|
|
1148
|
-
sheetViewNode
|
|
1149
|
-
]));
|
|
1150
|
-
}
|
|
1151
|
-
// Column widths
|
|
1152
|
-
if (this._columnWidths.size > 0) {
|
|
1153
|
-
const colNodes = [];
|
|
1154
|
-
const entries = Array.from(this._columnWidths.entries()).sort((a, b)=>a[0] - b[0]);
|
|
1155
|
-
for (const [colIndex, width] of entries){
|
|
1156
|
-
colNodes.push(createElement('col', {
|
|
1157
|
-
min: String(colIndex + 1),
|
|
1158
|
-
max: String(colIndex + 1),
|
|
1159
|
-
width: String(width),
|
|
1160
|
-
customWidth: '1'
|
|
1161
|
-
}, []));
|
|
1162
|
-
}
|
|
1163
|
-
worksheetChildren.push(createElement('cols', {}, colNodes));
|
|
1164
|
-
}
|
|
1165
|
-
worksheetChildren.push(sheetDataNode);
|
|
948
|
+
const worksheetChildren = [
|
|
949
|
+
sheetDataNode
|
|
950
|
+
];
|
|
1166
951
|
// Add merged cells if any
|
|
1167
952
|
if (this._mergedCells.size > 0) {
|
|
1168
953
|
const mergeCellNodes = [];
|
|
@@ -1349,40 +1134,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1349
1134
|
* Manages the styles (xl/styles.xml)
|
|
1350
1135
|
*/ class Styles {
|
|
1351
1136
|
/**
|
|
1352
|
-
* Generate a deterministic cache key for a style object.
|
|
1353
|
-
* More efficient than JSON.stringify as it avoids the overhead of
|
|
1354
|
-
* full JSON serialization and produces a consistent key regardless
|
|
1355
|
-
* of property order.
|
|
1356
|
-
*/ _getStyleKey(style) {
|
|
1357
|
-
// Use a delimiter that won't appear in values
|
|
1358
|
-
const SEP = '\x00';
|
|
1359
|
-
// Build key from all style properties in a fixed order
|
|
1360
|
-
const parts = [
|
|
1361
|
-
style.bold ? '1' : '0',
|
|
1362
|
-
style.italic ? '1' : '0',
|
|
1363
|
-
style.underline === true ? '1' : style.underline === 'single' ? 's' : style.underline === 'double' ? 'd' : '0',
|
|
1364
|
-
style.strike ? '1' : '0',
|
|
1365
|
-
style.fontSize?.toString() ?? '',
|
|
1366
|
-
style.fontName ?? '',
|
|
1367
|
-
style.fontColor ?? '',
|
|
1368
|
-
style.fill ?? '',
|
|
1369
|
-
style.numberFormat ?? ''
|
|
1370
|
-
];
|
|
1371
|
-
// Border properties
|
|
1372
|
-
if (style.border) {
|
|
1373
|
-
parts.push(style.border.top ?? '', style.border.bottom ?? '', style.border.left ?? '', style.border.right ?? '');
|
|
1374
|
-
} else {
|
|
1375
|
-
parts.push('', '', '', '');
|
|
1376
|
-
}
|
|
1377
|
-
// Alignment properties
|
|
1378
|
-
if (style.alignment) {
|
|
1379
|
-
parts.push(style.alignment.horizontal ?? '', style.alignment.vertical ?? '', style.alignment.wrapText ? '1' : '0', style.alignment.textRotation?.toString() ?? '');
|
|
1380
|
-
} else {
|
|
1381
|
-
parts.push('', '', '0', '');
|
|
1382
|
-
}
|
|
1383
|
-
return parts.join(SEP);
|
|
1384
|
-
}
|
|
1385
|
-
/**
|
|
1386
1137
|
* Parse styles from XML content
|
|
1387
1138
|
*/ static parse(xml) {
|
|
1388
1139
|
const styles = new Styles();
|
|
@@ -1549,10 +1300,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1549
1300
|
/**
|
|
1550
1301
|
* Get a style by index
|
|
1551
1302
|
*/ getStyle(index) {
|
|
1552
|
-
const cached = this._styleObjectCache.get(index);
|
|
1553
|
-
if (cached) return {
|
|
1554
|
-
...cached
|
|
1555
|
-
};
|
|
1556
1303
|
const xf = this._cellXfs[index];
|
|
1557
1304
|
if (!xf) return {};
|
|
1558
1305
|
const font = this._fonts[xf.fontId];
|
|
@@ -1593,16 +1340,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1593
1340
|
textRotation: xf.alignment.textRotation
|
|
1594
1341
|
};
|
|
1595
1342
|
}
|
|
1596
|
-
this._styleObjectCache.set(index, {
|
|
1597
|
-
...style
|
|
1598
|
-
});
|
|
1599
1343
|
return style;
|
|
1600
1344
|
}
|
|
1601
1345
|
/**
|
|
1602
1346
|
* Create a style and return its index
|
|
1603
1347
|
* Uses caching to deduplicate identical styles
|
|
1604
1348
|
*/ createStyle(style) {
|
|
1605
|
-
const key =
|
|
1349
|
+
const key = JSON.stringify(style);
|
|
1606
1350
|
const cached = this._styleCache.get(key);
|
|
1607
1351
|
if (cached !== undefined) {
|
|
1608
1352
|
return cached;
|
|
@@ -1634,20 +1378,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1634
1378
|
const index = this._cellXfs.length;
|
|
1635
1379
|
this._cellXfs.push(xf);
|
|
1636
1380
|
this._styleCache.set(key, index);
|
|
1637
|
-
this._styleObjectCache.set(index, {
|
|
1638
|
-
...style
|
|
1639
|
-
});
|
|
1640
1381
|
return index;
|
|
1641
1382
|
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Clone an existing style by index, optionally overriding fields.
|
|
1644
|
-
*/ cloneStyle(index, overrides = {}) {
|
|
1645
|
-
const baseStyle = this.getStyle(index);
|
|
1646
|
-
return this.createStyle({
|
|
1647
|
-
...baseStyle,
|
|
1648
|
-
...overrides
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
1383
|
_findOrCreateFont(style) {
|
|
1652
1384
|
const font = {
|
|
1653
1385
|
bold: style.bold || false,
|
|
@@ -1883,7 +1615,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1883
1615
|
this._dirty = false;
|
|
1884
1616
|
// Cache for style deduplication
|
|
1885
1617
|
this._styleCache = new Map();
|
|
1886
|
-
this._styleObjectCache = new Map();
|
|
1887
1618
|
}
|
|
1888
1619
|
}
|
|
1889
1620
|
|
|
@@ -1895,7 +1626,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1895
1626
|
this._columnFields = [];
|
|
1896
1627
|
this._valueFields = [];
|
|
1897
1628
|
this._filterFields = [];
|
|
1898
|
-
this._fieldAssignments = new Map();
|
|
1899
1629
|
this._name = name;
|
|
1900
1630
|
this._cache = cache;
|
|
1901
1631
|
this._targetSheet = targetSheet;
|
|
@@ -1937,13 +1667,11 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1937
1667
|
if (fieldIndex < 0) {
|
|
1938
1668
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1939
1669
|
}
|
|
1940
|
-
|
|
1670
|
+
this._rowFields.push({
|
|
1941
1671
|
fieldName,
|
|
1942
1672
|
fieldIndex,
|
|
1943
1673
|
axis: 'row'
|
|
1944
|
-
};
|
|
1945
|
-
this._rowFields.push(assignment);
|
|
1946
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1674
|
+
});
|
|
1947
1675
|
return this;
|
|
1948
1676
|
}
|
|
1949
1677
|
/**
|
|
@@ -1954,13 +1682,11 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1954
1682
|
if (fieldIndex < 0) {
|
|
1955
1683
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1956
1684
|
}
|
|
1957
|
-
|
|
1685
|
+
this._columnFields.push({
|
|
1958
1686
|
fieldName,
|
|
1959
1687
|
fieldIndex,
|
|
1960
1688
|
axis: 'column'
|
|
1961
|
-
};
|
|
1962
|
-
this._columnFields.push(assignment);
|
|
1963
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1689
|
+
});
|
|
1964
1690
|
return this;
|
|
1965
1691
|
}
|
|
1966
1692
|
/**
|
|
@@ -1974,15 +1700,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1974
1700
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1975
1701
|
}
|
|
1976
1702
|
const defaultName = `${aggregation.charAt(0).toUpperCase() + aggregation.slice(1)} of ${fieldName}`;
|
|
1977
|
-
|
|
1703
|
+
this._valueFields.push({
|
|
1978
1704
|
fieldName,
|
|
1979
1705
|
fieldIndex,
|
|
1980
1706
|
axis: 'value',
|
|
1981
1707
|
aggregation,
|
|
1982
1708
|
displayName: displayName || defaultName
|
|
1983
|
-
};
|
|
1984
|
-
this._valueFields.push(assignment);
|
|
1985
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1709
|
+
});
|
|
1986
1710
|
return this;
|
|
1987
1711
|
}
|
|
1988
1712
|
/**
|
|
@@ -1993,44 +1717,11 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1993
1717
|
if (fieldIndex < 0) {
|
|
1994
1718
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1995
1719
|
}
|
|
1996
|
-
|
|
1720
|
+
this._filterFields.push({
|
|
1997
1721
|
fieldName,
|
|
1998
1722
|
fieldIndex,
|
|
1999
1723
|
axis: 'filter'
|
|
2000
|
-
};
|
|
2001
|
-
this._filterFields.push(assignment);
|
|
2002
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
2003
|
-
return this;
|
|
2004
|
-
}
|
|
2005
|
-
/**
|
|
2006
|
-
* Set a sort order for a row/column field
|
|
2007
|
-
*/ sortField(fieldName, order) {
|
|
2008
|
-
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
2009
|
-
if (fieldIndex < 0) {
|
|
2010
|
-
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
2011
|
-
}
|
|
2012
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2013
|
-
if (!assignment || assignment.axis !== 'row' && assignment.axis !== 'column') {
|
|
2014
|
-
throw new Error(`Field is not assigned to row or column axis: ${fieldName}`);
|
|
2015
|
-
}
|
|
2016
|
-
assignment.sortOrder = order;
|
|
2017
|
-
return this;
|
|
2018
|
-
}
|
|
2019
|
-
/**
|
|
2020
|
-
* Filter items for a field (include or exclude list)
|
|
2021
|
-
*/ filterField(fieldName, filter) {
|
|
2022
|
-
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
2023
|
-
if (fieldIndex < 0) {
|
|
2024
|
-
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
2025
|
-
}
|
|
2026
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2027
|
-
if (!assignment) {
|
|
2028
|
-
throw new Error(`Field is not assigned to pivot table: ${fieldName}`);
|
|
2029
|
-
}
|
|
2030
|
-
if (filter.include && filter.exclude) {
|
|
2031
|
-
throw new Error('Pivot field filter cannot use both include and exclude');
|
|
2032
|
-
}
|
|
2033
|
-
assignment.filter = filter;
|
|
1724
|
+
});
|
|
2034
1725
|
return this;
|
|
2035
1726
|
}
|
|
2036
1727
|
/**
|
|
@@ -2195,28 +1886,17 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2195
1886
|
const colField = this._columnFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2196
1887
|
const filterField = this._filterFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2197
1888
|
const valueField = this._valueFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2198
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2199
1889
|
if (rowField) {
|
|
2200
1890
|
attrs.axis = 'axisRow';
|
|
2201
1891
|
attrs.showAll = '0';
|
|
2202
|
-
if (assignment?.sortOrder) {
|
|
2203
|
-
attrs.sortType = 'ascending';
|
|
2204
|
-
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
2205
|
-
}
|
|
2206
1892
|
// Add items for shared values
|
|
2207
1893
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2208
1894
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2209
1895
|
const itemNodes = [];
|
|
2210
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2211
1896
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2212
|
-
|
|
2213
|
-
const itemAttrs = {
|
|
1897
|
+
itemNodes.push(createElement('item', {
|
|
2214
1898
|
x: String(i)
|
|
2215
|
-
};
|
|
2216
|
-
if (!shouldInclude) {
|
|
2217
|
-
itemAttrs.h = '1';
|
|
2218
|
-
}
|
|
2219
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
1899
|
+
}, []));
|
|
2220
1900
|
}
|
|
2221
1901
|
// Add default subtotal item
|
|
2222
1902
|
itemNodes.push(createElement('item', {
|
|
@@ -2229,23 +1909,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2229
1909
|
} else if (colField) {
|
|
2230
1910
|
attrs.axis = 'axisCol';
|
|
2231
1911
|
attrs.showAll = '0';
|
|
2232
|
-
if (assignment?.sortOrder) {
|
|
2233
|
-
attrs.sortType = 'ascending';
|
|
2234
|
-
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
2235
|
-
}
|
|
2236
1912
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2237
1913
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2238
1914
|
const itemNodes = [];
|
|
2239
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2240
1915
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2241
|
-
|
|
2242
|
-
const itemAttrs = {
|
|
1916
|
+
itemNodes.push(createElement('item', {
|
|
2243
1917
|
x: String(i)
|
|
2244
|
-
};
|
|
2245
|
-
if (!shouldInclude) {
|
|
2246
|
-
itemAttrs.h = '1';
|
|
2247
|
-
}
|
|
2248
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
1918
|
+
}, []));
|
|
2249
1919
|
}
|
|
2250
1920
|
itemNodes.push(createElement('item', {
|
|
2251
1921
|
t: 'default'
|
|
@@ -2260,16 +1930,10 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2260
1930
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2261
1931
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2262
1932
|
const itemNodes = [];
|
|
2263
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2264
1933
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2265
|
-
|
|
2266
|
-
const itemAttrs = {
|
|
1934
|
+
itemNodes.push(createElement('item', {
|
|
2267
1935
|
x: String(i)
|
|
2268
|
-
};
|
|
2269
|
-
if (!shouldInclude) {
|
|
2270
|
-
itemAttrs.h = '1';
|
|
2271
|
-
}
|
|
2272
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
1936
|
+
}, []));
|
|
2273
1937
|
}
|
|
2274
1938
|
itemNodes.push(createElement('item', {
|
|
2275
1939
|
t: 'default'
|
|
@@ -2286,31 +1950,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2286
1950
|
}
|
|
2287
1951
|
return createElement('pivotField', attrs, children);
|
|
2288
1952
|
}
|
|
2289
|
-
_resolveItemFilter(items, filter) {
|
|
2290
|
-
const allowed = new Set();
|
|
2291
|
-
if (!filter || !filter.include && !filter.exclude) {
|
|
2292
|
-
for(let i = 0; i < items.length; i++){
|
|
2293
|
-
allowed.add(i);
|
|
2294
|
-
}
|
|
2295
|
-
return allowed;
|
|
2296
|
-
}
|
|
2297
|
-
if (filter.include) {
|
|
2298
|
-
for(let i = 0; i < items.length; i++){
|
|
2299
|
-
if (filter.include.includes(items[i])) {
|
|
2300
|
-
allowed.add(i);
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
return allowed;
|
|
2304
|
-
}
|
|
2305
|
-
if (filter.exclude) {
|
|
2306
|
-
for(let i = 0; i < items.length; i++){
|
|
2307
|
-
if (!filter.exclude.includes(items[i])) {
|
|
2308
|
-
allowed.add(i);
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
return allowed;
|
|
2313
|
-
}
|
|
2314
1953
|
/**
|
|
2315
1954
|
* Build row items based on unique values in row fields
|
|
2316
1955
|
*/ _buildRowItems() {
|
|
@@ -2461,7 +2100,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2461
2100
|
this._records = [];
|
|
2462
2101
|
this._recordCount = 0;
|
|
2463
2102
|
this._refreshOnLoad = true; // Default to true
|
|
2464
|
-
|
|
2103
|
+
// Optimized lookup: Map<fieldIndex, Map<stringValue, sharedItemsIndex>>
|
|
2104
|
+
this._sharedItemsIndexMap = new Map();
|
|
2465
2105
|
this._cacheId = cacheId;
|
|
2466
2106
|
this._sourceSheet = sourceSheet;
|
|
2467
2107
|
this._sourceRange = sourceRange;
|
|
@@ -2522,6 +2162,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2522
2162
|
minValue: undefined,
|
|
2523
2163
|
maxValue: undefined
|
|
2524
2164
|
}));
|
|
2165
|
+
// Use Sets for O(1) unique value collection during analysis
|
|
2166
|
+
const sharedItemsSets = this._fields.map(()=>new Set());
|
|
2525
2167
|
// Analyze data to determine field types and collect unique values
|
|
2526
2168
|
for (const row of data){
|
|
2527
2169
|
for(let colIdx = 0; colIdx < row.length && colIdx < this._fields.length; colIdx++){
|
|
@@ -2532,9 +2174,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2532
2174
|
}
|
|
2533
2175
|
if (typeof value === 'string') {
|
|
2534
2176
|
field.isNumeric = false;
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
}
|
|
2177
|
+
// O(1) Set.add instead of O(n) Array.includes + push
|
|
2178
|
+
sharedItemsSets[colIdx].add(value);
|
|
2538
2179
|
} else if (typeof value === 'number') {
|
|
2539
2180
|
if (field.minValue === undefined || value < field.minValue) {
|
|
2540
2181
|
field.minValue = value;
|
|
@@ -2550,8 +2191,22 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2550
2191
|
}
|
|
2551
2192
|
}
|
|
2552
2193
|
}
|
|
2553
|
-
//
|
|
2554
|
-
this.
|
|
2194
|
+
// Convert Sets to arrays and build reverse index Maps for O(1) lookup during XML generation
|
|
2195
|
+
this._sharedItemsIndexMap.clear();
|
|
2196
|
+
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
2197
|
+
const field = this._fields[colIdx];
|
|
2198
|
+
const set = sharedItemsSets[colIdx];
|
|
2199
|
+
// Convert Set to array (maintains insertion order in ES6+)
|
|
2200
|
+
field.sharedItems = Array.from(set);
|
|
2201
|
+
// Build reverse lookup Map: value -> index
|
|
2202
|
+
if (field.sharedItems.length > 0) {
|
|
2203
|
+
const indexMap = new Map();
|
|
2204
|
+
for(let i = 0; i < field.sharedItems.length; i++){
|
|
2205
|
+
indexMap.set(field.sharedItems[i], i);
|
|
2206
|
+
}
|
|
2207
|
+
this._sharedItemsIndexMap.set(colIdx, indexMap);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2555
2210
|
// Store records
|
|
2556
2211
|
this._records = data;
|
|
2557
2212
|
}
|
|
@@ -2580,8 +2235,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2580
2235
|
v: item
|
|
2581
2236
|
}, []));
|
|
2582
2237
|
}
|
|
2583
|
-
} else if (field.isDate) {
|
|
2584
|
-
sharedItemsAttrs.containsDate = '1';
|
|
2585
2238
|
} else if (field.isNumeric) {
|
|
2586
2239
|
// Numeric field - use "0"/"1" for boolean attributes as Excel expects
|
|
2587
2240
|
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
@@ -2612,13 +2265,9 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2612
2265
|
ref: this._sourceRange,
|
|
2613
2266
|
sheet: this._sourceSheet
|
|
2614
2267
|
}, []);
|
|
2615
|
-
const
|
|
2268
|
+
const cacheSourceNode = createElement('cacheSource', {
|
|
2616
2269
|
type: 'worksheet'
|
|
2617
|
-
}
|
|
2618
|
-
if (this._dateGrouping) {
|
|
2619
|
-
cacheSourceAttrs.grouping = '1';
|
|
2620
|
-
}
|
|
2621
|
-
const cacheSourceNode = createElement('cacheSource', cacheSourceAttrs, [
|
|
2270
|
+
}, [
|
|
2622
2271
|
worksheetSourceNode
|
|
2623
2272
|
]);
|
|
2624
2273
|
// Build attributes - refreshOnLoad should come early per OOXML schema
|
|
@@ -2652,15 +2301,15 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2652
2301
|
for (const row of this._records){
|
|
2653
2302
|
const fieldNodes = [];
|
|
2654
2303
|
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
2655
|
-
const field = this._fields[colIdx];
|
|
2656
2304
|
const value = colIdx < row.length ? row[colIdx] : null;
|
|
2657
2305
|
if (value === null || value === undefined) {
|
|
2658
2306
|
// Missing value
|
|
2659
2307
|
fieldNodes.push(createElement('m', {}, []));
|
|
2660
2308
|
} else if (typeof value === 'string') {
|
|
2661
|
-
// String value - use index into sharedItems
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2309
|
+
// String value - use index into sharedItems via O(1) Map lookup
|
|
2310
|
+
const indexMap = this._sharedItemsIndexMap.get(colIdx);
|
|
2311
|
+
const idx = indexMap?.get(value);
|
|
2312
|
+
if (idx !== undefined) {
|
|
2664
2313
|
fieldNodes.push(createElement('x', {
|
|
2665
2314
|
v: String(idx)
|
|
2666
2315
|
}, []));
|
|
@@ -2764,8 +2413,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2764
2413
|
this._pivotTables = [];
|
|
2765
2414
|
this._pivotCaches = [];
|
|
2766
2415
|
this._nextCacheId = 0;
|
|
2767
|
-
// Date serialization handling
|
|
2768
|
-
this._dateHandling = 'jsDate';
|
|
2769
2416
|
this._sharedStrings = new SharedStrings();
|
|
2770
2417
|
this._styles = Styles.createDefault();
|
|
2771
2418
|
}
|
|
@@ -2830,16 +2477,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2830
2477
|
return this._styles;
|
|
2831
2478
|
}
|
|
2832
2479
|
/**
|
|
2833
|
-
* Get the workbook date handling strategy.
|
|
2834
|
-
*/ get dateHandling() {
|
|
2835
|
-
return this._dateHandling;
|
|
2836
|
-
}
|
|
2837
|
-
/**
|
|
2838
|
-
* Set the workbook date handling strategy.
|
|
2839
|
-
*/ set dateHandling(value) {
|
|
2840
|
-
this._dateHandling = value;
|
|
2841
|
-
}
|
|
2842
|
-
/**
|
|
2843
2480
|
* Get a worksheet by name or index
|
|
2844
2481
|
*/ sheet(nameOrIndex) {
|
|
2845
2482
|
let def;
|
|
@@ -3519,4 +3156,4 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
3519
3156
|
}
|
|
3520
3157
|
}
|
|
3521
3158
|
|
|
3522
|
-
export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet,
|
|
3159
|
+
export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet, parseAddress, parseRange, toAddress, toRange };
|