@cj-tech-master/excelts 2.0.1-canary.20251228093548.379d895 → 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 +40 -22
- package/dist/browser/excelts.esm.js.map +1 -1
- package/dist/browser/excelts.esm.min.js +16 -10
- package/dist/browser/excelts.iife.js +40 -22
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +16 -10
- package/dist/cjs/doc/pivot-table.js +12 -5
- package/dist/cjs/xlsx/xform/pivot-table/cache-field-xform.js +17 -21
- package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +37 -8
- 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 +15 -16
- package/dist/esm/doc/pivot-table.js +12 -5
- package/dist/esm/xlsx/xform/pivot-table/cache-field-xform.js +17 -21
- package/dist/esm/xlsx/xform/pivot-table/cache-field.js +37 -8
- 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 +15 -16
- 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 +1 -1
- package/package.json +1 -1
|
@@ -114,7 +114,7 @@ function makePivotTable(worksheet, model) {
|
|
|
114
114
|
validate(worksheet, model, source);
|
|
115
115
|
const { rows, values } = model;
|
|
116
116
|
const columns = model.columns ?? [];
|
|
117
|
-
const cacheFields = makeCacheFields(source, [...rows, ...columns]);
|
|
117
|
+
const cacheFields = makeCacheFields(source, [...rows, ...columns], values);
|
|
118
118
|
const nameToIndex = cacheFields.reduce((result, cacheField, index) => {
|
|
119
119
|
result[cacheField.name] = index;
|
|
120
120
|
return result;
|
|
@@ -171,13 +171,15 @@ function validate(_worksheet, model, source) {
|
|
|
171
171
|
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.");
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
-
function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
174
|
+
function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
|
|
175
175
|
// Cache fields are used in pivot tables to reference source data.
|
|
176
176
|
// Fields in fieldNamesWithSharedItems get their unique values extracted as sharedItems.
|
|
177
|
-
//
|
|
177
|
+
// Fields in valueFieldNames (but not in fieldNamesWithSharedItems) get min/max calculated.
|
|
178
|
+
// Other fields are unused and get empty sharedItems.
|
|
178
179
|
const names = source.getRow(1).values;
|
|
179
180
|
// Use Set for O(1) lookup instead of object
|
|
180
181
|
const sharedItemsFields = new Set(fieldNamesWithSharedItems);
|
|
182
|
+
const valueFields = new Set(valueFieldNames);
|
|
181
183
|
const aggregate = (columnIndex) => {
|
|
182
184
|
const columnValues = source.getColumn(columnIndex).values;
|
|
183
185
|
// Build unique values set directly, skipping header (index 0,1) and null/undefined
|
|
@@ -215,10 +217,11 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
|
215
217
|
for (const columnIndex of (0, utils_1.range)(1, names.length)) {
|
|
216
218
|
const name = names[columnIndex];
|
|
217
219
|
if (sharedItemsFields.has(name)) {
|
|
220
|
+
// Field used for rows/columns - extract unique values as sharedItems
|
|
218
221
|
result.push({ name, sharedItems: aggregate(columnIndex) });
|
|
219
222
|
}
|
|
220
|
-
else {
|
|
221
|
-
//
|
|
223
|
+
else if (valueFields.has(name)) {
|
|
224
|
+
// Field used only for values (aggregation) - calculate min/max
|
|
222
225
|
const minMax = getMinMax(columnIndex);
|
|
223
226
|
result.push({
|
|
224
227
|
name,
|
|
@@ -227,6 +230,10 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
|
227
230
|
maxValue: minMax?.maxValue
|
|
228
231
|
});
|
|
229
232
|
}
|
|
233
|
+
else {
|
|
234
|
+
// Unused field - just empty sharedItems (like Excel does)
|
|
235
|
+
result.push({ name, sharedItems: null });
|
|
236
|
+
}
|
|
230
237
|
}
|
|
231
238
|
return result;
|
|
232
239
|
}
|
|
@@ -47,28 +47,24 @@ class CacheFieldXform extends base_xform_1.BaseXform {
|
|
|
47
47
|
break;
|
|
48
48
|
case "sharedItems":
|
|
49
49
|
this.inSharedItems = true;
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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;
|
|
50
|
+
// Store numeric field metadata
|
|
51
|
+
if (this.model) {
|
|
52
|
+
this.model.containsNumber = attributes.containsNumber === "1";
|
|
53
|
+
this.model.containsInteger = attributes.containsInteger === "1";
|
|
54
|
+
if (attributes.minValue !== undefined) {
|
|
55
|
+
this.model.minValue = parseFloat(attributes.minValue);
|
|
63
56
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
if (attributes.maxValue !== undefined) {
|
|
58
|
+
this.model.maxValue = parseFloat(attributes.maxValue);
|
|
59
|
+
}
|
|
60
|
+
// Initialize sharedItems array if count > 0 (for both string and numeric fields)
|
|
61
|
+
const count = parseInt(attributes.count || "0", 10);
|
|
62
|
+
if (count > 0) {
|
|
63
|
+
this.model.sharedItems = [];
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// No count means no individual items (pure numeric field)
|
|
67
|
+
this.model.sharedItems = null;
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
70
|
break;
|
|
@@ -13,7 +13,7 @@ class CacheField {
|
|
|
13
13
|
//
|
|
14
14
|
// or
|
|
15
15
|
//
|
|
16
|
-
// integer type
|
|
16
|
+
// integer type (no sharedItems)
|
|
17
17
|
//
|
|
18
18
|
// {
|
|
19
19
|
// 'name': 'D',
|
|
@@ -21,6 +21,15 @@ class CacheField {
|
|
|
21
21
|
// 'minValue': 5,
|
|
22
22
|
// 'maxValue': 45
|
|
23
23
|
// }
|
|
24
|
+
//
|
|
25
|
+
// or
|
|
26
|
+
//
|
|
27
|
+
// numeric type with shared items (used as both row/column and value field)
|
|
28
|
+
//
|
|
29
|
+
// {
|
|
30
|
+
// 'name': 'C',
|
|
31
|
+
// 'sharedItems': [5, 24, 35, 45]
|
|
32
|
+
// }
|
|
24
33
|
this.name = name;
|
|
25
34
|
this.sharedItems = sharedItems;
|
|
26
35
|
this.minValue = minValue;
|
|
@@ -31,17 +40,37 @@ class CacheField {
|
|
|
31
40
|
// Shared Items: http://www.datypic.com/sc/ooxml/e-ssml_sharedItems-1.html
|
|
32
41
|
// Escape XML special characters in name attribute
|
|
33
42
|
const escapedName = (0, utils_1.xmlEncode)(this.name);
|
|
34
|
-
//
|
|
43
|
+
// No shared items - field not used for rows/columns
|
|
35
44
|
if (this.sharedItems === null) {
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
// If no minValue/maxValue, this is an unused field - use empty sharedItems like Excel does
|
|
46
|
+
if (this.minValue === undefined || this.maxValue === undefined) {
|
|
47
|
+
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
48
|
+
<sharedItems />
|
|
49
|
+
</cacheField>`;
|
|
50
|
+
}
|
|
51
|
+
// Numeric field used only for values (not rows/columns) - include min/max
|
|
40
52
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
41
|
-
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1"${
|
|
53
|
+
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" minValue="${this.minValue}" maxValue="${this.maxValue}" />
|
|
54
|
+
</cacheField>`;
|
|
55
|
+
}
|
|
56
|
+
// Shared items exist - check if all values are numeric
|
|
57
|
+
// Note: empty array returns true for every(), so check length first
|
|
58
|
+
const allNumeric = this.sharedItems.length > 0 &&
|
|
59
|
+
this.sharedItems.every(item => typeof item === "number" && Number.isFinite(item));
|
|
60
|
+
const allInteger = allNumeric && this.sharedItems.every(item => Number.isInteger(item));
|
|
61
|
+
if (allNumeric) {
|
|
62
|
+
// Numeric shared items - used when field is both a row/column field AND a value field
|
|
63
|
+
// This allows Excel to both group by unique values AND perform aggregation
|
|
64
|
+
const minValue = Math.min(...this.sharedItems);
|
|
65
|
+
const maxValue = Math.max(...this.sharedItems);
|
|
66
|
+
const integerAttr = allInteger ? ' containsInteger="1"' : "";
|
|
67
|
+
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
68
|
+
<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1"${integerAttr} minValue="${minValue}" maxValue="${maxValue}" count="${this.sharedItems.length}">
|
|
69
|
+
${this.sharedItems.map(item => `<n v="${item}" />`).join("")}
|
|
70
|
+
</sharedItems>
|
|
42
71
|
</cacheField>`;
|
|
43
72
|
}
|
|
44
|
-
//
|
|
73
|
+
// String shared items - escape XML special characters in each value
|
|
45
74
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
46
75
|
<sharedItems count="${this.sharedItems.length}">
|
|
47
76
|
${this.sharedItems.map(item => `<s v="${(0, utils_1.xmlEncode)(String(item))}" />`).join("")}
|
|
@@ -119,7 +119,7 @@ class PivotCacheRecordsXform extends base_xform_1.BaseXform {
|
|
|
119
119
|
}
|
|
120
120
|
return `<s v="${(0, utils_1.xmlEncode)(String(value))}" />`;
|
|
121
121
|
}
|
|
122
|
-
// shared items
|
|
122
|
+
// shared items - use indexOf for value lookup (works for both string and numeric)
|
|
123
123
|
const sharedItemsIndex = sharedItems.indexOf(value);
|
|
124
124
|
if (sharedItemsIndex < 0) {
|
|
125
125
|
throw new Error(`${JSON.stringify(value)} not in sharedItems ${JSON.stringify(sharedItems)}`);
|
|
@@ -585,25 +585,24 @@ function renderPivotFields(pivotTable) {
|
|
|
585
585
|
const valueSet = new Set(pivotTable.values);
|
|
586
586
|
return pivotTable.cacheFields
|
|
587
587
|
.map((cacheField, fieldIndex) => {
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
: valueSet.has(fieldIndex)
|
|
593
|
-
? "value"
|
|
594
|
-
: null;
|
|
595
|
-
return renderPivotField(fieldType, cacheField.sharedItems);
|
|
588
|
+
const isRow = rowSet.has(fieldIndex);
|
|
589
|
+
const isCol = colSet.has(fieldIndex);
|
|
590
|
+
const isValue = valueSet.has(fieldIndex);
|
|
591
|
+
return renderPivotField(isRow, isCol, isValue, cacheField.sharedItems);
|
|
596
592
|
})
|
|
597
593
|
.join("");
|
|
598
594
|
}
|
|
599
|
-
function renderPivotField(
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const axis = fieldType === "row" ? "axisRow" : "axisCol";
|
|
595
|
+
function renderPivotField(isRow, isCol, isValue, sharedItems) {
|
|
596
|
+
// A field can be both a row/column field AND a value field (issue #15)
|
|
597
|
+
// In this case, it needs both axis attribute AND dataField="1"
|
|
598
|
+
if (isRow || isCol) {
|
|
599
|
+
const axis = isRow ? "axisRow" : "axisCol";
|
|
605
600
|
// Row and column fields should NOT have defaultSubtotal="0"
|
|
606
|
-
|
|
601
|
+
let axisAttributes = 'compact="0" outline="0" showAll="0"';
|
|
602
|
+
// If also a value field, add dataField="1"
|
|
603
|
+
if (isValue) {
|
|
604
|
+
axisAttributes = `dataField="1" ${axisAttributes}`;
|
|
605
|
+
}
|
|
607
606
|
// items = one for each shared item + one default item
|
|
608
607
|
const itemsXml = [
|
|
609
608
|
...sharedItems.map((_item, index) => `<item x="${index}" />`),
|
|
@@ -621,7 +620,7 @@ function renderPivotField(fieldType, sharedItems) {
|
|
|
621
620
|
const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
|
|
622
621
|
return `
|
|
623
622
|
<pivotField
|
|
624
|
-
${
|
|
623
|
+
${isValue ? 'dataField="1"' : ""}
|
|
625
624
|
${defaultAttributes}
|
|
626
625
|
/>
|
|
627
626
|
`;
|
|
@@ -111,7 +111,7 @@ function makePivotTable(worksheet, model) {
|
|
|
111
111
|
validate(worksheet, model, source);
|
|
112
112
|
const { rows, values } = model;
|
|
113
113
|
const columns = model.columns ?? [];
|
|
114
|
-
const cacheFields = makeCacheFields(source, [...rows, ...columns]);
|
|
114
|
+
const cacheFields = makeCacheFields(source, [...rows, ...columns], values);
|
|
115
115
|
const nameToIndex = cacheFields.reduce((result, cacheField, index) => {
|
|
116
116
|
result[cacheField.name] = index;
|
|
117
117
|
return result;
|
|
@@ -168,13 +168,15 @@ function validate(_worksheet, model, source) {
|
|
|
168
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.");
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
171
|
+
function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
|
|
172
172
|
// Cache fields are used in pivot tables to reference source data.
|
|
173
173
|
// Fields in fieldNamesWithSharedItems get their unique values extracted as sharedItems.
|
|
174
|
-
//
|
|
174
|
+
// Fields in valueFieldNames (but not in fieldNamesWithSharedItems) get min/max calculated.
|
|
175
|
+
// Other fields are unused and get empty sharedItems.
|
|
175
176
|
const names = source.getRow(1).values;
|
|
176
177
|
// Use Set for O(1) lookup instead of object
|
|
177
178
|
const sharedItemsFields = new Set(fieldNamesWithSharedItems);
|
|
179
|
+
const valueFields = new Set(valueFieldNames);
|
|
178
180
|
const aggregate = (columnIndex) => {
|
|
179
181
|
const columnValues = source.getColumn(columnIndex).values;
|
|
180
182
|
// Build unique values set directly, skipping header (index 0,1) and null/undefined
|
|
@@ -212,10 +214,11 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
|
212
214
|
for (const columnIndex of range(1, names.length)) {
|
|
213
215
|
const name = names[columnIndex];
|
|
214
216
|
if (sharedItemsFields.has(name)) {
|
|
217
|
+
// Field used for rows/columns - extract unique values as sharedItems
|
|
215
218
|
result.push({ name, sharedItems: aggregate(columnIndex) });
|
|
216
219
|
}
|
|
217
|
-
else {
|
|
218
|
-
//
|
|
220
|
+
else if (valueFields.has(name)) {
|
|
221
|
+
// Field used only for values (aggregation) - calculate min/max
|
|
219
222
|
const minMax = getMinMax(columnIndex);
|
|
220
223
|
result.push({
|
|
221
224
|
name,
|
|
@@ -224,6 +227,10 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
|
|
|
224
227
|
maxValue: minMax?.maxValue
|
|
225
228
|
});
|
|
226
229
|
}
|
|
230
|
+
else {
|
|
231
|
+
// Unused field - just empty sharedItems (like Excel does)
|
|
232
|
+
result.push({ name, sharedItems: null });
|
|
233
|
+
}
|
|
227
234
|
}
|
|
228
235
|
return result;
|
|
229
236
|
}
|
|
@@ -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;
|
|
@@ -10,7 +10,7 @@ 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',
|
|
@@ -18,6 +18,15 @@ class CacheField {
|
|
|
18
18
|
// 'minValue': 5,
|
|
19
19
|
// 'maxValue': 45
|
|
20
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]
|
|
29
|
+
// }
|
|
21
30
|
this.name = name;
|
|
22
31
|
this.sharedItems = sharedItems;
|
|
23
32
|
this.minValue = minValue;
|
|
@@ -28,17 +37,37 @@ class CacheField {
|
|
|
28
37
|
// Shared Items: http://www.datypic.com/sc/ooxml/e-ssml_sharedItems-1.html
|
|
29
38
|
// Escape XML special characters in name attribute
|
|
30
39
|
const escapedName = xmlEncode(this.name);
|
|
31
|
-
//
|
|
40
|
+
// No shared items - field not used for rows/columns
|
|
32
41
|
if (this.sharedItems === null) {
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
37
49
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
38
|
-
<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>
|
|
39
68
|
</cacheField>`;
|
|
40
69
|
}
|
|
41
|
-
//
|
|
70
|
+
// String shared items - escape XML special characters in each value
|
|
42
71
|
return `<cacheField name="${escapedName}" numFmtId="0">
|
|
43
72
|
<sharedItems count="${this.sharedItems.length}">
|
|
44
73
|
${this.sharedItems.map(item => `<s v="${xmlEncode(String(item))}" />`).join("")}
|
|
@@ -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)}`);
|
|
@@ -581,25 +581,24 @@ function renderPivotFields(pivotTable) {
|
|
|
581
581
|
const valueSet = new Set(pivotTable.values);
|
|
582
582
|
return pivotTable.cacheFields
|
|
583
583
|
.map((cacheField, fieldIndex) => {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
: valueSet.has(fieldIndex)
|
|
589
|
-
? "value"
|
|
590
|
-
: null;
|
|
591
|
-
return renderPivotField(fieldType, cacheField.sharedItems);
|
|
584
|
+
const isRow = rowSet.has(fieldIndex);
|
|
585
|
+
const isCol = colSet.has(fieldIndex);
|
|
586
|
+
const isValue = valueSet.has(fieldIndex);
|
|
587
|
+
return renderPivotField(isRow, isCol, isValue, cacheField.sharedItems);
|
|
592
588
|
})
|
|
593
589
|
.join("");
|
|
594
590
|
}
|
|
595
|
-
function renderPivotField(
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const axis = fieldType === "row" ? "axisRow" : "axisCol";
|
|
591
|
+
function renderPivotField(isRow, isCol, isValue, sharedItems) {
|
|
592
|
+
// A field can be both a row/column field AND a value field (issue #15)
|
|
593
|
+
// In this case, it needs both axis attribute AND dataField="1"
|
|
594
|
+
if (isRow || isCol) {
|
|
595
|
+
const axis = isRow ? "axisRow" : "axisCol";
|
|
601
596
|
// Row and column fields should NOT have defaultSubtotal="0"
|
|
602
|
-
|
|
597
|
+
let axisAttributes = 'compact="0" outline="0" showAll="0"';
|
|
598
|
+
// If also a value field, add dataField="1"
|
|
599
|
+
if (isValue) {
|
|
600
|
+
axisAttributes = `dataField="1" ${axisAttributes}`;
|
|
601
|
+
}
|
|
603
602
|
// items = one for each shared item + one default item
|
|
604
603
|
const itemsXml = [
|
|
605
604
|
...sharedItems.map((_item, index) => `<item x="${index}" />`),
|
|
@@ -617,7 +616,7 @@ function renderPivotField(fieldType, sharedItems) {
|
|
|
617
616
|
const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
|
|
618
617
|
return `
|
|
619
618
|
<pivotField
|
|
620
|
-
${
|
|
619
|
+
${isValue ? 'dataField="1"' : ""}
|
|
621
620
|
${defaultAttributes}
|
|
622
621
|
/>
|
|
623
622
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cj-tech-master/excelts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-canary.20251228183403.d3eb98d",
|
|
4
4
|
"description": "TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|