@cj-tech-master/excelts 1.5.0 → 1.6.1-canary.20251220005548.c4c1e36

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.
Files changed (48) hide show
  1. package/README.md +0 -15
  2. package/README_zh.md +0 -15
  3. package/dist/browser/excelts.iife.js +1057 -222
  4. package/dist/browser/excelts.iife.js.map +1 -1
  5. package/dist/browser/excelts.iife.min.js +63 -33
  6. package/dist/cjs/doc/column.js +7 -3
  7. package/dist/cjs/doc/pivot-table.js +149 -61
  8. package/dist/cjs/doc/workbook.js +3 -1
  9. package/dist/cjs/doc/worksheet.js +2 -23
  10. package/dist/cjs/stream/xlsx/worksheet-writer.js +1 -1
  11. package/dist/cjs/utils/unzip/zip-parser.js +2 -5
  12. package/dist/cjs/xlsx/xform/book/workbook-xform.js +3 -0
  13. package/dist/cjs/xlsx/xform/core/content-types-xform.js +19 -14
  14. package/dist/cjs/xlsx/xform/pivot-table/cache-field-xform.js +135 -0
  15. package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +7 -4
  16. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
  17. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
  18. package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
  19. package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -0
  20. package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +14 -3
  21. package/dist/cjs/xlsx/xlsx.js +261 -38
  22. package/dist/esm/doc/column.js +7 -3
  23. package/dist/esm/doc/pivot-table.js +150 -62
  24. package/dist/esm/doc/workbook.js +3 -1
  25. package/dist/esm/doc/worksheet.js +2 -23
  26. package/dist/esm/stream/xlsx/worksheet-writer.js +1 -1
  27. package/dist/esm/utils/unzip/zip-parser.js +2 -5
  28. package/dist/esm/xlsx/xform/book/workbook-xform.js +3 -0
  29. package/dist/esm/xlsx/xform/core/content-types-xform.js +19 -14
  30. package/dist/esm/xlsx/xform/pivot-table/cache-field-xform.js +132 -0
  31. package/dist/esm/xlsx/xform/pivot-table/cache-field.js +7 -4
  32. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +135 -13
  33. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-records-xform.js +193 -45
  34. package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +390 -39
  35. package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -0
  36. package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +14 -3
  37. package/dist/esm/xlsx/xlsx.js +261 -38
  38. package/dist/types/doc/column.d.ts +13 -6
  39. package/dist/types/doc/pivot-table.d.ts +135 -9
  40. package/dist/types/doc/workbook.d.ts +2 -0
  41. package/dist/types/doc/worksheet.d.ts +6 -27
  42. package/dist/types/index.d.ts +1 -0
  43. package/dist/types/xlsx/xform/pivot-table/cache-field-xform.d.ts +42 -0
  44. package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +45 -6
  45. package/dist/types/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +52 -5
  46. package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +98 -5
  47. package/dist/types/xlsx/xlsx.d.ts +27 -0
  48. package/package.json +17 -17
@@ -1,20 +1,48 @@
1
1
  import { BaseXform } from "../base-xform.js";
2
2
  import { CacheField } from "./cache-field.js";
3
+ import { CacheFieldXform } from "./cache-field-xform.js";
3
4
  import { XmlStream } from "../../../utils/xml-stream.js";
4
5
  class PivotCacheDefinitionXform extends BaseXform {
5
6
  constructor() {
6
7
  super();
7
8
  this.map = {};
9
+ this.model = null;
10
+ this.currentCacheField = null;
11
+ this.inCacheFields = false;
12
+ this.inCacheSource = false;
8
13
  }
9
14
  prepare(_model) {
10
- // TK
15
+ // No preparation needed for writing
11
16
  }
12
17
  get tag() {
13
18
  // http://www.datypic.com/sc/ooxml/e-ssml_pivotCacheDefinition.html
14
19
  return "pivotCacheDefinition";
15
20
  }
21
+ reset() {
22
+ this.model = null;
23
+ this.currentCacheField = null;
24
+ this.inCacheFields = false;
25
+ this.inCacheSource = false;
26
+ }
27
+ /**
28
+ * Render pivot cache definition XML.
29
+ * Supports both newly created models (with PivotTableSource) and loaded models.
30
+ */
16
31
  render(xmlStream, model) {
17
- const { sourceSheet, cacheFields } = model;
32
+ // Check if this is a loaded model (has isLoaded flag or no source property)
33
+ const isLoaded = model.isLoaded || !("source" in model);
34
+ if (isLoaded) {
35
+ this.renderLoaded(xmlStream, model);
36
+ }
37
+ else {
38
+ this.renderNew(xmlStream, model);
39
+ }
40
+ }
41
+ /**
42
+ * Render newly created pivot cache definition
43
+ */
44
+ renderNew(xmlStream, model) {
45
+ const { source, cacheFields } = model;
18
46
  xmlStream.openXml(XmlStream.StdDocAttributes);
19
47
  xmlStream.openNode(this.tag, {
20
48
  ...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
@@ -29,8 +57,8 @@ class PivotCacheDefinitionXform extends BaseXform {
29
57
  });
30
58
  xmlStream.openNode("cacheSource", { type: "worksheet" });
31
59
  xmlStream.leafNode("worksheetSource", {
32
- ref: sourceSheet.dimensions.shortRange,
33
- sheet: sourceSheet.name
60
+ ref: source.dimensions.shortRange,
61
+ sheet: source.name
34
62
  });
35
63
  xmlStream.closeNode();
36
64
  xmlStream.openNode("cacheFields", { count: cacheFields.length });
@@ -39,19 +67,113 @@ class PivotCacheDefinitionXform extends BaseXform {
39
67
  xmlStream.closeNode();
40
68
  xmlStream.closeNode();
41
69
  }
42
- parseOpen(_node) {
43
- // TK
44
- return false;
70
+ /**
71
+ * Render loaded pivot cache definition (preserving original structure)
72
+ */
73
+ renderLoaded(xmlStream, model) {
74
+ const { cacheFields, sourceRef, sourceSheet, recordCount } = model;
75
+ xmlStream.openXml(XmlStream.StdDocAttributes);
76
+ xmlStream.openNode(this.tag, {
77
+ ...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
78
+ "r:id": model.rId || "rId1",
79
+ refreshOnLoad: model.refreshOnLoad || "1",
80
+ refreshedBy: model.refreshedBy || "Author",
81
+ refreshedDate: model.refreshedDate || "45125.026046874998",
82
+ createdVersion: model.createdVersion || "8",
83
+ refreshedVersion: model.refreshedVersion || "8",
84
+ minRefreshableVersion: model.minRefreshableVersion || "3",
85
+ recordCount: recordCount || cacheFields.length + 1
86
+ });
87
+ xmlStream.openNode("cacheSource", { type: "worksheet" });
88
+ xmlStream.leafNode("worksheetSource", {
89
+ ref: sourceRef,
90
+ sheet: sourceSheet
91
+ });
92
+ xmlStream.closeNode();
93
+ xmlStream.openNode("cacheFields", { count: cacheFields.length });
94
+ xmlStream.writeXml(cacheFields
95
+ .map((cacheField) => new CacheField(cacheField).render())
96
+ .join("\n "));
97
+ xmlStream.closeNode();
98
+ xmlStream.closeNode();
99
+ }
100
+ parseOpen(node) {
101
+ const { name, attributes } = node;
102
+ // Delegate to current cacheField parser if active
103
+ if (this.currentCacheField) {
104
+ this.currentCacheField.parseOpen(node);
105
+ return true;
106
+ }
107
+ switch (name) {
108
+ case this.tag:
109
+ // pivotCacheDefinition root element
110
+ this.reset();
111
+ this.model = {
112
+ cacheFields: [],
113
+ rId: attributes["r:id"],
114
+ refreshOnLoad: attributes.refreshOnLoad,
115
+ refreshedBy: attributes.refreshedBy,
116
+ refreshedDate: attributes.refreshedDate,
117
+ createdVersion: attributes.createdVersion,
118
+ refreshedVersion: attributes.refreshedVersion,
119
+ minRefreshableVersion: attributes.minRefreshableVersion,
120
+ recordCount: attributes.recordCount ? parseInt(attributes.recordCount, 10) : undefined,
121
+ isLoaded: true
122
+ };
123
+ break;
124
+ case "cacheSource":
125
+ this.inCacheSource = true;
126
+ break;
127
+ case "worksheetSource":
128
+ if (this.inCacheSource && this.model) {
129
+ this.model.sourceRef = attributes.ref;
130
+ this.model.sourceSheet = attributes.sheet;
131
+ }
132
+ break;
133
+ case "cacheFields":
134
+ this.inCacheFields = true;
135
+ break;
136
+ case "cacheField":
137
+ if (this.inCacheFields) {
138
+ this.currentCacheField = new CacheFieldXform();
139
+ this.currentCacheField.parseOpen(node);
140
+ }
141
+ break;
142
+ }
143
+ return true;
45
144
  }
46
- parseText(_text) {
47
- // TK
145
+ parseText(text) {
146
+ if (this.currentCacheField) {
147
+ this.currentCacheField.parseText(text);
148
+ }
48
149
  }
49
- parseClose(_name) {
50
- // TK
51
- return false;
150
+ parseClose(name) {
151
+ // Delegate to current cacheField parser if active
152
+ if (this.currentCacheField) {
153
+ if (!this.currentCacheField.parseClose(name)) {
154
+ // cacheField parsing complete, add to model
155
+ if (this.model && this.currentCacheField.model) {
156
+ this.model.cacheFields.push(this.currentCacheField.model);
157
+ }
158
+ this.currentCacheField = null;
159
+ }
160
+ return true;
161
+ }
162
+ switch (name) {
163
+ case this.tag:
164
+ // End of pivotCacheDefinition
165
+ return false;
166
+ case "cacheSource":
167
+ this.inCacheSource = false;
168
+ break;
169
+ case "cacheFields":
170
+ this.inCacheFields = false;
171
+ break;
172
+ }
173
+ return true;
52
174
  }
53
175
  reconcile(_model, _options) {
54
- // TK
176
+ // No reconciliation needed
55
177
  }
56
178
  }
57
179
  PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES = {
@@ -1,79 +1,227 @@
1
1
  import { XmlStream } from "../../../utils/xml-stream.js";
2
+ import { xmlEncode, xmlDecode } from "../../../utils/utils.js";
2
3
  import { BaseXform } from "../base-xform.js";
3
4
  class PivotCacheRecordsXform extends BaseXform {
4
5
  constructor() {
5
6
  super();
6
7
  this.map = {};
8
+ this.model = null;
9
+ this.currentRecord = null;
7
10
  }
8
11
  prepare(_model) {
9
- // TK
12
+ // No preparation needed
10
13
  }
11
14
  get tag() {
12
15
  // http://www.datypic.com/sc/ooxml/e-ssml_pivotCacheRecords.html
13
16
  return "pivotCacheRecords";
14
17
  }
18
+ reset() {
19
+ this.model = null;
20
+ this.currentRecord = null;
21
+ }
22
+ /**
23
+ * Render pivot cache records XML.
24
+ * Supports both newly created models (with PivotTableSource) and loaded models.
25
+ */
15
26
  render(xmlStream, model) {
16
- const { sourceSheet, cacheFields } = model;
17
- const sourceBodyRows = sourceSheet.getSheetValues().slice(2);
27
+ // Check if this is a loaded model
28
+ const isLoaded = model.isLoaded || !("source" in model);
29
+ if (isLoaded) {
30
+ this.renderLoaded(xmlStream, model);
31
+ }
32
+ else {
33
+ this.renderNew(xmlStream, model);
34
+ }
35
+ }
36
+ /**
37
+ * Render newly created pivot cache records
38
+ */
39
+ renderNew(xmlStream, model) {
40
+ const { source, cacheFields } = model;
41
+ const sourceBodyRows = source.getSheetValues().slice(2);
18
42
  xmlStream.openXml(XmlStream.StdDocAttributes);
19
43
  xmlStream.openNode(this.tag, {
20
44
  ...PivotCacheRecordsXform.PIVOT_CACHE_RECORDS_ATTRIBUTES,
21
45
  count: sourceBodyRows.length
22
46
  });
23
- xmlStream.writeXml(renderTable());
47
+ xmlStream.writeXml(this.renderTableNew(sourceBodyRows, cacheFields));
24
48
  xmlStream.closeNode();
25
- // Helpers
26
- function renderTable() {
27
- const rowsInXML = sourceBodyRows.map((row) => {
28
- const realRow = row.slice(1);
29
- return [...renderRowLines(realRow)].join("");
30
- });
31
- return rowsInXML.join("");
32
- }
33
- function* renderRowLines(row) {
34
- // PivotCache Record: http://www.datypic.com/sc/ooxml/e-ssml_r-1.html
35
- // Note: pretty-printing this for now to ease debugging.
36
- yield "\n <r>";
37
- for (const [index, cellValue] of row.entries()) {
38
- yield "\n ";
39
- yield renderCell(cellValue, cacheFields[index].sharedItems);
49
+ }
50
+ /**
51
+ * Render loaded pivot cache records
52
+ */
53
+ renderLoaded(xmlStream, model) {
54
+ xmlStream.openXml(XmlStream.StdDocAttributes);
55
+ xmlStream.openNode(this.tag, {
56
+ ...PivotCacheRecordsXform.PIVOT_CACHE_RECORDS_ATTRIBUTES,
57
+ count: model.count
58
+ });
59
+ // Render each record
60
+ for (const record of model.records) {
61
+ xmlStream.writeXml("\n <r>");
62
+ for (const value of record) {
63
+ xmlStream.writeXml("\n ");
64
+ xmlStream.writeXml(this.renderRecordValue(value));
40
65
  }
41
- yield "\n </r>";
66
+ xmlStream.writeXml("\n </r>");
42
67
  }
43
- function renderCell(value, sharedItems) {
44
- // no shared items
45
- // --------------------------------------------------
46
- if (sharedItems === null) {
47
- if (Number.isFinite(value)) {
48
- // Numeric value: http://www.datypic.com/sc/ooxml/e-ssml_n-2.html
49
- return `<n v="${value}" />`;
50
- }
51
- // Character Value: http://www.datypic.com/sc/ooxml/e-ssml_s-2.html
52
- return `<s v="${value}" />`;
68
+ xmlStream.closeNode();
69
+ }
70
+ /**
71
+ * Render a single record value to XML
72
+ */
73
+ renderRecordValue(value) {
74
+ switch (value.type) {
75
+ case "x":
76
+ return `<x v="${value.value}" />`;
77
+ case "n":
78
+ return `<n v="${value.value}" />`;
79
+ case "s":
80
+ return `<s v="${xmlEncode(String(value.value))}" />`;
81
+ case "b":
82
+ return `<b v="${value.value ? "1" : "0"}" />`;
83
+ case "m":
84
+ return "<m />";
85
+ case "d":
86
+ return `<d v="${value.value.toISOString()}" />`;
87
+ case "e":
88
+ return `<e v="${value.value}" />`;
89
+ default:
90
+ return "<m />";
91
+ }
92
+ }
93
+ // Helper methods for rendering new records
94
+ renderTableNew(sourceBodyRows, cacheFields) {
95
+ const parts = [];
96
+ for (const row of sourceBodyRows) {
97
+ const realRow = row.slice(1);
98
+ parts.push("\n <r>");
99
+ for (let i = 0; i < realRow.length; i++) {
100
+ parts.push("\n ");
101
+ parts.push(this.renderCellNew(realRow[i], cacheFields[i].sharedItems));
53
102
  }
54
- // shared items
55
- // --------------------------------------------------
56
- const sharedItemsIndex = sharedItems.indexOf(value);
57
- if (sharedItemsIndex < 0) {
58
- throw new Error(`${JSON.stringify(value)} not in sharedItems ${JSON.stringify(sharedItems)}`);
103
+ parts.push("\n </r>");
104
+ }
105
+ return parts.join("");
106
+ }
107
+ renderCellNew(value, sharedItems) {
108
+ // Handle null/undefined values first
109
+ if (value === null || value === undefined) {
110
+ return "<m />";
111
+ }
112
+ // no shared items
113
+ if (sharedItems === null) {
114
+ if (Number.isFinite(value)) {
115
+ return `<n v="${value}" />`;
59
116
  }
60
- // Shared Items Index: http://www.datypic.com/sc/ooxml/e-ssml_x-9.html
61
- return `<x v="${sharedItemsIndex}" />`;
117
+ return `<s v="${xmlEncode(String(value))}" />`;
118
+ }
119
+ // shared items
120
+ const sharedItemsIndex = sharedItems.indexOf(value);
121
+ if (sharedItemsIndex < 0) {
122
+ throw new Error(`${JSON.stringify(value)} not in sharedItems ${JSON.stringify(sharedItems)}`);
62
123
  }
124
+ return `<x v="${sharedItemsIndex}" />`;
63
125
  }
64
- parseOpen(_node) {
65
- // TK
66
- return false;
126
+ parseOpen(node) {
127
+ const { name, attributes } = node;
128
+ switch (name) {
129
+ case this.tag:
130
+ // pivotCacheRecords root element
131
+ this.reset();
132
+ this.model = {
133
+ records: [],
134
+ count: parseInt(attributes.count || "0", 10),
135
+ isLoaded: true
136
+ };
137
+ break;
138
+ case "r":
139
+ // Start of a new record
140
+ this.currentRecord = [];
141
+ break;
142
+ case "x":
143
+ // Shared item index
144
+ if (this.currentRecord) {
145
+ this.currentRecord.push({
146
+ type: "x",
147
+ value: parseInt(attributes.v || "0", 10)
148
+ });
149
+ }
150
+ break;
151
+ case "n":
152
+ // Numeric value
153
+ if (this.currentRecord) {
154
+ this.currentRecord.push({
155
+ type: "n",
156
+ value: parseFloat(attributes.v || "0")
157
+ });
158
+ }
159
+ break;
160
+ case "s":
161
+ // String value
162
+ if (this.currentRecord) {
163
+ this.currentRecord.push({
164
+ type: "s",
165
+ value: xmlDecode(attributes.v || "")
166
+ });
167
+ }
168
+ break;
169
+ case "b":
170
+ // Boolean value
171
+ if (this.currentRecord) {
172
+ this.currentRecord.push({
173
+ type: "b",
174
+ value: attributes.v === "1"
175
+ });
176
+ }
177
+ break;
178
+ case "m":
179
+ // Missing/null value
180
+ if (this.currentRecord) {
181
+ this.currentRecord.push({ type: "m" });
182
+ }
183
+ break;
184
+ case "d":
185
+ // Date value
186
+ if (this.currentRecord) {
187
+ this.currentRecord.push({
188
+ type: "d",
189
+ value: new Date(attributes.v || "")
190
+ });
191
+ }
192
+ break;
193
+ case "e":
194
+ // Error value
195
+ if (this.currentRecord) {
196
+ this.currentRecord.push({
197
+ type: "e",
198
+ value: attributes.v || ""
199
+ });
200
+ }
201
+ break;
202
+ }
203
+ return true;
67
204
  }
68
205
  parseText(_text) {
69
- // TK
206
+ // No text content in cache records elements
70
207
  }
71
- parseClose(_name) {
72
- // TK
73
- return false;
208
+ parseClose(name) {
209
+ switch (name) {
210
+ case this.tag:
211
+ // End of pivotCacheRecords
212
+ return false;
213
+ case "r":
214
+ // End of record - add to model
215
+ if (this.model && this.currentRecord) {
216
+ this.model.records.push(this.currentRecord);
217
+ this.currentRecord = null;
218
+ }
219
+ break;
220
+ }
221
+ return true;
74
222
  }
75
223
  reconcile(_model, _options) {
76
- // TK
224
+ // No reconciliation needed
77
225
  }
78
226
  }
79
227
  PivotCacheRecordsXform.PIVOT_CACHE_RECORDS_ATTRIBUTES = {