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