@cj-tech-master/excelts 2.0.1 → 3.0.0-canary.20251228183403.d3eb98d
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 +181 -133
- package/dist/browser/excelts.esm.js.map +1 -1
- package/dist/browser/excelts.esm.min.js +30 -24
- package/dist/browser/excelts.iife.js +181 -133
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +30 -24
- package/dist/cjs/doc/pivot-table.js +47 -6
- 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-xform.js +17 -21
- package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +43 -7
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +1 -10
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-records-xform.js +1 -1
- package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +51 -30
- 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 +47 -6
- 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-xform.js +17 -21
- package/dist/esm/xlsx/xform/pivot-table/cache-field.js +43 -7
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +1 -10
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-records-xform.js +1 -1
- package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +51 -30
- 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-xform.d.ts +1 -1
- package/dist/types/xlsx/xform/pivot-table/cache-field.d.ts +6 -2
- 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
|
@@ -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
|
};
|
|
@@ -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 };
|
|
@@ -108,7 +111,7 @@ function makePivotTable(worksheet, model) {
|
|
|
108
111
|
validate(worksheet, model, source);
|
|
109
112
|
const { rows, values } = model;
|
|
110
113
|
const columns = model.columns ?? [];
|
|
111
|
-
const cacheFields = makeCacheFields(source, [...rows, ...columns]);
|
|
114
|
+
const cacheFields = makeCacheFields(source, [...rows, ...columns], values);
|
|
112
115
|
const nameToIndex = cacheFields.reduce((result, cacheField, index) => {
|
|
113
116
|
result[cacheField.name] = index;
|
|
114
117
|
return result;
|
|
@@ -165,13 +168,15 @@ function validate(_worksheet, model, source) {
|
|
|
165
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.");
|
|
166
169
|
}
|
|
167
170
|
}
|
|
168
|
-
function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
171
|
+
function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
|
|
169
172
|
// Cache fields are used in pivot tables to reference source data.
|
|
170
173
|
// Fields in fieldNamesWithSharedItems get their unique values extracted as sharedItems.
|
|
171
|
-
//
|
|
174
|
+
// Fields in valueFieldNames (but not in fieldNamesWithSharedItems) get min/max calculated.
|
|
175
|
+
// Other fields are unused and get empty sharedItems.
|
|
172
176
|
const names = source.getRow(1).values;
|
|
173
177
|
// Use Set for O(1) lookup instead of object
|
|
174
178
|
const sharedItemsFields = new Set(fieldNamesWithSharedItems);
|
|
179
|
+
const valueFields = new Set(valueFieldNames);
|
|
175
180
|
const aggregate = (columnIndex) => {
|
|
176
181
|
const columnValues = source.getColumn(columnIndex).values;
|
|
177
182
|
// Build unique values set directly, skipping header (index 0,1) and null/undefined
|
|
@@ -184,12 +189,48 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
|
184
189
|
}
|
|
185
190
|
return toSortedArray(uniqueValues);
|
|
186
191
|
};
|
|
192
|
+
// Calculate min/max for numeric fields
|
|
193
|
+
const getMinMax = (columnIndex) => {
|
|
194
|
+
const columnValues = source.getColumn(columnIndex).values;
|
|
195
|
+
let min = Infinity;
|
|
196
|
+
let max = -Infinity;
|
|
197
|
+
let hasNumeric = false;
|
|
198
|
+
for (let i = 2; i < columnValues.length; i++) {
|
|
199
|
+
const v = columnValues[i];
|
|
200
|
+
if (typeof v === "number" && !isNaN(v)) {
|
|
201
|
+
hasNumeric = true;
|
|
202
|
+
if (v < min) {
|
|
203
|
+
min = v;
|
|
204
|
+
}
|
|
205
|
+
if (v > max) {
|
|
206
|
+
max = v;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return hasNumeric ? { minValue: min, maxValue: max } : null;
|
|
211
|
+
};
|
|
187
212
|
// Build result array
|
|
188
213
|
const result = [];
|
|
189
214
|
for (const columnIndex of range(1, names.length)) {
|
|
190
215
|
const name = names[columnIndex];
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
if (sharedItemsFields.has(name)) {
|
|
217
|
+
// Field used for rows/columns - extract unique values as sharedItems
|
|
218
|
+
result.push({ name, sharedItems: aggregate(columnIndex) });
|
|
219
|
+
}
|
|
220
|
+
else if (valueFields.has(name)) {
|
|
221
|
+
// Field used only for values (aggregation) - 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
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Unused field - just empty sharedItems (like Excel does)
|
|
232
|
+
result.push({ name, sharedItems: null });
|
|
233
|
+
}
|
|
193
234
|
}
|
|
194
235
|
return result;
|
|
195
236
|
}
|
|
@@ -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"
|
|
@@ -44,28 +44,24 @@ class CacheFieldXform extends BaseXform {
|
|
|
44
44
|
break;
|
|
45
45
|
case "sharedItems":
|
|
46
46
|
this.inSharedItems = true;
|
|
47
|
-
//
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.model.minValue = parseFloat(attributes.minValue);
|
|
54
|
-
}
|
|
55
|
-
if (attributes.maxValue !== undefined) {
|
|
56
|
-
this.model.maxValue = parseFloat(attributes.maxValue);
|
|
57
|
-
}
|
|
58
|
-
// Numeric fields have sharedItems = null
|
|
59
|
-
this.model.sharedItems = null;
|
|
47
|
+
// Store numeric field metadata
|
|
48
|
+
if (this.model) {
|
|
49
|
+
this.model.containsNumber = attributes.containsNumber === "1";
|
|
50
|
+
this.model.containsInteger = attributes.containsInteger === "1";
|
|
51
|
+
if (attributes.minValue !== undefined) {
|
|
52
|
+
this.model.minValue = parseFloat(attributes.minValue);
|
|
60
53
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
if (attributes.maxValue !== undefined) {
|
|
55
|
+
this.model.maxValue = parseFloat(attributes.maxValue);
|
|
56
|
+
}
|
|
57
|
+
// Initialize sharedItems array if count > 0 (for both string and numeric fields)
|
|
58
|
+
const count = parseInt(attributes.count || "0", 10);
|
|
59
|
+
if (count > 0) {
|
|
60
|
+
this.model.sharedItems = [];
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// No count means no individual items (pure numeric field)
|
|
64
|
+
this.model.sharedItems = null;
|
|
69
65
|
}
|
|
70
66
|
}
|
|
71
67
|
break;
|
|
@@ -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
|
// {
|
|
@@ -10,28 +10,64 @@ class CacheField {
|
|
|
10
10
|
//
|
|
11
11
|
// or
|
|
12
12
|
//
|
|
13
|
-
// integer type
|
|
13
|
+
// integer type (no sharedItems)
|
|
14
14
|
//
|
|
15
15
|
// {
|
|
16
16
|
// 'name': 'D',
|
|
17
|
-
// 'sharedItems': null
|
|
17
|
+
// 'sharedItems': null,
|
|
18
|
+
// 'minValue': 5,
|
|
19
|
+
// 'maxValue': 45
|
|
20
|
+
// }
|
|
21
|
+
//
|
|
22
|
+
// or
|
|
23
|
+
//
|
|
24
|
+
// numeric type with shared items (used as both row/column and value field)
|
|
25
|
+
//
|
|
26
|
+
// {
|
|
27
|
+
// 'name': 'C',
|
|
28
|
+
// 'sharedItems': [5, 24, 35, 45]
|
|
18
29
|
// }
|
|
19
30
|
this.name = name;
|
|
20
31
|
this.sharedItems = sharedItems;
|
|
32
|
+
this.minValue = minValue;
|
|
33
|
+
this.maxValue = maxValue;
|
|
21
34
|
}
|
|
22
35
|
render() {
|
|
23
36
|
// PivotCache Field: http://www.datypic.com/sc/ooxml/e-ssml_cacheField-1.html
|
|
24
37
|
// Shared Items: http://www.datypic.com/sc/ooxml/e-ssml_sharedItems-1.html
|
|
25
38
|
// Escape XML special characters in name attribute
|
|
26
39
|
const escapedName = xmlEncode(this.name);
|
|
27
|
-
//
|
|
40
|
+
// No shared items - field not used for rows/columns
|
|
28
41
|
if (this.sharedItems === null) {
|
|
29
|
-
//
|
|
42
|
+
// If no minValue/maxValue, this is an unused field - use empty sharedItems like Excel does
|
|
43
|
+
if (this.minValue === undefined || this.maxValue === undefined) {
|
|
44
|
+
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
45
|
+
<sharedItems />
|
|
46
|
+
</cacheField>`;
|
|
47
|
+
}
|
|
48
|
+
// Numeric field used only for values (not rows/columns) - include min/max
|
|
30
49
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
31
|
-
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" />
|
|
50
|
+
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" minValue="${this.minValue}" maxValue="${this.maxValue}" />
|
|
51
|
+
</cacheField>`;
|
|
52
|
+
}
|
|
53
|
+
// Shared items exist - check if all values are numeric
|
|
54
|
+
// Note: empty array returns true for every(), so check length first
|
|
55
|
+
const allNumeric = this.sharedItems.length > 0 &&
|
|
56
|
+
this.sharedItems.every(item => typeof item === "number" && Number.isFinite(item));
|
|
57
|
+
const allInteger = allNumeric && this.sharedItems.every(item => Number.isInteger(item));
|
|
58
|
+
if (allNumeric) {
|
|
59
|
+
// Numeric shared items - used when field is both a row/column field AND a value field
|
|
60
|
+
// This allows Excel to both group by unique values AND perform aggregation
|
|
61
|
+
const minValue = Math.min(...this.sharedItems);
|
|
62
|
+
const maxValue = Math.max(...this.sharedItems);
|
|
63
|
+
const integerAttr = allInteger ? ' containsInteger="1"' : "";
|
|
64
|
+
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
65
|
+
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1"${integerAttr} minValue="${minValue}" maxValue="${maxValue}" count="${this.sharedItems.length}">
|
|
66
|
+
${this.sharedItems.map(item => `<n v="${item}" />`).join("")}
|
|
67
|
+
</sharedItems>
|
|
32
68
|
</cacheField>`;
|
|
33
69
|
}
|
|
34
|
-
//
|
|
70
|
+
// String shared items - escape XML special characters in each value
|
|
35
71
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
36
72
|
<sharedItems count="${this.sharedItems.length}">
|
|
37
73
|
${this.sharedItems.map(item => `<s v="${xmlEncode(String(item))}" />`).join("")}
|
|
@@ -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 };
|
|
@@ -116,7 +116,7 @@ class PivotCacheRecordsXform extends BaseXform {
|
|
|
116
116
|
}
|
|
117
117
|
return `<s v="${xmlEncode(String(value))}" />`;
|
|
118
118
|
}
|
|
119
|
-
// shared items
|
|
119
|
+
// shared items - use indexOf for value lookup (works for both string and numeric)
|
|
120
120
|
const sharedItemsIndex = sharedItems.indexOf(value);
|
|
121
121
|
if (sharedItemsIndex < 0) {
|
|
122
122
|
throw new Error(`${JSON.stringify(value)} not in sharedItems ${JSON.stringify(sharedItems)}`);
|