@cj-tech-master/excelts 2.0.0-canary.20251228024848.8822305 → 2.0.1-canary.20251228093548.379d895
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.esm.js +146 -116
- package/dist/browser/excelts.esm.js.map +1 -1
- package/dist/browser/excelts.esm.min.js +22 -22
- package/dist/browser/excelts.iife.js +146 -116
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +22 -22
- package/dist/cjs/doc/pivot-table.js +37 -3
- package/dist/cjs/doc/worksheet.js +0 -1
- package/dist/cjs/stream/xlsx/worksheet-writer.js +0 -1
- package/dist/cjs/xlsx/xform/book/sheet-xform.js +3 -2
- package/dist/cjs/xlsx/xform/book/workbook-properties-xform.js +1 -1
- package/dist/cjs/xlsx/xform/core/content-types-xform.js +12 -6
- package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +11 -4
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +1 -10
- package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +39 -17
- package/dist/cjs/xlsx/xform/sheet/page-setup-xform.js +4 -3
- package/dist/cjs/xlsx/xform/sheet/row-xform.js +1 -1
- package/dist/cjs/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
- package/dist/cjs/xlsx/xform/sheet/sheet-view-xform.js +8 -4
- package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +2 -2
- package/dist/cjs/xlsx/xform/style/font-xform.js +36 -23
- package/dist/cjs/xlsx/xform/table/auto-filter-xform.js +3 -1
- package/dist/cjs/xlsx/xform/table/table-column-xform.js +2 -1
- package/dist/cjs/xlsx/xform/table/table-xform.js +5 -9
- package/dist/esm/doc/pivot-table.js +37 -3
- package/dist/esm/doc/worksheet.js +0 -1
- package/dist/esm/stream/xlsx/worksheet-writer.js +0 -1
- package/dist/esm/xlsx/xform/book/sheet-xform.js +3 -2
- package/dist/esm/xlsx/xform/book/workbook-properties-xform.js +1 -1
- package/dist/esm/xlsx/xform/core/content-types-xform.js +12 -6
- package/dist/esm/xlsx/xform/pivot-table/cache-field.js +11 -4
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +1 -10
- package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +39 -17
- package/dist/esm/xlsx/xform/sheet/page-setup-xform.js +4 -3
- package/dist/esm/xlsx/xform/sheet/row-xform.js +1 -1
- package/dist/esm/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
- package/dist/esm/xlsx/xform/sheet/sheet-view-xform.js +8 -4
- package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +2 -2
- package/dist/esm/xlsx/xform/style/font-xform.js +36 -23
- package/dist/esm/xlsx/xform/table/auto-filter-xform.js +3 -1
- package/dist/esm/xlsx/xform/table/table-column-xform.js +2 -1
- package/dist/esm/xlsx/xform/table/table-xform.js +5 -9
- package/dist/types/doc/pivot-table.d.ts +5 -1
- package/dist/types/stream/xlsx/worksheet-writer.d.ts +1 -1
- package/dist/types/types.d.ts +1 -1
- package/dist/types/xlsx/xform/pivot-table/cache-field.d.ts +5 -1
- package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +0 -5
- package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +0 -3
- package/dist/types/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -1
- package/dist/types/xlsx/xform/sheet/worksheet-xform.d.ts +1 -1
- package/dist/types/xlsx/xform/style/font-xform.d.ts +1 -0
- package/dist/types/xlsx/xform/table/table-xform.d.ts +0 -4
- package/package.json +1 -1
|
@@ -34,8 +34,11 @@ function createTableSourceAdapter(table) {
|
|
|
34
34
|
const endRow = startRow + tableModel.rows.length; // header row + data rows
|
|
35
35
|
const endCol = startCol + columnNames.length - 1;
|
|
36
36
|
const shortRange = colCache.encode(startRow, startCol, endRow, endCol);
|
|
37
|
+
// Use the worksheet name (not table name) for pivotCacheDefinition's worksheetSource
|
|
38
|
+
// The sheet attribute in worksheetSource must reference the actual worksheet name
|
|
39
|
+
const worksheetName = table.worksheet.name;
|
|
37
40
|
return {
|
|
38
|
-
name:
|
|
41
|
+
name: worksheetName,
|
|
39
42
|
getRow(rowNumber) {
|
|
40
43
|
if (rowNumber === 1) {
|
|
41
44
|
return { values: headerRow };
|
|
@@ -184,12 +187,43 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
|
184
187
|
}
|
|
185
188
|
return toSortedArray(uniqueValues);
|
|
186
189
|
};
|
|
190
|
+
// Calculate min/max for numeric fields
|
|
191
|
+
const getMinMax = (columnIndex) => {
|
|
192
|
+
const columnValues = source.getColumn(columnIndex).values;
|
|
193
|
+
let min = Infinity;
|
|
194
|
+
let max = -Infinity;
|
|
195
|
+
let hasNumeric = false;
|
|
196
|
+
for (let i = 2; i < columnValues.length; i++) {
|
|
197
|
+
const v = columnValues[i];
|
|
198
|
+
if (typeof v === "number" && !isNaN(v)) {
|
|
199
|
+
hasNumeric = true;
|
|
200
|
+
if (v < min) {
|
|
201
|
+
min = v;
|
|
202
|
+
}
|
|
203
|
+
if (v > max) {
|
|
204
|
+
max = v;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return hasNumeric ? { minValue: min, maxValue: max } : null;
|
|
209
|
+
};
|
|
187
210
|
// Build result array
|
|
188
211
|
const result = [];
|
|
189
212
|
for (const columnIndex of range(1, names.length)) {
|
|
190
213
|
const name = names[columnIndex];
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
if (sharedItemsFields.has(name)) {
|
|
215
|
+
result.push({ name, sharedItems: aggregate(columnIndex) });
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Numeric field - calculate min/max
|
|
219
|
+
const minMax = getMinMax(columnIndex);
|
|
220
|
+
result.push({
|
|
221
|
+
name,
|
|
222
|
+
sharedItems: null,
|
|
223
|
+
minValue: minMax?.minValue,
|
|
224
|
+
maxValue: minMax?.maxValue
|
|
225
|
+
});
|
|
226
|
+
}
|
|
193
227
|
}
|
|
194
228
|
return result;
|
|
195
229
|
}
|
|
@@ -3,9 +3,10 @@ import { BaseXform } from "../base-xform.js";
|
|
|
3
3
|
class WorksheetXform extends BaseXform {
|
|
4
4
|
render(xmlStream, model) {
|
|
5
5
|
xmlStream.leafNode("sheet", {
|
|
6
|
-
sheetId: model.id,
|
|
7
6
|
name: model.name,
|
|
8
|
-
|
|
7
|
+
sheetId: model.id,
|
|
8
|
+
// Excel doesn't output state when it's 'visible' (default)
|
|
9
|
+
state: model.state === "visible" ? undefined : model.state,
|
|
9
10
|
"r:id": model.rId
|
|
10
11
|
});
|
|
11
12
|
}
|
|
@@ -38,7 +38,7 @@ class ContentTypesXform extends BaseXform {
|
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
if ((model.pivotTables || []).length) {
|
|
41
|
-
// Add content types for
|
|
41
|
+
// Add content types for pivot cache (definition and records)
|
|
42
42
|
(model.pivotTables || []).forEach((pivotTable) => {
|
|
43
43
|
const n = pivotTable.tableNumber;
|
|
44
44
|
xmlStream.leafNode("Override", {
|
|
@@ -49,10 +49,6 @@ class ContentTypesXform extends BaseXform {
|
|
|
49
49
|
PartName: `/xl/pivotCache/pivotCacheRecords${n}.xml`,
|
|
50
50
|
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
|
|
51
51
|
});
|
|
52
|
-
xmlStream.leafNode("Override", {
|
|
53
|
-
PartName: `/xl/pivotTables/pivotTable${n}.xml`,
|
|
54
|
-
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
|
|
55
|
-
});
|
|
56
52
|
});
|
|
57
53
|
}
|
|
58
54
|
xmlStream.leafNode("Override", {
|
|
@@ -78,6 +74,16 @@ class ContentTypesXform extends BaseXform {
|
|
|
78
74
|
});
|
|
79
75
|
});
|
|
80
76
|
}
|
|
77
|
+
// Add pivot table overrides after tables (matches Excel order)
|
|
78
|
+
if ((model.pivotTables || []).length) {
|
|
79
|
+
(model.pivotTables || []).forEach((pivotTable) => {
|
|
80
|
+
const n = pivotTable.tableNumber;
|
|
81
|
+
xmlStream.leafNode("Override", {
|
|
82
|
+
PartName: `/xl/pivotTables/pivotTable${n}.xml`,
|
|
83
|
+
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
81
87
|
if (model.drawings) {
|
|
82
88
|
model.drawings.forEach((drawing) => {
|
|
83
89
|
xmlStream.leafNode("Override", {
|
|
@@ -86,7 +92,7 @@ class ContentTypesXform extends BaseXform {
|
|
|
86
92
|
});
|
|
87
93
|
});
|
|
88
94
|
}
|
|
89
|
-
if (model.commentRefs) {
|
|
95
|
+
if (model.commentRefs && model.commentRefs.length) {
|
|
90
96
|
xmlStream.leafNode("Default", {
|
|
91
97
|
Extension: "vml",
|
|
92
98
|
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { xmlEncode } from "../../../utils/utils.js";
|
|
2
2
|
class CacheField {
|
|
3
|
-
constructor({ name, sharedItems }) {
|
|
3
|
+
constructor({ name, sharedItems, minValue, maxValue }) {
|
|
4
4
|
// string type
|
|
5
5
|
//
|
|
6
6
|
// {
|
|
@@ -14,10 +14,14 @@ class CacheField {
|
|
|
14
14
|
//
|
|
15
15
|
// {
|
|
16
16
|
// 'name': 'D',
|
|
17
|
-
// 'sharedItems': null
|
|
17
|
+
// 'sharedItems': null,
|
|
18
|
+
// 'minValue': 5,
|
|
19
|
+
// 'maxValue': 45
|
|
18
20
|
// }
|
|
19
21
|
this.name = name;
|
|
20
22
|
this.sharedItems = sharedItems;
|
|
23
|
+
this.minValue = minValue;
|
|
24
|
+
this.maxValue = maxValue;
|
|
21
25
|
}
|
|
22
26
|
render() {
|
|
23
27
|
// PivotCache Field: http://www.datypic.com/sc/ooxml/e-ssml_cacheField-1.html
|
|
@@ -26,9 +30,12 @@ class CacheField {
|
|
|
26
30
|
const escapedName = xmlEncode(this.name);
|
|
27
31
|
// integer types
|
|
28
32
|
if (this.sharedItems === null) {
|
|
29
|
-
//
|
|
33
|
+
// Build minValue/maxValue attributes if available
|
|
34
|
+
const minMaxAttrs = this.minValue !== undefined && this.maxValue !== undefined
|
|
35
|
+
? ` minValue="${this.minValue}" maxValue="${this.maxValue}"`
|
|
36
|
+
: "";
|
|
30
37
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
31
|
-
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" />
|
|
38
|
+
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1"${minMaxAttrs} />
|
|
32
39
|
</cacheField>`;
|
|
33
40
|
}
|
|
34
41
|
// string types - escape XML special characters in each shared item value
|
|
@@ -50,8 +50,6 @@ class PivotCacheDefinitionXform extends BaseXform {
|
|
|
50
50
|
...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
|
|
51
51
|
"r:id": "rId1",
|
|
52
52
|
refreshOnLoad: "1", // important for our implementation to work
|
|
53
|
-
refreshedBy: "Author",
|
|
54
|
-
refreshedDate: "45125.026046874998",
|
|
55
53
|
createdVersion: "8",
|
|
56
54
|
refreshedVersion: "8",
|
|
57
55
|
minRefreshableVersion: "3",
|
|
@@ -79,8 +77,6 @@ class PivotCacheDefinitionXform extends BaseXform {
|
|
|
79
77
|
...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
|
|
80
78
|
"r:id": model.rId || "rId1",
|
|
81
79
|
refreshOnLoad: model.refreshOnLoad || "1",
|
|
82
|
-
refreshedBy: model.refreshedBy || "Author",
|
|
83
|
-
refreshedDate: model.refreshedDate || "45125.026046874998",
|
|
84
80
|
createdVersion: model.createdVersion || "8",
|
|
85
81
|
refreshedVersion: model.refreshedVersion || "8",
|
|
86
82
|
minRefreshableVersion: model.minRefreshableVersion || "3",
|
|
@@ -114,8 +110,6 @@ class PivotCacheDefinitionXform extends BaseXform {
|
|
|
114
110
|
cacheFields: [],
|
|
115
111
|
rId: attributes["r:id"],
|
|
116
112
|
refreshOnLoad: attributes.refreshOnLoad,
|
|
117
|
-
refreshedBy: attributes.refreshedBy,
|
|
118
|
-
refreshedDate: attributes.refreshedDate,
|
|
119
113
|
createdVersion: attributes.createdVersion,
|
|
120
114
|
refreshedVersion: attributes.refreshedVersion,
|
|
121
115
|
minRefreshableVersion: attributes.minRefreshableVersion,
|
|
@@ -180,9 +174,6 @@ class PivotCacheDefinitionXform extends BaseXform {
|
|
|
180
174
|
}
|
|
181
175
|
PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES = {
|
|
182
176
|
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
183
|
-
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
184
|
-
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
185
|
-
"mc:Ignorable": "xr",
|
|
186
|
-
"xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
|
|
177
|
+
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
187
178
|
};
|
|
188
179
|
export { PivotCacheDefinitionXform };
|
|
@@ -55,8 +55,6 @@ class PivotTableXform extends BaseXform {
|
|
|
55
55
|
*/
|
|
56
56
|
renderNew(xmlStream, model) {
|
|
57
57
|
const { rows, columns, values, cacheFields, cacheId, applyWidthHeightFormats } = model;
|
|
58
|
-
// Generate unique UID for each pivot table to prevent Excel treating them as identical
|
|
59
|
-
const uniqueUid = `{${crypto.randomUUID().toUpperCase()}}`;
|
|
60
58
|
// Build rowItems - need one <i> for each unique value in row fields, plus grand total
|
|
61
59
|
const rowItems = buildRowItems(rows, cacheFields);
|
|
62
60
|
// Build colItems - need one <i> for each unique value in col fields, plus grand total
|
|
@@ -68,15 +66,20 @@ class PivotTableXform extends BaseXform {
|
|
|
68
66
|
// - firstHeaderRow: 1 (column headers are in first row of pivot table)
|
|
69
67
|
// - firstDataRow: 2 (data starts in second row)
|
|
70
68
|
// - firstDataCol: 1 (data starts in second column, after row labels)
|
|
71
|
-
// Calculate ref based on actual data size
|
|
72
|
-
|
|
69
|
+
// Calculate ref based on actual data size:
|
|
70
|
+
// - Start row: 3
|
|
71
|
+
// - Header rows: 2 (column label row + subheader row)
|
|
72
|
+
// - Data rows: rowFieldItemCount
|
|
73
|
+
// - Grand total row: 1
|
|
74
|
+
// endRow = 3 + 2 + rowFieldItemCount + 1 - 1 = 5 + rowFieldItemCount
|
|
75
|
+
// Or simplified: startRow (3) + 1 (column labels) + rowFieldItemCount (data) + 1 (grand total)
|
|
76
|
+
const endRow = 3 + 1 + rowFieldItemCount + 1; // = 5 + rowFieldItemCount
|
|
73
77
|
const endCol = 1 + colFieldItemCount + 1; // start col + data cols + grand total
|
|
74
78
|
const endColLetter = String.fromCharCode(64 + endCol);
|
|
75
79
|
const locationRef = `A3:${endColLetter}${endRow}`;
|
|
76
80
|
xmlStream.openXml(XmlStream.StdDocAttributes);
|
|
77
81
|
xmlStream.openNode(this.tag, {
|
|
78
82
|
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
79
|
-
"xr:uid": uniqueUid,
|
|
80
83
|
name: "PivotTable2",
|
|
81
84
|
cacheId,
|
|
82
85
|
applyNumberFormats: "0",
|
|
@@ -153,11 +156,9 @@ class PivotTableXform extends BaseXform {
|
|
|
153
156
|
* Render loaded pivot table (preserving original structure)
|
|
154
157
|
*/
|
|
155
158
|
renderLoaded(xmlStream, model) {
|
|
156
|
-
const uniqueUid = model.uid || `{${crypto.randomUUID().toUpperCase()}}`;
|
|
157
159
|
xmlStream.openXml(XmlStream.StdDocAttributes);
|
|
158
160
|
xmlStream.openNode(this.tag, {
|
|
159
161
|
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
160
|
-
"xr:uid": uniqueUid,
|
|
161
162
|
name: model.name || "PivotTable1",
|
|
162
163
|
cacheId: model.cacheId,
|
|
163
164
|
applyNumberFormats: model.applyNumberFormats || "0",
|
|
@@ -470,17 +471,15 @@ class PivotTableXform extends BaseXform {
|
|
|
470
471
|
}
|
|
471
472
|
}
|
|
472
473
|
PivotTableXform.PIVOT_TABLE_ATTRIBUTES = {
|
|
473
|
-
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
474
|
-
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
475
|
-
"mc:Ignorable": "xr",
|
|
476
|
-
"xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
|
|
474
|
+
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
477
475
|
};
|
|
478
476
|
// Helpers
|
|
479
477
|
/**
|
|
480
478
|
* Build rowItems XML - one item for each unique value in row fields, plus grand total.
|
|
481
479
|
* Each <i> represents a row in the pivot table.
|
|
482
|
-
* - Regular items: <i><x v="index"/></i>
|
|
480
|
+
* - Regular items: <i><x/></i> for index 0, <i><x v="index"/></i> for index > 0
|
|
483
481
|
* - Grand total: <i t="grand"><x/></i>
|
|
482
|
+
* Note: When v=0, the v attribute should be omitted (Excel convention)
|
|
484
483
|
*/
|
|
485
484
|
function buildRowItems(rows, cacheFields) {
|
|
486
485
|
if (rows.length === 0) {
|
|
@@ -494,8 +493,14 @@ function buildRowItems(rows, cacheFields) {
|
|
|
494
493
|
// Build items: one for each unique value + grand total
|
|
495
494
|
const items = [];
|
|
496
495
|
// Regular items - reference each unique value by index
|
|
496
|
+
// Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
|
|
497
497
|
for (let i = 0; i < itemCount; i++) {
|
|
498
|
-
|
|
498
|
+
if (i === 0) {
|
|
499
|
+
items.push("<i><x /></i>");
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
items.push(`<i><x v="${i}" /></i>`);
|
|
503
|
+
}
|
|
499
504
|
}
|
|
500
505
|
// Grand total row
|
|
501
506
|
items.push('<i t="grand"><x /></i>');
|
|
@@ -507,6 +512,7 @@ function buildRowItems(rows, cacheFields) {
|
|
|
507
512
|
/**
|
|
508
513
|
* Build colItems XML - one item for each unique value in column fields, plus grand total.
|
|
509
514
|
* When there are multiple data fields (values), each column value may have sub-columns.
|
|
515
|
+
* Note: When v=0, the v attribute should be omitted (Excel convention)
|
|
510
516
|
*/
|
|
511
517
|
function buildColItems(columns, cacheFields, valueCount) {
|
|
512
518
|
if (columns.length === 0) {
|
|
@@ -515,7 +521,12 @@ function buildColItems(columns, cacheFields, valueCount) {
|
|
|
515
521
|
// Multiple values: one column per value + grand total
|
|
516
522
|
const items = [];
|
|
517
523
|
for (let i = 0; i < valueCount; i++) {
|
|
518
|
-
|
|
524
|
+
if (i === 0) {
|
|
525
|
+
items.push("<i><x /></i>");
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
items.push(`<i><x v="${i}" /></i>`);
|
|
529
|
+
}
|
|
519
530
|
}
|
|
520
531
|
items.push('<i t="grand"><x /></i>');
|
|
521
532
|
return { count: items.length, xml: items.join("\n ") };
|
|
@@ -530,8 +541,14 @@ function buildColItems(columns, cacheFields, valueCount) {
|
|
|
530
541
|
// Build items: one for each unique value + grand total
|
|
531
542
|
const items = [];
|
|
532
543
|
// Regular items - reference each unique value by index
|
|
544
|
+
// Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
|
|
533
545
|
for (let i = 0; i < itemCount; i++) {
|
|
534
|
-
|
|
546
|
+
if (i === 0) {
|
|
547
|
+
items.push("<i><x /></i>");
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
items.push(`<i><x v="${i}" /></i>`);
|
|
551
|
+
}
|
|
535
552
|
}
|
|
536
553
|
// Grand total column
|
|
537
554
|
items.push('<i t="grand"><x /></i>');
|
|
@@ -577,22 +594,27 @@ function renderPivotFields(pivotTable) {
|
|
|
577
594
|
}
|
|
578
595
|
function renderPivotField(fieldType, sharedItems) {
|
|
579
596
|
// fieldType: 'row', 'column', 'value', null
|
|
580
|
-
|
|
597
|
+
// Note: defaultSubtotal="0" should only be on value fields and non-axis fields,
|
|
598
|
+
// NOT on row/column axis fields (Excel will auto-calculate subtotals for them)
|
|
581
599
|
if (fieldType === "row" || fieldType === "column") {
|
|
582
600
|
const axis = fieldType === "row" ? "axisRow" : "axisCol";
|
|
601
|
+
// Row and column fields should NOT have defaultSubtotal="0"
|
|
602
|
+
const axisAttributes = 'compact="0" outline="0" showAll="0"';
|
|
583
603
|
// items = one for each shared item + one default item
|
|
584
604
|
const itemsXml = [
|
|
585
605
|
...sharedItems.map((_item, index) => `<item x="${index}" />`),
|
|
586
606
|
'<item t="default" />' // Required default item for subtotals/grand totals
|
|
587
607
|
].join("\n ");
|
|
588
608
|
return `
|
|
589
|
-
<pivotField axis="${axis}" ${
|
|
609
|
+
<pivotField axis="${axis}" ${axisAttributes}>
|
|
590
610
|
<items count="${sharedItems.length + 1}">
|
|
591
611
|
${itemsXml}
|
|
592
612
|
</items>
|
|
593
613
|
</pivotField>
|
|
594
614
|
`;
|
|
595
615
|
}
|
|
616
|
+
// Value fields and non-axis fields should have defaultSubtotal="0"
|
|
617
|
+
const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
|
|
596
618
|
return `
|
|
597
619
|
<pivotField
|
|
598
620
|
${fieldType === "value" ? 'dataField="1"' : ""}
|
|
@@ -48,9 +48,10 @@ class PageSetupXform extends BaseXform {
|
|
|
48
48
|
draft: booleanToXml(model.draft),
|
|
49
49
|
cellComments: cellCommentsToXml(model.cellComments),
|
|
50
50
|
errors: errorsToXml(model.errors),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
// Only output non-default values (matches Excel behavior)
|
|
52
|
+
scale: model.scale !== 100 ? model.scale : undefined,
|
|
53
|
+
fitToWidth: model.fitToWidth !== 1 ? model.fitToWidth : undefined,
|
|
54
|
+
fitToHeight: model.fitToHeight !== 1 ? model.fitToHeight : undefined,
|
|
54
55
|
firstPageNumber: model.firstPageNumber,
|
|
55
56
|
useFirstPageNumber: booleanToXml(!!model.firstPageNumber),
|
|
56
57
|
usePrinterDefaults: booleanToXml(model.usePrinterDefaults),
|
|
@@ -48,7 +48,7 @@ class RowXform extends BaseXform {
|
|
|
48
48
|
xmlStream.addAttribute("s", model.styleId);
|
|
49
49
|
xmlStream.addAttribute("customFormat", "1");
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
// Note: dyDescent is MS extension, not output by default (Excel auto-calculates)
|
|
52
52
|
if (model.outlineLevel) {
|
|
53
53
|
xmlStream.addAttribute("outlineLevel", model.outlineLevel);
|
|
54
54
|
}
|
|
@@ -7,10 +7,13 @@ class SheetFormatPropertiesXform extends BaseXform {
|
|
|
7
7
|
if (model) {
|
|
8
8
|
const attributes = {
|
|
9
9
|
defaultRowHeight: model.defaultRowHeight,
|
|
10
|
-
outlineLevelRow
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Only output outlineLevelRow/Col when non-zero (matches Excel behavior)
|
|
11
|
+
outlineLevelRow: model.outlineLevelRow || undefined,
|
|
12
|
+
outlineLevelCol: model.outlineLevelCol || undefined,
|
|
13
|
+
// Only output dyDescent if explicitly set (MS extension, not ECMA-376 standard)
|
|
14
|
+
"x14ac:dyDescent": model.dyDescent || undefined
|
|
13
15
|
};
|
|
16
|
+
// Only output defaultColWidth if explicitly set
|
|
14
17
|
if (model.defaultColWidth) {
|
|
15
18
|
attributes.defaultColWidth = model.defaultColWidth;
|
|
16
19
|
}
|
|
@@ -20,16 +20,20 @@ class SheetViewXform extends BaseXform {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
render(xmlStream, model) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Build initial attributes with correct order to match Excel output
|
|
24
|
+
const initialAttrs = {};
|
|
25
|
+
if (model.tabSelected) {
|
|
26
|
+
initialAttrs.tabSelected = "1";
|
|
27
|
+
}
|
|
28
|
+
initialAttrs.workbookViewId = model.workbookViewId || 0;
|
|
29
|
+
xmlStream.openNode("sheetView", initialAttrs);
|
|
26
30
|
const add = function (name, value, included) {
|
|
27
31
|
if (included) {
|
|
28
32
|
xmlStream.addAttribute(name, value);
|
|
29
33
|
}
|
|
30
34
|
};
|
|
31
35
|
add("rightToLeft", "1", model.rightToLeft === true);
|
|
32
|
-
|
|
36
|
+
// tabSelected is now in initialAttrs
|
|
33
37
|
add("showRuler", "0", model.showRuler === false);
|
|
34
38
|
add("showRowColHeaders", "0", model.showRowColHeaders === false);
|
|
35
39
|
add("showGridLines", "0", model.showGridLines === false);
|
|
@@ -511,7 +511,7 @@ WorkSheetXform.WORKSHEET_ATTRIBUTES = {
|
|
|
511
511
|
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
512
512
|
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
513
513
|
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
514
|
-
"
|
|
515
|
-
"
|
|
514
|
+
"xmlns:x14ac": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac",
|
|
515
|
+
"mc:Ignorable": "x14ac"
|
|
516
516
|
};
|
|
517
517
|
export { WorkSheetXform };
|
|
@@ -9,35 +9,48 @@ class FontXform extends BaseXform {
|
|
|
9
9
|
constructor(options) {
|
|
10
10
|
super();
|
|
11
11
|
this.options = options || FontXform.OPTIONS;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
// Define properties in render order (Excel's expected order)
|
|
13
|
+
const fontProperties = [
|
|
14
|
+
{ tag: "b", prop: "bold", xform: new BooleanXform({ tag: "b", attr: "val" }) },
|
|
15
|
+
{ tag: "i", prop: "italic", xform: new BooleanXform({ tag: "i", attr: "val" }) },
|
|
16
|
+
{ tag: "u", prop: "underline", xform: new UnderlineXform() },
|
|
17
|
+
{ tag: "strike", prop: "strike", xform: new BooleanXform({ tag: "strike", attr: "val" }) },
|
|
18
|
+
{
|
|
19
|
+
tag: "condense",
|
|
20
|
+
prop: "condense",
|
|
21
|
+
xform: new BooleanXform({ tag: "condense", attr: "val" })
|
|
22
|
+
},
|
|
23
|
+
{ tag: "extend", prop: "extend", xform: new BooleanXform({ tag: "extend", attr: "val" }) },
|
|
24
|
+
{ tag: "outline", prop: "outline", xform: new BooleanXform({ tag: "outline", attr: "val" }) },
|
|
25
|
+
{ tag: "shadow", prop: "shadow", xform: new BooleanXform({ tag: "shadow", attr: "val" }) },
|
|
26
|
+
{ tag: "sz", prop: "size", xform: new IntegerXform({ tag: "sz", attr: "val" }) },
|
|
27
|
+
{ tag: "color", prop: "color", xform: new ColorXform() },
|
|
28
|
+
{
|
|
29
|
+
tag: this.options.fontNameTag,
|
|
30
|
+
prop: "name",
|
|
31
|
+
xform: new StringXform({ tag: this.options.fontNameTag, attr: "val" })
|
|
32
|
+
},
|
|
33
|
+
{ tag: "family", prop: "family", xform: new IntegerXform({ tag: "family", attr: "val" }) },
|
|
34
|
+
{ tag: "scheme", prop: "scheme", xform: new StringXform({ tag: "scheme", attr: "val" }) },
|
|
35
|
+
{ tag: "charset", prop: "charset", xform: new IntegerXform({ tag: "charset", attr: "val" }) },
|
|
36
|
+
{
|
|
37
|
+
tag: "vertAlign",
|
|
38
|
+
prop: "vertAlign",
|
|
39
|
+
xform: new StringXform({ tag: "vertAlign", attr: "val" })
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
// Build map and renderOrder from single source of truth
|
|
43
|
+
this.map = Object.fromEntries(fontProperties.map(p => [p.tag, { prop: p.prop, xform: p.xform }]));
|
|
44
|
+
this.renderOrder = fontProperties.map(p => p.tag);
|
|
32
45
|
}
|
|
33
46
|
get tag() {
|
|
34
47
|
return this.options.tagName;
|
|
35
48
|
}
|
|
36
49
|
render(xmlStream, model) {
|
|
37
|
-
const { map } = this;
|
|
50
|
+
const { map, renderOrder } = this;
|
|
38
51
|
xmlStream.openNode(this.options.tagName);
|
|
39
|
-
|
|
40
|
-
map[tag].xform.render(xmlStream, model[
|
|
52
|
+
renderOrder.forEach(tag => {
|
|
53
|
+
map[tag].xform.render(xmlStream, model[map[tag].prop]);
|
|
41
54
|
});
|
|
42
55
|
xmlStream.closeNode();
|
|
43
56
|
}
|
|
@@ -17,7 +17,9 @@ class AutoFilterXform extends BaseXform {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
render(xmlStream, model) {
|
|
20
|
-
xmlStream.openNode(this.tag, {
|
|
20
|
+
xmlStream.openNode(this.tag, {
|
|
21
|
+
ref: model.autoFilterRef
|
|
22
|
+
});
|
|
21
23
|
model.columns.forEach((column) => {
|
|
22
24
|
this.map.filterColumn.render(xmlStream, column);
|
|
23
25
|
});
|
|
@@ -15,7 +15,8 @@ class TableColumnXform extends BaseXform {
|
|
|
15
15
|
id: model.id.toString(),
|
|
16
16
|
name: model.name,
|
|
17
17
|
totalsRowLabel: model.totalsRowLabel,
|
|
18
|
-
totalsRowFunction
|
|
18
|
+
// Excel doesn't output totalsRowFunction when value is 'none'
|
|
19
|
+
totalsRowFunction: model.totalsRowFunction === "none" ? undefined : model.totalsRowFunction,
|
|
19
20
|
dxfId: model.dxfId
|
|
20
21
|
});
|
|
21
22
|
}
|
|
@@ -40,8 +40,8 @@ class TableXform extends BaseXform {
|
|
|
40
40
|
displayName: model.displayName || model.name,
|
|
41
41
|
ref: model.tableRef,
|
|
42
42
|
totalsRowCount: model.totalsRow ? "1" : undefined,
|
|
43
|
-
|
|
44
|
-
headerRowCount: model.headerRow ?
|
|
43
|
+
// Excel doesn't output headerRowCount when it's 1 (default) or when there's a header row
|
|
44
|
+
headerRowCount: model.headerRow ? undefined : "0"
|
|
45
45
|
});
|
|
46
46
|
this.map.autoFilter.render(xmlStream, model);
|
|
47
47
|
this.map.tableColumns.render(xmlStream, model.columns);
|
|
@@ -62,7 +62,8 @@ class TableXform extends BaseXform {
|
|
|
62
62
|
displayName: attributes.displayName || attributes.name,
|
|
63
63
|
tableRef: attributes.ref,
|
|
64
64
|
totalsRow: attributes.totalsRowCount === "1",
|
|
65
|
-
|
|
65
|
+
// ECMA-376: headerRowCount defaults to 1, so missing attribute means has header
|
|
66
|
+
headerRow: attributes.headerRowCount !== "0"
|
|
66
67
|
};
|
|
67
68
|
break;
|
|
68
69
|
default:
|
|
@@ -120,11 +121,6 @@ class TableXform extends BaseXform {
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
TableXform.TABLE_ATTRIBUTES = {
|
|
123
|
-
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
124
|
-
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
125
|
-
"mc:Ignorable": "xr xr3",
|
|
126
|
-
"xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision",
|
|
127
|
-
"xmlns:xr3": "http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"
|
|
128
|
-
// 'xr:uid': '{00000000-000C-0000-FFFF-FFFF00000000}',
|
|
124
|
+
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
129
125
|
};
|
|
130
126
|
export { TableXform };
|
|
@@ -4,7 +4,7 @@ import type { Table } from "./table";
|
|
|
4
4
|
* This allows both Worksheet and Table to be used as pivot table data sources.
|
|
5
5
|
*/
|
|
6
6
|
export interface PivotTableSource {
|
|
7
|
-
/** Name of the source (
|
|
7
|
+
/** Name of the worksheet containing the source data (used in pivotCacheDefinition) */
|
|
8
8
|
name: string;
|
|
9
9
|
/** Get row values by 1-indexed row number */
|
|
10
10
|
getRow(rowNumber: number): {
|
|
@@ -71,6 +71,10 @@ export interface CacheField {
|
|
|
71
71
|
name: string;
|
|
72
72
|
/** Unique values for row/column fields, null for value fields */
|
|
73
73
|
sharedItems: any[] | null;
|
|
74
|
+
/** Minimum value for numeric fields */
|
|
75
|
+
minValue?: number;
|
|
76
|
+
/** Maximum value for numeric fields */
|
|
77
|
+
maxValue?: number;
|
|
74
78
|
}
|
|
75
79
|
/** Aggregation function types for pivot table data fields */
|
|
76
80
|
export type PivotTableSubtotal = "sum" | "count" | "average" | "max" | "min" | "product" | "countNums" | "stdDev" | "stdDevP" | "var" | "varP";
|
package/dist/types/types.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ export interface WorksheetProperties {
|
|
|
192
192
|
};
|
|
193
193
|
defaultRowHeight: number;
|
|
194
194
|
defaultColWidth?: number;
|
|
195
|
-
dyDescent
|
|
195
|
+
dyDescent?: number;
|
|
196
196
|
showGridLines: boolean;
|
|
197
197
|
}
|
|
198
198
|
export type WorksheetState = "visible" | "hidden" | "veryHidden";
|