@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.cjs
CHANGED
|
@@ -40,10 +40,8 @@ var fflate = require('fflate');
|
|
|
40
40
|
if (!match) {
|
|
41
41
|
throw new Error(`Invalid cell address: ${address}`);
|
|
42
42
|
}
|
|
43
|
-
const rowNumber = +match[2];
|
|
44
|
-
if (rowNumber <= 0) throw new Error(`Invalid cell address: ${address}`);
|
|
45
43
|
const col = letterToCol(match[1].toUpperCase());
|
|
46
|
-
const row =
|
|
44
|
+
const row = parseInt(match[2], 10) - 1; // Convert to 0-based
|
|
47
45
|
return {
|
|
48
46
|
row,
|
|
49
47
|
col
|
|
@@ -105,12 +103,6 @@ var fflate = require('fflate');
|
|
|
105
103
|
}
|
|
106
104
|
};
|
|
107
105
|
};
|
|
108
|
-
/**
|
|
109
|
-
* Checks if an address is within a range
|
|
110
|
-
*/ const isInRange = (addr, range)=>{
|
|
111
|
-
const norm = normalizeRange(range);
|
|
112
|
-
return addr.row >= norm.start.row && addr.row <= norm.end.row && addr.col >= norm.start.col && addr.col <= norm.end.col;
|
|
113
|
-
};
|
|
114
106
|
|
|
115
107
|
// Excel epoch: December 30, 1899 (accounting for the 1900 leap year bug)
|
|
116
108
|
const EXCEL_EPOCH = new Date(Date.UTC(1899, 11, 30));
|
|
@@ -443,23 +435,12 @@ _computedKey = Symbol.iterator;
|
|
|
443
435
|
/**
|
|
444
436
|
* Get all values in the range as a 2D array
|
|
445
437
|
*/ get values() {
|
|
446
|
-
return this.getValues();
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Get all values in the range as a 2D array with options
|
|
450
|
-
*/ getValues(options = {}) {
|
|
451
|
-
const { createMissing = true } = options;
|
|
452
438
|
const result = [];
|
|
453
439
|
for(let r = this._range.start.row; r <= this._range.end.row; r++){
|
|
454
440
|
const row = [];
|
|
455
441
|
for(let c = this._range.start.col; c <= this._range.end.col; c++){
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
row.push(cell.value);
|
|
459
|
-
} else {
|
|
460
|
-
const cell = this._worksheet.getCellIfExists(r, c);
|
|
461
|
-
row.push(cell?.value ?? null);
|
|
462
|
-
}
|
|
442
|
+
const cell = this._worksheet.cell(r, c);
|
|
443
|
+
row.push(cell.value);
|
|
463
444
|
}
|
|
464
445
|
result.push(row);
|
|
465
446
|
}
|
|
@@ -632,11 +613,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
632
613
|
this._dirty = false;
|
|
633
614
|
this._mergedCells = new Set();
|
|
634
615
|
this._sheetData = [];
|
|
635
|
-
this._columnWidths = new Map();
|
|
636
|
-
this._rowHeights = new Map();
|
|
637
|
-
this._frozenPane = null;
|
|
638
|
-
this._dataBoundsCache = null;
|
|
639
|
-
this._boundsDirty = true;
|
|
640
616
|
this._workbook = workbook;
|
|
641
617
|
this._name = name;
|
|
642
618
|
}
|
|
@@ -663,49 +639,12 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
663
639
|
const worksheet = findElement(this._xmlNodes, 'worksheet');
|
|
664
640
|
if (!worksheet) return;
|
|
665
641
|
const worksheetChildren = getChildren(worksheet, 'worksheet');
|
|
666
|
-
// Parse sheet views (freeze panes)
|
|
667
|
-
const sheetViews = findElement(worksheetChildren, 'sheetViews');
|
|
668
|
-
if (sheetViews) {
|
|
669
|
-
const viewChildren = getChildren(sheetViews, 'sheetViews');
|
|
670
|
-
const sheetView = findElement(viewChildren, 'sheetView');
|
|
671
|
-
if (sheetView) {
|
|
672
|
-
const sheetViewChildren = getChildren(sheetView, 'sheetView');
|
|
673
|
-
const pane = findElement(sheetViewChildren, 'pane');
|
|
674
|
-
if (pane && getAttr(pane, 'state') === 'frozen') {
|
|
675
|
-
const xSplit = parseInt(getAttr(pane, 'xSplit') || '0', 10);
|
|
676
|
-
const ySplit = parseInt(getAttr(pane, 'ySplit') || '0', 10);
|
|
677
|
-
if (xSplit > 0 || ySplit > 0) {
|
|
678
|
-
this._frozenPane = {
|
|
679
|
-
row: ySplit,
|
|
680
|
-
col: xSplit
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
642
|
// Parse sheet data (cells)
|
|
687
643
|
const sheetData = findElement(worksheetChildren, 'sheetData');
|
|
688
644
|
if (sheetData) {
|
|
689
645
|
this._sheetData = getChildren(sheetData, 'sheetData');
|
|
690
646
|
this._parseSheetData(this._sheetData);
|
|
691
647
|
}
|
|
692
|
-
// Parse column widths
|
|
693
|
-
const cols = findElement(worksheetChildren, 'cols');
|
|
694
|
-
if (cols) {
|
|
695
|
-
const colChildren = getChildren(cols, 'cols');
|
|
696
|
-
for (const col of colChildren){
|
|
697
|
-
if (!('col' in col)) continue;
|
|
698
|
-
const min = parseInt(getAttr(col, 'min') || '0', 10);
|
|
699
|
-
const max = parseInt(getAttr(col, 'max') || '0', 10);
|
|
700
|
-
const width = parseFloat(getAttr(col, 'width') || '0');
|
|
701
|
-
if (!Number.isFinite(width) || width <= 0) continue;
|
|
702
|
-
if (min > 0 && max > 0) {
|
|
703
|
-
for(let idx = min; idx <= max; idx++){
|
|
704
|
-
this._columnWidths.set(idx - 1, width);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
648
|
// Parse merged cells
|
|
710
649
|
const mergeCells = findElement(worksheetChildren, 'mergeCells');
|
|
711
650
|
if (mergeCells) {
|
|
@@ -725,11 +664,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
725
664
|
*/ _parseSheetData(rows) {
|
|
726
665
|
for (const rowNode of rows){
|
|
727
666
|
if (!('row' in rowNode)) continue;
|
|
728
|
-
const rowIndex = parseInt(getAttr(rowNode, 'r') || '0', 10) - 1;
|
|
729
|
-
const rowHeight = parseFloat(getAttr(rowNode, 'ht') || '0');
|
|
730
|
-
if (rowIndex >= 0 && Number.isFinite(rowHeight) && rowHeight > 0) {
|
|
731
|
-
this._rowHeights.set(rowIndex, rowHeight);
|
|
732
|
-
}
|
|
733
667
|
const rowChildren = getChildren(rowNode, 'row');
|
|
734
668
|
for (const cellNode of rowChildren){
|
|
735
669
|
if (!('c' in cellNode)) continue;
|
|
@@ -741,7 +675,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
741
675
|
this._cells.set(ref, cell);
|
|
742
676
|
}
|
|
743
677
|
}
|
|
744
|
-
this._boundsDirty = true;
|
|
745
678
|
}
|
|
746
679
|
/**
|
|
747
680
|
* Parse a cell XML node to CellData
|
|
@@ -828,17 +761,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
828
761
|
if (!cell) {
|
|
829
762
|
cell = new Cell(this, row, c);
|
|
830
763
|
this._cells.set(address, cell);
|
|
831
|
-
this._boundsDirty = true;
|
|
832
764
|
}
|
|
833
765
|
return cell;
|
|
834
766
|
}
|
|
835
|
-
/**
|
|
836
|
-
* Get an existing cell without creating it.
|
|
837
|
-
*/ getCellIfExists(rowOrAddress, col) {
|
|
838
|
-
const { row, col: c } = parseCellRef(rowOrAddress, col);
|
|
839
|
-
const address = toAddress(row, c);
|
|
840
|
-
return this._cells.get(address);
|
|
841
|
-
}
|
|
842
767
|
range(startRowOrRange, startCol, endRow, endCol) {
|
|
843
768
|
let rangeAddr;
|
|
844
769
|
if (typeof startRowOrRange === 'string') {
|
|
@@ -898,65 +823,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
898
823
|
return this._cells;
|
|
899
824
|
}
|
|
900
825
|
/**
|
|
901
|
-
* Set a column width (0-based index or column letter)
|
|
902
|
-
*/ setColumnWidth(col, width) {
|
|
903
|
-
if (!Number.isFinite(width) || width <= 0) {
|
|
904
|
-
throw new Error('Column width must be a positive number');
|
|
905
|
-
}
|
|
906
|
-
const colIndex = typeof col === 'number' ? col : letterToCol(col);
|
|
907
|
-
if (colIndex < 0) {
|
|
908
|
-
throw new Error(`Invalid column: ${col}`);
|
|
909
|
-
}
|
|
910
|
-
this._columnWidths.set(colIndex, width);
|
|
911
|
-
this._dirty = true;
|
|
912
|
-
}
|
|
913
|
-
/**
|
|
914
|
-
* Get a column width if set
|
|
915
|
-
*/ getColumnWidth(col) {
|
|
916
|
-
const colIndex = typeof col === 'number' ? col : letterToCol(col);
|
|
917
|
-
return this._columnWidths.get(colIndex);
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Set a row height (0-based index)
|
|
921
|
-
*/ setRowHeight(row, height) {
|
|
922
|
-
if (!Number.isFinite(height) || height <= 0) {
|
|
923
|
-
throw new Error('Row height must be a positive number');
|
|
924
|
-
}
|
|
925
|
-
if (row < 0) {
|
|
926
|
-
throw new Error('Row index must be >= 0');
|
|
927
|
-
}
|
|
928
|
-
this._rowHeights.set(row, height);
|
|
929
|
-
this._dirty = true;
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Get a row height if set
|
|
933
|
-
*/ getRowHeight(row) {
|
|
934
|
-
return this._rowHeights.get(row);
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* Freeze panes at a given row/column split (counts from top-left)
|
|
938
|
-
*/ freezePane(rowSplit, colSplit) {
|
|
939
|
-
if (rowSplit < 0 || colSplit < 0) {
|
|
940
|
-
throw new Error('Freeze pane splits must be >= 0');
|
|
941
|
-
}
|
|
942
|
-
if (rowSplit === 0 && colSplit === 0) {
|
|
943
|
-
this._frozenPane = null;
|
|
944
|
-
} else {
|
|
945
|
-
this._frozenPane = {
|
|
946
|
-
row: rowSplit,
|
|
947
|
-
col: colSplit
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
this._dirty = true;
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Get current frozen pane configuration
|
|
954
|
-
*/ getFrozenPane() {
|
|
955
|
-
return this._frozenPane ? {
|
|
956
|
-
...this._frozenPane
|
|
957
|
-
} : null;
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
826
|
* Convert sheet data to an array of JSON objects.
|
|
961
827
|
*
|
|
962
828
|
* @param config - Configuration options
|
|
@@ -974,7 +840,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
974
840
|
* const data = sheet.toJson({ startRow: 2, startCol: 1 });
|
|
975
841
|
* ```
|
|
976
842
|
*/ toJson(config = {}) {
|
|
977
|
-
const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true
|
|
843
|
+
const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true } = config;
|
|
978
844
|
// Get the bounds of data in the sheet
|
|
979
845
|
const bounds = this._getDataBounds();
|
|
980
846
|
if (!bounds) {
|
|
@@ -1007,10 +873,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1007
873
|
for(let colOffset = 0; colOffset < fieldNames.length; colOffset++){
|
|
1008
874
|
const col = startCol + colOffset;
|
|
1009
875
|
const cell = this._cells.get(toAddress(row, col));
|
|
1010
|
-
|
|
1011
|
-
if (value instanceof Date) {
|
|
1012
|
-
value = this._serializeDate(value, dateHandling, cell);
|
|
1013
|
-
}
|
|
876
|
+
const value = cell?.value ?? null;
|
|
1014
877
|
if (value !== null) {
|
|
1015
878
|
hasData = true;
|
|
1016
879
|
}
|
|
@@ -1027,24 +890,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1027
890
|
}
|
|
1028
891
|
return result;
|
|
1029
892
|
}
|
|
1030
|
-
_serializeDate(value, dateHandling, cell) {
|
|
1031
|
-
if (dateHandling === 'excelSerial') {
|
|
1032
|
-
return cell?._jsDateToExcel(value) ?? value;
|
|
1033
|
-
}
|
|
1034
|
-
if (dateHandling === 'isoString') {
|
|
1035
|
-
return value.toISOString();
|
|
1036
|
-
}
|
|
1037
|
-
return value;
|
|
1038
|
-
}
|
|
1039
893
|
/**
|
|
1040
894
|
* Get the bounds of data in the sheet (min/max row and column with data)
|
|
1041
895
|
*/ _getDataBounds() {
|
|
1042
|
-
if (!this._boundsDirty && this._dataBoundsCache) {
|
|
1043
|
-
return this._dataBoundsCache;
|
|
1044
|
-
}
|
|
1045
896
|
if (this._cells.size === 0) {
|
|
1046
|
-
this._dataBoundsCache = null;
|
|
1047
|
-
this._boundsDirty = false;
|
|
1048
897
|
return null;
|
|
1049
898
|
}
|
|
1050
899
|
let minRow = Infinity;
|
|
@@ -1060,18 +909,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1060
909
|
}
|
|
1061
910
|
}
|
|
1062
911
|
if (minRow === Infinity) {
|
|
1063
|
-
this._dataBoundsCache = null;
|
|
1064
|
-
this._boundsDirty = false;
|
|
1065
912
|
return null;
|
|
1066
913
|
}
|
|
1067
|
-
|
|
914
|
+
return {
|
|
1068
915
|
minRow,
|
|
1069
916
|
maxRow,
|
|
1070
917
|
minCol,
|
|
1071
918
|
maxCol
|
|
1072
919
|
};
|
|
1073
|
-
this._boundsDirty = false;
|
|
1074
|
-
return this._dataBoundsCache;
|
|
1075
920
|
}
|
|
1076
921
|
/**
|
|
1077
922
|
* Generate XML for this worksheet
|
|
@@ -1085,11 +930,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1085
930
|
}
|
|
1086
931
|
rowMap.get(row).push(cell);
|
|
1087
932
|
}
|
|
1088
|
-
for (const rowIdx of this._rowHeights.keys()){
|
|
1089
|
-
if (!rowMap.has(rowIdx)) {
|
|
1090
|
-
rowMap.set(rowIdx, []);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
933
|
// Sort rows and cells
|
|
1094
934
|
const sortedRows = Array.from(rowMap.entries()).sort((a, b)=>a[0] - b[0]);
|
|
1095
935
|
const rowNodes = [];
|
|
@@ -1100,71 +940,16 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1100
940
|
const cellNode = this._buildCellNode(cell);
|
|
1101
941
|
cellNodes.push(cellNode);
|
|
1102
942
|
}
|
|
1103
|
-
const
|
|
943
|
+
const rowNode = createElement('row', {
|
|
1104
944
|
r: String(rowIdx + 1)
|
|
1105
|
-
};
|
|
1106
|
-
const rowHeight = this._rowHeights.get(rowIdx);
|
|
1107
|
-
if (rowHeight !== undefined) {
|
|
1108
|
-
rowAttrs.ht = String(rowHeight);
|
|
1109
|
-
rowAttrs.customHeight = '1';
|
|
1110
|
-
}
|
|
1111
|
-
const rowNode = createElement('row', rowAttrs, cellNodes);
|
|
945
|
+
}, cellNodes);
|
|
1112
946
|
rowNodes.push(rowNode);
|
|
1113
947
|
}
|
|
1114
948
|
const sheetDataNode = createElement('sheetData', {}, rowNodes);
|
|
1115
949
|
// Build worksheet structure
|
|
1116
|
-
const worksheetChildren = [
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const paneAttrs = {
|
|
1120
|
-
state: 'frozen'
|
|
1121
|
-
};
|
|
1122
|
-
const topLeftCell = toAddress(this._frozenPane.row, this._frozenPane.col);
|
|
1123
|
-
paneAttrs.topLeftCell = topLeftCell;
|
|
1124
|
-
if (this._frozenPane.col > 0) {
|
|
1125
|
-
paneAttrs.xSplit = String(this._frozenPane.col);
|
|
1126
|
-
}
|
|
1127
|
-
if (this._frozenPane.row > 0) {
|
|
1128
|
-
paneAttrs.ySplit = String(this._frozenPane.row);
|
|
1129
|
-
}
|
|
1130
|
-
let activePane = 'bottomRight';
|
|
1131
|
-
if (this._frozenPane.row > 0 && this._frozenPane.col === 0) {
|
|
1132
|
-
activePane = 'bottomLeft';
|
|
1133
|
-
} else if (this._frozenPane.row === 0 && this._frozenPane.col > 0) {
|
|
1134
|
-
activePane = 'topRight';
|
|
1135
|
-
}
|
|
1136
|
-
paneAttrs.activePane = activePane;
|
|
1137
|
-
const paneNode = createElement('pane', paneAttrs, []);
|
|
1138
|
-
const selectionNode = createElement('selection', {
|
|
1139
|
-
pane: activePane,
|
|
1140
|
-
activeCell: topLeftCell,
|
|
1141
|
-
sqref: topLeftCell
|
|
1142
|
-
}, []);
|
|
1143
|
-
const sheetViewNode = createElement('sheetView', {
|
|
1144
|
-
workbookViewId: '0'
|
|
1145
|
-
}, [
|
|
1146
|
-
paneNode,
|
|
1147
|
-
selectionNode
|
|
1148
|
-
]);
|
|
1149
|
-
worksheetChildren.push(createElement('sheetViews', {}, [
|
|
1150
|
-
sheetViewNode
|
|
1151
|
-
]));
|
|
1152
|
-
}
|
|
1153
|
-
// Column widths
|
|
1154
|
-
if (this._columnWidths.size > 0) {
|
|
1155
|
-
const colNodes = [];
|
|
1156
|
-
const entries = Array.from(this._columnWidths.entries()).sort((a, b)=>a[0] - b[0]);
|
|
1157
|
-
for (const [colIndex, width] of entries){
|
|
1158
|
-
colNodes.push(createElement('col', {
|
|
1159
|
-
min: String(colIndex + 1),
|
|
1160
|
-
max: String(colIndex + 1),
|
|
1161
|
-
width: String(width),
|
|
1162
|
-
customWidth: '1'
|
|
1163
|
-
}, []));
|
|
1164
|
-
}
|
|
1165
|
-
worksheetChildren.push(createElement('cols', {}, colNodes));
|
|
1166
|
-
}
|
|
1167
|
-
worksheetChildren.push(sheetDataNode);
|
|
950
|
+
const worksheetChildren = [
|
|
951
|
+
sheetDataNode
|
|
952
|
+
];
|
|
1168
953
|
// Add merged cells if any
|
|
1169
954
|
if (this._mergedCells.size > 0) {
|
|
1170
955
|
const mergeCellNodes = [];
|
|
@@ -1332,6 +1117,129 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1332
1117
|
}
|
|
1333
1118
|
}
|
|
1334
1119
|
|
|
1120
|
+
/**
|
|
1121
|
+
* Excel built-in number format IDs (0-163 are reserved).
|
|
1122
|
+
* These formats don't need to be defined in the numFmts element.
|
|
1123
|
+
*/ const BUILTIN_NUM_FMTS = new Map([
|
|
1124
|
+
[
|
|
1125
|
+
'General',
|
|
1126
|
+
0
|
|
1127
|
+
],
|
|
1128
|
+
[
|
|
1129
|
+
'0',
|
|
1130
|
+
1
|
|
1131
|
+
],
|
|
1132
|
+
[
|
|
1133
|
+
'0.00',
|
|
1134
|
+
2
|
|
1135
|
+
],
|
|
1136
|
+
[
|
|
1137
|
+
'#,##0',
|
|
1138
|
+
3
|
|
1139
|
+
],
|
|
1140
|
+
[
|
|
1141
|
+
'#,##0.00',
|
|
1142
|
+
4
|
|
1143
|
+
],
|
|
1144
|
+
[
|
|
1145
|
+
'0%',
|
|
1146
|
+
9
|
|
1147
|
+
],
|
|
1148
|
+
[
|
|
1149
|
+
'0.00%',
|
|
1150
|
+
10
|
|
1151
|
+
],
|
|
1152
|
+
[
|
|
1153
|
+
'0.00E+00',
|
|
1154
|
+
11
|
|
1155
|
+
],
|
|
1156
|
+
[
|
|
1157
|
+
'# ?/?',
|
|
1158
|
+
12
|
|
1159
|
+
],
|
|
1160
|
+
[
|
|
1161
|
+
'# ??/??',
|
|
1162
|
+
13
|
|
1163
|
+
],
|
|
1164
|
+
[
|
|
1165
|
+
'mm-dd-yy',
|
|
1166
|
+
14
|
|
1167
|
+
],
|
|
1168
|
+
[
|
|
1169
|
+
'd-mmm-yy',
|
|
1170
|
+
15
|
|
1171
|
+
],
|
|
1172
|
+
[
|
|
1173
|
+
'd-mmm',
|
|
1174
|
+
16
|
|
1175
|
+
],
|
|
1176
|
+
[
|
|
1177
|
+
'mmm-yy',
|
|
1178
|
+
17
|
|
1179
|
+
],
|
|
1180
|
+
[
|
|
1181
|
+
'h:mm AM/PM',
|
|
1182
|
+
18
|
|
1183
|
+
],
|
|
1184
|
+
[
|
|
1185
|
+
'h:mm:ss AM/PM',
|
|
1186
|
+
19
|
|
1187
|
+
],
|
|
1188
|
+
[
|
|
1189
|
+
'h:mm',
|
|
1190
|
+
20
|
|
1191
|
+
],
|
|
1192
|
+
[
|
|
1193
|
+
'h:mm:ss',
|
|
1194
|
+
21
|
|
1195
|
+
],
|
|
1196
|
+
[
|
|
1197
|
+
'm/d/yy h:mm',
|
|
1198
|
+
22
|
|
1199
|
+
],
|
|
1200
|
+
[
|
|
1201
|
+
'#,##0 ;(#,##0)',
|
|
1202
|
+
37
|
|
1203
|
+
],
|
|
1204
|
+
[
|
|
1205
|
+
'#,##0 ;[Red](#,##0)',
|
|
1206
|
+
38
|
|
1207
|
+
],
|
|
1208
|
+
[
|
|
1209
|
+
'#,##0.00;(#,##0.00)',
|
|
1210
|
+
39
|
|
1211
|
+
],
|
|
1212
|
+
[
|
|
1213
|
+
'#,##0.00;[Red](#,##0.00)',
|
|
1214
|
+
40
|
|
1215
|
+
],
|
|
1216
|
+
[
|
|
1217
|
+
'mm:ss',
|
|
1218
|
+
45
|
|
1219
|
+
],
|
|
1220
|
+
[
|
|
1221
|
+
'[h]:mm:ss',
|
|
1222
|
+
46
|
|
1223
|
+
],
|
|
1224
|
+
[
|
|
1225
|
+
'mmss.0',
|
|
1226
|
+
47
|
|
1227
|
+
],
|
|
1228
|
+
[
|
|
1229
|
+
'##0.0E+0',
|
|
1230
|
+
48
|
|
1231
|
+
],
|
|
1232
|
+
[
|
|
1233
|
+
'@',
|
|
1234
|
+
49
|
|
1235
|
+
]
|
|
1236
|
+
]);
|
|
1237
|
+
/**
|
|
1238
|
+
* Reverse lookup: built-in format ID -> format code
|
|
1239
|
+
*/ const BUILTIN_NUM_FMT_CODES = new Map(Array.from(BUILTIN_NUM_FMTS.entries()).map(([code, id])=>[
|
|
1240
|
+
id,
|
|
1241
|
+
code
|
|
1242
|
+
]));
|
|
1335
1243
|
/**
|
|
1336
1244
|
* Normalize a color to ARGB format (8 hex chars).
|
|
1337
1245
|
* Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
|
|
@@ -1351,40 +1259,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1351
1259
|
* Manages the styles (xl/styles.xml)
|
|
1352
1260
|
*/ class Styles {
|
|
1353
1261
|
/**
|
|
1354
|
-
* Generate a deterministic cache key for a style object.
|
|
1355
|
-
* More efficient than JSON.stringify as it avoids the overhead of
|
|
1356
|
-
* full JSON serialization and produces a consistent key regardless
|
|
1357
|
-
* of property order.
|
|
1358
|
-
*/ _getStyleKey(style) {
|
|
1359
|
-
// Use a delimiter that won't appear in values
|
|
1360
|
-
const SEP = '\x00';
|
|
1361
|
-
// Build key from all style properties in a fixed order
|
|
1362
|
-
const parts = [
|
|
1363
|
-
style.bold ? '1' : '0',
|
|
1364
|
-
style.italic ? '1' : '0',
|
|
1365
|
-
style.underline === true ? '1' : style.underline === 'single' ? 's' : style.underline === 'double' ? 'd' : '0',
|
|
1366
|
-
style.strike ? '1' : '0',
|
|
1367
|
-
style.fontSize?.toString() ?? '',
|
|
1368
|
-
style.fontName ?? '',
|
|
1369
|
-
style.fontColor ?? '',
|
|
1370
|
-
style.fill ?? '',
|
|
1371
|
-
style.numberFormat ?? ''
|
|
1372
|
-
];
|
|
1373
|
-
// Border properties
|
|
1374
|
-
if (style.border) {
|
|
1375
|
-
parts.push(style.border.top ?? '', style.border.bottom ?? '', style.border.left ?? '', style.border.right ?? '');
|
|
1376
|
-
} else {
|
|
1377
|
-
parts.push('', '', '', '');
|
|
1378
|
-
}
|
|
1379
|
-
// Alignment properties
|
|
1380
|
-
if (style.alignment) {
|
|
1381
|
-
parts.push(style.alignment.horizontal ?? '', style.alignment.vertical ?? '', style.alignment.wrapText ? '1' : '0', style.alignment.textRotation?.toString() ?? '');
|
|
1382
|
-
} else {
|
|
1383
|
-
parts.push('', '', '0', '');
|
|
1384
|
-
}
|
|
1385
|
-
return parts.join(SEP);
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
1262
|
* Parse styles from XML content
|
|
1389
1263
|
*/ static parse(xml) {
|
|
1390
1264
|
const styles = new Styles();
|
|
@@ -1551,16 +1425,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1551
1425
|
/**
|
|
1552
1426
|
* Get a style by index
|
|
1553
1427
|
*/ getStyle(index) {
|
|
1554
|
-
const cached = this._styleObjectCache.get(index);
|
|
1555
|
-
if (cached) return {
|
|
1556
|
-
...cached
|
|
1557
|
-
};
|
|
1558
1428
|
const xf = this._cellXfs[index];
|
|
1559
1429
|
if (!xf) return {};
|
|
1560
1430
|
const font = this._fonts[xf.fontId];
|
|
1561
1431
|
const fill = this._fills[xf.fillId];
|
|
1562
1432
|
const border = this._borders[xf.borderId];
|
|
1563
|
-
|
|
1433
|
+
// Check custom formats first, then fall back to built-in format codes
|
|
1434
|
+
const numFmt = this._numFmts.get(xf.numFmtId) ?? BUILTIN_NUM_FMT_CODES.get(xf.numFmtId);
|
|
1564
1435
|
const style = {};
|
|
1565
1436
|
if (font) {
|
|
1566
1437
|
if (font.bold) style.bold = true;
|
|
@@ -1595,16 +1466,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1595
1466
|
textRotation: xf.alignment.textRotation
|
|
1596
1467
|
};
|
|
1597
1468
|
}
|
|
1598
|
-
this._styleObjectCache.set(index, {
|
|
1599
|
-
...style
|
|
1600
|
-
});
|
|
1601
1469
|
return style;
|
|
1602
1470
|
}
|
|
1603
1471
|
/**
|
|
1604
1472
|
* Create a style and return its index
|
|
1605
1473
|
* Uses caching to deduplicate identical styles
|
|
1606
1474
|
*/ createStyle(style) {
|
|
1607
|
-
const key =
|
|
1475
|
+
const key = JSON.stringify(style);
|
|
1608
1476
|
const cached = this._styleCache.get(key);
|
|
1609
1477
|
if (cached !== undefined) {
|
|
1610
1478
|
return cached;
|
|
@@ -1636,20 +1504,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1636
1504
|
const index = this._cellXfs.length;
|
|
1637
1505
|
this._cellXfs.push(xf);
|
|
1638
1506
|
this._styleCache.set(key, index);
|
|
1639
|
-
this._styleObjectCache.set(index, {
|
|
1640
|
-
...style
|
|
1641
|
-
});
|
|
1642
1507
|
return index;
|
|
1643
1508
|
}
|
|
1644
|
-
/**
|
|
1645
|
-
* Clone an existing style by index, optionally overriding fields.
|
|
1646
|
-
*/ cloneStyle(index, overrides = {}) {
|
|
1647
|
-
const baseStyle = this.getStyle(index);
|
|
1648
|
-
return this.createStyle({
|
|
1649
|
-
...baseStyle,
|
|
1650
|
-
...overrides
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
1653
1509
|
_findOrCreateFont(style) {
|
|
1654
1510
|
const font = {
|
|
1655
1511
|
bold: style.bold || false,
|
|
@@ -1707,16 +1563,30 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1707
1563
|
return this._borders.length - 1;
|
|
1708
1564
|
}
|
|
1709
1565
|
_findOrCreateNumFmt(format) {
|
|
1710
|
-
// Check
|
|
1566
|
+
// Check built-in formats first (IDs 0-163)
|
|
1567
|
+
const builtinId = BUILTIN_NUM_FMTS.get(format);
|
|
1568
|
+
if (builtinId !== undefined) {
|
|
1569
|
+
return builtinId;
|
|
1570
|
+
}
|
|
1571
|
+
// Check if already exists in custom formats
|
|
1711
1572
|
for (const [id, code] of this._numFmts){
|
|
1712
1573
|
if (code === format) return id;
|
|
1713
1574
|
}
|
|
1714
|
-
// Create new
|
|
1715
|
-
const
|
|
1575
|
+
// Create new custom format (IDs 164+)
|
|
1576
|
+
const existingIds = Array.from(this._numFmts.keys());
|
|
1577
|
+
const id = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 164;
|
|
1716
1578
|
this._numFmts.set(id, format);
|
|
1717
1579
|
return id;
|
|
1718
1580
|
}
|
|
1719
1581
|
/**
|
|
1582
|
+
* Get or create a number format ID for the given format string.
|
|
1583
|
+
* Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
|
|
1584
|
+
* @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
|
|
1585
|
+
*/ getOrCreateNumFmtId(format) {
|
|
1586
|
+
this._dirty = true;
|
|
1587
|
+
return this._findOrCreateNumFmt(format);
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1720
1590
|
* Check if styles have been modified
|
|
1721
1591
|
*/ get dirty() {
|
|
1722
1592
|
return this._dirty;
|
|
@@ -1885,7 +1755,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1885
1755
|
this._dirty = false;
|
|
1886
1756
|
// Cache for style deduplication
|
|
1887
1757
|
this._styleCache = new Map();
|
|
1888
|
-
this._styleObjectCache = new Map();
|
|
1889
1758
|
}
|
|
1890
1759
|
}
|
|
1891
1760
|
|
|
@@ -1897,7 +1766,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1897
1766
|
this._columnFields = [];
|
|
1898
1767
|
this._valueFields = [];
|
|
1899
1768
|
this._filterFields = [];
|
|
1900
|
-
this.
|
|
1769
|
+
this._styles = null;
|
|
1901
1770
|
this._name = name;
|
|
1902
1771
|
this._cache = cache;
|
|
1903
1772
|
this._targetSheet = targetSheet;
|
|
@@ -1932,6 +1801,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1932
1801
|
return this._pivotTableIndex;
|
|
1933
1802
|
}
|
|
1934
1803
|
/**
|
|
1804
|
+
* Set the styles reference for number format resolution
|
|
1805
|
+
* @internal
|
|
1806
|
+
*/ setStyles(styles) {
|
|
1807
|
+
this._styles = styles;
|
|
1808
|
+
return this;
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1935
1811
|
* Add a field to the row area
|
|
1936
1812
|
* @param fieldName - Name of the source field (column header)
|
|
1937
1813
|
*/ addRowField(fieldName) {
|
|
@@ -1939,13 +1815,11 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1939
1815
|
if (fieldIndex < 0) {
|
|
1940
1816
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1941
1817
|
}
|
|
1942
|
-
|
|
1818
|
+
this._rowFields.push({
|
|
1943
1819
|
fieldName,
|
|
1944
1820
|
fieldIndex,
|
|
1945
1821
|
axis: 'row'
|
|
1946
|
-
};
|
|
1947
|
-
this._rowFields.push(assignment);
|
|
1948
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1822
|
+
});
|
|
1949
1823
|
return this;
|
|
1950
1824
|
}
|
|
1951
1825
|
/**
|
|
@@ -1956,13 +1830,11 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1956
1830
|
if (fieldIndex < 0) {
|
|
1957
1831
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1958
1832
|
}
|
|
1959
|
-
|
|
1833
|
+
this._columnFields.push({
|
|
1960
1834
|
fieldName,
|
|
1961
1835
|
fieldIndex,
|
|
1962
1836
|
axis: 'column'
|
|
1963
|
-
};
|
|
1964
|
-
this._columnFields.push(assignment);
|
|
1965
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1837
|
+
});
|
|
1966
1838
|
return this;
|
|
1967
1839
|
}
|
|
1968
1840
|
/**
|
|
@@ -1970,21 +1842,21 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1970
1842
|
* @param fieldName - Name of the source field (column header)
|
|
1971
1843
|
* @param aggregation - Aggregation function (sum, count, average, min, max)
|
|
1972
1844
|
* @param displayName - Optional display name (defaults to "Sum of FieldName")
|
|
1973
|
-
|
|
1845
|
+
* @param numberFormat - Optional number format (e.g., '$#,##0.00', '0.00%')
|
|
1846
|
+
*/ addValueField(fieldName, aggregation = 'sum', displayName, numberFormat) {
|
|
1974
1847
|
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
1975
1848
|
if (fieldIndex < 0) {
|
|
1976
1849
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1977
1850
|
}
|
|
1978
1851
|
const defaultName = `${aggregation.charAt(0).toUpperCase() + aggregation.slice(1)} of ${fieldName}`;
|
|
1979
|
-
|
|
1852
|
+
this._valueFields.push({
|
|
1980
1853
|
fieldName,
|
|
1981
1854
|
fieldIndex,
|
|
1982
1855
|
axis: 'value',
|
|
1983
1856
|
aggregation,
|
|
1984
|
-
displayName: displayName || defaultName
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
1857
|
+
displayName: displayName || defaultName,
|
|
1858
|
+
numberFormat
|
|
1859
|
+
});
|
|
1988
1860
|
return this;
|
|
1989
1861
|
}
|
|
1990
1862
|
/**
|
|
@@ -1995,44 +1867,11 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1995
1867
|
if (fieldIndex < 0) {
|
|
1996
1868
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1997
1869
|
}
|
|
1998
|
-
|
|
1870
|
+
this._filterFields.push({
|
|
1999
1871
|
fieldName,
|
|
2000
1872
|
fieldIndex,
|
|
2001
1873
|
axis: 'filter'
|
|
2002
|
-
};
|
|
2003
|
-
this._filterFields.push(assignment);
|
|
2004
|
-
this._fieldAssignments.set(fieldIndex, assignment);
|
|
2005
|
-
return this;
|
|
2006
|
-
}
|
|
2007
|
-
/**
|
|
2008
|
-
* Set a sort order for a row/column field
|
|
2009
|
-
*/ sortField(fieldName, order) {
|
|
2010
|
-
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
2011
|
-
if (fieldIndex < 0) {
|
|
2012
|
-
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
2013
|
-
}
|
|
2014
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2015
|
-
if (!assignment || assignment.axis !== 'row' && assignment.axis !== 'column') {
|
|
2016
|
-
throw new Error(`Field is not assigned to row or column axis: ${fieldName}`);
|
|
2017
|
-
}
|
|
2018
|
-
assignment.sortOrder = order;
|
|
2019
|
-
return this;
|
|
2020
|
-
}
|
|
2021
|
-
/**
|
|
2022
|
-
* Filter items for a field (include or exclude list)
|
|
2023
|
-
*/ filterField(fieldName, filter) {
|
|
2024
|
-
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
2025
|
-
if (fieldIndex < 0) {
|
|
2026
|
-
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
2027
|
-
}
|
|
2028
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2029
|
-
if (!assignment) {
|
|
2030
|
-
throw new Error(`Field is not assigned to pivot table: ${fieldName}`);
|
|
2031
|
-
}
|
|
2032
|
-
if (filter.include && filter.exclude) {
|
|
2033
|
-
throw new Error('Pivot field filter cannot use both include and exclude');
|
|
2034
|
-
}
|
|
2035
|
-
assignment.filter = filter;
|
|
1874
|
+
});
|
|
2036
1875
|
return this;
|
|
2037
1876
|
}
|
|
2038
1877
|
/**
|
|
@@ -2139,17 +1978,26 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2139
1978
|
}
|
|
2140
1979
|
// Data fields (values)
|
|
2141
1980
|
if (this._valueFields.length > 0) {
|
|
2142
|
-
const dataFieldNodes = this._valueFields.map((f)=>
|
|
1981
|
+
const dataFieldNodes = this._valueFields.map((f)=>{
|
|
1982
|
+
const attrs = {
|
|
2143
1983
|
name: f.displayName || f.fieldName,
|
|
2144
1984
|
fld: String(f.fieldIndex),
|
|
2145
1985
|
baseField: '0',
|
|
2146
1986
|
baseItem: '0',
|
|
2147
1987
|
subtotal: f.aggregation || 'sum'
|
|
2148
|
-
}
|
|
1988
|
+
};
|
|
1989
|
+
// Add numFmtId if format specified and styles available
|
|
1990
|
+
if (f.numberFormat && this._styles) {
|
|
1991
|
+
attrs.numFmtId = String(this._styles.getOrCreateNumFmtId(f.numberFormat));
|
|
1992
|
+
}
|
|
1993
|
+
return createElement('dataField', attrs, []);
|
|
1994
|
+
});
|
|
2149
1995
|
children.push(createElement('dataFields', {
|
|
2150
1996
|
count: String(dataFieldNodes.length)
|
|
2151
1997
|
}, dataFieldNodes));
|
|
2152
1998
|
}
|
|
1999
|
+
// Check if any value field has a number format
|
|
2000
|
+
const hasNumberFormats = this._valueFields.some((f)=>f.numberFormat);
|
|
2153
2001
|
// Pivot table style
|
|
2154
2002
|
children.push(createElement('pivotTableStyleInfo', {
|
|
2155
2003
|
name: 'PivotStyleMedium9',
|
|
@@ -2164,7 +2012,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2164
2012
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
2165
2013
|
name: this._name,
|
|
2166
2014
|
cacheId: String(this._cache.cacheId),
|
|
2167
|
-
applyNumberFormats: '0',
|
|
2015
|
+
applyNumberFormats: hasNumberFormats ? '1' : '0',
|
|
2168
2016
|
applyBorderFormats: '0',
|
|
2169
2017
|
applyFontFormats: '0',
|
|
2170
2018
|
applyPatternFormats: '0',
|
|
@@ -2197,28 +2045,17 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2197
2045
|
const colField = this._columnFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2198
2046
|
const filterField = this._filterFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2199
2047
|
const valueField = this._valueFields.find((f)=>f.fieldIndex === fieldIndex);
|
|
2200
|
-
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
2201
2048
|
if (rowField) {
|
|
2202
2049
|
attrs.axis = 'axisRow';
|
|
2203
2050
|
attrs.showAll = '0';
|
|
2204
|
-
if (assignment?.sortOrder) {
|
|
2205
|
-
attrs.sortType = 'ascending';
|
|
2206
|
-
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
2207
|
-
}
|
|
2208
2051
|
// Add items for shared values
|
|
2209
2052
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2210
2053
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2211
2054
|
const itemNodes = [];
|
|
2212
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2213
2055
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2214
|
-
|
|
2215
|
-
const itemAttrs = {
|
|
2056
|
+
itemNodes.push(createElement('item', {
|
|
2216
2057
|
x: String(i)
|
|
2217
|
-
};
|
|
2218
|
-
if (!shouldInclude) {
|
|
2219
|
-
itemAttrs.h = '1';
|
|
2220
|
-
}
|
|
2221
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
2058
|
+
}, []));
|
|
2222
2059
|
}
|
|
2223
2060
|
// Add default subtotal item
|
|
2224
2061
|
itemNodes.push(createElement('item', {
|
|
@@ -2231,23 +2068,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2231
2068
|
} else if (colField) {
|
|
2232
2069
|
attrs.axis = 'axisCol';
|
|
2233
2070
|
attrs.showAll = '0';
|
|
2234
|
-
if (assignment?.sortOrder) {
|
|
2235
|
-
attrs.sortType = 'ascending';
|
|
2236
|
-
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
2237
|
-
}
|
|
2238
2071
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2239
2072
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2240
2073
|
const itemNodes = [];
|
|
2241
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2242
2074
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2243
|
-
|
|
2244
|
-
const itemAttrs = {
|
|
2075
|
+
itemNodes.push(createElement('item', {
|
|
2245
2076
|
x: String(i)
|
|
2246
|
-
};
|
|
2247
|
-
if (!shouldInclude) {
|
|
2248
|
-
itemAttrs.h = '1';
|
|
2249
|
-
}
|
|
2250
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
2077
|
+
}, []));
|
|
2251
2078
|
}
|
|
2252
2079
|
itemNodes.push(createElement('item', {
|
|
2253
2080
|
t: 'default'
|
|
@@ -2262,16 +2089,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2262
2089
|
const cacheField = this._cache.fields[fieldIndex];
|
|
2263
2090
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
2264
2091
|
const itemNodes = [];
|
|
2265
|
-
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
2266
2092
|
for(let i = 0; i < cacheField.sharedItems.length; i++){
|
|
2267
|
-
|
|
2268
|
-
const itemAttrs = {
|
|
2093
|
+
itemNodes.push(createElement('item', {
|
|
2269
2094
|
x: String(i)
|
|
2270
|
-
};
|
|
2271
|
-
if (!shouldInclude) {
|
|
2272
|
-
itemAttrs.h = '1';
|
|
2273
|
-
}
|
|
2274
|
-
itemNodes.push(createElement('item', itemAttrs, []));
|
|
2095
|
+
}, []));
|
|
2275
2096
|
}
|
|
2276
2097
|
itemNodes.push(createElement('item', {
|
|
2277
2098
|
t: 'default'
|
|
@@ -2288,31 +2109,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2288
2109
|
}
|
|
2289
2110
|
return createElement('pivotField', attrs, children);
|
|
2290
2111
|
}
|
|
2291
|
-
_resolveItemFilter(items, filter) {
|
|
2292
|
-
const allowed = new Set();
|
|
2293
|
-
if (!filter || !filter.include && !filter.exclude) {
|
|
2294
|
-
for(let i = 0; i < items.length; i++){
|
|
2295
|
-
allowed.add(i);
|
|
2296
|
-
}
|
|
2297
|
-
return allowed;
|
|
2298
|
-
}
|
|
2299
|
-
if (filter.include) {
|
|
2300
|
-
for(let i = 0; i < items.length; i++){
|
|
2301
|
-
if (filter.include.includes(items[i])) {
|
|
2302
|
-
allowed.add(i);
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
return allowed;
|
|
2306
|
-
}
|
|
2307
|
-
if (filter.exclude) {
|
|
2308
|
-
for(let i = 0; i < items.length; i++){
|
|
2309
|
-
if (!filter.exclude.includes(items[i])) {
|
|
2310
|
-
allowed.add(i);
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
return allowed;
|
|
2315
|
-
}
|
|
2316
2112
|
/**
|
|
2317
2113
|
* Build row items based on unique values in row fields
|
|
2318
2114
|
*/ _buildRowItems() {
|
|
@@ -2463,7 +2259,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2463
2259
|
this._records = [];
|
|
2464
2260
|
this._recordCount = 0;
|
|
2465
2261
|
this._refreshOnLoad = true; // Default to true
|
|
2466
|
-
|
|
2262
|
+
// Optimized lookup: Map<fieldIndex, Map<stringValue, sharedItemsIndex>>
|
|
2263
|
+
this._sharedItemsIndexMap = new Map();
|
|
2467
2264
|
this._cacheId = cacheId;
|
|
2468
2265
|
this._sourceSheet = sourceSheet;
|
|
2469
2266
|
this._sourceRange = sourceRange;
|
|
@@ -2524,6 +2321,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2524
2321
|
minValue: undefined,
|
|
2525
2322
|
maxValue: undefined
|
|
2526
2323
|
}));
|
|
2324
|
+
// Use Sets for O(1) unique value collection during analysis
|
|
2325
|
+
const sharedItemsSets = this._fields.map(()=>new Set());
|
|
2527
2326
|
// Analyze data to determine field types and collect unique values
|
|
2528
2327
|
for (const row of data){
|
|
2529
2328
|
for(let colIdx = 0; colIdx < row.length && colIdx < this._fields.length; colIdx++){
|
|
@@ -2534,9 +2333,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2534
2333
|
}
|
|
2535
2334
|
if (typeof value === 'string') {
|
|
2536
2335
|
field.isNumeric = false;
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2336
|
+
// O(1) Set.add instead of O(n) Array.includes + push
|
|
2337
|
+
sharedItemsSets[colIdx].add(value);
|
|
2540
2338
|
} else if (typeof value === 'number') {
|
|
2541
2339
|
if (field.minValue === undefined || value < field.minValue) {
|
|
2542
2340
|
field.minValue = value;
|
|
@@ -2552,8 +2350,22 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2552
2350
|
}
|
|
2553
2351
|
}
|
|
2554
2352
|
}
|
|
2555
|
-
//
|
|
2556
|
-
this.
|
|
2353
|
+
// Convert Sets to arrays and build reverse index Maps for O(1) lookup during XML generation
|
|
2354
|
+
this._sharedItemsIndexMap.clear();
|
|
2355
|
+
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
2356
|
+
const field = this._fields[colIdx];
|
|
2357
|
+
const set = sharedItemsSets[colIdx];
|
|
2358
|
+
// Convert Set to array (maintains insertion order in ES6+)
|
|
2359
|
+
field.sharedItems = Array.from(set);
|
|
2360
|
+
// Build reverse lookup Map: value -> index
|
|
2361
|
+
if (field.sharedItems.length > 0) {
|
|
2362
|
+
const indexMap = new Map();
|
|
2363
|
+
for(let i = 0; i < field.sharedItems.length; i++){
|
|
2364
|
+
indexMap.set(field.sharedItems[i], i);
|
|
2365
|
+
}
|
|
2366
|
+
this._sharedItemsIndexMap.set(colIdx, indexMap);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2557
2369
|
// Store records
|
|
2558
2370
|
this._records = data;
|
|
2559
2371
|
}
|
|
@@ -2582,8 +2394,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2582
2394
|
v: item
|
|
2583
2395
|
}, []));
|
|
2584
2396
|
}
|
|
2585
|
-
} else if (field.isDate) {
|
|
2586
|
-
sharedItemsAttrs.containsDate = '1';
|
|
2587
2397
|
} else if (field.isNumeric) {
|
|
2588
2398
|
// Numeric field - use "0"/"1" for boolean attributes as Excel expects
|
|
2589
2399
|
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
@@ -2614,13 +2424,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2614
2424
|
ref: this._sourceRange,
|
|
2615
2425
|
sheet: this._sourceSheet
|
|
2616
2426
|
}, []);
|
|
2617
|
-
const
|
|
2427
|
+
const cacheSourceNode = createElement('cacheSource', {
|
|
2618
2428
|
type: 'worksheet'
|
|
2619
|
-
}
|
|
2620
|
-
if (this._dateGrouping) {
|
|
2621
|
-
cacheSourceAttrs.grouping = '1';
|
|
2622
|
-
}
|
|
2623
|
-
const cacheSourceNode = createElement('cacheSource', cacheSourceAttrs, [
|
|
2429
|
+
}, [
|
|
2624
2430
|
worksheetSourceNode
|
|
2625
2431
|
]);
|
|
2626
2432
|
// Build attributes - refreshOnLoad should come early per OOXML schema
|
|
@@ -2654,15 +2460,15 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2654
2460
|
for (const row of this._records){
|
|
2655
2461
|
const fieldNodes = [];
|
|
2656
2462
|
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
2657
|
-
const field = this._fields[colIdx];
|
|
2658
2463
|
const value = colIdx < row.length ? row[colIdx] : null;
|
|
2659
2464
|
if (value === null || value === undefined) {
|
|
2660
2465
|
// Missing value
|
|
2661
2466
|
fieldNodes.push(createElement('m', {}, []));
|
|
2662
2467
|
} else if (typeof value === 'string') {
|
|
2663
|
-
// String value - use index into sharedItems
|
|
2664
|
-
const
|
|
2665
|
-
|
|
2468
|
+
// String value - use index into sharedItems via O(1) Map lookup
|
|
2469
|
+
const indexMap = this._sharedItemsIndexMap.get(colIdx);
|
|
2470
|
+
const idx = indexMap?.get(value);
|
|
2471
|
+
if (idx !== undefined) {
|
|
2666
2472
|
fieldNodes.push(createElement('x', {
|
|
2667
2473
|
v: String(idx)
|
|
2668
2474
|
}, []));
|
|
@@ -2766,8 +2572,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2766
2572
|
this._pivotTables = [];
|
|
2767
2573
|
this._pivotCaches = [];
|
|
2768
2574
|
this._nextCacheId = 0;
|
|
2769
|
-
// Date serialization handling
|
|
2770
|
-
this._dateHandling = 'jsDate';
|
|
2771
2575
|
this._sharedStrings = new SharedStrings();
|
|
2772
2576
|
this._styles = Styles.createDefault();
|
|
2773
2577
|
}
|
|
@@ -2832,16 +2636,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2832
2636
|
return this._styles;
|
|
2833
2637
|
}
|
|
2834
2638
|
/**
|
|
2835
|
-
* Get the workbook date handling strategy.
|
|
2836
|
-
*/ get dateHandling() {
|
|
2837
|
-
return this._dateHandling;
|
|
2838
|
-
}
|
|
2839
|
-
/**
|
|
2840
|
-
* Set the workbook date handling strategy.
|
|
2841
|
-
*/ set dateHandling(value) {
|
|
2842
|
-
this._dateHandling = value;
|
|
2843
|
-
}
|
|
2844
|
-
/**
|
|
2845
2639
|
* Get a worksheet by name or index
|
|
2846
2640
|
*/ sheet(nameOrIndex) {
|
|
2847
2641
|
let def;
|
|
@@ -3152,6 +2946,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3152
2946
|
// Create pivot table
|
|
3153
2947
|
const pivotTableIndex = this._pivotTables.length + 1;
|
|
3154
2948
|
const pivotTable = new PivotTable(config.name, cache, targetSheet, targetCell, targetAddr.row + 1, targetAddr.col, pivotTableIndex);
|
|
2949
|
+
// Set styles reference for number format resolution
|
|
2950
|
+
pivotTable.setStyles(this._styles);
|
|
3155
2951
|
this._pivotTables.push(pivotTable);
|
|
3156
2952
|
return pivotTable;
|
|
3157
2953
|
}
|
|
@@ -3529,10 +3325,6 @@ exports.SharedStrings = SharedStrings;
|
|
|
3529
3325
|
exports.Styles = Styles;
|
|
3530
3326
|
exports.Workbook = Workbook;
|
|
3531
3327
|
exports.Worksheet = Worksheet;
|
|
3532
|
-
exports.colToLetter = colToLetter;
|
|
3533
|
-
exports.isInRange = isInRange;
|
|
3534
|
-
exports.letterToCol = letterToCol;
|
|
3535
|
-
exports.normalizeRange = normalizeRange;
|
|
3536
3328
|
exports.parseAddress = parseAddress;
|
|
3537
3329
|
exports.parseRange = parseRange;
|
|
3538
3330
|
exports.toAddress = toAddress;
|