@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/cjs/doc/column.js
CHANGED
|
@@ -67,6 +67,9 @@ class Column {
|
|
|
67
67
|
this.outlineLevel = 0;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Get header values as an array (for multi-row header support)
|
|
72
|
+
*/
|
|
70
73
|
get headers() {
|
|
71
74
|
if (Array.isArray(this._header)) {
|
|
72
75
|
return this._header;
|
|
@@ -77,7 +80,8 @@ class Column {
|
|
|
77
80
|
return [];
|
|
78
81
|
}
|
|
79
82
|
/**
|
|
80
|
-
* Can be a
|
|
83
|
+
* Can be a single value or an array for multi-row headers.
|
|
84
|
+
* Supports any CellValue type including Date, number, string, etc.
|
|
81
85
|
*/
|
|
82
86
|
get header() {
|
|
83
87
|
return this._header;
|
|
@@ -85,8 +89,8 @@ class Column {
|
|
|
85
89
|
set header(value) {
|
|
86
90
|
if (value !== undefined) {
|
|
87
91
|
this._header = value;
|
|
88
|
-
this.headers.forEach((
|
|
89
|
-
this._worksheet.getCell(index + 1, this.number).value =
|
|
92
|
+
this.headers.forEach((cellValue, index) => {
|
|
93
|
+
this._worksheet.getCell(index + 1, this.number).value = cellValue;
|
|
90
94
|
});
|
|
91
95
|
}
|
|
92
96
|
else {
|
|
@@ -2,25 +2,116 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.makePivotTable = makePivotTable;
|
|
4
4
|
const utils_js_1 = require("../utils/utils");
|
|
5
|
+
const col_cache_js_1 = require("../utils/col-cache");
|
|
5
6
|
// TK(2023-10-10): turn this into a class constructor.
|
|
7
|
+
/**
|
|
8
|
+
* Creates a PivotTableSource adapter from a Table object.
|
|
9
|
+
* This allows Tables to be used as pivot table data sources with the same interface as Worksheets.
|
|
10
|
+
*/
|
|
11
|
+
function createTableSourceAdapter(table) {
|
|
12
|
+
const tableModel = table.model;
|
|
13
|
+
// Validate that table has headerRow enabled (required for pivot table column names)
|
|
14
|
+
if (tableModel.headerRow === false) {
|
|
15
|
+
throw new Error("Cannot create pivot table from a table without headers. Set headerRow: true on the table.");
|
|
16
|
+
}
|
|
17
|
+
// Validate table has data rows
|
|
18
|
+
if (!tableModel.rows || tableModel.rows.length === 0) {
|
|
19
|
+
throw new Error("Cannot create pivot table from an empty table. Add data rows to the table.");
|
|
20
|
+
}
|
|
21
|
+
const columnNames = tableModel.columns.map(col => col.name);
|
|
22
|
+
// Check for duplicate column names
|
|
23
|
+
const nameSet = new Set();
|
|
24
|
+
for (const name of columnNames) {
|
|
25
|
+
if (nameSet.has(name)) {
|
|
26
|
+
throw new Error(`Duplicate column name "${name}" found in table. Pivot tables require unique column names.`);
|
|
27
|
+
}
|
|
28
|
+
nameSet.add(name);
|
|
29
|
+
}
|
|
30
|
+
// Build the full data array: headers + rows
|
|
31
|
+
const headerRow = [undefined, ...columnNames]; // sparse array starting at index 1
|
|
32
|
+
const dataRows = tableModel.rows.map(row => [undefined, ...row]); // sparse array starting at index 1
|
|
33
|
+
// Calculate the range reference for the table
|
|
34
|
+
const tl = tableModel.tl;
|
|
35
|
+
const startRow = tl.row;
|
|
36
|
+
const startCol = tl.col;
|
|
37
|
+
const endRow = startRow + tableModel.rows.length; // header row + data rows
|
|
38
|
+
const endCol = startCol + columnNames.length - 1;
|
|
39
|
+
const shortRange = col_cache_js_1.colCache.encode(startRow, startCol, endRow, endCol);
|
|
40
|
+
return {
|
|
41
|
+
name: tableModel.name,
|
|
42
|
+
getRow(rowNumber) {
|
|
43
|
+
if (rowNumber === 1) {
|
|
44
|
+
return { values: headerRow };
|
|
45
|
+
}
|
|
46
|
+
const dataIndex = rowNumber - 2; // rowNumber 2 maps to index 0
|
|
47
|
+
if (dataIndex >= 0 && dataIndex < dataRows.length) {
|
|
48
|
+
return { values: dataRows[dataIndex] };
|
|
49
|
+
}
|
|
50
|
+
return { values: [] };
|
|
51
|
+
},
|
|
52
|
+
getColumn(columnNumber) {
|
|
53
|
+
// Validate column number is within bounds
|
|
54
|
+
if (columnNumber < 1 || columnNumber > columnNames.length) {
|
|
55
|
+
return { values: [] };
|
|
56
|
+
}
|
|
57
|
+
// Values should be sparse array with header at index 1, data starting at index 2
|
|
58
|
+
const values = [];
|
|
59
|
+
values[1] = columnNames[columnNumber - 1];
|
|
60
|
+
for (let i = 0; i < tableModel.rows.length; i++) {
|
|
61
|
+
values[i + 2] = tableModel.rows[i][columnNumber - 1];
|
|
62
|
+
}
|
|
63
|
+
return { values };
|
|
64
|
+
},
|
|
65
|
+
getSheetValues() {
|
|
66
|
+
// Return sparse array where index 1 is header row, and subsequent indices are data rows
|
|
67
|
+
const result = [];
|
|
68
|
+
result[1] = headerRow;
|
|
69
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
70
|
+
result[i + 2] = dataRows[i];
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
},
|
|
74
|
+
dimensions: { shortRange }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolves the data source from the model, supporting both sourceSheet and sourceTable.
|
|
79
|
+
*/
|
|
80
|
+
function resolveSource(model) {
|
|
81
|
+
if (model.sourceTable) {
|
|
82
|
+
return createTableSourceAdapter(model.sourceTable);
|
|
83
|
+
}
|
|
84
|
+
// For sourceSheet, it already implements the required interface
|
|
85
|
+
return model.sourceSheet;
|
|
86
|
+
}
|
|
6
87
|
function makePivotTable(worksheet, model) {
|
|
7
88
|
// Example `model`:
|
|
8
89
|
// {
|
|
9
|
-
// // Source of data
|
|
10
|
-
// //
|
|
11
|
-
//
|
|
90
|
+
// // Source of data (either sourceSheet OR sourceTable):
|
|
91
|
+
// sourceSheet: worksheet1, // Use entire sheet range
|
|
92
|
+
// // OR
|
|
93
|
+
// sourceTable: table, // Use table data
|
|
12
94
|
//
|
|
13
95
|
// // Pivot table fields: values indicate field names;
|
|
14
|
-
// // they come from the first row in `worksheet1
|
|
96
|
+
// // they come from the first row in `worksheet1` or table column names.
|
|
15
97
|
// rows: ['A', 'B'],
|
|
16
98
|
// columns: ['C'],
|
|
17
99
|
// values: ['E'], // only 1 item possible for now
|
|
18
100
|
// metric: 'sum', // only 'sum' possible for now
|
|
19
101
|
// }
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
102
|
+
// Validate source exists before trying to resolve it
|
|
103
|
+
if (!model.sourceSheet && !model.sourceTable) {
|
|
104
|
+
throw new Error("Either sourceSheet or sourceTable must be provided.");
|
|
105
|
+
}
|
|
106
|
+
if (model.sourceSheet && model.sourceTable) {
|
|
107
|
+
throw new Error("Cannot specify both sourceSheet and sourceTable. Choose one.");
|
|
108
|
+
}
|
|
109
|
+
// Resolve source first to avoid creating adapter multiple times
|
|
110
|
+
const source = resolveSource(model);
|
|
111
|
+
validate(worksheet, model, source);
|
|
112
|
+
const { rows, values } = model;
|
|
113
|
+
const columns = model.columns ?? [];
|
|
114
|
+
const cacheFields = makeCacheFields(source, [...rows, ...columns]);
|
|
24
115
|
const nameToIndex = cacheFields.reduce((result, cacheField, index) => {
|
|
25
116
|
result[cacheField.name] = index;
|
|
26
117
|
return result;
|
|
@@ -28,82 +119,79 @@ function makePivotTable(worksheet, model) {
|
|
|
28
119
|
const rowIndices = rows.map(row => nameToIndex[row]);
|
|
29
120
|
const columnIndices = columns.map(column => nameToIndex[column]);
|
|
30
121
|
const valueIndices = values.map(value => nameToIndex[value]);
|
|
122
|
+
// Calculate tableNumber based on existing pivot tables (1-indexed)
|
|
123
|
+
const tableNumber = worksheet.workbook.pivotTables.length + 1;
|
|
124
|
+
// Base cache ID starts at 10 (Excel convention), each subsequent table increments
|
|
125
|
+
const BASE_CACHE_ID = 10;
|
|
31
126
|
// form pivot table object
|
|
32
127
|
return {
|
|
33
|
-
|
|
128
|
+
source,
|
|
34
129
|
rows: rowIndices,
|
|
35
130
|
columns: columnIndices,
|
|
36
131
|
values: valueIndices,
|
|
37
|
-
metric: "sum",
|
|
132
|
+
metric: model.metric ?? "sum",
|
|
38
133
|
cacheFields,
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
cacheId:
|
|
134
|
+
// Dynamic cacheId: 10 for first table, 11 for second, etc.
|
|
135
|
+
// Used in <pivotTableDefinition> and xl/workbook.xml
|
|
136
|
+
cacheId: String(BASE_CACHE_ID + tableNumber - 1),
|
|
137
|
+
// Control whether pivot table style overrides worksheet column widths
|
|
138
|
+
// '0' = preserve worksheet column widths (useful for custom sizing)
|
|
139
|
+
// '1' = apply pivot table style width/height (default Excel behavior)
|
|
140
|
+
applyWidthHeightFormats: model.applyWidthHeightFormats ?? "1",
|
|
141
|
+
// Table number for file naming (pivotTable1.xml, pivotTable2.xml, etc.)
|
|
142
|
+
tableNumber
|
|
42
143
|
};
|
|
43
144
|
}
|
|
44
|
-
function validate(
|
|
45
|
-
if (
|
|
46
|
-
throw new Error("
|
|
145
|
+
function validate(_worksheet, model, source) {
|
|
146
|
+
if (model.metric && model.metric !== "sum" && model.metric !== "count") {
|
|
147
|
+
throw new Error('Only the "sum" and "count" metrics are supported at this time.');
|
|
47
148
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
for (const name of [...model.rows, ...
|
|
54
|
-
if (!
|
|
55
|
-
throw new Error(`The header name "${name}" was not found in ${
|
|
149
|
+
const columns = model.columns ?? [];
|
|
150
|
+
// Get header names from source (already resolved)
|
|
151
|
+
const headerNames = source.getRow(1).values.slice(1);
|
|
152
|
+
// Use Set for O(1) lookup
|
|
153
|
+
const headerNameSet = new Set(headerNames);
|
|
154
|
+
for (const name of [...model.rows, ...columns, ...model.values]) {
|
|
155
|
+
if (!headerNameSet.has(name)) {
|
|
156
|
+
throw new Error(`The header name "${name}" was not found in ${source.name}.`);
|
|
56
157
|
}
|
|
57
158
|
}
|
|
58
159
|
if (!model.rows.length) {
|
|
59
160
|
throw new Error("No pivot table rows specified.");
|
|
60
161
|
}
|
|
61
|
-
|
|
62
|
-
|
|
162
|
+
// Allow empty columns - Excel will use "Values" as column field
|
|
163
|
+
// But can't have multiple values with columns specified
|
|
164
|
+
if (model.values.length < 1) {
|
|
165
|
+
throw new Error("Must have at least one value.");
|
|
63
166
|
}
|
|
64
|
-
if (model.values.length
|
|
65
|
-
throw new Error("
|
|
167
|
+
if (model.values.length > 1 && columns.length > 0) {
|
|
168
|
+
throw new Error("It is currently not possible to have multiple values when columns are specified. Please either supply an empty array for columns or a single value.");
|
|
66
169
|
}
|
|
67
170
|
}
|
|
68
|
-
function makeCacheFields(
|
|
171
|
+
function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
69
172
|
// Cache fields are used in pivot tables to reference source data.
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
// `worksheet` sheet values [
|
|
76
|
-
// ['A', 'B', 'C', 'D', 'E'],
|
|
77
|
-
// ['a1', 'b1', 'c1', 4, 5],
|
|
78
|
-
// ['a1', 'b2', 'c1', 4, 5],
|
|
79
|
-
// ['a2', 'b1', 'c2', 14, 24],
|
|
80
|
-
// ['a2', 'b2', 'c2', 24, 35],
|
|
81
|
-
// ['a3', 'b1', 'c3', 34, 45],
|
|
82
|
-
// ['a3', 'b2', 'c3', 44, 45]
|
|
83
|
-
// ];
|
|
84
|
-
// fieldNamesWithSharedItems = ['A', 'B', 'C'];
|
|
85
|
-
//
|
|
86
|
-
// into
|
|
87
|
-
//
|
|
88
|
-
// [
|
|
89
|
-
// { name: 'A', sharedItems: ['a1', 'a2', 'a3'] },
|
|
90
|
-
// { name: 'B', sharedItems: ['b1', 'b2'] },
|
|
91
|
-
// { name: 'C', sharedItems: ['c1', 'c2', 'c3'] },
|
|
92
|
-
// { name: 'D', sharedItems: null },
|
|
93
|
-
// { name: 'E', sharedItems: null }
|
|
94
|
-
// ]
|
|
95
|
-
const names = worksheet.getRow(1).values;
|
|
96
|
-
const nameToHasSharedItems = (0, utils_js_1.objectFromProps)(fieldNamesWithSharedItems, true);
|
|
173
|
+
// Fields in fieldNamesWithSharedItems get their unique values extracted as sharedItems.
|
|
174
|
+
// Other fields (typically numeric) have sharedItems = null.
|
|
175
|
+
const names = source.getRow(1).values;
|
|
176
|
+
// Use Set for O(1) lookup instead of object
|
|
177
|
+
const sharedItemsFields = new Set(fieldNamesWithSharedItems);
|
|
97
178
|
const aggregate = (columnIndex) => {
|
|
98
|
-
const columnValues =
|
|
99
|
-
|
|
100
|
-
|
|
179
|
+
const columnValues = source.getColumn(columnIndex).values;
|
|
180
|
+
// Build unique values set directly, skipping header (index 0,1) and null/undefined
|
|
181
|
+
const uniqueValues = new Set();
|
|
182
|
+
for (let i = 2; i < columnValues.length; i++) {
|
|
183
|
+
const v = columnValues[i];
|
|
184
|
+
if (v !== null && v !== undefined) {
|
|
185
|
+
uniqueValues.add(v);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return (0, utils_js_1.toSortedArray)(uniqueValues);
|
|
101
189
|
};
|
|
102
|
-
//
|
|
190
|
+
// Build result array
|
|
103
191
|
const result = [];
|
|
104
192
|
for (const columnIndex of (0, utils_js_1.range)(1, names.length)) {
|
|
105
193
|
const name = names[columnIndex];
|
|
106
|
-
const sharedItems =
|
|
194
|
+
const sharedItems = sharedItemsFields.has(name) ? aggregate(columnIndex) : null;
|
|
107
195
|
result.push({ name, sharedItems });
|
|
108
196
|
}
|
|
109
197
|
return result;
|
package/dist/cjs/doc/workbook.js
CHANGED
|
@@ -199,7 +199,9 @@ class Workbook {
|
|
|
199
199
|
this.views = value.views;
|
|
200
200
|
this._themes = value.themes;
|
|
201
201
|
this.media = value.media || [];
|
|
202
|
-
|
|
202
|
+
// Handle pivot tables - either newly created or loaded from file
|
|
203
|
+
// Loaded pivot tables come from loadedPivotTables after reconciliation
|
|
204
|
+
this.pivotTables = value.pivotTables || value.loadedPivotTables || [];
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
exports.Workbook = Workbook;
|
|
@@ -833,8 +833,6 @@ class Worksheet {
|
|
|
833
833
|
// =========================================================================
|
|
834
834
|
// Pivot Tables
|
|
835
835
|
addPivotTable(model) {
|
|
836
|
-
console.warn(`Warning: Pivot Table support is experimental.
|
|
837
|
-
Please leave feedback at https://github.com/excelts/excelts/discussions/2575`);
|
|
838
836
|
const pivotTable = (0, pivot_table_js_1.makePivotTable)(this, model);
|
|
839
837
|
this.pivotTables.push(pivotTable);
|
|
840
838
|
this.workbook.pivotTables.push(pivotTable);
|
|
@@ -183,6 +183,7 @@ class WorksheetWriter {
|
|
|
183
183
|
this._writeOpenSheetData();
|
|
184
184
|
}
|
|
185
185
|
this._writeCloseSheetData();
|
|
186
|
+
this._writeSheetProtection(); // Note: must be after sheetData and before autoFilter
|
|
186
187
|
this._writeAutoFilter();
|
|
187
188
|
this._writeMergeCells();
|
|
188
189
|
// for some reason, Excel can't handle dimensions at the bottom of the file
|
|
@@ -190,7 +191,6 @@ class WorksheetWriter {
|
|
|
190
191
|
this._writeHyperlinks();
|
|
191
192
|
this._writeConditionalFormatting();
|
|
192
193
|
this._writeDataValidations();
|
|
193
|
-
this._writeSheetProtection();
|
|
194
194
|
this._writePageMargins();
|
|
195
195
|
this._writePageSetup();
|
|
196
196
|
this._writeBackground();
|
|
@@ -181,7 +181,7 @@ function parseZipEntries(data, options = {}) {
|
|
|
181
181
|
reader.skip(2); // disk where central dir starts
|
|
182
182
|
reader.skip(2); // entries on this disk
|
|
183
183
|
let totalEntries = reader.readUint16(); // total entries
|
|
184
|
-
|
|
184
|
+
reader.skip(4); // central directory size (unused)
|
|
185
185
|
let centralDirOffset = reader.readUint32();
|
|
186
186
|
// Check for ZIP64
|
|
187
187
|
const zip64LocatorOffset = findZip64EOCDLocator(data, eocdOffset);
|
|
@@ -200,15 +200,12 @@ function parseZipEntries(data, options = {}) {
|
|
|
200
200
|
zip64Reader.skip(4); // disk number
|
|
201
201
|
zip64Reader.skip(4); // disk with central dir
|
|
202
202
|
const zip64TotalEntries = Number(zip64Reader.readBigUint64());
|
|
203
|
-
|
|
203
|
+
zip64Reader.skip(8); // central directory size (unused)
|
|
204
204
|
const zip64CentralDirOffset = Number(zip64Reader.readBigUint64());
|
|
205
205
|
// Use ZIP64 values if standard values are maxed out
|
|
206
206
|
if (totalEntries === 0xffff) {
|
|
207
207
|
totalEntries = zip64TotalEntries;
|
|
208
208
|
}
|
|
209
|
-
if (centralDirSize === 0xffffffff) {
|
|
210
|
-
centralDirSize = zip64CentralDirSize;
|
|
211
|
-
}
|
|
212
209
|
if (centralDirOffset === 0xffffffff) {
|
|
213
210
|
centralDirOffset = zip64CentralDirOffset;
|
|
214
211
|
}
|
|
@@ -133,6 +133,9 @@ class WorkbookXform extends base_xform_js_1.BaseXform {
|
|
|
133
133
|
if (this.map.definedNames.model) {
|
|
134
134
|
this.model.definedNames = this.map.definedNames.model;
|
|
135
135
|
}
|
|
136
|
+
if (this.map.pivotCaches.model && this.map.pivotCaches.model.length > 0) {
|
|
137
|
+
this.model.pivotCaches = this.map.pivotCaches.model;
|
|
138
|
+
}
|
|
136
139
|
return false;
|
|
137
140
|
default:
|
|
138
141
|
// not quite sure how we get here!
|
|
@@ -31,26 +31,31 @@ class ContentTypesXform extends base_xform_js_1.BaseXform {
|
|
|
31
31
|
PartName: "/xl/workbook.xml",
|
|
32
32
|
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
|
|
33
33
|
});
|
|
34
|
-
model.worksheets.forEach((worksheet) => {
|
|
35
|
-
|
|
34
|
+
model.worksheets.forEach((worksheet, index) => {
|
|
35
|
+
// Use fileIndex if set, otherwise use sequential index (1-based)
|
|
36
|
+
const fileIndex = worksheet.fileIndex || index + 1;
|
|
37
|
+
const name = `/xl/worksheets/sheet${fileIndex}.xml`;
|
|
36
38
|
xmlStream.leafNode("Override", {
|
|
37
39
|
PartName: name,
|
|
38
40
|
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
|
|
39
41
|
});
|
|
40
42
|
});
|
|
41
43
|
if ((model.pivotTables || []).length) {
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
// Add content types for each pivot table
|
|
45
|
+
(model.pivotTables || []).forEach((pivotTable) => {
|
|
46
|
+
const n = pivotTable.tableNumber;
|
|
47
|
+
xmlStream.leafNode("Override", {
|
|
48
|
+
PartName: `/xl/pivotCache/pivotCacheDefinition${n}.xml`,
|
|
49
|
+
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
|
|
50
|
+
});
|
|
51
|
+
xmlStream.leafNode("Override", {
|
|
52
|
+
PartName: `/xl/pivotCache/pivotCacheRecords${n}.xml`,
|
|
53
|
+
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
|
|
54
|
+
});
|
|
55
|
+
xmlStream.leafNode("Override", {
|
|
56
|
+
PartName: `/xl/pivotTables/pivotTable${n}.xml`,
|
|
57
|
+
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
|
|
58
|
+
});
|
|
54
59
|
});
|
|
55
60
|
}
|
|
56
61
|
xmlStream.leafNode("Override", {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CacheFieldXform = void 0;
|
|
4
|
+
const base_xform_js_1 = require("../base-xform");
|
|
5
|
+
const utils_js_1 = require("../../../utils/utils");
|
|
6
|
+
/**
|
|
7
|
+
* Xform for parsing individual <cacheField> elements within a pivot cache definition.
|
|
8
|
+
*
|
|
9
|
+
* Example XML:
|
|
10
|
+
* ```xml
|
|
11
|
+
* <cacheField name="Category" numFmtId="0">
|
|
12
|
+
* <sharedItems count="3">
|
|
13
|
+
* <s v="A" />
|
|
14
|
+
* <s v="B" />
|
|
15
|
+
* <s v="C" />
|
|
16
|
+
* </sharedItems>
|
|
17
|
+
* </cacheField>
|
|
18
|
+
*
|
|
19
|
+
* <cacheField name="Value" numFmtId="0">
|
|
20
|
+
* <sharedItems containsSemiMixedTypes="0" containsString="0"
|
|
21
|
+
* containsNumber="1" containsInteger="1" minValue="5" maxValue="45" />
|
|
22
|
+
* </cacheField>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
class CacheFieldXform extends base_xform_js_1.BaseXform {
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.model = null;
|
|
29
|
+
this.inSharedItems = false;
|
|
30
|
+
}
|
|
31
|
+
get tag() {
|
|
32
|
+
return "cacheField";
|
|
33
|
+
}
|
|
34
|
+
reset() {
|
|
35
|
+
this.model = null;
|
|
36
|
+
this.inSharedItems = false;
|
|
37
|
+
}
|
|
38
|
+
parseOpen(node) {
|
|
39
|
+
const { name, attributes } = node;
|
|
40
|
+
switch (name) {
|
|
41
|
+
case "cacheField":
|
|
42
|
+
// Initialize the model with field name
|
|
43
|
+
this.model = {
|
|
44
|
+
name: (0, utils_js_1.xmlDecode)(attributes.name || ""),
|
|
45
|
+
sharedItems: null
|
|
46
|
+
};
|
|
47
|
+
break;
|
|
48
|
+
case "sharedItems":
|
|
49
|
+
this.inSharedItems = true;
|
|
50
|
+
// Check if this is a numeric field (no string items)
|
|
51
|
+
if (attributes.containsNumber === "1" || attributes.containsInteger === "1") {
|
|
52
|
+
if (this.model) {
|
|
53
|
+
this.model.containsNumber = attributes.containsNumber === "1";
|
|
54
|
+
this.model.containsInteger = attributes.containsInteger === "1";
|
|
55
|
+
if (attributes.minValue !== undefined) {
|
|
56
|
+
this.model.minValue = parseFloat(attributes.minValue);
|
|
57
|
+
}
|
|
58
|
+
if (attributes.maxValue !== undefined) {
|
|
59
|
+
this.model.maxValue = parseFloat(attributes.maxValue);
|
|
60
|
+
}
|
|
61
|
+
// Numeric fields have sharedItems = null
|
|
62
|
+
this.model.sharedItems = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// String field - initialize sharedItems array if count > 0
|
|
67
|
+
if (this.model) {
|
|
68
|
+
const count = parseInt(attributes.count || "0", 10);
|
|
69
|
+
if (count > 0) {
|
|
70
|
+
this.model.sharedItems = [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case "s":
|
|
76
|
+
// String value in sharedItems
|
|
77
|
+
if (this.inSharedItems && this.model?.sharedItems !== null) {
|
|
78
|
+
// Decode XML entities in the value
|
|
79
|
+
const value = (0, utils_js_1.xmlDecode)(attributes.v || "");
|
|
80
|
+
this.model.sharedItems.push(value);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case "n":
|
|
84
|
+
// Numeric value in sharedItems (less common, but possible)
|
|
85
|
+
if (this.inSharedItems && this.model?.sharedItems !== null) {
|
|
86
|
+
const value = parseFloat(attributes.v || "0");
|
|
87
|
+
this.model.sharedItems.push(value);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
case "b":
|
|
91
|
+
// Boolean value in sharedItems
|
|
92
|
+
if (this.inSharedItems && this.model?.sharedItems !== null) {
|
|
93
|
+
const value = attributes.v === "1";
|
|
94
|
+
this.model.sharedItems.push(value);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case "e":
|
|
98
|
+
// Error value in sharedItems
|
|
99
|
+
if (this.inSharedItems && this.model?.sharedItems !== null) {
|
|
100
|
+
const value = `#${attributes.v || "ERROR!"}`;
|
|
101
|
+
this.model.sharedItems.push(value);
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case "m":
|
|
105
|
+
// Missing/null value in sharedItems
|
|
106
|
+
if (this.inSharedItems && this.model?.sharedItems !== null) {
|
|
107
|
+
this.model.sharedItems.push(null);
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
case "d":
|
|
111
|
+
// Date value in sharedItems
|
|
112
|
+
if (this.inSharedItems && this.model?.sharedItems !== null) {
|
|
113
|
+
const value = new Date(attributes.v || "");
|
|
114
|
+
this.model.sharedItems.push(value);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
parseText(_text) {
|
|
121
|
+
// No text content in cacheField elements
|
|
122
|
+
}
|
|
123
|
+
parseClose(name) {
|
|
124
|
+
switch (name) {
|
|
125
|
+
case "cacheField":
|
|
126
|
+
// End of this cacheField element
|
|
127
|
+
return false;
|
|
128
|
+
case "sharedItems":
|
|
129
|
+
this.inSharedItems = false;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.CacheFieldXform = CacheFieldXform;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CacheField = void 0;
|
|
4
|
+
const utils_js_1 = require("../../../utils/utils");
|
|
4
5
|
class CacheField {
|
|
5
6
|
constructor({ name, sharedItems }) {
|
|
6
7
|
// string type
|
|
@@ -24,17 +25,19 @@ class CacheField {
|
|
|
24
25
|
render() {
|
|
25
26
|
// PivotCache Field: http://www.datypic.com/sc/ooxml/e-ssml_cacheField-1.html
|
|
26
27
|
// Shared Items: http://www.datypic.com/sc/ooxml/e-ssml_sharedItems-1.html
|
|
28
|
+
// Escape XML special characters in name attribute
|
|
29
|
+
const escapedName = (0, utils_js_1.xmlEncode)(this.name);
|
|
27
30
|
// integer types
|
|
28
31
|
if (this.sharedItems === null) {
|
|
29
32
|
// TK(2023-07-18): left out attributes... minValue="5" maxValue="45"
|
|
30
|
-
return `<cacheField name="${
|
|
33
|
+
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
31
34
|
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" />
|
|
32
35
|
</cacheField>`;
|
|
33
36
|
}
|
|
34
|
-
// string types
|
|
35
|
-
return `<cacheField name="${
|
|
37
|
+
// string types - escape XML special characters in each shared item value
|
|
38
|
+
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
36
39
|
<sharedItems count="${this.sharedItems.length}">
|
|
37
|
-
${this.sharedItems.map(item => `<s v="${item}" />`).join("")}
|
|
40
|
+
${this.sharedItems.map(item => `<s v="${(0, utils_js_1.xmlEncode)(String(item))}" />`).join("")}
|
|
38
41
|
</sharedItems>
|
|
39
42
|
</cacheField>`;
|
|
40
43
|
}
|