@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.
- package/dist/browser/excelts.iife.js +1057 -201
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +63 -33
- package/dist/cjs/doc/column.js +7 -3
- package/dist/cjs/doc/pivot-table.js +149 -61
- package/dist/cjs/doc/workbook.js +3 -1
- package/dist/cjs/doc/worksheet.js +0 -2
- package/dist/cjs/stream/xlsx/worksheet-writer.js +1 -1
- package/dist/cjs/utils/unzip/zip-parser.js +2 -5
- package/dist/cjs/xlsx/xform/book/workbook-xform.js +3 -0
- package/dist/cjs/xlsx/xform/core/content-types-xform.js +19 -14
- package/dist/cjs/xlsx/xform/pivot-table/cache-field-xform.js +135 -0
- package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +7 -4
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
- package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
- package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -0
- package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +14 -3
- package/dist/cjs/xlsx/xlsx.js +261 -38
- package/dist/esm/doc/column.js +7 -3
- package/dist/esm/doc/pivot-table.js +150 -62
- package/dist/esm/doc/workbook.js +3 -1
- package/dist/esm/doc/worksheet.js +0 -2
- package/dist/esm/stream/xlsx/worksheet-writer.js +1 -1
- package/dist/esm/utils/unzip/zip-parser.js +2 -5
- package/dist/esm/xlsx/xform/book/workbook-xform.js +3 -0
- package/dist/esm/xlsx/xform/core/content-types-xform.js +19 -14
- package/dist/esm/xlsx/xform/pivot-table/cache-field-xform.js +132 -0
- package/dist/esm/xlsx/xform/pivot-table/cache-field.js +7 -4
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
- package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
- package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -0
- package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +14 -3
- package/dist/esm/xlsx/xlsx.js +261 -38
- package/dist/types/doc/column.d.ts +13 -6
- package/dist/types/doc/pivot-table.d.ts +135 -9
- package/dist/types/doc/workbook.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/xlsx/xform/pivot-table/cache-field-xform.d.ts +42 -0
- package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +45 -6
- package/dist/types/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +52 -5
- package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +98 -5
- package/dist/types/xlsx/xlsx.d.ts +27 -0
- package/package.json +17 -17
package/dist/esm/xlsx/xlsx.js
CHANGED
|
@@ -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
|
-
|
|
519
|
-
|
|
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:
|
|
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.
|
|
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${
|
|
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${
|
|
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${
|
|
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${
|
|
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
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
52
|
+
* Get header values as an array (for multi-row header support)
|
|
51
53
|
*/
|
|
52
|
-
get
|
|
53
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
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 {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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 };
|