@cj-tech-master/excelts 3.0.0-canary.20251228183403.d3eb98d → 3.0.0

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.
@@ -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], values);
117
+ const cacheFields = makeCacheFields(source, [...rows, ...columns]);
118
118
  const nameToIndex = cacheFields.reduce((result, cacheField, index) => {
119
119
  result[cacheField.name] = index;
120
120
  return result;
@@ -171,15 +171,13 @@ 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, valueFieldNames) {
174
+ function makeCacheFields(source, fieldNamesWithSharedItems) {
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
- // Fields in valueFieldNames (but not in fieldNamesWithSharedItems) get min/max calculated.
178
- // Other fields are unused and get empty sharedItems.
177
+ // Other fields (typically numeric) have sharedItems = null.
179
178
  const names = source.getRow(1).values;
180
179
  // Use Set for O(1) lookup instead of object
181
180
  const sharedItemsFields = new Set(fieldNamesWithSharedItems);
182
- const valueFields = new Set(valueFieldNames);
183
181
  const aggregate = (columnIndex) => {
184
182
  const columnValues = source.getColumn(columnIndex).values;
185
183
  // Build unique values set directly, skipping header (index 0,1) and null/undefined
@@ -217,11 +215,10 @@ function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
217
215
  for (const columnIndex of (0, utils_1.range)(1, names.length)) {
218
216
  const name = names[columnIndex];
219
217
  if (sharedItemsFields.has(name)) {
220
- // Field used for rows/columns - extract unique values as sharedItems
221
218
  result.push({ name, sharedItems: aggregate(columnIndex) });
222
219
  }
223
- else if (valueFields.has(name)) {
224
- // Field used only for values (aggregation) - calculate min/max
220
+ else {
221
+ // Numeric field - calculate min/max
225
222
  const minMax = getMinMax(columnIndex);
226
223
  result.push({
227
224
  name,
@@ -230,10 +227,6 @@ function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
230
227
  maxValue: minMax?.maxValue
231
228
  });
232
229
  }
233
- else {
234
- // Unused field - just empty sharedItems (like Excel does)
235
- result.push({ name, sharedItems: null });
236
- }
237
230
  }
238
231
  return result;
239
232
  }
@@ -47,26 +47,30 @@ class CacheFieldXform extends base_xform_1.BaseXform {
47
47
  break;
48
48
  case "sharedItems":
49
49
  this.inSharedItems = true;
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);
56
- }
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)
50
+ // Check if this is a numeric field (no string items)
51
+ if (attributes.containsNumber === "1" || attributes.containsInteger === "1") {
52
+ if (this.model) {
53
+ this.model.containsNumber = attributes.containsNumber === "1";
54
+ this.model.containsInteger = attributes.containsInteger === "1";
55
+ if (attributes.minValue !== undefined) {
56
+ this.model.minValue = parseFloat(attributes.minValue);
57
+ }
58
+ if (attributes.maxValue !== undefined) {
59
+ this.model.maxValue = parseFloat(attributes.maxValue);
60
+ }
61
+ // Numeric fields have sharedItems = null
67
62
  this.model.sharedItems = null;
68
63
  }
69
64
  }
65
+ else {
66
+ // String field - initialize sharedItems array if count > 0
67
+ if (this.model) {
68
+ const count = parseInt(attributes.count || "0", 10);
69
+ if (count > 0) {
70
+ this.model.sharedItems = [];
71
+ }
72
+ }
73
+ }
70
74
  break;
71
75
  case "s":
72
76
  // String value in sharedItems
@@ -13,7 +13,7 @@ class CacheField {
13
13
  //
14
14
  // or
15
15
  //
16
- // integer type (no sharedItems)
16
+ // integer type
17
17
  //
18
18
  // {
19
19
  // 'name': 'D',
@@ -21,15 +21,6 @@ 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
- // }
33
24
  this.name = name;
34
25
  this.sharedItems = sharedItems;
35
26
  this.minValue = minValue;
@@ -40,37 +31,17 @@ class CacheField {
40
31
  // Shared Items: http://www.datypic.com/sc/ooxml/e-ssml_sharedItems-1.html
41
32
  // Escape XML special characters in name attribute
42
33
  const escapedName = (0, utils_1.xmlEncode)(this.name);
43
- // No shared items - field not used for rows/columns
34
+ // integer types
44
35
  if (this.sharedItems === null) {
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
36
+ // Build minValue/maxValue attributes if available
37
+ const minMaxAttrs = this.minValue !== undefined && this.maxValue !== undefined
38
+ ? ` minValue="${this.minValue}" maxValue="${this.maxValue}"`
39
+ : "";
52
40
  return `<cacheField name="${escapedName}" numFmtId="0">
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>
41
+ <sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1"${minMaxAttrs} />
71
42
  </cacheField>`;
72
43
  }
73
- // String shared items - escape XML special characters in each value
44
+ // string types - escape XML special characters in each shared item value
74
45
  return `<cacheField name="${escapedName}" numFmtId="0">
75
46
  <sharedItems count="${this.sharedItems.length}">
76
47
  ${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 - use indexOf for value lookup (works for both string and numeric)
122
+ // shared items
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,24 +585,25 @@ function renderPivotFields(pivotTable) {
585
585
  const valueSet = new Set(pivotTable.values);
586
586
  return pivotTable.cacheFields
587
587
  .map((cacheField, fieldIndex) => {
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);
588
+ const fieldType = rowSet.has(fieldIndex)
589
+ ? "row"
590
+ : colSet.has(fieldIndex)
591
+ ? "column"
592
+ : valueSet.has(fieldIndex)
593
+ ? "value"
594
+ : null;
595
+ return renderPivotField(fieldType, cacheField.sharedItems);
592
596
  })
593
597
  .join("");
594
598
  }
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";
599
+ function renderPivotField(fieldType, sharedItems) {
600
+ // fieldType: 'row', 'column', 'value', null
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)
603
+ if (fieldType === "row" || fieldType === "column") {
604
+ const axis = fieldType === "row" ? "axisRow" : "axisCol";
600
605
  // Row and column fields should NOT have defaultSubtotal="0"
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
- }
606
+ const axisAttributes = 'compact="0" outline="0" showAll="0"';
606
607
  // items = one for each shared item + one default item
607
608
  const itemsXml = [
608
609
  ...sharedItems.map((_item, index) => `<item x="${index}" />`),
@@ -620,7 +621,7 @@ function renderPivotField(isRow, isCol, isValue, sharedItems) {
620
621
  const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
621
622
  return `
622
623
  <pivotField
623
- ${isValue ? 'dataField="1"' : ""}
624
+ ${fieldType === "value" ? 'dataField="1"' : ""}
624
625
  ${defaultAttributes}
625
626
  />
626
627
  `;
@@ -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], values);
114
+ const cacheFields = makeCacheFields(source, [...rows, ...columns]);
115
115
  const nameToIndex = cacheFields.reduce((result, cacheField, index) => {
116
116
  result[cacheField.name] = index;
117
117
  return result;
@@ -168,15 +168,13 @@ 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, valueFieldNames) {
171
+ function makeCacheFields(source, fieldNamesWithSharedItems) {
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
- // Fields in valueFieldNames (but not in fieldNamesWithSharedItems) get min/max calculated.
175
- // Other fields are unused and get empty sharedItems.
174
+ // Other fields (typically numeric) have sharedItems = null.
176
175
  const names = source.getRow(1).values;
177
176
  // Use Set for O(1) lookup instead of object
178
177
  const sharedItemsFields = new Set(fieldNamesWithSharedItems);
179
- const valueFields = new Set(valueFieldNames);
180
178
  const aggregate = (columnIndex) => {
181
179
  const columnValues = source.getColumn(columnIndex).values;
182
180
  // Build unique values set directly, skipping header (index 0,1) and null/undefined
@@ -214,11 +212,10 @@ function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
214
212
  for (const columnIndex of range(1, names.length)) {
215
213
  const name = names[columnIndex];
216
214
  if (sharedItemsFields.has(name)) {
217
- // Field used for rows/columns - extract unique values as sharedItems
218
215
  result.push({ name, sharedItems: aggregate(columnIndex) });
219
216
  }
220
- else if (valueFields.has(name)) {
221
- // Field used only for values (aggregation) - calculate min/max
217
+ else {
218
+ // Numeric field - calculate min/max
222
219
  const minMax = getMinMax(columnIndex);
223
220
  result.push({
224
221
  name,
@@ -227,10 +224,6 @@ function makeCacheFields(source, fieldNamesWithSharedItems, valueFieldNames) {
227
224
  maxValue: minMax?.maxValue
228
225
  });
229
226
  }
230
- else {
231
- // Unused field - just empty sharedItems (like Excel does)
232
- result.push({ name, sharedItems: null });
233
- }
234
227
  }
235
228
  return result;
236
229
  }
@@ -44,26 +44,30 @@ class CacheFieldXform extends BaseXform {
44
44
  break;
45
45
  case "sharedItems":
46
46
  this.inSharedItems = true;
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);
53
- }
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)
47
+ // Check if this is a numeric field (no string items)
48
+ if (attributes.containsNumber === "1" || attributes.containsInteger === "1") {
49
+ if (this.model) {
50
+ this.model.containsNumber = attributes.containsNumber === "1";
51
+ this.model.containsInteger = attributes.containsInteger === "1";
52
+ if (attributes.minValue !== undefined) {
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
64
59
  this.model.sharedItems = null;
65
60
  }
66
61
  }
62
+ else {
63
+ // String field - initialize sharedItems array if count > 0
64
+ if (this.model) {
65
+ const count = parseInt(attributes.count || "0", 10);
66
+ if (count > 0) {
67
+ this.model.sharedItems = [];
68
+ }
69
+ }
70
+ }
67
71
  break;
68
72
  case "s":
69
73
  // String value in sharedItems
@@ -10,7 +10,7 @@ class CacheField {
10
10
  //
11
11
  // or
12
12
  //
13
- // integer type (no sharedItems)
13
+ // integer type
14
14
  //
15
15
  // {
16
16
  // 'name': 'D',
@@ -18,15 +18,6 @@ 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
- // }
30
21
  this.name = name;
31
22
  this.sharedItems = sharedItems;
32
23
  this.minValue = minValue;
@@ -37,37 +28,17 @@ class CacheField {
37
28
  // Shared Items: http://www.datypic.com/sc/ooxml/e-ssml_sharedItems-1.html
38
29
  // Escape XML special characters in name attribute
39
30
  const escapedName = xmlEncode(this.name);
40
- // No shared items - field not used for rows/columns
31
+ // integer types
41
32
  if (this.sharedItems === null) {
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
33
+ // Build minValue/maxValue attributes if available
34
+ const minMaxAttrs = this.minValue !== undefined && this.maxValue !== undefined
35
+ ? ` minValue="${this.minValue}" maxValue="${this.maxValue}"`
36
+ : "";
49
37
  return `<cacheField name="${escapedName}" numFmtId="0">
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>
38
+ <sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1"${minMaxAttrs} />
68
39
  </cacheField>`;
69
40
  }
70
- // String shared items - escape XML special characters in each value
41
+ // string types - escape XML special characters in each shared item value
71
42
  return `<cacheField name="${escapedName}" numFmtId="0">
72
43
  <sharedItems count="${this.sharedItems.length}">
73
44
  ${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 - use indexOf for value lookup (works for both string and numeric)
119
+ // shared items
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,24 +581,25 @@ function renderPivotFields(pivotTable) {
581
581
  const valueSet = new Set(pivotTable.values);
582
582
  return pivotTable.cacheFields
583
583
  .map((cacheField, fieldIndex) => {
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);
584
+ const fieldType = rowSet.has(fieldIndex)
585
+ ? "row"
586
+ : colSet.has(fieldIndex)
587
+ ? "column"
588
+ : valueSet.has(fieldIndex)
589
+ ? "value"
590
+ : null;
591
+ return renderPivotField(fieldType, cacheField.sharedItems);
588
592
  })
589
593
  .join("");
590
594
  }
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";
595
+ function renderPivotField(fieldType, sharedItems) {
596
+ // fieldType: 'row', 'column', 'value', null
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)
599
+ if (fieldType === "row" || fieldType === "column") {
600
+ const axis = fieldType === "row" ? "axisRow" : "axisCol";
596
601
  // Row and column fields should NOT have defaultSubtotal="0"
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
- }
602
+ const axisAttributes = 'compact="0" outline="0" showAll="0"';
602
603
  // items = one for each shared item + one default item
603
604
  const itemsXml = [
604
605
  ...sharedItems.map((_item, index) => `<item x="${index}" />`),
@@ -616,7 +617,7 @@ function renderPivotField(isRow, isCol, isValue, sharedItems) {
616
617
  const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
617
618
  return `
618
619
  <pivotField
619
- ${isValue ? 'dataField="1"' : ""}
620
+ ${fieldType === "value" ? 'dataField="1"' : ""}
620
621
  ${defaultAttributes}
621
622
  />
622
623
  `;
@@ -4,7 +4,7 @@ import { BaseXform } from "../base-xform";
4
4
  */
5
5
  interface CacheFieldModel {
6
6
  name: string;
7
- sharedItems: any[] | null;
7
+ sharedItems: string[] | null;
8
8
  containsNumber?: boolean;
9
9
  containsInteger?: boolean;
10
10
  minValue?: number;
@@ -1,6 +1,6 @@
1
1
  interface CacheFieldConfig {
2
2
  name: string;
3
- sharedItems: any[] | null;
3
+ sharedItems: string[] | null;
4
4
  minValue?: number;
5
5
  maxValue?: number;
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cj-tech-master/excelts",
3
- "version": "3.0.0-canary.20251228183403.d3eb98d",
3
+ "version": "3.0.0",
4
4
  "description": "TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.",
5
5
  "type": "module",
6
6
  "publishConfig": {