@niicojs/excel 0.2.4 → 0.2.6
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 +222 -430
- package/dist/index.d.cts +17 -117
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +17 -117
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -427
- package/package.json +1 -1
- package/src/index.ts +1 -15
- package/src/pivot-cache.ts +30 -17
- package/src/pivot-table.ts +52 -137
- package/src/range.ts +2 -15
- package/src/styles.ts +65 -64
- package/src/types.ts +0 -23
- package/src/utils/address.ts +1 -4
- package/src/utils/xml.ts +7 -0
- package/src/workbook.ts +4 -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 = [];
|
|
@@ -1330,6 +1115,129 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1330
1115
|
}
|
|
1331
1116
|
}
|
|
1332
1117
|
|
|
1118
|
+
/**
|
|
1119
|
+
* Excel built-in number format IDs (0-163 are reserved).
|
|
1120
|
+
* These formats don't need to be defined in the numFmts element.
|
|
1121
|
+
*/ const BUILTIN_NUM_FMTS = new Map([
|
|
1122
|
+
[
|
|
1123
|
+
'General',
|
|
1124
|
+
0
|
|
1125
|
+
],
|
|
1126
|
+
[
|
|
1127
|
+
'0',
|
|
1128
|
+
1
|
|
1129
|
+
],
|
|
1130
|
+
[
|
|
1131
|
+
'0.00',
|
|
1132
|
+
2
|
|
1133
|
+
],
|
|
1134
|
+
[
|
|
1135
|
+
'#,##0',
|
|
1136
|
+
3
|
|
1137
|
+
],
|
|
1138
|
+
[
|
|
1139
|
+
'#,##0.00',
|
|
1140
|
+
4
|
|
1141
|
+
],
|
|
1142
|
+
[
|
|
1143
|
+
'0%',
|
|
1144
|
+
9
|
|
1145
|
+
],
|
|
1146
|
+
[
|
|
1147
|
+
'0.00%',
|
|
1148
|
+
10
|
|
1149
|
+
],
|
|
1150
|
+
[
|
|
1151
|
+
'0.00E+00',
|
|
1152
|
+
11
|
|
1153
|
+
],
|
|
1154
|
+
[
|
|
1155
|
+
'# ?/?',
|
|
1156
|
+
12
|
|
1157
|
+
],
|
|
1158
|
+
[
|
|
1159
|
+
'# ??/??',
|
|
1160
|
+
13
|
|
1161
|
+
],
|
|
1162
|
+
[
|
|
1163
|
+
'mm-dd-yy',
|
|
1164
|
+
14
|
|
1165
|
+
],
|
|
1166
|
+
[
|
|
1167
|
+
'd-mmm-yy',
|
|
1168
|
+
15
|
|
1169
|
+
],
|
|
1170
|
+
[
|
|
1171
|
+
'd-mmm',
|
|
1172
|
+
16
|
|
1173
|
+
],
|
|
1174
|
+
[
|
|
1175
|
+
'mmm-yy',
|
|
1176
|
+
17
|
|
1177
|
+
],
|
|
1178
|
+
[
|
|
1179
|
+
'h:mm AM/PM',
|
|
1180
|
+
18
|
|
1181
|
+
],
|
|
1182
|
+
[
|
|
1183
|
+
'h:mm:ss AM/PM',
|
|
1184
|
+
19
|
|
1185
|
+
],
|
|
1186
|
+
[
|
|
1187
|
+
'h:mm',
|
|
1188
|
+
20
|
|
1189
|
+
],
|
|
1190
|
+
[
|
|
1191
|
+
'h:mm:ss',
|
|
1192
|
+
21
|
|
1193
|
+
],
|
|
1194
|
+
[
|
|
1195
|
+
'm/d/yy h:mm',
|
|
1196
|
+
22
|
|
1197
|
+
],
|
|
1198
|
+
[
|
|
1199
|
+
'#,##0 ;(#,##0)',
|
|
1200
|
+
37
|
|
1201
|
+
],
|
|
1202
|
+
[
|
|
1203
|
+
'#,##0 ;[Red](#,##0)',
|
|
1204
|
+
38
|
|
1205
|
+
],
|
|
1206
|
+
[
|
|
1207
|
+
'#,##0.00;(#,##0.00)',
|
|
1208
|
+
39
|
|
1209
|
+
],
|
|
1210
|
+
[
|
|
1211
|
+
'#,##0.00;[Red](#,##0.00)',
|
|
1212
|
+
40
|
|
1213
|
+
],
|
|
1214
|
+
[
|
|
1215
|
+
'mm:ss',
|
|
1216
|
+
45
|
|
1217
|
+
],
|
|
1218
|
+
[
|
|
1219
|
+
'[h]:mm:ss',
|
|
1220
|
+
46
|
|
1221
|
+
],
|
|
1222
|
+
[
|
|
1223
|
+
'mmss.0',
|
|
1224
|
+
47
|
|
1225
|
+
],
|
|
1226
|
+
[
|
|
1227
|
+
'##0.0E+0',
|
|
1228
|
+
48
|
|
1229
|
+
],
|
|
1230
|
+
[
|
|
1231
|
+
'@',
|
|
1232
|
+
49
|
|
1233
|
+
]
|
|
1234
|
+
]);
|
|
1235
|
+
/**
|
|
1236
|
+
* Reverse lookup: built-in format ID -> format code
|
|
1237
|
+
*/ const BUILTIN_NUM_FMT_CODES = new Map(Array.from(BUILTIN_NUM_FMTS.entries()).map(([code, id])=>[
|
|
1238
|
+
id,
|
|
1239
|
+
code
|
|
1240
|
+
]));
|
|
1333
1241
|
/**
|
|
1334
1242
|
* Normalize a color to ARGB format (8 hex chars).
|
|
1335
1243
|
* Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
|
|
@@ -1349,40 +1257,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1349
1257
|
* Manages the styles (xl/styles.xml)
|
|
1350
1258
|
*/ class Styles {
|
|
1351
1259
|
/**
|
|
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
1260
|
* Parse styles from XML content
|
|
1387
1261
|
*/ static parse(xml) {
|
|
1388
1262
|
const styles = new Styles();
|
|
@@ -1549,16 +1423,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1549
1423
|
/**
|
|
1550
1424
|
* Get a style by index
|
|
1551
1425
|
*/ getStyle(index) {
|
|
1552
|
-
const cached = this._styleObjectCache.get(index);
|
|
1553
|
-
if (cached) return {
|
|
1554
|
-
...cached
|
|
1555
|
-
};
|
|
1556
1426
|
const xf = this._cellXfs[index];
|
|
1557
1427
|
if (!xf) return {};
|
|
1558
1428
|
const font = this._fonts[xf.fontId];
|
|
1559
1429
|
const fill = this._fills[xf.fillId];
|
|
1560
1430
|
const border = this._borders[xf.borderId];
|
|
1561
|
-
|
|
1431
|
+
// Check custom formats first, then fall back to built-in format codes
|
|
1432
|
+
const numFmt = this._numFmts.get(xf.numFmtId) ?? BUILTIN_NUM_FMT_CODES.get(xf.numFmtId);
|
|
1562
1433
|
const style = {};
|
|
1563
1434
|
if (font) {
|
|
1564
1435
|
if (font.bold) style.bold = true;
|
|
@@ -1593,16 +1464,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1593
1464
|
textRotation: xf.alignment.textRotation
|
|
1594
1465
|
};
|
|
1595
1466
|
}
|
|
1596
|
-
this._styleObjectCache.set(index, {
|
|
1597
|
-
...style
|
|
1598
|
-
});
|
|
1599
1467
|
return style;
|
|
1600
1468
|
}
|
|
1601
1469
|
/**
|
|
1602
1470
|
* Create a style and return its index
|
|
1603
1471
|
* Uses caching to deduplicate identical styles
|
|
1604
1472
|
*/ createStyle(style) {
|
|
1605
|
-
const key =
|
|
1473
|
+
const key = JSON.stringify(style);
|
|
1606
1474
|
const cached = this._styleCache.get(key);
|
|
1607
1475
|
if (cached !== undefined) {
|
|
1608
1476
|
return cached;
|
|
@@ -1634,20 +1502,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1634
1502
|
const index = this._cellXfs.length;
|
|
1635
1503
|
this._cellXfs.push(xf);
|
|
1636
1504
|
this._styleCache.set(key, index);
|
|
1637
|
-
this._styleObjectCache.set(index, {
|
|
1638
|
-
...style
|
|
1639
|
-
});
|
|
1640
1505
|
return index;
|
|
1641
1506
|
}
|
|
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
1507
|
_findOrCreateFont(style) {
|
|
1652
1508
|
const font = {
|
|
1653
1509
|
bold: style.bold || false,
|
|
@@ -1705,16 +1561,30 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1705
1561
|
return this._borders.length - 1;
|
|
1706
1562
|
}
|
|
1707
1563
|
_findOrCreateNumFmt(format) {
|
|
1708
|
-
// Check
|
|
1564
|
+
// Check built-in formats first (IDs 0-163)
|
|
1565
|
+
const builtinId = BUILTIN_NUM_FMTS.get(format);
|
|
1566
|
+
if (builtinId !== undefined) {
|
|
1567
|
+
return builtinId;
|
|
1568
|
+
}
|
|
1569
|
+
// Check if already exists in custom formats
|
|
1709
1570
|
for (const [id, code] of this._numFmts){
|
|
1710
1571
|
if (code === format) return id;
|
|
1711
1572
|
}
|
|
1712
|
-
// Create new
|
|
1713
|
-
const
|
|
1573
|
+
// Create new custom format (IDs 164+)
|
|
1574
|
+
const existingIds = Array.from(this._numFmts.keys());
|
|
1575
|
+
const id = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 164;
|
|
1714
1576
|
this._numFmts.set(id, format);
|
|
1715
1577
|
return id;
|
|
1716
1578
|
}
|
|
1717
1579
|
/**
|
|
1580
|
+
* Get or create a number format ID for the given format string.
|
|
1581
|
+
* Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
|
|
1582
|
+
* @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
|
|
1583
|
+
*/ getOrCreateNumFmtId(format) {
|
|
1584
|
+
this._dirty = true;
|
|
1585
|
+
return this._findOrCreateNumFmt(format);
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1718
1588
|
* Check if styles have been modified
|
|
1719
1589
|
*/ get dirty() {
|
|
1720
1590
|
return this._dirty;
|
|
@@ -1883,7 +1753,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1883
1753
|
this._dirty = false;
|
|
1884
1754
|
// Cache for style deduplication
|
|
1885
1755
|
this._styleCache = new Map();
|
|
1886
|
-
this._styleObjectCache = new Map();
|
|
1887
1756
|
}
|
|
1888
1757
|
}
|
|
1889
1758
|
|
|
@@ -1895,7 +1764,7 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1895
1764
|
this._columnFields = [];
|
|
1896
1765
|
this._valueFields = [];
|
|
1897
1766
|
this._filterFields = [];
|
|
1898
|
-
this.
|
|
1767
|
+
this._styles = null;
|
|
1899
1768
|
this._name = name;
|
|
1900
1769
|
this._cache = cache;
|
|
1901
1770
|
this._targetSheet = targetSheet;
|
|
@@ -1930,6 +1799,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1930
1799
|
return this._pivotTableIndex;
|
|
1931
1800
|
}
|
|
1932
1801
|
/**
|
|
1802
|
+
* Set the styles reference for number format resolution
|
|
1803
|
+
* @internal
|
|
1804
|
+
*/ setStyles(styles) {
|
|
1805
|
+
this._styles = styles;
|
|
1806
|
+
return this;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1933
1809
|
* Add a field to the row area
|
|
1934
1810
|
* @param fieldName - Name of the source field (column header)
|
|
1935
1811
|
*/ addRowField(fieldName) {
|
|
@@ -1937,13 +1813,11 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1937
1813
|
if (fieldIndex < 0) {
|
|
1938
1814
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1939
1815
|
}
|
|
1940
|
-
|
|
1816
|
+
this._rowFields.push({
|
|
1941
1817
|
fieldName,
|
|
1942
1818
|
fieldIndex,
|
|
1943
1819
|
axis: 'row'
|
|
1944
|
-
};
|
|
1945
|
-
this._rowFields.push(assignment);
|
|
1946
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1820
|
+
});
|
|
1947
1821
|
return this;
|
|
1948
1822
|
}
|
|
1949
1823
|
/**
|
|
@@ -1954,13 +1828,11 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1954
1828
|
if (fieldIndex < 0) {
|
|
1955
1829
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1956
1830
|
}
|
|
1957
|
-
|
|
1831
|
+
this._columnFields.push({
|
|
1958
1832
|
fieldName,
|
|
1959
1833
|
fieldIndex,
|
|
1960
1834
|
axis: 'column'
|
|
1961
|
-
};
|
|
1962
|
-
this._columnFields.push(assignment);
|
|
1963
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1835
|
+
});
|
|
1964
1836
|
return this;
|
|
1965
1837
|
}
|
|
1966
1838
|
/**
|
|
@@ -1968,21 +1840,21 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1968
1840
|
* @param fieldName - Name of the source field (column header)
|
|
1969
1841
|
* @param aggregation - Aggregation function (sum, count, average, min, max)
|
|
1970
1842
|
* @param displayName - Optional display name (defaults to "Sum of FieldName")
|
|
1971
|
-
|
|
1843
|
+
* @param numberFormat - Optional number format (e.g., '$#,##0.00', '0.00%')
|
|
1844
|
+
*/ addValueField(fieldName, aggregation = 'sum', displayName, numberFormat) {
|
|
1972
1845
|
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
1973
1846
|
if (fieldIndex < 0) {
|
|
1974
1847
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1975
1848
|
}
|
|
1976
1849
|
const defaultName = `${aggregation.charAt(0).toUpperCase() + aggregation.slice(1)} of ${fieldName}`;
|
|
1977
|
-
|
|
1850
|
+
this._valueFields.push({
|
|
1978
1851
|
fieldName,
|
|
1979
1852
|
fieldIndex,
|
|
1980
1853
|
axis: 'value',
|
|
1981
1854
|
aggregation,
|
|
1982
|
-
displayName: displayName || defaultName
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1855
|
+
displayName: displayName || defaultName,
|
|
1856
|
+
numberFormat
|
|
1857
|
+
});
|
|
1986
1858
|
return this;
|
|
1987
1859
|
}
|
|
1988
1860
|
/**
|
|
@@ -1993,44 +1865,11 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1993
1865
|
if (fieldIndex < 0) {
|
|
1994
1866
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1995
1867
|
}
|
|
1996
|
-
|
|
1868
|
+
this._filterFields.push({
|
|
1997
1869
|
fieldName,
|
|
1998
1870
|
fieldIndex,
|
|
1999
1871
|
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;
|
|
1872
|
+
});
|
|
2034
1873
|
return this;
|
|
2035
1874
|
}
|
|
2036
1875
|
/**
|
|
@@ -2137,17 +1976,26 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2137
1976
|
}
|
|
2138
1977
|
// Data fields (values)
|
|
2139
1978
|
if (this._valueFields.length > 0) {
|
|
2140
|
-
const dataFieldNodes = this._valueFields.map((f)=>
|
|
1979
|
+
const dataFieldNodes = this._valueFields.map((f)=>{
|
|
1980
|
+
const attrs = {
|
|
2141
1981
|
name: f.displayName || f.fieldName,
|
|
2142
1982
|
fld: String(f.fieldIndex),
|
|
2143
1983
|
baseField: '0',
|
|
2144
1984
|
baseItem: '0',
|
|
2145
1985
|
subtotal: f.aggregation || 'sum'
|
|
2146
|
-
}
|
|
1986
|
+
};
|
|
1987
|
+
// Add numFmtId if format specified and styles available
|
|
1988
|
+
if (f.numberFormat && this._styles) {
|
|
1989
|
+
attrs.numFmtId = String(this._styles.getOrCreateNumFmtId(f.numberFormat));
|
|
1990
|
+
}
|
|
1991
|
+
return createElement('dataField', attrs, []);
|
|
1992
|
+
});
|
|
2147
1993
|
children.push(createElement('dataFields', {
|
|
2148
1994
|
count: String(dataFieldNodes.length)
|
|
2149
1995
|
}, dataFieldNodes));
|
|
2150
1996
|
}
|
|
1997
|
+
// Check if any value field has a number format
|
|
1998
|
+
const hasNumberFormats = this._valueFields.some((f)=>f.numberFormat);
|
|
2151
1999
|
// Pivot table style
|
|
2152
2000
|
children.push(createElement('pivotTableStyleInfo', {
|
|
2153
2001
|
name: 'PivotStyleMedium9',
|
|
@@ -2162,7 +2010,7 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2162
2010
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
2163
2011
|
name: this._name,
|
|
2164
2012
|
cacheId: String(this._cache.cacheId),
|
|
2165
|
-
applyNumberFormats: '0',
|
|
2013
|
+
applyNumberFormats: hasNumberFormats ? '1' : '0',
|
|
2166
2014
|
applyBorderFormats: '0',
|
|
2167
2015
|
applyFontFormats: '0',
|
|
2168
2016
|
applyPatternFormats: '0',
|
|
@@ -2195,28 +2043,17 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2195
2043
|
const colField = this._columnFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2196
2044
|
const filterField = this._filterFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2197
2045
|
const valueField = this._valueFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2198
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2199
2046
|
if (rowField) {
|
|
2200
2047
|
attrs.axis = 'axisRow';
|
|
2201
2048
|
attrs.showAll = '0';
|
|
2202
|
-
if (assignment?.sortOrder) {
|
|
2203
|
-
attrs.sortType = 'ascending';
|
|
2204
|
-
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
2205
|
-
}
|
|
2206
2049
|
// Add items for shared values
|
|
2207
2050
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2208
2051
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2209
2052
|
const itemNodes = [];
|
|
2210
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2211
2053
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2212
|
-
|
|
2213
|
-
const itemAttrs = {
|
|
2054
|
+
itemNodes.push(createElement('item', {
|
|
2214
2055
|
x: String(i)
|
|
2215
|
-
};
|
|
2216
|
-
if (!shouldInclude) {
|
|
2217
|
-
itemAttrs.h = '1';
|
|
2218
|
-
}
|
|
2219
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
2056
|
+
}, []));
|
|
2220
2057
|
}
|
|
2221
2058
|
// Add default subtotal item
|
|
2222
2059
|
itemNodes.push(createElement('item', {
|
|
@@ -2229,23 +2066,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2229
2066
|
} else if (colField) {
|
|
2230
2067
|
attrs.axis = 'axisCol';
|
|
2231
2068
|
attrs.showAll = '0';
|
|
2232
|
-
if (assignment?.sortOrder) {
|
|
2233
|
-
attrs.sortType = 'ascending';
|
|
2234
|
-
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
2235
|
-
}
|
|
2236
2069
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2237
2070
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2238
2071
|
const itemNodes = [];
|
|
2239
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2240
2072
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2241
|
-
|
|
2242
|
-
const itemAttrs = {
|
|
2073
|
+
itemNodes.push(createElement('item', {
|
|
2243
2074
|
x: String(i)
|
|
2244
|
-
};
|
|
2245
|
-
if (!shouldInclude) {
|
|
2246
|
-
itemAttrs.h = '1';
|
|
2247
|
-
}
|
|
2248
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
2075
|
+
}, []));
|
|
2249
2076
|
}
|
|
2250
2077
|
itemNodes.push(createElement('item', {
|
|
2251
2078
|
t: 'default'
|
|
@@ -2260,16 +2087,10 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2260
2087
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2261
2088
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2262
2089
|
const itemNodes = [];
|
|
2263
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2264
2090
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2265
|
-
|
|
2266
|
-
const itemAttrs = {
|
|
2091
|
+
itemNodes.push(createElement('item', {
|
|
2267
2092
|
x: String(i)
|
|
2268
|
-
};
|
|
2269
|
-
if (!shouldInclude) {
|
|
2270
|
-
itemAttrs.h = '1';
|
|
2271
|
-
}
|
|
2272
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
2093
|
+
}, []));
|
|
2273
2094
|
}
|
|
2274
2095
|
itemNodes.push(createElement('item', {
|
|
2275
2096
|
t: 'default'
|
|
@@ -2286,31 +2107,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2286
2107
|
}
|
|
2287
2108
|
return createElement('pivotField', attrs, children);
|
|
2288
2109
|
}
|
|
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
2110
|
/**
|
|
2315
2111
|
* Build row items based on unique values in row fields
|
|
2316
2112
|
*/ _buildRowItems() {
|
|
@@ -2461,7 +2257,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2461
2257
|
this._records = [];
|
|
2462
2258
|
this._recordCount = 0;
|
|
2463
2259
|
this._refreshOnLoad = true; // Default to true
|
|
2464
|
-
|
|
2260
|
+
// Optimized lookup: Map<fieldIndex, Map<stringValue, sharedItemsIndex>>
|
|
2261
|
+
this._sharedItemsIndexMap = new Map();
|
|
2465
2262
|
this._cacheId = cacheId;
|
|
2466
2263
|
this._sourceSheet = sourceSheet;
|
|
2467
2264
|
this._sourceRange = sourceRange;
|
|
@@ -2522,6 +2319,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2522
2319
|
minValue: undefined,
|
|
2523
2320
|
maxValue: undefined
|
|
2524
2321
|
}));
|
|
2322
|
+
// Use Sets for O(1) unique value collection during analysis
|
|
2323
|
+
const sharedItemsSets = this._fields.map(()=>new Set());
|
|
2525
2324
|
// Analyze data to determine field types and collect unique values
|
|
2526
2325
|
for (const row of data){
|
|
2527
2326
|
for(let colIdx = 0; colIdx < row.length && colIdx < this._fields.length; colIdx++){
|
|
@@ -2532,9 +2331,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2532
2331
|
}
|
|
2533
2332
|
if (typeof value === 'string') {
|
|
2534
2333
|
field.isNumeric = false;
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
}
|
|
2334
|
+
// O(1) Set.add instead of O(n) Array.includes + push
|
|
2335
|
+
sharedItemsSets[colIdx].add(value);
|
|
2538
2336
|
} else if (typeof value === 'number') {
|
|
2539
2337
|
if (field.minValue === undefined || value < field.minValue) {
|
|
2540
2338
|
field.minValue = value;
|
|
@@ -2550,8 +2348,22 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2550
2348
|
}
|
|
2551
2349
|
}
|
|
2552
2350
|
}
|
|
2553
|
-
//
|
|
2554
|
-
this.
|
|
2351
|
+
// Convert Sets to arrays and build reverse index Maps for O(1) lookup during XML generation
|
|
2352
|
+
this._sharedItemsIndexMap.clear();
|
|
2353
|
+
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
2354
|
+
const field = this._fields[colIdx];
|
|
2355
|
+
const set = sharedItemsSets[colIdx];
|
|
2356
|
+
// Convert Set to array (maintains insertion order in ES6+)
|
|
2357
|
+
field.sharedItems = Array.from(set);
|
|
2358
|
+
// Build reverse lookup Map: value -> index
|
|
2359
|
+
if (field.sharedItems.length > 0) {
|
|
2360
|
+
const indexMap = new Map();
|
|
2361
|
+
for(let i = 0; i < field.sharedItems.length; i++){
|
|
2362
|
+
indexMap.set(field.sharedItems[i], i);
|
|
2363
|
+
}
|
|
2364
|
+
this._sharedItemsIndexMap.set(colIdx, indexMap);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2555
2367
|
// Store records
|
|
2556
2368
|
this._records = data;
|
|
2557
2369
|
}
|
|
@@ -2580,8 +2392,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2580
2392
|
v: item
|
|
2581
2393
|
}, []));
|
|
2582
2394
|
}
|
|
2583
|
-
} else if (field.isDate) {
|
|
2584
|
-
sharedItemsAttrs.containsDate = '1';
|
|
2585
2395
|
} else if (field.isNumeric) {
|
|
2586
2396
|
// Numeric field - use "0"/"1" for boolean attributes as Excel expects
|
|
2587
2397
|
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
@@ -2612,13 +2422,9 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2612
2422
|
ref: this._sourceRange,
|
|
2613
2423
|
sheet: this._sourceSheet
|
|
2614
2424
|
}, []);
|
|
2615
|
-
const
|
|
2425
|
+
const cacheSourceNode = createElement('cacheSource', {
|
|
2616
2426
|
type: 'worksheet'
|
|
2617
|
-
}
|
|
2618
|
-
if (this._dateGrouping) {
|
|
2619
|
-
cacheSourceAttrs.grouping = '1';
|
|
2620
|
-
}
|
|
2621
|
-
const cacheSourceNode = createElement('cacheSource', cacheSourceAttrs, [
|
|
2427
|
+
}, [
|
|
2622
2428
|
worksheetSourceNode
|
|
2623
2429
|
]);
|
|
2624
2430
|
// Build attributes - refreshOnLoad should come early per OOXML schema
|
|
@@ -2652,15 +2458,15 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2652
2458
|
for (const row of this._records){
|
|
2653
2459
|
const fieldNodes = [];
|
|
2654
2460
|
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
2655
|
-
const field = this._fields[colIdx];
|
|
2656
2461
|
const value = colIdx < row.length ? row[colIdx] : null;
|
|
2657
2462
|
if (value === null || value === undefined) {
|
|
2658
2463
|
// Missing value
|
|
2659
2464
|
fieldNodes.push(createElement('m', {}, []));
|
|
2660
2465
|
} else if (typeof value === 'string') {
|
|
2661
|
-
// String value - use index into sharedItems
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2466
|
+
// String value - use index into sharedItems via O(1) Map lookup
|
|
2467
|
+
const indexMap = this._sharedItemsIndexMap.get(colIdx);
|
|
2468
|
+
const idx = indexMap?.get(value);
|
|
2469
|
+
if (idx !== undefined) {
|
|
2664
2470
|
fieldNodes.push(createElement('x', {
|
|
2665
2471
|
v: String(idx)
|
|
2666
2472
|
}, []));
|
|
@@ -2764,8 +2570,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2764
2570
|
this._pivotTables = [];
|
|
2765
2571
|
this._pivotCaches = [];
|
|
2766
2572
|
this._nextCacheId = 0;
|
|
2767
|
-
// Date serialization handling
|
|
2768
|
-
this._dateHandling = 'jsDate';
|
|
2769
2573
|
this._sharedStrings = new SharedStrings();
|
|
2770
2574
|
this._styles = Styles.createDefault();
|
|
2771
2575
|
}
|
|
@@ -2830,16 +2634,6 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2830
2634
|
return this._styles;
|
|
2831
2635
|
}
|
|
2832
2636
|
/**
|
|
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
2637
|
* Get a worksheet by name or index
|
|
2844
2638
|
*/ sheet(nameOrIndex) {
|
|
2845
2639
|
let def;
|
|
@@ -3150,6 +2944,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
3150
2944
|
// Create pivot table
|
|
3151
2945
|
const pivotTableIndex = this._pivotTables.length + 1;
|
|
3152
2946
|
const pivotTable = new PivotTable(config.name, cache, targetSheet, targetCell, targetAddr.row + 1, targetAddr.col, pivotTableIndex);
|
|
2947
|
+
// Set styles reference for number format resolution
|
|
2948
|
+
pivotTable.setStyles(this._styles);
|
|
3153
2949
|
this._pivotTables.push(pivotTable);
|
|
3154
2950
|
return pivotTable;
|
|
3155
2951
|
}
|
|
@@ -3519,4 +3315,4 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
3519
3315
|
}
|
|
3520
3316
|
}
|
|
3521
3317
|
|
|
3522
|
-
export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet,
|
|
3318
|
+
export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet, parseAddress, parseRange, toAddress, toRange };
|