@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/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 = rowNumber - 1; // Convert to 0-based
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
- if (createMissing) {
455
- const cell = this._worksheet.cell(r, c);
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, dateHandling = this._workbook.dateHandling } = config;
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
- let value = cell?.value ?? null;
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
- this._dataBoundsCache = {
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 rowAttrs = {
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
- // Sheet views (freeze panes)
1116
- if (this._frozenPane) {
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 = this._getStyleKey(style);
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
- const assignment = {
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
- const assignment = {
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
- const assignment = {
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
- const assignment = {
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
- const shouldInclude = allowedIndexes.has(i);
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
- const shouldInclude = allowedIndexes.has(i);
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
- const shouldInclude = allowedIndexes.has(i);
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
- this._dateGrouping = false;
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
- if (!field.sharedItems.includes(value)) {
2536
- field.sharedItems.push(value);
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
- // Enable date grouping flag if any date field exists
2554
- this._dateGrouping = this._fields.some((field)=>field.isDate);
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 cacheSourceAttrs = {
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 idx = field.sharedItems.indexOf(value);
2663
- if (idx >= 0) {
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, colToLetter, isInRange, letterToCol, normalizeRange, parseAddress, parseRange, toAddress, toRange };
3159
+ export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet, parseAddress, parseRange, toAddress, toRange };