@niicojs/excel 0.2.5 → 0.2.7

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.cjs CHANGED
@@ -1117,6 +1117,129 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1117
1117
  }
1118
1118
  }
1119
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
+ ]));
1120
1243
  /**
1121
1244
  * Normalize a color to ARGB format (8 hex chars).
1122
1245
  * Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
@@ -1307,7 +1430,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1307
1430
  const font = this._fonts[xf.fontId];
1308
1431
  const fill = this._fills[xf.fillId];
1309
1432
  const border = this._borders[xf.borderId];
1310
- const numFmt = this._numFmts.get(xf.numFmtId);
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);
1311
1435
  const style = {};
1312
1436
  if (font) {
1313
1437
  if (font.bold) style.bold = true;
@@ -1439,16 +1563,30 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1439
1563
  return this._borders.length - 1;
1440
1564
  }
1441
1565
  _findOrCreateNumFmt(format) {
1442
- // Check if already exists
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
1443
1572
  for (const [id, code] of this._numFmts){
1444
1573
  if (code === format) return id;
1445
1574
  }
1446
- // Create new (custom formats start at 164)
1447
- const id = Math.max(164, ...Array.from(this._numFmts.keys())) + 1;
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;
1448
1578
  this._numFmts.set(id, format);
1449
1579
  return id;
1450
1580
  }
1451
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
+ /**
1452
1590
  * Check if styles have been modified
1453
1591
  */ get dirty() {
1454
1592
  return this._dirty;
@@ -1628,6 +1766,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1628
1766
  this._columnFields = [];
1629
1767
  this._valueFields = [];
1630
1768
  this._filterFields = [];
1769
+ this._styles = null;
1631
1770
  this._name = name;
1632
1771
  this._cache = cache;
1633
1772
  this._targetSheet = targetSheet;
@@ -1662,6 +1801,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1662
1801
  return this._pivotTableIndex;
1663
1802
  }
1664
1803
  /**
1804
+ * Set the styles reference for number format resolution
1805
+ * @internal
1806
+ */ setStyles(styles) {
1807
+ this._styles = styles;
1808
+ return this;
1809
+ }
1810
+ /**
1665
1811
  * Add a field to the row area
1666
1812
  * @param fieldName - Name of the source field (column header)
1667
1813
  */ addRowField(fieldName) {
@@ -1691,23 +1837,40 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1691
1837
  });
1692
1838
  return this;
1693
1839
  }
1694
- /**
1695
- * Add a field to the values area with aggregation
1696
- * @param fieldName - Name of the source field (column header)
1697
- * @param aggregation - Aggregation function (sum, count, average, min, max)
1698
- * @param displayName - Optional display name (defaults to "Sum of FieldName")
1699
- */ addValueField(fieldName, aggregation = 'sum', displayName) {
1840
+ addValueField(fieldNameOrConfig, aggregation = 'sum', displayName, numberFormat) {
1841
+ // Normalize arguments to a common form
1842
+ let fieldName;
1843
+ let agg;
1844
+ let name;
1845
+ let format;
1846
+ if (typeof fieldNameOrConfig === 'object') {
1847
+ fieldName = fieldNameOrConfig.field;
1848
+ agg = fieldNameOrConfig.aggregation ?? 'sum';
1849
+ name = fieldNameOrConfig.name;
1850
+ format = fieldNameOrConfig.numberFormat;
1851
+ } else {
1852
+ fieldName = fieldNameOrConfig;
1853
+ agg = aggregation;
1854
+ name = displayName;
1855
+ format = numberFormat;
1856
+ }
1700
1857
  const fieldIndex = this._cache.getFieldIndex(fieldName);
1701
1858
  if (fieldIndex < 0) {
1702
1859
  throw new Error(`Field not found in source data: ${fieldName}`);
1703
1860
  }
1704
- const defaultName = `${aggregation.charAt(0).toUpperCase() + aggregation.slice(1)} of ${fieldName}`;
1861
+ const defaultName = `${agg.charAt(0).toUpperCase() + agg.slice(1)} of ${fieldName}`;
1862
+ // Resolve numFmtId immediately if format is provided and styles are available
1863
+ let numFmtId;
1864
+ if (format && this._styles) {
1865
+ numFmtId = this._styles.getOrCreateNumFmtId(format);
1866
+ }
1705
1867
  this._valueFields.push({
1706
1868
  fieldName,
1707
1869
  fieldIndex,
1708
1870
  axis: 'value',
1709
- aggregation,
1710
- displayName: displayName || defaultName
1871
+ aggregation: agg,
1872
+ displayName: name || defaultName,
1873
+ numFmtId
1711
1874
  });
1712
1875
  return this;
1713
1876
  }
@@ -1830,17 +1993,26 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1830
1993
  }
1831
1994
  // Data fields (values)
1832
1995
  if (this._valueFields.length > 0) {
1833
- const dataFieldNodes = this._valueFields.map((f)=>createElement('dataField', {
1996
+ const dataFieldNodes = this._valueFields.map((f)=>{
1997
+ const attrs = {
1834
1998
  name: f.displayName || f.fieldName,
1835
1999
  fld: String(f.fieldIndex),
1836
2000
  baseField: '0',
1837
2001
  baseItem: '0',
1838
2002
  subtotal: f.aggregation || 'sum'
1839
- }, []));
2003
+ };
2004
+ // Add numFmtId if it was resolved during addValueField
2005
+ if (f.numFmtId !== undefined) {
2006
+ attrs.numFmtId = String(f.numFmtId);
2007
+ }
2008
+ return createElement('dataField', attrs, []);
2009
+ });
1840
2010
  children.push(createElement('dataFields', {
1841
2011
  count: String(dataFieldNodes.length)
1842
2012
  }, dataFieldNodes));
1843
2013
  }
2014
+ // Check if any value field has a number format
2015
+ const hasNumberFormats = this._valueFields.some((f)=>f.numFmtId !== undefined);
1844
2016
  // Pivot table style
1845
2017
  children.push(createElement('pivotTableStyleInfo', {
1846
2018
  name: 'PivotStyleMedium9',
@@ -1855,7 +2027,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1855
2027
  'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
1856
2028
  name: this._name,
1857
2029
  cacheId: String(this._cache.cacheId),
1858
- applyNumberFormats: '0',
2030
+ applyNumberFormats: hasNumberFormats ? '1' : '0',
1859
2031
  applyBorderFormats: '0',
1860
2032
  applyFontFormats: '0',
1861
2033
  applyPatternFormats: '0',
@@ -2789,6 +2961,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2789
2961
  // Create pivot table
2790
2962
  const pivotTableIndex = this._pivotTables.length + 1;
2791
2963
  const pivotTable = new PivotTable(config.name, cache, targetSheet, targetCell, targetAddr.row + 1, targetAddr.col, pivotTableIndex);
2964
+ // Set styles reference for number format resolution
2965
+ pivotTable.setStyles(this._styles);
2792
2966
  this._pivotTables.push(pivotTable);
2793
2967
  return pivotTable;
2794
2968
  }
package/dist/index.d.cts CHANGED
@@ -89,10 +89,12 @@ type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
89
89
  interface PivotValueConfig {
90
90
  /** Source field name (column header) */
91
91
  field: string;
92
- /** Aggregation function */
93
- aggregation: AggregationType;
92
+ /** Aggregation function (default: 'sum') */
93
+ aggregation?: AggregationType;
94
94
  /** Display name (e.g., "Sum of Sales") */
95
95
  name?: string;
96
+ /** Number format (e.g., '$#,##0.00', '0.00%') */
97
+ numberFormat?: string;
96
98
  }
97
99
  /**
98
100
  * Configuration for creating a pivot table
@@ -521,6 +523,12 @@ declare class Styles {
521
523
  private _findOrCreateFill;
522
524
  private _findOrCreateBorder;
523
525
  private _findOrCreateNumFmt;
526
+ /**
527
+ * Get or create a number format ID for the given format string.
528
+ * Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
529
+ * @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
530
+ */
531
+ getOrCreateNumFmtId(format: string): number;
524
532
  /**
525
533
  * Check if styles have been modified
526
534
  */
@@ -620,6 +628,7 @@ declare class PivotTable {
620
628
  private _valueFields;
621
629
  private _filterFields;
622
630
  private _pivotTableIndex;
631
+ private _styles;
623
632
  constructor(name: string, cache: PivotCache, targetSheet: string, targetCell: string, targetRow: number, targetCol: number, pivotTableIndex: number);
624
633
  /**
625
634
  * Get the pivot table name
@@ -641,6 +650,11 @@ declare class PivotTable {
641
650
  * Get the pivot table index (for file naming)
642
651
  */
643
652
  get index(): number;
653
+ /**
654
+ * Set the styles reference for number format resolution
655
+ * @internal
656
+ */
657
+ setStyles(styles: Styles): this;
644
658
  /**
645
659
  * Add a field to the row area
646
660
  * @param fieldName - Name of the source field (column header)
@@ -652,12 +666,21 @@ declare class PivotTable {
652
666
  */
653
667
  addColumnField(fieldName: string): this;
654
668
  /**
655
- * Add a field to the values area with aggregation
656
- * @param fieldName - Name of the source field (column header)
657
- * @param aggregation - Aggregation function (sum, count, average, min, max)
658
- * @param displayName - Optional display name (defaults to "Sum of FieldName")
669
+ * Add a field to the values area with aggregation.
670
+ *
671
+ * Supports two call signatures:
672
+ * - Positional: `addValueField(fieldName, aggregation?, displayName?, numberFormat?)`
673
+ * - Object: `addValueField({ field, aggregation?, name?, numberFormat? })`
674
+ *
675
+ * @example
676
+ * // Positional arguments
677
+ * pivot.addValueField('Sales', 'sum', 'Total Sales', '$#,##0.00');
678
+ *
679
+ * // Object form
680
+ * pivot.addValueField({ field: 'Sales', aggregation: 'sum', name: 'Total Sales', numberFormat: '$#,##0.00' });
659
681
  */
660
- addValueField(fieldName: string, aggregation?: AggregationType, displayName?: string): this;
682
+ addValueField(config: PivotValueConfig): this;
683
+ addValueField(fieldName: string, aggregation?: AggregationType, displayName?: string, numberFormat?: string): this;
661
684
  /**
662
685
  * Add a field to the filter (page) area
663
686
  * @param fieldName - Name of the source field (column header)