@cj-tech-master/excelts 4.2.3-canary.20260115111903.b80904d → 4.2.3-canary.20260122073152.a9bb6b0
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/modules/csv/csv-core.d.ts +0 -9
- package/dist/browser/modules/csv/csv.browser.js +3 -3
- package/dist/browser/modules/excel/utils/passthrough-manager.d.ts +77 -0
- package/dist/browser/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/browser/modules/excel/workbook.d.ts +8 -0
- package/dist/browser/modules/excel/workbook.js +9 -1
- package/dist/browser/modules/excel/worksheet.d.ts +4 -0
- package/dist/browser/modules/excel/worksheet.js +4 -1
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +213 -131
- package/dist/cjs/modules/csv/csv.browser.js +3 -3
- package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
- package/dist/cjs/modules/excel/workbook.js +9 -1
- package/dist/cjs/modules/excel/worksheet.js +4 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +213 -131
- package/dist/esm/modules/csv/csv.browser.js +3 -3
- package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/esm/modules/excel/workbook.js +9 -1
- package/dist/esm/modules/excel/worksheet.js +4 -1
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +213 -131
- package/dist/iife/excelts.iife.js +512 -241
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +24 -51
- package/dist/types/modules/csv/csv-core.d.ts +0 -9
- package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
- package/dist/types/modules/excel/workbook.d.ts +8 -0
- package/dist/types/modules/excel/worksheet.d.ts +4 -0
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/package.json +2 -2
|
@@ -93,11 +93,16 @@ class ContentTypesXform extends BaseXform {
|
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
// VML extension is needed for comments or form controls
|
|
97
|
+
const hasComments = model.commentRefs && model.commentRefs.length > 0;
|
|
98
|
+
const hasFormControls = model.formControlRefs && model.formControlRefs.length > 0;
|
|
99
|
+
if (hasComments || hasFormControls) {
|
|
97
100
|
xmlStream.leafNode("Default", {
|
|
98
101
|
Extension: "vml",
|
|
99
102
|
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
100
103
|
});
|
|
104
|
+
}
|
|
105
|
+
if (hasComments) {
|
|
101
106
|
model.commentRefs.forEach(({ commentName }) => {
|
|
102
107
|
xmlStream.leafNode("Override", {
|
|
103
108
|
PartName: toContentTypesPartName(commentsPathFromName(commentName)),
|
|
@@ -105,15 +110,7 @@ class ContentTypesXform extends BaseXform {
|
|
|
105
110
|
});
|
|
106
111
|
});
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
if (model.formControlRefs) {
|
|
110
|
-
// Ensure vml extension is declared (may already be declared for comments)
|
|
111
|
-
if (!model.commentRefs) {
|
|
112
|
-
xmlStream.leafNode("Default", {
|
|
113
|
-
Extension: "vml",
|
|
114
|
-
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
115
|
-
});
|
|
116
|
-
}
|
|
113
|
+
if (hasFormControls) {
|
|
117
114
|
for (const ctrlPropId of model.formControlRefs) {
|
|
118
115
|
xmlStream.leafNode("Override", {
|
|
119
116
|
PartName: toContentTypesPartName(ctrlPropPath(ctrlPropId)),
|
|
@@ -121,6 +118,15 @@ class ContentTypesXform extends BaseXform {
|
|
|
121
118
|
});
|
|
122
119
|
}
|
|
123
120
|
}
|
|
121
|
+
// Add passthrough content types (charts, etc.)
|
|
122
|
+
if (model.passthroughContentTypes) {
|
|
123
|
+
for (const { partName, contentType } of model.passthroughContentTypes) {
|
|
124
|
+
xmlStream.leafNode("Override", {
|
|
125
|
+
PartName: toContentTypesPartName(partName),
|
|
126
|
+
ContentType: contentType
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
124
130
|
xmlStream.leafNode("Override", {
|
|
125
131
|
PartName: toContentTypesPartName(OOXML_PATHS.docPropsCore),
|
|
126
132
|
ContentType: "application/vnd.openxmlformats-package.core-properties+xml"
|
|
@@ -4,18 +4,30 @@ import { BaseXform } from "../base-xform.js";
|
|
|
4
4
|
class PivotTableXform extends BaseXform {
|
|
5
5
|
constructor() {
|
|
6
6
|
super();
|
|
7
|
+
// Parser state consolidated into object for easier reset
|
|
8
|
+
this.state = {
|
|
9
|
+
inPivotFields: false,
|
|
10
|
+
inRowFields: false,
|
|
11
|
+
inColFields: false,
|
|
12
|
+
inDataFields: false,
|
|
13
|
+
inRowItems: false,
|
|
14
|
+
inColItems: false,
|
|
15
|
+
inLocation: false,
|
|
16
|
+
inItems: false,
|
|
17
|
+
inPivotTableStyleInfo: false,
|
|
18
|
+
inChartFormats: false,
|
|
19
|
+
inPivotArea: false
|
|
20
|
+
};
|
|
21
|
+
// Current parsing context
|
|
22
|
+
this.currentPivotField = null;
|
|
23
|
+
this.currentRowItem = null;
|
|
24
|
+
this.currentColItem = null;
|
|
25
|
+
this.currentChartFormat = null;
|
|
26
|
+
// Buffer for collecting pivotArea XML
|
|
27
|
+
this.pivotAreaXmlBuffer = [];
|
|
28
|
+
this.pivotAreaDepth = 0;
|
|
7
29
|
this.map = {};
|
|
8
30
|
this.model = null;
|
|
9
|
-
this.inPivotFields = false;
|
|
10
|
-
this.inRowFields = false;
|
|
11
|
-
this.inColFields = false;
|
|
12
|
-
this.inDataFields = false;
|
|
13
|
-
this.inRowItems = false;
|
|
14
|
-
this.inColItems = false;
|
|
15
|
-
this.inLocation = false;
|
|
16
|
-
this.currentPivotField = null;
|
|
17
|
-
this.inItems = false;
|
|
18
|
-
this.inPivotTableStyleInfo = false;
|
|
19
31
|
}
|
|
20
32
|
prepare(_model) {
|
|
21
33
|
// No preparation needed
|
|
@@ -26,16 +38,17 @@ class PivotTableXform extends BaseXform {
|
|
|
26
38
|
}
|
|
27
39
|
reset() {
|
|
28
40
|
this.model = null;
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.inColItems = false;
|
|
35
|
-
this.inLocation = false;
|
|
41
|
+
// Reset all parser state flags using object
|
|
42
|
+
Object.keys(this.state).forEach(key => {
|
|
43
|
+
this.state[key] = false;
|
|
44
|
+
});
|
|
45
|
+
// Reset current context
|
|
36
46
|
this.currentPivotField = null;
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
47
|
+
this.currentRowItem = null;
|
|
48
|
+
this.currentColItem = null;
|
|
49
|
+
this.currentChartFormat = null;
|
|
50
|
+
this.pivotAreaXmlBuffer = [];
|
|
51
|
+
this.pivotAreaDepth = 0;
|
|
39
52
|
}
|
|
40
53
|
/**
|
|
41
54
|
* Render pivot table XML.
|
|
@@ -156,8 +169,7 @@ class PivotTableXform extends BaseXform {
|
|
|
156
169
|
* Render loaded pivot table (preserving original structure)
|
|
157
170
|
*/
|
|
158
171
|
renderLoaded(xmlStream, model) {
|
|
159
|
-
|
|
160
|
-
xmlStream.openNode(this.tag, {
|
|
172
|
+
const attrs = {
|
|
161
173
|
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
162
174
|
name: model.name || "PivotTable1",
|
|
163
175
|
cacheId: model.cacheId,
|
|
@@ -166,7 +178,8 @@ class PivotTableXform extends BaseXform {
|
|
|
166
178
|
applyFontFormats: model.applyFontFormats || "0",
|
|
167
179
|
applyPatternFormats: model.applyPatternFormats || "0",
|
|
168
180
|
applyAlignmentFormats: model.applyAlignmentFormats || "0",
|
|
169
|
-
|
|
181
|
+
// Preserve original value when present; default to Excel's typical "0".
|
|
182
|
+
applyWidthHeightFormats: model.applyWidthHeightFormats ?? "0",
|
|
170
183
|
dataCaption: model.dataCaption || "Values",
|
|
171
184
|
updatedVersion: model.updatedVersion || "8",
|
|
172
185
|
minRefreshableVersion: model.minRefreshableVersion || "3",
|
|
@@ -174,10 +187,27 @@ class PivotTableXform extends BaseXform {
|
|
|
174
187
|
itemPrintTitles: model.itemPrintTitles ? "1" : "0",
|
|
175
188
|
createdVersion: model.createdVersion || "8",
|
|
176
189
|
indent: model.indent !== undefined ? String(model.indent) : "0",
|
|
177
|
-
compact: model.compact ? "1" : "0",
|
|
178
|
-
compactData: model.compactData ? "1" : "0",
|
|
179
190
|
multipleFieldFilters: model.multipleFieldFilters ? "1" : "0"
|
|
180
|
-
}
|
|
191
|
+
};
|
|
192
|
+
// Add outline attributes if present
|
|
193
|
+
if (model.outline) {
|
|
194
|
+
attrs.outline = "1";
|
|
195
|
+
}
|
|
196
|
+
if (model.outlineData) {
|
|
197
|
+
attrs.outlineData = "1";
|
|
198
|
+
}
|
|
199
|
+
if (model.chartFormat !== undefined) {
|
|
200
|
+
attrs.chartFormat = String(model.chartFormat);
|
|
201
|
+
}
|
|
202
|
+
// Only add compact/compactData if they are true (some files don't have them)
|
|
203
|
+
if (model.compact) {
|
|
204
|
+
attrs.compact = "1";
|
|
205
|
+
}
|
|
206
|
+
if (model.compactData) {
|
|
207
|
+
attrs.compactData = "1";
|
|
208
|
+
}
|
|
209
|
+
xmlStream.openXml(XmlStream.StdDocAttributes);
|
|
210
|
+
xmlStream.openNode(this.tag, attrs);
|
|
181
211
|
// Location
|
|
182
212
|
if (model.location) {
|
|
183
213
|
xmlStream.leafNode("location", {
|
|
@@ -203,12 +233,19 @@ class PivotTableXform extends BaseXform {
|
|
|
203
233
|
}
|
|
204
234
|
xmlStream.closeNode();
|
|
205
235
|
}
|
|
206
|
-
// Row items
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
236
|
+
// Row items - use parsed items if available; otherwise emit a minimal grand total
|
|
237
|
+
if (model.rowItems && model.rowItems.length > 0) {
|
|
238
|
+
xmlStream.openNode("rowItems", { count: model.rowItems.length });
|
|
239
|
+
for (const item of model.rowItems) {
|
|
240
|
+
this.renderRowColItem(xmlStream, item);
|
|
241
|
+
}
|
|
242
|
+
xmlStream.closeNode();
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
xmlStream.writeXml('<rowItems count="1"><i t="grand"><x/></i></rowItems>');
|
|
246
|
+
}
|
|
211
247
|
// Col fields
|
|
248
|
+
// Excel commonly emits a synthetic field x=-2 when there are no column fields.
|
|
212
249
|
const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
|
|
213
250
|
xmlStream.openNode("colFields", { count: colFieldCount });
|
|
214
251
|
if (model.colFields.length === 0) {
|
|
@@ -220,25 +257,52 @@ class PivotTableXform extends BaseXform {
|
|
|
220
257
|
}
|
|
221
258
|
}
|
|
222
259
|
xmlStream.closeNode();
|
|
223
|
-
// Col items
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
260
|
+
// Col items - use parsed items if available
|
|
261
|
+
if (model.colItems && model.colItems.length > 0) {
|
|
262
|
+
xmlStream.openNode("colItems", { count: model.colItems.length });
|
|
263
|
+
for (const item of model.colItems) {
|
|
264
|
+
this.renderRowColItem(xmlStream, item);
|
|
265
|
+
}
|
|
266
|
+
xmlStream.closeNode();
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
xmlStream.writeXml('<colItems count="1"><i t="grand"><x/></i></colItems>');
|
|
270
|
+
}
|
|
228
271
|
// Data fields
|
|
229
272
|
if (model.dataFields.length > 0) {
|
|
230
273
|
xmlStream.openNode("dataFields", { count: model.dataFields.length });
|
|
231
274
|
for (const dataField of model.dataFields) {
|
|
232
|
-
const
|
|
275
|
+
const dfAttrs = {
|
|
233
276
|
name: dataField.name,
|
|
234
277
|
fld: dataField.fld,
|
|
235
278
|
baseField: dataField.baseField ?? 0,
|
|
236
279
|
baseItem: dataField.baseItem ?? 0
|
|
237
280
|
};
|
|
238
281
|
if (dataField.subtotal && dataField.subtotal !== "sum") {
|
|
239
|
-
|
|
282
|
+
dfAttrs.subtotal = dataField.subtotal;
|
|
283
|
+
}
|
|
284
|
+
xmlStream.leafNode("dataField", dfAttrs);
|
|
285
|
+
}
|
|
286
|
+
xmlStream.closeNode();
|
|
287
|
+
}
|
|
288
|
+
// Chart formats (for pivot charts) - preserve original pivotArea XML
|
|
289
|
+
if (model.chartFormats && model.chartFormats.length > 0) {
|
|
290
|
+
xmlStream.openNode("chartFormats", { count: model.chartFormats.length });
|
|
291
|
+
for (const cf of model.chartFormats) {
|
|
292
|
+
xmlStream.openNode("chartFormat", {
|
|
293
|
+
chart: cf.chart,
|
|
294
|
+
format: cf.format,
|
|
295
|
+
series: cf.series ? "1" : undefined
|
|
296
|
+
});
|
|
297
|
+
// Use preserved pivotArea XML or fallback to default
|
|
298
|
+
if (cf.pivotAreaXml) {
|
|
299
|
+
xmlStream.writeXml(cf.pivotAreaXml);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
// Fallback for newly created chart formats (shouldn't happen for loaded models)
|
|
303
|
+
xmlStream.writeXml(`<pivotArea type="data" outline="0" fieldPosition="0"><references count="1"><reference field="4294967294" count="1" selected="0"><x v="0"/></reference></references></pivotArea>`);
|
|
240
304
|
}
|
|
241
|
-
xmlStream.
|
|
305
|
+
xmlStream.closeNode();
|
|
242
306
|
}
|
|
243
307
|
xmlStream.closeNode();
|
|
244
308
|
}
|
|
@@ -252,46 +316,48 @@ class PivotTableXform extends BaseXform {
|
|
|
252
316
|
showLastColumn: "1"
|
|
253
317
|
});
|
|
254
318
|
// Extensions
|
|
255
|
-
xmlStream.writeXml(
|
|
256
|
-
<extLst>
|
|
257
|
-
<ext
|
|
258
|
-
uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}"
|
|
259
|
-
xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
|
|
260
|
-
>
|
|
261
|
-
<x14:pivotTableDefinition
|
|
262
|
-
hideValuesRow="1"
|
|
263
|
-
xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"
|
|
264
|
-
/>
|
|
265
|
-
</ext>
|
|
266
|
-
<ext
|
|
267
|
-
uri="{747A6164-185A-40DC-8AA5-F01512510D54}"
|
|
268
|
-
xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"
|
|
269
|
-
>
|
|
270
|
-
<xpdl:pivotTableDefinition16
|
|
271
|
-
EnabledSubtotalsDefault="0"
|
|
272
|
-
SubtotalsOnTopDefault="0"
|
|
273
|
-
/>
|
|
274
|
-
</ext>
|
|
275
|
-
</extLst>
|
|
276
|
-
`);
|
|
319
|
+
xmlStream.writeXml(`<extLst><ext uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><x14:pivotTableDefinition hideValuesRow="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"/></ext><ext uri="{747A6164-185A-40DC-8AA5-F01512510D54}" xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"><xpdl:pivotTableDefinition16/></ext></extLst>`);
|
|
277
320
|
xmlStream.closeNode();
|
|
278
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Render a row or column item element
|
|
324
|
+
*/
|
|
325
|
+
renderRowColItem(xmlStream, item) {
|
|
326
|
+
const attrs = {};
|
|
327
|
+
if (item.t) {
|
|
328
|
+
attrs.t = item.t;
|
|
329
|
+
}
|
|
330
|
+
if (item.x && item.x.length > 0) {
|
|
331
|
+
xmlStream.openNode("i", attrs);
|
|
332
|
+
for (const x of item.x) {
|
|
333
|
+
if (x.v && x.v !== 0) {
|
|
334
|
+
xmlStream.leafNode("x", { v: x.v });
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
xmlStream.leafNode("x");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
xmlStream.closeNode();
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Empty item (like <i/> in colItems)
|
|
344
|
+
xmlStream.leafNode("i", attrs);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
279
347
|
/**
|
|
280
348
|
* Render a loaded pivot field
|
|
281
349
|
*/
|
|
282
350
|
renderPivotFieldLoaded(xmlStream, field) {
|
|
283
|
-
const attrs = {
|
|
284
|
-
|
|
285
|
-
outline: field.outline ? "1" : "0",
|
|
286
|
-
showAll: field.showAll ? "1" : "0",
|
|
287
|
-
defaultSubtotal: field.defaultSubtotal ? "1" : "0"
|
|
288
|
-
};
|
|
351
|
+
const attrs = {};
|
|
352
|
+
// Only add attributes that were present in the original
|
|
289
353
|
if (field.axis) {
|
|
290
354
|
attrs.axis = field.axis;
|
|
291
355
|
}
|
|
292
356
|
if (field.dataField) {
|
|
293
357
|
attrs.dataField = "1";
|
|
294
358
|
}
|
|
359
|
+
// showAll is typically always present
|
|
360
|
+
attrs.showAll = field.showAll ? "1" : "0";
|
|
295
361
|
if (field.items && field.items.length > 0) {
|
|
296
362
|
xmlStream.openNode("pivotField", attrs);
|
|
297
363
|
xmlStream.openNode("items", { count: field.items.length + 1 });
|
|
@@ -299,7 +365,7 @@ class PivotTableXform extends BaseXform {
|
|
|
299
365
|
xmlStream.leafNode("item", { x: itemIndex });
|
|
300
366
|
}
|
|
301
367
|
// Grand total item
|
|
302
|
-
xmlStream.writeXml('<item t="default"
|
|
368
|
+
xmlStream.writeXml('<item t="default"/>');
|
|
303
369
|
xmlStream.closeNode(); // items
|
|
304
370
|
xmlStream.closeNode(); // pivotField
|
|
305
371
|
}
|
|
@@ -337,6 +403,12 @@ class PivotTableXform extends BaseXform {
|
|
|
337
403
|
compact: attributes.compact === "1",
|
|
338
404
|
compactData: attributes.compactData === "1",
|
|
339
405
|
multipleFieldFilters: attributes.multipleFieldFilters === "1",
|
|
406
|
+
outline: attributes.outline === "1",
|
|
407
|
+
outlineData: attributes.outlineData === "1",
|
|
408
|
+
chartFormat: attributes.chartFormat ? parseInt(attributes.chartFormat, 10) : undefined,
|
|
409
|
+
rowItems: [],
|
|
410
|
+
colItems: [],
|
|
411
|
+
chartFormats: [],
|
|
340
412
|
isLoaded: true
|
|
341
413
|
};
|
|
342
414
|
break;
|
|
@@ -357,10 +429,10 @@ class PivotTableXform extends BaseXform {
|
|
|
357
429
|
}
|
|
358
430
|
break;
|
|
359
431
|
case "pivotFields":
|
|
360
|
-
this.inPivotFields = true;
|
|
432
|
+
this.state.inPivotFields = true;
|
|
361
433
|
break;
|
|
362
434
|
case "pivotField":
|
|
363
|
-
if (this.inPivotFields) {
|
|
435
|
+
if (this.state.inPivotFields) {
|
|
364
436
|
this.currentPivotField = {
|
|
365
437
|
axis: attributes.axis,
|
|
366
438
|
dataField: attributes.dataField === "1",
|
|
@@ -374,43 +446,101 @@ class PivotTableXform extends BaseXform {
|
|
|
374
446
|
break;
|
|
375
447
|
case "items":
|
|
376
448
|
if (this.currentPivotField) {
|
|
377
|
-
this.inItems = true;
|
|
449
|
+
this.state.inItems = true;
|
|
378
450
|
}
|
|
379
451
|
break;
|
|
380
452
|
case "item":
|
|
381
|
-
if (this.inItems && this.currentPivotField && attributes.x !== undefined) {
|
|
453
|
+
if (this.state.inItems && this.currentPivotField && attributes.x !== undefined) {
|
|
382
454
|
this.currentPivotField.items.push(parseInt(attributes.x, 10));
|
|
383
455
|
}
|
|
384
456
|
break;
|
|
385
457
|
case "rowFields":
|
|
386
|
-
this.inRowFields = true;
|
|
458
|
+
this.state.inRowFields = true;
|
|
387
459
|
break;
|
|
388
460
|
case "colFields":
|
|
389
|
-
this.inColFields = true;
|
|
461
|
+
this.state.inColFields = true;
|
|
390
462
|
break;
|
|
391
463
|
case "dataFields":
|
|
392
|
-
this.inDataFields = true;
|
|
464
|
+
this.state.inDataFields = true;
|
|
393
465
|
break;
|
|
394
466
|
case "rowItems":
|
|
395
|
-
this.inRowItems = true;
|
|
467
|
+
this.state.inRowItems = true;
|
|
396
468
|
break;
|
|
397
469
|
case "colItems":
|
|
398
|
-
this.inColItems = true;
|
|
470
|
+
this.state.inColItems = true;
|
|
471
|
+
break;
|
|
472
|
+
case "i":
|
|
473
|
+
// Handle row/col item element
|
|
474
|
+
if (this.state.inRowItems && this.model) {
|
|
475
|
+
this.currentRowItem = { t: attributes.t, x: [] };
|
|
476
|
+
}
|
|
477
|
+
else if (this.state.inColItems && this.model) {
|
|
478
|
+
this.currentColItem = { t: attributes.t, x: [] };
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
case "x":
|
|
482
|
+
// Handle x element inside row/col items or pivotArea
|
|
483
|
+
if (this.state.inPivotArea) {
|
|
484
|
+
// Collect x element for pivotArea XML
|
|
485
|
+
const xAttrs = Object.entries(attributes)
|
|
486
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
487
|
+
.join(" ");
|
|
488
|
+
this.pivotAreaXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
|
|
489
|
+
}
|
|
490
|
+
else if (this.currentRowItem) {
|
|
491
|
+
this.currentRowItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
|
|
492
|
+
}
|
|
493
|
+
else if (this.currentColItem) {
|
|
494
|
+
this.currentColItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
case "chartFormats":
|
|
498
|
+
this.state.inChartFormats = true;
|
|
499
|
+
break;
|
|
500
|
+
case "chartFormat":
|
|
501
|
+
if (this.state.inChartFormats && this.model) {
|
|
502
|
+
this.currentChartFormat = {
|
|
503
|
+
chart: attributes.chart ? parseInt(attributes.chart, 10) : 0,
|
|
504
|
+
format: attributes.format ? parseInt(attributes.format, 10) : 0,
|
|
505
|
+
series: attributes.series === "1"
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
break;
|
|
509
|
+
case "pivotArea":
|
|
510
|
+
// Start collecting pivotArea XML for chartFormat
|
|
511
|
+
if (this.currentChartFormat) {
|
|
512
|
+
this.state.inPivotArea = true;
|
|
513
|
+
const attrsStr = Object.entries(attributes)
|
|
514
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
515
|
+
.join(" ");
|
|
516
|
+
this.pivotAreaXmlBuffer = [attrsStr ? `<pivotArea ${attrsStr}>` : "<pivotArea>"];
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
case "references":
|
|
520
|
+
case "reference":
|
|
521
|
+
// Collect nested elements in pivotArea
|
|
522
|
+
if (this.state.inPivotArea) {
|
|
523
|
+
this.pivotAreaDepth++;
|
|
524
|
+
const attrsStr = Object.entries(attributes)
|
|
525
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
526
|
+
.join(" ");
|
|
527
|
+
this.pivotAreaXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
|
|
528
|
+
}
|
|
399
529
|
break;
|
|
400
530
|
case "field":
|
|
401
531
|
// Handle field element (used in rowFields, colFields)
|
|
402
532
|
if (this.model) {
|
|
403
533
|
const fieldIndex = parseInt(attributes.x || "0", 10);
|
|
404
|
-
if (this.inRowFields) {
|
|
534
|
+
if (this.state.inRowFields) {
|
|
405
535
|
this.model.rowFields.push(fieldIndex);
|
|
406
536
|
}
|
|
407
|
-
else if (this.inColFields) {
|
|
537
|
+
else if (this.state.inColFields) {
|
|
408
538
|
this.model.colFields.push(fieldIndex);
|
|
409
539
|
}
|
|
410
540
|
}
|
|
411
541
|
break;
|
|
412
542
|
case "dataField":
|
|
413
|
-
if (this.inDataFields && this.model) {
|
|
543
|
+
if (this.state.inDataFields && this.model) {
|
|
414
544
|
this.model.dataFields.push({
|
|
415
545
|
name: xmlDecode(attributes.name || ""),
|
|
416
546
|
fld: parseInt(attributes.fld || "0", 10),
|
|
@@ -432,12 +562,32 @@ class PivotTableXform extends BaseXform {
|
|
|
432
562
|
// No text content in pivot table elements
|
|
433
563
|
}
|
|
434
564
|
parseClose(name) {
|
|
565
|
+
// Handle pivotArea nested elements - close tags
|
|
566
|
+
if (this.state.inPivotArea) {
|
|
567
|
+
if (name === "pivotArea") {
|
|
568
|
+
this.pivotAreaXmlBuffer.push("</pivotArea>");
|
|
569
|
+
if (this.currentChartFormat) {
|
|
570
|
+
this.currentChartFormat.pivotAreaXml = this.pivotAreaXmlBuffer.join("");
|
|
571
|
+
}
|
|
572
|
+
this.state.inPivotArea = false;
|
|
573
|
+
this.pivotAreaXmlBuffer = [];
|
|
574
|
+
this.pivotAreaDepth = 0;
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
else if (name === "references" || name === "reference") {
|
|
578
|
+
this.pivotAreaXmlBuffer.push(`</${name}>`);
|
|
579
|
+
this.pivotAreaDepth--;
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
// x elements are self-closing, no need to handle close
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
435
585
|
switch (name) {
|
|
436
586
|
case this.tag:
|
|
437
587
|
// End of pivotTableDefinition
|
|
438
588
|
return false;
|
|
439
589
|
case "pivotFields":
|
|
440
|
-
this.inPivotFields = false;
|
|
590
|
+
this.state.inPivotFields = false;
|
|
441
591
|
break;
|
|
442
592
|
case "pivotField":
|
|
443
593
|
if (this.currentPivotField && this.model) {
|
|
@@ -446,22 +596,42 @@ class PivotTableXform extends BaseXform {
|
|
|
446
596
|
}
|
|
447
597
|
break;
|
|
448
598
|
case "items":
|
|
449
|
-
this.inItems = false;
|
|
599
|
+
this.state.inItems = false;
|
|
450
600
|
break;
|
|
451
601
|
case "rowFields":
|
|
452
|
-
this.inRowFields = false;
|
|
602
|
+
this.state.inRowFields = false;
|
|
453
603
|
break;
|
|
454
604
|
case "colFields":
|
|
455
|
-
this.inColFields = false;
|
|
605
|
+
this.state.inColFields = false;
|
|
456
606
|
break;
|
|
457
607
|
case "dataFields":
|
|
458
|
-
this.inDataFields = false;
|
|
608
|
+
this.state.inDataFields = false;
|
|
459
609
|
break;
|
|
460
610
|
case "rowItems":
|
|
461
|
-
this.inRowItems = false;
|
|
611
|
+
this.state.inRowItems = false;
|
|
462
612
|
break;
|
|
463
613
|
case "colItems":
|
|
464
|
-
this.inColItems = false;
|
|
614
|
+
this.state.inColItems = false;
|
|
615
|
+
break;
|
|
616
|
+
case "i":
|
|
617
|
+
// Finish row/col item
|
|
618
|
+
if (this.currentRowItem && this.model) {
|
|
619
|
+
this.model.rowItems.push(this.currentRowItem);
|
|
620
|
+
this.currentRowItem = null;
|
|
621
|
+
}
|
|
622
|
+
else if (this.currentColItem && this.model) {
|
|
623
|
+
this.model.colItems.push(this.currentColItem);
|
|
624
|
+
this.currentColItem = null;
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
627
|
+
case "chartFormats":
|
|
628
|
+
this.state.inChartFormats = false;
|
|
629
|
+
break;
|
|
630
|
+
case "chartFormat":
|
|
631
|
+
if (this.currentChartFormat && this.model) {
|
|
632
|
+
this.model.chartFormats.push(this.currentChartFormat);
|
|
633
|
+
this.currentChartFormat = null;
|
|
634
|
+
}
|
|
465
635
|
break;
|
|
466
636
|
}
|
|
467
637
|
return true;
|
|
@@ -188,6 +188,23 @@ class WorkSheetXform extends BaseXform {
|
|
|
188
188
|
vmlDrawing: `vmlDrawing${model.id}`
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
|
+
// Handle pre-loaded drawing (from file read) that may contain charts or other non-image content
|
|
192
|
+
// This preserves drawings through round-trip even if they don't contain images
|
|
193
|
+
// Note: Always assign a new rId since rels are rebuilt during write
|
|
194
|
+
if (model.drawing && model.drawing.anchors) {
|
|
195
|
+
// This is a loaded drawing that needs to be added to relationships
|
|
196
|
+
const drawing = model.drawing;
|
|
197
|
+
drawing.rId = nextRid(rels);
|
|
198
|
+
if (!drawing.name) {
|
|
199
|
+
drawing.name = `drawing${++options.drawingsCount}`;
|
|
200
|
+
}
|
|
201
|
+
options.drawings.push(drawing);
|
|
202
|
+
rels.push({
|
|
203
|
+
Id: drawing.rId,
|
|
204
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
205
|
+
Target: drawingRelTargetFromWorksheet(drawing.name)
|
|
206
|
+
});
|
|
207
|
+
}
|
|
191
208
|
const drawingRelsHash = [];
|
|
192
209
|
let bookImage;
|
|
193
210
|
model.media.forEach(medium => {
|
|
@@ -599,17 +616,27 @@ class WorkSheetXform extends BaseXform {
|
|
|
599
616
|
if (match) {
|
|
600
617
|
const drawingName = match[1];
|
|
601
618
|
const drawing = options.drawings[drawingName];
|
|
602
|
-
drawing
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
619
|
+
if (drawing) {
|
|
620
|
+
// Preserve the drawing object for round-trip (charts, etc.)
|
|
621
|
+
// This includes the name, anchors, and rels
|
|
622
|
+
model.drawing = {
|
|
623
|
+
...drawing,
|
|
624
|
+
name: drawingName,
|
|
625
|
+
rels: options.drawingRels?.[drawingName] || drawing.rels || []
|
|
626
|
+
};
|
|
627
|
+
// Also extract images to model.media for backward compatibility
|
|
628
|
+
drawing.anchors.forEach(anchor => {
|
|
629
|
+
if (anchor.medium) {
|
|
630
|
+
const image = {
|
|
631
|
+
type: "image",
|
|
632
|
+
imageId: anchor.medium.index,
|
|
633
|
+
range: anchor.range,
|
|
634
|
+
hyperlinks: anchor.picture.hyperlinks
|
|
635
|
+
};
|
|
636
|
+
model.media.push(image);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
}
|
|
613
640
|
}
|
|
614
641
|
}
|
|
615
642
|
const backgroundRel = model.background && rels[model.background.rId];
|