@cj-tech-master/excelts 1.5.0 → 1.6.1

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.
Files changed (45) hide show
  1. package/dist/browser/excelts.iife.js +1057 -201
  2. package/dist/browser/excelts.iife.js.map +1 -1
  3. package/dist/browser/excelts.iife.min.js +63 -33
  4. package/dist/cjs/doc/column.js +7 -3
  5. package/dist/cjs/doc/pivot-table.js +149 -61
  6. package/dist/cjs/doc/workbook.js +3 -1
  7. package/dist/cjs/doc/worksheet.js +0 -2
  8. package/dist/cjs/stream/xlsx/worksheet-writer.js +1 -1
  9. package/dist/cjs/utils/unzip/zip-parser.js +2 -5
  10. package/dist/cjs/xlsx/xform/book/workbook-xform.js +3 -0
  11. package/dist/cjs/xlsx/xform/core/content-types-xform.js +19 -14
  12. package/dist/cjs/xlsx/xform/pivot-table/cache-field-xform.js +135 -0
  13. package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +7 -4
  14. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
  15. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
  16. package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
  17. package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -0
  18. package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +14 -3
  19. package/dist/cjs/xlsx/xlsx.js +261 -38
  20. package/dist/esm/doc/column.js +7 -3
  21. package/dist/esm/doc/pivot-table.js +150 -62
  22. package/dist/esm/doc/workbook.js +3 -1
  23. package/dist/esm/doc/worksheet.js +0 -2
  24. package/dist/esm/stream/xlsx/worksheet-writer.js +1 -1
  25. package/dist/esm/utils/unzip/zip-parser.js +2 -5
  26. package/dist/esm/xlsx/xform/book/workbook-xform.js +3 -0
  27. package/dist/esm/xlsx/xform/core/content-types-xform.js +19 -14
  28. package/dist/esm/xlsx/xform/pivot-table/cache-field-xform.js +132 -0
  29. package/dist/esm/xlsx/xform/pivot-table/cache-field.js +7 -4
  30. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
  31. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
  32. package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
  33. package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -0
  34. package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +14 -3
  35. package/dist/esm/xlsx/xlsx.js +261 -38
  36. package/dist/types/doc/column.d.ts +13 -6
  37. package/dist/types/doc/pivot-table.d.ts +135 -9
  38. package/dist/types/doc/workbook.d.ts +2 -0
  39. package/dist/types/index.d.ts +1 -0
  40. package/dist/types/xlsx/xform/pivot-table/cache-field-xform.d.ts +42 -0
  41. package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +45 -6
  42. package/dist/types/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +52 -5
  43. package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +98 -5
  44. package/dist/types/xlsx/xlsx.d.ts +27 -0
  45. package/package.json +17 -17
@@ -105,6 +105,8 @@ class XLSX {
105
105
  Object.values(model.tables).forEach((table) => {
106
106
  tableXform.reconcile(table, tableOptions);
107
107
  });
108
+ // Reconcile pivot tables - link pivot tables to worksheets and cache data
109
+ this._reconcilePivotTables(model);
108
110
  const sheetOptions = {
109
111
  styles: model.styles,
110
112
  sharedStrings: model.sharedStrings,
@@ -114,7 +116,8 @@ class XLSX {
114
116
  drawings: model.drawings,
115
117
  comments: model.comments,
116
118
  tables: model.tables,
117
- vmlDrawings: model.vmlDrawings
119
+ vmlDrawings: model.vmlDrawings,
120
+ pivotTables: model.pivotTablesIndexed
118
121
  };
119
122
  model.worksheets.forEach((worksheet) => {
120
123
  worksheet.relationships = model.worksheetRels[worksheet.sheetNo];
@@ -132,6 +135,136 @@ class XLSX {
132
135
  delete model.drawings;
133
136
  delete model.drawingRels;
134
137
  delete model.vmlDrawings;
138
+ // Clean up raw pivot table data after reconciliation
139
+ delete model.pivotTableRels;
140
+ delete model.pivotCacheDefinitionRels;
141
+ }
142
+ /**
143
+ * Reconcile pivot tables by linking them to worksheets and their cache data.
144
+ * This builds a complete pivot table model ready for writing.
145
+ */
146
+ _reconcilePivotTables(model) {
147
+ // Skip if no pivot tables were loaded (object is empty or undefined)
148
+ const rawPivotTables = model.pivotTables || {};
149
+ if (typeof rawPivotTables !== "object" || Object.keys(rawPivotTables).length === 0) {
150
+ // Ensure pivotTables is an empty array (not an object)
151
+ model.pivotTables = [];
152
+ // Also create an empty indexed object for worksheet reconciliation
153
+ model.pivotTablesIndexed = {};
154
+ return;
155
+ }
156
+ // Build mapping from definition name to cacheId
157
+ const definitionToCacheId = this._buildDefinitionToCacheIdMap(model);
158
+ // Build a map of cache IDs to their definitions and records
159
+ const cacheMap = new Map();
160
+ // Process cache definitions
161
+ Object.entries(model.pivotCacheDefinitions || {}).forEach(([name, definition]) => {
162
+ // Get the cacheId from the mapping (derived from workbook.xml pivotCaches)
163
+ const cacheId = definitionToCacheId.get(name);
164
+ if (cacheId !== undefined) {
165
+ const recordsName = name.replace("Definition", "Records");
166
+ cacheMap.set(cacheId, {
167
+ definition,
168
+ records: model.pivotCacheRecords?.[recordsName],
169
+ definitionName: name
170
+ });
171
+ }
172
+ });
173
+ // Process pivot tables and link to worksheets
174
+ const loadedPivotTables = [];
175
+ // Create indexed object for worksheet reconciliation (keyed by relative path)
176
+ const pivotTablesIndexed = {};
177
+ Object.entries(rawPivotTables).forEach(([pivotName, pivotTable]) => {
178
+ const pt = pivotTable;
179
+ const tableNumber = this._extractTableNumber(pivotName);
180
+ // Get cache data for this pivot table
181
+ const cacheData = cacheMap.get(pt.cacheId);
182
+ // Build complete pivot table model
183
+ const completePivotTable = {
184
+ // Core model data
185
+ ...pt,
186
+ tableNumber,
187
+ // Link to cache data
188
+ cacheDefinition: cacheData?.definition,
189
+ cacheRecords: cacheData?.records,
190
+ // Reconstruct cacheFields from definition for compatibility
191
+ cacheFields: cacheData?.definition?.cacheFields || [],
192
+ // Determine rows, columns, values from parsed data
193
+ rows: pt.rowFields.filter(f => f >= 0),
194
+ columns: pt.colFields.filter(f => f >= 0 && f !== -2),
195
+ values: pt.dataFields.map(df => df.fld),
196
+ // Determine metric from dataFields
197
+ metric: this._determineMetric(pt.dataFields),
198
+ // Preserve formatting options
199
+ applyWidthHeightFormats: pt.applyWidthHeightFormats || "0"
200
+ };
201
+ loadedPivotTables.push(completePivotTable);
202
+ // Index by relative path for worksheet reconciliation
203
+ pivotTablesIndexed[`../pivotTables/${pivotName}.xml`] = completePivotTable;
204
+ });
205
+ // Sort by table number to maintain order
206
+ loadedPivotTables.sort((a, b) => a.tableNumber - b.tableNumber);
207
+ // Replace pivotTables object with the processed array
208
+ // This is what the Workbook model setter expects
209
+ model.pivotTables = loadedPivotTables;
210
+ // Also keep indexed version for worksheet reconciliation
211
+ model.pivotTablesIndexed = pivotTablesIndexed;
212
+ // Also keep as loadedPivotTables for backward compatibility
213
+ model.loadedPivotTables = loadedPivotTables;
214
+ }
215
+ /**
216
+ * Extract table number from pivot table name (e.g., "pivotTable1" -> 1)
217
+ */
218
+ _extractTableNumber(name) {
219
+ const match = name.match(/pivotTable(\d+)/);
220
+ return match ? parseInt(match[1], 10) : 1;
221
+ }
222
+ /**
223
+ * Build a mapping from rId to cacheId using pivotCaches from workbook.xml
224
+ * and workbookRels to determine which definition file corresponds to which cacheId
225
+ */
226
+ _buildCacheIdMap(model) {
227
+ const rIdToCacheId = new Map();
228
+ // pivotCaches from workbook.xml contains {cacheId, rId} mappings
229
+ const pivotCaches = model.pivotCaches || [];
230
+ for (const cache of pivotCaches) {
231
+ if (cache.cacheId && cache.rId) {
232
+ rIdToCacheId.set(cache.rId, parseInt(cache.cacheId, 10));
233
+ }
234
+ }
235
+ return rIdToCacheId;
236
+ }
237
+ /**
238
+ * Build a mapping from definition name to cacheId
239
+ */
240
+ _buildDefinitionToCacheIdMap(model) {
241
+ const definitionToCacheId = new Map();
242
+ const rIdToCacheId = this._buildCacheIdMap(model);
243
+ const workbookRels = model.workbookRels || [];
244
+ // Map workbook rels to get definitionNumber -> cacheId mapping
245
+ for (const rel of workbookRels) {
246
+ if (rel.Type === XLSX.RelType.PivotCacheDefinition && rel.Target) {
247
+ // Extract definition number from target (e.g., "pivotCache/pivotCacheDefinition1.xml" -> 1)
248
+ const match = rel.Target.match(/pivotCacheDefinition(\d+)\.xml/);
249
+ if (match) {
250
+ const defName = `pivotCacheDefinition${match[1]}`;
251
+ const cacheId = rIdToCacheId.get(rel.Id);
252
+ if (cacheId !== undefined) {
253
+ definitionToCacheId.set(defName, cacheId);
254
+ }
255
+ }
256
+ }
257
+ }
258
+ return definitionToCacheId;
259
+ }
260
+ /**
261
+ * Determine the aggregation metric from dataFields
262
+ */
263
+ _determineMetric(dataFields) {
264
+ if (dataFields.length > 0 && dataFields[0].subtotal === "count") {
265
+ return "count";
266
+ }
267
+ return "sum";
135
268
  }
136
269
  async _processWorksheetEntry(stream, model, sheetNo, options, path) {
137
270
  const xform = new WorkSheetXform(options);
@@ -234,6 +367,37 @@ class XLSX {
234
367
  stream.pipe(streamBuf);
235
368
  });
236
369
  }
370
+ async _processPivotTableEntry(stream, model, name) {
371
+ const xform = new PivotTableXform();
372
+ const pivotTable = await xform.parseStream(stream);
373
+ if (pivotTable) {
374
+ model.pivotTables[name] = pivotTable;
375
+ }
376
+ }
377
+ async _processPivotTableRelsEntry(stream, model, name) {
378
+ const xform = new RelationshipsXform();
379
+ const relationships = await xform.parseStream(stream);
380
+ model.pivotTableRels[name] = relationships;
381
+ }
382
+ async _processPivotCacheDefinitionEntry(stream, model, name) {
383
+ const xform = new PivotCacheDefinitionXform();
384
+ const cacheDefinition = await xform.parseStream(stream);
385
+ if (cacheDefinition) {
386
+ model.pivotCacheDefinitions[name] = cacheDefinition;
387
+ }
388
+ }
389
+ async _processPivotCacheDefinitionRelsEntry(stream, model, name) {
390
+ const xform = new RelationshipsXform();
391
+ const relationships = await xform.parseStream(stream);
392
+ model.pivotCacheDefinitionRels[name] = relationships;
393
+ }
394
+ async _processPivotCacheRecordsEntry(stream, model, name) {
395
+ const xform = new PivotCacheRecordsXform();
396
+ const cacheRecords = await xform.parseStream(stream);
397
+ if (cacheRecords) {
398
+ model.pivotCacheRecords[name] = cacheRecords;
399
+ }
400
+ }
237
401
  async read(stream, options) {
238
402
  // Use streaming unzip with fflate
239
403
  const allFiles = {};
@@ -343,7 +507,13 @@ class XLSX {
343
507
  drawingRels: {},
344
508
  comments: {},
345
509
  tables: {},
346
- vmlDrawings: {}
510
+ vmlDrawings: {},
511
+ // Pivot table storage for loaded files
512
+ pivotTables: {},
513
+ pivotTableRels: {},
514
+ pivotCacheDefinitions: {},
515
+ pivotCacheDefinitionRels: {},
516
+ pivotCacheRecords: {}
347
517
  };
348
518
  // Convert fflate format to JSZip-like structure for compatibility
349
519
  const entries = Object.keys(zipData).map(name => ({
@@ -391,6 +561,9 @@ class XLSX {
391
561
  model.views = workbook.views;
392
562
  model.properties = workbook.properties;
393
563
  model.calcProperties = workbook.calcProperties;
564
+ // pivotCaches contains the mapping from rId to cacheId
565
+ // needed for linking pivot tables to their cache data
566
+ model.pivotCaches = workbook.pivotCaches;
394
567
  break;
395
568
  }
396
569
  case "xl/sharedStrings.xml":
@@ -459,6 +632,33 @@ class XLSX {
459
632
  await this._processThemeEntry(stream, model, match[1]);
460
633
  break;
461
634
  }
635
+ // Pivot table files
636
+ match = entryName.match(/xl\/pivotTables\/(pivotTable\d+)[.]xml/);
637
+ if (match) {
638
+ await this._processPivotTableEntry(stream, model, match[1]);
639
+ break;
640
+ }
641
+ match = entryName.match(/xl\/pivotTables\/_rels\/(pivotTable\d+)[.]xml[.]rels/);
642
+ if (match) {
643
+ await this._processPivotTableRelsEntry(stream, model, match[1]);
644
+ break;
645
+ }
646
+ // Pivot cache files
647
+ match = entryName.match(/xl\/pivotCache\/(pivotCacheDefinition\d+)[.]xml/);
648
+ if (match) {
649
+ await this._processPivotCacheDefinitionEntry(stream, model, match[1]);
650
+ break;
651
+ }
652
+ match = entryName.match(/xl\/pivotCache\/_rels\/(pivotCacheDefinition\d+)[.]xml[.]rels/);
653
+ if (match) {
654
+ await this._processPivotCacheDefinitionRelsEntry(stream, model, match[1]);
655
+ break;
656
+ }
657
+ match = entryName.match(/xl\/pivotCache\/(pivotCacheRecords\d+)[.]xml/);
658
+ if (match) {
659
+ await this._processPivotCacheRecordsEntry(stream, model, match[1]);
660
+ break;
661
+ }
462
662
  }
463
663
  }
464
664
  }
@@ -515,21 +715,24 @@ class XLSX {
515
715
  Target: "sharedStrings.xml"
516
716
  });
517
717
  }
518
- if ((model.pivotTables || []).length) {
519
- const pivotTable = model.pivotTables[0];
718
+ // Add relationships for all pivot tables
719
+ (model.pivotTables || []).forEach((pivotTable) => {
520
720
  pivotTable.rId = `rId${count++}`;
521
721
  relationships.push({
522
722
  Id: pivotTable.rId,
523
723
  Type: XLSX.RelType.PivotCacheDefinition,
524
- Target: "pivotCache/pivotCacheDefinition1.xml"
724
+ Target: `pivotCache/pivotCacheDefinition${pivotTable.tableNumber}.xml`
525
725
  });
526
- }
527
- model.worksheets.forEach((worksheet) => {
726
+ });
727
+ model.worksheets.forEach((worksheet, index) => {
728
+ // Use sequential index (1-based) for file naming, not worksheet.id (sheetId)
729
+ // sheetId can be non-sequential (e.g., 1, 3, 5) which would create gaps in filenames
528
730
  worksheet.rId = `rId${count++}`;
731
+ worksheet.fileIndex = index + 1; // Store for use in addWorksheets and content types
529
732
  relationships.push({
530
733
  Id: worksheet.rId,
531
734
  Type: XLSX.RelType.Worksheet,
532
- Target: `worksheets/sheet${worksheet.id}.xml`
735
+ Target: `worksheets/sheet${worksheet.fileIndex}.xml`
533
736
  });
534
737
  });
535
738
  const xform = new RelationshipsXform();
@@ -558,22 +761,24 @@ class XLSX {
558
761
  const commentsXform = new CommentsXform();
559
762
  const vmlNotesXform = new VmlNotesXform();
560
763
  // write sheets
561
- model.worksheets.forEach((worksheet) => {
764
+ model.worksheets.forEach((worksheet, index) => {
765
+ // Use fileIndex if set by addWorkbookRels, otherwise use index + 1
766
+ const fileIndex = worksheet.fileIndex || index + 1;
562
767
  let xmlStream = new XmlStream();
563
768
  worksheetXform.render(xmlStream, worksheet);
564
- zip.append(xmlStream.xml, { name: `xl/worksheets/sheet${worksheet.id}.xml` });
769
+ zip.append(xmlStream.xml, { name: `xl/worksheets/sheet${fileIndex}.xml` });
565
770
  if (worksheet.rels && worksheet.rels.length) {
566
771
  xmlStream = new XmlStream();
567
772
  relationshipsXform.render(xmlStream, worksheet.rels);
568
- zip.append(xmlStream.xml, { name: `xl/worksheets/_rels/sheet${worksheet.id}.xml.rels` });
773
+ zip.append(xmlStream.xml, { name: `xl/worksheets/_rels/sheet${fileIndex}.xml.rels` });
569
774
  }
570
775
  if (worksheet.comments.length > 0) {
571
776
  xmlStream = new XmlStream();
572
777
  commentsXform.render(xmlStream, worksheet);
573
- zip.append(xmlStream.xml, { name: `xl/comments${worksheet.id}.xml` });
778
+ zip.append(xmlStream.xml, { name: `xl/comments${fileIndex}.xml` });
574
779
  xmlStream = new XmlStream();
575
780
  vmlNotesXform.render(xmlStream, worksheet);
576
- zip.append(xmlStream.xml, { name: `xl/drawings/vmlDrawing${worksheet.id}.vml` });
781
+ zip.append(xmlStream.xml, { name: `xl/drawings/vmlDrawing${fileIndex}.vml` });
577
782
  }
578
783
  });
579
784
  }
@@ -626,37 +831,55 @@ class XLSX {
626
831
  if (!model.pivotTables.length) {
627
832
  return;
628
833
  }
629
- const pivotTable = model.pivotTables[0];
630
834
  const pivotCacheRecordsXform = new PivotCacheRecordsXform();
631
835
  const pivotCacheDefinitionXform = new PivotCacheDefinitionXform();
632
836
  const pivotTableXform = new PivotTableXform();
633
837
  const relsXform = new RelationshipsXform();
634
- // pivot cache records
635
- let xml = pivotCacheRecordsXform.toXml(pivotTable);
636
- zip.append(xml, { name: "xl/pivotCache/pivotCacheRecords1.xml" });
637
- // pivot cache definition
638
- xml = pivotCacheDefinitionXform.toXml(pivotTable);
639
- zip.append(xml, { name: "xl/pivotCache/pivotCacheDefinition1.xml" });
640
- // pivot cache definition rels
641
- xml = relsXform.toXml([
642
- {
643
- Id: "rId1",
644
- Type: XLSX.RelType.PivotCacheRecords,
645
- Target: "pivotCacheRecords1.xml"
838
+ // Generate files for each pivot table
839
+ model.pivotTables.forEach((pivotTable) => {
840
+ const n = pivotTable.tableNumber;
841
+ // For loaded pivot tables, use the stored cache data
842
+ // For new pivot tables, use the source data
843
+ const isLoaded = pivotTable.isLoaded;
844
+ if (isLoaded) {
845
+ // Loaded pivot table - use stored cache definition and records
846
+ if (pivotTable.cacheDefinition) {
847
+ const xml = pivotCacheDefinitionXform.toXml(pivotTable.cacheDefinition);
848
+ zip.append(xml, { name: `xl/pivotCache/pivotCacheDefinition${n}.xml` });
849
+ }
850
+ if (pivotTable.cacheRecords) {
851
+ const xml = pivotCacheRecordsXform.toXml(pivotTable.cacheRecords);
852
+ zip.append(xml, { name: `xl/pivotCache/pivotCacheRecords${n}.xml` });
853
+ }
646
854
  }
647
- ]);
648
- zip.append(xml, { name: "xl/pivotCache/_rels/pivotCacheDefinition1.xml.rels" });
649
- // pivot table
650
- xml = pivotTableXform.toXml(pivotTable);
651
- zip.append(xml, { name: "xl/pivotTables/pivotTable1.xml" });
652
- xml = relsXform.toXml([
653
- {
654
- Id: "rId1",
655
- Type: XLSX.RelType.PivotCacheDefinition,
656
- Target: "../pivotCache/pivotCacheDefinition1.xml"
855
+ else {
856
+ // New pivot table - generate from source
857
+ let xml = pivotCacheRecordsXform.toXml(pivotTable);
858
+ zip.append(xml, { name: `xl/pivotCache/pivotCacheRecords${n}.xml` });
859
+ xml = pivotCacheDefinitionXform.toXml(pivotTable);
860
+ zip.append(xml, { name: `xl/pivotCache/pivotCacheDefinition${n}.xml` });
657
861
  }
658
- ]);
659
- zip.append(xml, { name: "xl/pivotTables/_rels/pivotTable1.xml.rels" });
862
+ // pivot cache definition rels (same for both)
863
+ let xml = relsXform.toXml([
864
+ {
865
+ Id: "rId1",
866
+ Type: XLSX.RelType.PivotCacheRecords,
867
+ Target: `pivotCacheRecords${n}.xml`
868
+ }
869
+ ]);
870
+ zip.append(xml, { name: `xl/pivotCache/_rels/pivotCacheDefinition${n}.xml.rels` });
871
+ // pivot table
872
+ xml = pivotTableXform.toXml(pivotTable);
873
+ zip.append(xml, { name: `xl/pivotTables/pivotTable${n}.xml` });
874
+ xml = relsXform.toXml([
875
+ {
876
+ Id: "rId1",
877
+ Type: XLSX.RelType.PivotCacheDefinition,
878
+ Target: `../pivotCache/pivotCacheDefinition${n}.xml`
879
+ }
880
+ ]);
881
+ zip.append(xml, { name: `xl/pivotTables/_rels/pivotTable${n}.xml.rels` });
882
+ });
660
883
  }
661
884
  _finalize(zip) {
662
885
  return new Promise((resolve, reject) => {
@@ -1,8 +1,11 @@
1
1
  import type { Cell, CellValueType } from "./cell.js";
2
2
  import type { Worksheet } from "./worksheet.js";
3
- import type { Style, NumFmt, Font, Alignment, Protection, Borders, Fill } from "../types.js";
3
+ import type { Style, NumFmt, Font, Alignment, Protection, Borders, Fill, CellValue } from "../types.js";
4
+ /** Header value type - can be a single value or array for multi-row headers */
5
+ export type ColumnHeaderValue = CellValue | CellValue[];
4
6
  export interface ColumnDefn {
5
- header?: string | string[];
7
+ /** Column header value(s). Can be string, Date, number, or any CellValue type */
8
+ header?: ColumnHeaderValue;
6
9
  key?: string;
7
10
  width?: number;
8
11
  outlineLevel?: number;
@@ -45,12 +48,16 @@ declare class Column {
45
48
  get isCustomWidth(): boolean;
46
49
  get defn(): ColumnDefn;
47
50
  set defn(value: ColumnDefn | undefined);
48
- get headers(): string[];
49
51
  /**
50
- * Can be a string to set one row high header or an array to set multi-row high header
52
+ * Get header values as an array (for multi-row header support)
51
53
  */
52
- get header(): string | string[] | undefined;
53
- set header(value: string | string[] | undefined);
54
+ get headers(): CellValue[];
55
+ /**
56
+ * Can be a single value or an array for multi-row headers.
57
+ * Supports any CellValue type including Date, number, string, etc.
58
+ */
59
+ get header(): ColumnHeaderValue | undefined;
60
+ set header(value: ColumnHeaderValue | undefined);
54
61
  /**
55
62
  * The name of the properties associated with this column in each row
56
63
  */
@@ -1,22 +1,148 @@
1
- interface PivotTableModel {
2
- sourceSheet: any;
1
+ import type { Table } from "./table.js";
2
+ /**
3
+ * Interface representing the source data abstraction for pivot tables.
4
+ * This allows both Worksheet and Table to be used as pivot table data sources.
5
+ */
6
+ export interface PivotTableSource {
7
+ /** Name of the source (worksheet name or table name) */
8
+ name: string;
9
+ /** Get row values by 1-indexed row number */
10
+ getRow(rowNumber: number): {
11
+ values: any[];
12
+ };
13
+ /** Get column values by 1-indexed column number */
14
+ getColumn(columnNumber: number): {
15
+ values: any[];
16
+ };
17
+ /** Get all sheet values as a sparse 2D array */
18
+ getSheetValues(): any[][];
19
+ /** Dimensions with short range reference (e.g., "A1:E10") */
20
+ dimensions: {
21
+ shortRange: string;
22
+ };
23
+ }
24
+ /**
25
+ * Model for creating a new pivot table.
26
+ * Pass this to worksheet.addPivotTable() to create a pivot table.
27
+ */
28
+ export interface PivotTableModel {
29
+ /**
30
+ * Source worksheet for the pivot table data.
31
+ * Either sourceSheet or sourceTable must be provided (mutually exclusive).
32
+ */
33
+ sourceSheet?: PivotTableSource;
34
+ /**
35
+ * Source table for the pivot table data.
36
+ * Either sourceSheet or sourceTable must be provided (mutually exclusive).
37
+ * The table must have headerRow=true and contain at least one data row.
38
+ */
39
+ sourceTable?: Table;
40
+ /** Column names to use as row fields in the pivot table */
3
41
  rows: string[];
4
- columns: string[];
42
+ /**
43
+ * Column names to use as column fields in the pivot table.
44
+ * If omitted or empty, Excel will use "Values" as the column field.
45
+ * @default []
46
+ */
47
+ columns?: string[];
48
+ /** Column names to aggregate as values in the pivot table */
5
49
  values: string[];
6
- metric?: string;
50
+ /**
51
+ * Aggregation metric for the pivot table values.
52
+ * - 'sum': Sum of values (default)
53
+ * - 'count': Count of values
54
+ * @default 'sum'
55
+ */
56
+ metric?: "sum" | "count";
57
+ /**
58
+ * Controls whether pivot table style overrides worksheet column widths.
59
+ * - '0': Preserve worksheet column widths (useful for custom sizing)
60
+ * - '1': Apply pivot table style width/height (default Excel behavior)
61
+ * @default '1'
62
+ */
63
+ applyWidthHeightFormats?: "0" | "1";
7
64
  }
8
- interface CacheField {
65
+ /**
66
+ * Represents a cache field in a pivot table.
67
+ * Cache fields store unique values from source columns for row/column grouping.
68
+ */
69
+ export interface CacheField {
70
+ /** Name of the field (column header from source) */
9
71
  name: string;
72
+ /** Unique values for row/column fields, null for value fields */
10
73
  sharedItems: any[] | null;
11
74
  }
12
- interface PivotTable {
13
- sourceSheet: any;
75
+ /** Aggregation function types for pivot table data fields */
76
+ export type PivotTableSubtotal = "sum" | "count" | "average" | "max" | "min" | "product" | "countNums" | "stdDev" | "stdDevP" | "var" | "varP";
77
+ /**
78
+ * Data field configuration for pivot table aggregation.
79
+ * Defines how values are aggregated in the pivot table.
80
+ */
81
+ export interface DataField {
82
+ /** Display name for the data field (e.g., "Sum of Sales") */
83
+ name: string;
84
+ /** Index of the source field in cacheFields */
85
+ fld: number;
86
+ /** Base field index for calculated fields */
87
+ baseField?: number;
88
+ /** Base item index for calculated fields */
89
+ baseItem?: number;
90
+ /** Aggregation function (default: 'sum') */
91
+ subtotal?: PivotTableSubtotal;
92
+ }
93
+ /**
94
+ * Internal pivot table representation used by the library.
95
+ * This is the processed model after calling makePivotTable().
96
+ */
97
+ export interface PivotTable {
98
+ /** Source data adapter (always present for new pivot tables) */
99
+ source?: PivotTableSource;
100
+ /** Field indices for row fields */
14
101
  rows: number[];
102
+ /** Field indices for column fields */
15
103
  columns: number[];
104
+ /** Field indices for value fields */
16
105
  values: number[];
17
- metric: string;
106
+ /** Aggregation metric */
107
+ metric: "sum" | "count";
108
+ /** Cache fields with shared items */
18
109
  cacheFields: CacheField[];
110
+ /** Cache ID for linking to pivot cache */
19
111
  cacheId: string;
112
+ /** Width/height format setting */
113
+ applyWidthHeightFormats: "0" | "1";
114
+ /** 1-indexed table number for file naming (pivotTable1.xml, pivotTable2.xml, etc.) */
115
+ tableNumber: number;
116
+ /** Flag indicating this pivot table was loaded from file (not newly created) */
117
+ isLoaded?: boolean;
118
+ /** Data fields for loaded pivot tables */
119
+ dataFields?: DataField[];
120
+ /** Cache definition for loaded pivot tables */
121
+ cacheDefinition?: ParsedCacheDefinition;
122
+ /** Cache records for loaded pivot tables */
123
+ cacheRecords?: ParsedCacheRecords;
124
+ }
125
+ /**
126
+ * Parsed cache definition from loaded pivot table files.
127
+ */
128
+ export interface ParsedCacheDefinition {
129
+ sourceRef?: string;
130
+ sourceSheet?: string;
131
+ cacheFields: CacheField[];
132
+ recordCount?: number;
133
+ rId?: string;
134
+ isLoaded?: boolean;
135
+ }
136
+ /**
137
+ * Parsed cache records from loaded pivot table files.
138
+ */
139
+ export interface ParsedCacheRecords {
140
+ records: Array<Array<{
141
+ type: string;
142
+ value?: any;
143
+ }>>;
144
+ count: number;
145
+ isLoaded?: boolean;
20
146
  }
21
147
  declare function makePivotTable(worksheet: any, model: PivotTableModel): PivotTable;
22
- export { makePivotTable, type PivotTable, type PivotTableModel };
148
+ export { makePivotTable };
@@ -36,6 +36,8 @@ interface WorkbookModel {
36
36
  themes?: unknown;
37
37
  media: WorkbookMedia[];
38
38
  pivotTables: PivotTable[];
39
+ /** Loaded pivot tables from file - used during reconciliation */
40
+ loadedPivotTables?: any[];
39
41
  calcProperties: Partial<CalculationProperties>;
40
42
  }
41
43
  declare class Workbook {
@@ -13,6 +13,7 @@ export { Image } from "./doc/image.js";
13
13
  export * from "./doc/anchor.js";
14
14
  export { Table } from "./doc/table.js";
15
15
  export { DataValidations } from "./doc/data-validations.js";
16
+ export type { PivotTable, PivotTableModel, PivotTableSource, CacheField, DataField, PivotTableSubtotal, ParsedCacheDefinition, ParsedCacheRecords } from "./doc/pivot-table.js";
16
17
  export * from "./doc/enums.js";
17
18
  export * from "./types.js";
18
19
  export * from "./utils/sheet-utils.js";
@@ -0,0 +1,42 @@
1
+ import { BaseXform } from "../base-xform.js";
2
+ /**
3
+ * Parsed cache field model containing name and shared items (if any)
4
+ */
5
+ interface CacheFieldModel {
6
+ name: string;
7
+ sharedItems: string[] | null;
8
+ containsNumber?: boolean;
9
+ containsInteger?: boolean;
10
+ minValue?: number;
11
+ maxValue?: number;
12
+ }
13
+ /**
14
+ * Xform for parsing individual <cacheField> elements within a pivot cache definition.
15
+ *
16
+ * Example XML:
17
+ * ```xml
18
+ * <cacheField name="Category" numFmtId="0">
19
+ * <sharedItems count="3">
20
+ * <s v="A" />
21
+ * <s v="B" />
22
+ * <s v="C" />
23
+ * </sharedItems>
24
+ * </cacheField>
25
+ *
26
+ * <cacheField name="Value" numFmtId="0">
27
+ * <sharedItems containsSemiMixedTypes="0" containsString="0"
28
+ * containsNumber="1" containsInteger="1" minValue="5" maxValue="45" />
29
+ * </cacheField>
30
+ * ```
31
+ */
32
+ declare class CacheFieldXform extends BaseXform {
33
+ model: CacheFieldModel | null;
34
+ private inSharedItems;
35
+ constructor();
36
+ get tag(): string;
37
+ reset(): void;
38
+ parseOpen(node: any): boolean;
39
+ parseText(_text: string): void;
40
+ parseClose(name: string): boolean;
41
+ }
42
+ export { CacheFieldXform, type CacheFieldModel };