@office-open/xlsx 0.6.10 → 0.7.1
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/index.d.mts +179 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +990 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -4
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { AppProperties, BaseXmlComponent, ChartCollection, ChartSpace, Formatter, IgnoreIfEmptyXmlComponent, OoxmlMimeType, Relationships, buildCorePropertiesXmlString, compileMapping, createPacker, parseArchive, parseCorePropsElement, strFromU8, toJson, unzipSync, zipAndConvert } from "@office-open/core";
|
|
1
|
+
import { AppProperties, BaseXmlComponent, ChartCollection, ChartSpace, Formatter, IgnoreIfEmptyXmlComponent, OoxmlMimeType, Relationships, buildCorePropertiesXmlString, compileMapping, createPacker, parseArchive, parseCorePropsElement, strFromU8, toJson, toUint8Array, unzipSync, zipAndConvert } from "@office-open/core";
|
|
2
2
|
import { attr, attrNum, attrs, escapeXml, findChild, js2xml, selfCloseElement, textOf } from "@office-open/xml";
|
|
3
|
-
import { toUint8Array } from "undio";
|
|
4
3
|
//#region src/file/content-types.ts
|
|
5
4
|
/**
|
|
6
5
|
* Content Types module for XLSX packages.
|
|
@@ -87,6 +86,21 @@ var ContentTypes = class extends BaseXmlComponent {
|
|
|
87
86
|
key: `/xl/drawings/drawing${index}.xml`
|
|
88
87
|
});
|
|
89
88
|
}
|
|
89
|
+
addComments(index) {
|
|
90
|
+
this.dynamicEntries.push({
|
|
91
|
+
type: "Override",
|
|
92
|
+
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
|
|
93
|
+
key: `/xl/comments${index}.xml`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
addVmlDrawing() {
|
|
97
|
+
if (this.dynamicEntries.some((e) => e.type === "Default" && e.key === "vml")) return;
|
|
98
|
+
this.dynamicEntries.push({
|
|
99
|
+
type: "Default",
|
|
100
|
+
contentType: "application/vnd.openxmlformats-officedocument.vmlDrawing",
|
|
101
|
+
key: "vml"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
90
104
|
addImageType(extension) {
|
|
91
105
|
const contentType = extension === "png" ? "image/png" : "image/jpeg";
|
|
92
106
|
if (this.dynamicEntries.some((e) => e.type === "Default" && e.key === extension)) return;
|
|
@@ -96,6 +110,27 @@ var ContentTypes = class extends BaseXmlComponent {
|
|
|
96
110
|
key: extension
|
|
97
111
|
});
|
|
98
112
|
}
|
|
113
|
+
addPivotTable(index) {
|
|
114
|
+
this.dynamicEntries.push({
|
|
115
|
+
type: "Override",
|
|
116
|
+
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml",
|
|
117
|
+
key: `/xl/pivotTables/pivotTable${index}.xml`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
addPivotCacheDefinition(index) {
|
|
121
|
+
this.dynamicEntries.push({
|
|
122
|
+
type: "Override",
|
|
123
|
+
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml",
|
|
124
|
+
key: `/xl/pivotCache/pivotCacheDefinition${index}.xml`
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
addPivotCacheRecords(index) {
|
|
128
|
+
this.dynamicEntries.push({
|
|
129
|
+
type: "Override",
|
|
130
|
+
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml",
|
|
131
|
+
key: `/xl/pivotCache/pivotCacheRecords${index}.xml`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
99
134
|
toXml(_context) {
|
|
100
135
|
const p = ["<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">", STATIC_XML];
|
|
101
136
|
for (const e of this.dynamicEntries) if (e.type === "Default") p.push(`<Default ContentType="${e.contentType}" Extension="${e.key}"/>`);
|
|
@@ -241,6 +276,7 @@ var Styles = class extends BaseXmlComponent {
|
|
|
241
276
|
numFmtId: 0
|
|
242
277
|
}];
|
|
243
278
|
cellXfKeys = /* @__PURE__ */ new Map();
|
|
279
|
+
dxfs = [];
|
|
244
280
|
constructor() {
|
|
245
281
|
super("styleSheet");
|
|
246
282
|
this.fontKeys.set(fontKey(this.fonts[0]), 0);
|
|
@@ -269,6 +305,15 @@ var Styles = class extends BaseXmlComponent {
|
|
|
269
305
|
this.cellXfKeys.set(key, idx);
|
|
270
306
|
return idx;
|
|
271
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Register a differential format and return its index (dxfId).
|
|
310
|
+
* Used by conditional formatting rules.
|
|
311
|
+
*/
|
|
312
|
+
registerDxf(opts) {
|
|
313
|
+
const idx = this.dxfs.length;
|
|
314
|
+
this.dxfs.push(opts);
|
|
315
|
+
return idx;
|
|
316
|
+
}
|
|
272
317
|
registerFont(opts) {
|
|
273
318
|
if (!opts) return 0;
|
|
274
319
|
const key = fontKey(opts);
|
|
@@ -357,7 +402,22 @@ var Styles = class extends BaseXmlComponent {
|
|
|
357
402
|
}
|
|
358
403
|
p.push("</cellXfs>");
|
|
359
404
|
p.push("<cellStyles count=\"1\"><cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\"/></cellStyles>");
|
|
360
|
-
|
|
405
|
+
if (this.dxfs.length > 0) {
|
|
406
|
+
p.push(`<dxfs count="${this.dxfs.length}">`);
|
|
407
|
+
for (const dxf of this.dxfs) {
|
|
408
|
+
const dParts = [];
|
|
409
|
+
if (dxf.font) dParts.push(`<font>${this.fontXmlStr(dxf.font)}</font>`);
|
|
410
|
+
if (dxf.fill) {
|
|
411
|
+
const bgColor = dxf.fill.color ? `<bgColor rgb="FF${dxf.fill.color}"/>` : "";
|
|
412
|
+
const patAttrs = attrs({ patternType: dxf.fill.patternType ?? "solid" });
|
|
413
|
+
dParts.push(`<fill><patternFill${patAttrs}>${bgColor}</patternFill></fill>`);
|
|
414
|
+
}
|
|
415
|
+
if (dxf.numFmt) dParts.push(`<numFmt formatCode="${escapeXml(dxf.numFmt)}"/>`);
|
|
416
|
+
if (dParts.length > 0) p.push(`<dxf>${dParts.join("")}</dxf>`);
|
|
417
|
+
else p.push("<dxf/>");
|
|
418
|
+
}
|
|
419
|
+
p.push("</dxfs>");
|
|
420
|
+
} else p.push("<dxfs count=\"0\"/>");
|
|
361
421
|
p.push("<tableStyles count=\"0\" defaultTableStyle=\"TableStyleMedium2\" defaultPivotStyle=\"PivotStyleLight16\"/>");
|
|
362
422
|
p.push("<extLst/>");
|
|
363
423
|
p.push("</styleSheet>");
|
|
@@ -432,13 +492,17 @@ var DefaultTheme = class extends BaseXmlComponent {
|
|
|
432
492
|
*/
|
|
433
493
|
var WorkbookXml = class extends BaseXmlComponent {
|
|
434
494
|
sheets;
|
|
435
|
-
|
|
495
|
+
pivotCaches;
|
|
496
|
+
constructor(sheets, pivotCaches) {
|
|
436
497
|
super("workbook");
|
|
437
498
|
this.sheets = sheets;
|
|
499
|
+
this.pivotCaches = pivotCaches ?? [];
|
|
438
500
|
}
|
|
439
501
|
toXml(_context) {
|
|
440
502
|
const p = [
|
|
441
|
-
"<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">",
|
|
503
|
+
"<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" mc:Ignorable=\"x15 xr xr6 xr10 xr2\" xmlns:x15=\"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main\" xmlns:xr=\"http://schemas.microsoft.com/office/spreadsheetml/2014/revision\" xmlns:xr6=\"http://schemas.microsoft.com/office/spreadsheetml/2016/revision6\" xmlns:xr10=\"http://schemas.microsoft.com/office/spreadsheetml/2016/revision10\" xmlns:xr2=\"http://schemas.microsoft.com/office/spreadsheetml/2015/revision2\">",
|
|
504
|
+
"<fileVersion appName=\"xl\" lastEdited=\"7\" lowestEdited=\"6\" rupBuild=\"29929\"/>",
|
|
505
|
+
"<workbookPr/>",
|
|
442
506
|
"<bookViews><workbookView xWindow=\"0\" yWindow=\"0\" windowWidth=\"28800\" windowHeight=\"12300\"/></bookViews>",
|
|
443
507
|
"<sheets>"
|
|
444
508
|
];
|
|
@@ -446,7 +510,14 @@ var WorkbookXml = class extends BaseXmlComponent {
|
|
|
446
510
|
const stateAttr = s.state && s.state !== "visible" ? ` state="${s.state}"` : "";
|
|
447
511
|
p.push(`<sheet name="${escapeXml(s.name)}" sheetId="${s.sheetId}" r:id="${s.rId}"${stateAttr}/>`);
|
|
448
512
|
}
|
|
449
|
-
p.push("</sheets
|
|
513
|
+
p.push("</sheets>");
|
|
514
|
+
p.push("<calcPr calcId=\"162913\"/>");
|
|
515
|
+
if (this.pivotCaches.length > 0) {
|
|
516
|
+
p.push("<pivotCaches>");
|
|
517
|
+
for (const pc of this.pivotCaches) p.push(`<pivotCache cacheId="${pc.cacheId}" r:id="${pc.rId}"/>`);
|
|
518
|
+
p.push("</pivotCaches>");
|
|
519
|
+
}
|
|
520
|
+
p.push("</workbook>");
|
|
450
521
|
return p.join("");
|
|
451
522
|
}
|
|
452
523
|
};
|
|
@@ -468,22 +539,38 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
468
539
|
columns;
|
|
469
540
|
mergeCells;
|
|
470
541
|
freezePanes;
|
|
542
|
+
protection;
|
|
471
543
|
autoFilter;
|
|
472
544
|
images;
|
|
473
545
|
chartOptions;
|
|
474
546
|
dataValidations;
|
|
475
547
|
conditionalFormats;
|
|
548
|
+
hyperlinks;
|
|
549
|
+
comments;
|
|
550
|
+
headerFooter;
|
|
551
|
+
pageSetup;
|
|
552
|
+
tabColor;
|
|
553
|
+
sheetView;
|
|
554
|
+
pivotTableOptions;
|
|
476
555
|
constructor(options) {
|
|
477
556
|
super("worksheet");
|
|
478
557
|
this.rows = options.rows ?? [];
|
|
479
558
|
this.columns = options.columns ?? [];
|
|
480
559
|
this.mergeCells = options.mergeCells ?? [];
|
|
481
560
|
this.freezePanes = options.freezePanes;
|
|
561
|
+
this.protection = options.protection;
|
|
482
562
|
this.autoFilter = options.autoFilter;
|
|
483
563
|
this.images = options.images ?? [];
|
|
484
564
|
this.chartOptions = options.charts ?? [];
|
|
485
565
|
this.dataValidations = options.dataValidations ?? [];
|
|
486
566
|
this.conditionalFormats = options.conditionalFormats ?? [];
|
|
567
|
+
this.hyperlinks = options.hyperlinks ?? [];
|
|
568
|
+
this.comments = options.comments ?? [];
|
|
569
|
+
this.headerFooter = options.headerFooter;
|
|
570
|
+
this.pageSetup = options.pageSetup;
|
|
571
|
+
this.tabColor = options.tabColor;
|
|
572
|
+
this.sheetView = options.sheetView;
|
|
573
|
+
this.pivotTableOptions = options.pivotTables ?? [];
|
|
487
574
|
}
|
|
488
575
|
get imageOptions() {
|
|
489
576
|
return this.images;
|
|
@@ -491,6 +578,18 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
491
578
|
get charts() {
|
|
492
579
|
return this.chartOptions;
|
|
493
580
|
}
|
|
581
|
+
get hyperlinkOptions() {
|
|
582
|
+
return this.hyperlinks;
|
|
583
|
+
}
|
|
584
|
+
get worksheetRows() {
|
|
585
|
+
return this.rows;
|
|
586
|
+
}
|
|
587
|
+
get commentOptions() {
|
|
588
|
+
return this.comments;
|
|
589
|
+
}
|
|
590
|
+
get pivotTables() {
|
|
591
|
+
return this.pivotTableOptions;
|
|
592
|
+
}
|
|
494
593
|
/**
|
|
495
594
|
* Zero-allocation fast path: directly concatenate XML string.
|
|
496
595
|
* Bypasses the IXmlableObject intermediate tree entirely.
|
|
@@ -499,7 +598,22 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
499
598
|
const fileData = context.fileData;
|
|
500
599
|
const sharedStrings = fileData?.sharedStrings;
|
|
501
600
|
const styles = fileData?.styles;
|
|
502
|
-
const p = ["<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">"];
|
|
601
|
+
const p = ["<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" mc:Ignorable=\"x14ac xr xr2 xr3\" xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\" xmlns:xr=\"http://schemas.microsoft.com/office/spreadsheetml/2014/revision\" xmlns:xr2=\"http://schemas.microsoft.com/office/spreadsheetml/2015/revision2\" xmlns:xr3=\"http://schemas.microsoft.com/office/spreadsheetml/2016/revision3\">"];
|
|
602
|
+
const hasTabColor = !!this.tabColor;
|
|
603
|
+
const hasOutline = this.columns.some((c) => c.outlineLevel !== void 0);
|
|
604
|
+
if (hasTabColor || hasOutline) {
|
|
605
|
+
const prParts = [];
|
|
606
|
+
if (this.tabColor) {
|
|
607
|
+
const tc = this.tabColor;
|
|
608
|
+
const tcAttrs = {};
|
|
609
|
+
if (tc.rgb) tcAttrs.rgb = tc.rgb;
|
|
610
|
+
if (tc.theme !== void 0) tcAttrs.theme = tc.theme;
|
|
611
|
+
if (tc.tint !== void 0) tcAttrs.tint = tc.tint;
|
|
612
|
+
prParts.push(`<tabColor${attrs(tcAttrs)}/>`);
|
|
613
|
+
}
|
|
614
|
+
if (hasOutline) prParts.push("<outlinePr summaryBelow=\"1\" summaryRight=\"1\"/>");
|
|
615
|
+
p.push(`<sheetPr>${prParts.join("")}</sheetPr>`);
|
|
616
|
+
}
|
|
503
617
|
const maxRow = this.rows.length;
|
|
504
618
|
let maxCol = 0;
|
|
505
619
|
for (const row of this.rows) if (row.cells && row.cells.length > maxCol) maxCol = row.cells.length;
|
|
@@ -515,8 +629,12 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
515
629
|
const leftCol = fp.col ? fp.col + 1 : 1;
|
|
516
630
|
const topLeftCell = this.defaultCellRef(topRow, leftCol);
|
|
517
631
|
const activePane = ySplit > 0 && xSplit > 0 ? "bottomRight" : ySplit > 0 ? "bottomLeft" : "topRight";
|
|
518
|
-
|
|
519
|
-
|
|
632
|
+
const svAttrs = this.buildSheetViewAttrs();
|
|
633
|
+
p.push(`<sheetViews><sheetView${svAttrs}>`, `<pane ySplit="${ySplit}" xSplit="${xSplit}" topLeftCell="${topLeftCell}" activePane="${activePane}" state="frozen"/>`, "</sheetView></sheetViews>");
|
|
634
|
+
} else {
|
|
635
|
+
const svAttrs = this.buildSheetViewAttrs();
|
|
636
|
+
p.push(`<sheetViews><sheetView${svAttrs}/></sheetViews>`);
|
|
637
|
+
}
|
|
520
638
|
p.push("<sheetFormatPr defaultRowHeight=\"15\"/>");
|
|
521
639
|
if (this.columns.length > 0) {
|
|
522
640
|
p.push("<cols>");
|
|
@@ -530,6 +648,8 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
530
648
|
colAttrs.customWidth = 1;
|
|
531
649
|
}
|
|
532
650
|
if (col.hidden) colAttrs.hidden = 1;
|
|
651
|
+
if (col.outlineLevel !== void 0) colAttrs.outlineLevel = col.outlineLevel;
|
|
652
|
+
if (col.collapsed) colAttrs.collapsed = 1;
|
|
533
653
|
p.push(selfCloseElement("col", attrs(colAttrs)));
|
|
534
654
|
}
|
|
535
655
|
p.push("</cols>");
|
|
@@ -556,7 +676,62 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
556
676
|
} else p.push(`<row${attrs(rowAttrs)}/>`);
|
|
557
677
|
}
|
|
558
678
|
p.push("</sheetData>");
|
|
559
|
-
if (this.
|
|
679
|
+
if (this.protection) {
|
|
680
|
+
const prot = this.protection;
|
|
681
|
+
const protAttrs = {};
|
|
682
|
+
if (prot.password) protAttrs.password = this.hashPassword(prot.password);
|
|
683
|
+
if (prot.sheet) protAttrs.sheet = 1;
|
|
684
|
+
if (prot.objects) protAttrs.objects = 1;
|
|
685
|
+
if (prot.scenarios) protAttrs.scenarios = 1;
|
|
686
|
+
if (prot.formatCells === false) protAttrs.formatCells = 0;
|
|
687
|
+
if (prot.formatColumns === false) protAttrs.formatColumns = 0;
|
|
688
|
+
if (prot.formatRows === false) protAttrs.formatRows = 0;
|
|
689
|
+
if (prot.insertColumns === false) protAttrs.insertColumns = 0;
|
|
690
|
+
if (prot.insertRows === false) protAttrs.insertRows = 0;
|
|
691
|
+
if (prot.insertHyperlinks === false) protAttrs.insertHyperlinks = 0;
|
|
692
|
+
if (prot.deleteColumns === false) protAttrs.deleteColumns = 0;
|
|
693
|
+
if (prot.deleteRows === false) protAttrs.deleteRows = 0;
|
|
694
|
+
if (prot.selectLockedCells) protAttrs.selectLockedCells = 1;
|
|
695
|
+
if (prot.sort === false) protAttrs.sort = 0;
|
|
696
|
+
if (prot.autoFilter === false) protAttrs.autoFilter = 0;
|
|
697
|
+
if (prot.pivotTables === false) protAttrs.pivotTables = 0;
|
|
698
|
+
if (prot.selectUnlockedCells) protAttrs.selectUnlockedCells = 1;
|
|
699
|
+
p.push(selfCloseElement("sheetProtection", attrs(protAttrs)));
|
|
700
|
+
}
|
|
701
|
+
if (this.autoFilter) if (typeof this.autoFilter === "string") p.push(selfCloseElement("autoFilter", attrs({ ref: this.autoFilter })));
|
|
702
|
+
else {
|
|
703
|
+
const af = this.autoFilter;
|
|
704
|
+
const inner = [];
|
|
705
|
+
for (const t10 of af.top10 ?? []) {
|
|
706
|
+
const t10Attrs = { val: t10.val };
|
|
707
|
+
if (t10.top === false) t10Attrs.top = 0;
|
|
708
|
+
if (t10.percent) t10Attrs.percent = 1;
|
|
709
|
+
inner.push(`<filterColumn colId="${t10.colId}"><top10${attrs(t10Attrs)}/></filterColumn>`);
|
|
710
|
+
}
|
|
711
|
+
for (const cf of af.customFilters ?? []) {
|
|
712
|
+
const cfAttrs = {};
|
|
713
|
+
if (cf.and) cfAttrs.and = 1;
|
|
714
|
+
const filters = [];
|
|
715
|
+
if (cf.val !== void 0) {
|
|
716
|
+
const fAttrs = { val: cf.val };
|
|
717
|
+
if (cf.operator) fAttrs.operator = cf.operator;
|
|
718
|
+
filters.push(selfCloseElement("customFilter", attrs(fAttrs)));
|
|
719
|
+
}
|
|
720
|
+
if (cf.val2 !== void 0) filters.push(selfCloseElement("customFilter", attrs({ val: cf.val2 })));
|
|
721
|
+
if (filters.length > 0) inner.push(`<filterColumn colId="${cf.colId}"><customFilters${attrs(cfAttrs)}>${filters.join("")}</customFilters></filterColumn>`);
|
|
722
|
+
}
|
|
723
|
+
if (af.sort && af.sort.length > 0) {
|
|
724
|
+
const sortParts = [];
|
|
725
|
+
for (const sc of af.sort) {
|
|
726
|
+
const scAttrs = { ref: sc.ref };
|
|
727
|
+
if (sc.descending) scAttrs.descending = 1;
|
|
728
|
+
sortParts.push(selfCloseElement("sortCondition", attrs(scAttrs)));
|
|
729
|
+
}
|
|
730
|
+
inner.push(`<sortState ref="${af.ref}">${sortParts.join("")}</sortState>`);
|
|
731
|
+
}
|
|
732
|
+
if (inner.length > 0) p.push(`<autoFilter ref="${af.ref}">`, ...inner, "</autoFilter>");
|
|
733
|
+
else p.push(selfCloseElement("autoFilter", attrs({ ref: af.ref })));
|
|
734
|
+
}
|
|
560
735
|
if (this.mergeCells.length > 0) {
|
|
561
736
|
p.push(`<mergeCells count="${this.mergeCells.length}">`);
|
|
562
737
|
for (const mc of this.mergeCells) {
|
|
@@ -604,10 +779,82 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
604
779
|
}
|
|
605
780
|
p.push("</dataValidations>");
|
|
606
781
|
}
|
|
782
|
+
if (this.hyperlinks.length > 0) {
|
|
783
|
+
p.push("<hyperlinks>");
|
|
784
|
+
let hlIdx = 0;
|
|
785
|
+
for (const hl of this.hyperlinks) {
|
|
786
|
+
const hlAttrs = { ref: hl.cell };
|
|
787
|
+
if (hl.target.type === "external") {
|
|
788
|
+
hlIdx++;
|
|
789
|
+
hlAttrs["r:id"] = `rId${hlIdx}`;
|
|
790
|
+
} else hlAttrs.location = hl.target.location;
|
|
791
|
+
if (hl.tooltip) hlAttrs.tooltip = hl.tooltip;
|
|
792
|
+
if (hl.display) hlAttrs.display = hl.display;
|
|
793
|
+
p.push(selfCloseElement("hyperlink", attrs(hlAttrs)));
|
|
794
|
+
}
|
|
795
|
+
p.push("</hyperlinks>");
|
|
796
|
+
}
|
|
607
797
|
p.push("<pageMargins left=\"0.75\" right=\"0.75\" top=\"1\" bottom=\"1\" header=\"0.5\" footer=\"0.5\"/>");
|
|
798
|
+
if (this.pageSetup) {
|
|
799
|
+
const ps = this.pageSetup;
|
|
800
|
+
const psAttrs = {};
|
|
801
|
+
if (ps.paperSize !== void 0) psAttrs.paperSize = ps.paperSize;
|
|
802
|
+
if (ps.orientation && ps.orientation !== "default") psAttrs.orientation = ps.orientation;
|
|
803
|
+
if (ps.scale !== void 0) psAttrs.scale = ps.scale;
|
|
804
|
+
if (ps.fitToWidth !== void 0) psAttrs.fitToWidth = ps.fitToWidth;
|
|
805
|
+
if (ps.fitToHeight !== void 0) psAttrs.fitToHeight = ps.fitToHeight;
|
|
806
|
+
if (ps.pageOrder && ps.pageOrder !== "downThenOver") psAttrs.pageOrder = ps.pageOrder;
|
|
807
|
+
if (ps.useFirstPageNumber) psAttrs.useFirstPageNumber = 1;
|
|
808
|
+
if (ps.firstPageNumber !== void 0) psAttrs.firstPageNumber = ps.firstPageNumber;
|
|
809
|
+
p.push(selfCloseElement("pageSetup", attrs(psAttrs)));
|
|
810
|
+
}
|
|
811
|
+
if (this.headerFooter) {
|
|
812
|
+
const hf = this.headerFooter;
|
|
813
|
+
const hfAttrs = {};
|
|
814
|
+
if (hf.differentOddEven) hfAttrs.differentOddEven = 1;
|
|
815
|
+
if (hf.differentFirst) hfAttrs.differentFirst = 1;
|
|
816
|
+
const inner = [];
|
|
817
|
+
if (hf.oddHeader) inner.push(`<oddHeader>${escapeXml(hf.oddHeader)}</oddHeader>`);
|
|
818
|
+
if (hf.oddFooter) inner.push(`<oddFooter>${escapeXml(hf.oddFooter)}</oddFooter>`);
|
|
819
|
+
if (hf.evenHeader) inner.push(`<evenHeader>${escapeXml(hf.evenHeader)}</evenHeader>`);
|
|
820
|
+
if (hf.evenFooter) inner.push(`<evenFooter>${escapeXml(hf.evenFooter)}</evenFooter>`);
|
|
821
|
+
if (hf.firstHeader) inner.push(`<firstHeader>${escapeXml(hf.firstHeader)}</firstHeader>`);
|
|
822
|
+
if (hf.firstFooter) inner.push(`<firstFooter>${escapeXml(hf.firstFooter)}</firstFooter>`);
|
|
823
|
+
if (inner.length > 0) p.push(`<headerFooter${attrs(hfAttrs)}>`, ...inner, "</headerFooter>");
|
|
824
|
+
else if (hfAttrs.differentOddEven || hfAttrs.differentFirst) p.push(selfCloseElement("headerFooter", attrs(hfAttrs)));
|
|
825
|
+
}
|
|
608
826
|
p.push("</worksheet>");
|
|
609
827
|
return p.join("");
|
|
610
828
|
}
|
|
829
|
+
buildSheetViewAttrs() {
|
|
830
|
+
const sv = this.sheetView;
|
|
831
|
+
const svMap = { workbookViewId: 0 };
|
|
832
|
+
if (sv?.tabSelected !== void 0) svMap.tabSelected = sv.tabSelected ? 1 : 0;
|
|
833
|
+
else svMap.tabSelected = 1;
|
|
834
|
+
if (sv?.showGridLines === false) svMap.showGridLines = 0;
|
|
835
|
+
if (sv?.showRowColHeaders === false) svMap.showRowColHeaders = 0;
|
|
836
|
+
if (sv?.showZeros === false) svMap.showZeros = 0;
|
|
837
|
+
if (sv?.zoomScale !== void 0) svMap.zoomScale = sv.zoomScale;
|
|
838
|
+
if (sv?.rightToLeft) svMap.rightToLeft = 1;
|
|
839
|
+
return attrs(svMap);
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Excel legacy password hash (16-bit, little-endian hex).
|
|
843
|
+
* Matches the algorithm used by ECMA-376 Part 1, §18.2.27.
|
|
844
|
+
*/
|
|
845
|
+
hashPassword(password) {
|
|
846
|
+
let hash = 0;
|
|
847
|
+
for (let i = 0; i < password.length; i++) {
|
|
848
|
+
const c = password.charCodeAt(i);
|
|
849
|
+
hash = (hash >> 14 & 1) + (hash << 1 & 32767);
|
|
850
|
+
hash ^= c;
|
|
851
|
+
hash = hash & 16384 ? hash ^ 1 : hash;
|
|
852
|
+
}
|
|
853
|
+
hash = (hash >> 14 & 1) + (hash << 1 & 32767);
|
|
854
|
+
hash = (hash >> 14 & 1) + (hash << 1 & 32767);
|
|
855
|
+
hash ^= password.length;
|
|
856
|
+
return hash.toString(16).toUpperCase().padStart(4, "0");
|
|
857
|
+
}
|
|
611
858
|
/**
|
|
612
859
|
* Build the <f> element string for a cell formula.
|
|
613
860
|
*/
|
|
@@ -695,6 +942,7 @@ var Worksheet = class extends IgnoreIfEmptyXmlComponent {
|
|
|
695
942
|
var File = class {
|
|
696
943
|
worksheetOptions;
|
|
697
944
|
corePropsOptions;
|
|
945
|
+
dxfOptions;
|
|
698
946
|
_coreProperties;
|
|
699
947
|
_appProperties;
|
|
700
948
|
_contentTypes;
|
|
@@ -707,9 +955,11 @@ var File = class {
|
|
|
707
955
|
_fileRels;
|
|
708
956
|
_workbookRels;
|
|
709
957
|
_charts;
|
|
958
|
+
_pivotCacheRefs = [];
|
|
710
959
|
constructor(options) {
|
|
711
960
|
this.worksheetOptions = options.worksheets ?? [];
|
|
712
961
|
this.corePropsOptions = options;
|
|
962
|
+
this.dxfOptions = options.dxfs ?? [];
|
|
713
963
|
}
|
|
714
964
|
get coreProperties() {
|
|
715
965
|
return this._coreProperties ??= new CoreProperties(this.corePropsOptions);
|
|
@@ -730,6 +980,13 @@ var File = class {
|
|
|
730
980
|
get styles() {
|
|
731
981
|
return this._styles ??= new Styles();
|
|
732
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Register a differential format and return its dxfId.
|
|
985
|
+
* Call this before generating XML to use the ID in conditional formatting rules.
|
|
986
|
+
*/
|
|
987
|
+
registerDxf(opts) {
|
|
988
|
+
return this.styles.registerDxf(opts);
|
|
989
|
+
}
|
|
733
990
|
get theme() {
|
|
734
991
|
return this._theme ??= new DefaultTheme();
|
|
735
992
|
}
|
|
@@ -740,7 +997,7 @@ var File = class {
|
|
|
740
997
|
sheetId: i + 1,
|
|
741
998
|
rId: `rId${i + 1}`
|
|
742
999
|
}));
|
|
743
|
-
this._workbookXml = new WorkbookXml(sheets);
|
|
1000
|
+
this._workbookXml = new WorkbookXml(sheets, this._pivotCacheRefs);
|
|
744
1001
|
}
|
|
745
1002
|
return this._workbookXml;
|
|
746
1003
|
}
|
|
@@ -753,6 +1010,21 @@ var File = class {
|
|
|
753
1010
|
get charts() {
|
|
754
1011
|
return this._charts ??= new ChartCollection();
|
|
755
1012
|
}
|
|
1013
|
+
get dxfEntries() {
|
|
1014
|
+
return this.dxfOptions;
|
|
1015
|
+
}
|
|
1016
|
+
get pivotCacheRefs() {
|
|
1017
|
+
return this._pivotCacheRefs;
|
|
1018
|
+
}
|
|
1019
|
+
registerPivotCache(cacheId, rId) {
|
|
1020
|
+
this._pivotCacheRefs.push({
|
|
1021
|
+
cacheId,
|
|
1022
|
+
rId
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
get worksheetConfigs() {
|
|
1026
|
+
return this.worksheetOptions;
|
|
1027
|
+
}
|
|
756
1028
|
get worksheets() {
|
|
757
1029
|
if (!this._worksheets) this._worksheets = this.worksheetOptions.map((ws) => new Worksheet(ws));
|
|
758
1030
|
return this._worksheets;
|
|
@@ -779,6 +1051,41 @@ var File = class {
|
|
|
779
1051
|
}
|
|
780
1052
|
};
|
|
781
1053
|
//#endregion
|
|
1054
|
+
//#region src/file/comments.ts
|
|
1055
|
+
/**
|
|
1056
|
+
* Generates xl/comments{n}.xml — cell comment data.
|
|
1057
|
+
*
|
|
1058
|
+
* @module
|
|
1059
|
+
*/
|
|
1060
|
+
var Comments = class extends BaseXmlComponent {
|
|
1061
|
+
entries;
|
|
1062
|
+
constructor(entries) {
|
|
1063
|
+
super("comments");
|
|
1064
|
+
this.entries = entries;
|
|
1065
|
+
}
|
|
1066
|
+
toXml(_context) {
|
|
1067
|
+
const authors = this.collectAuthors();
|
|
1068
|
+
const p = ["<comments xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">", `<authors>`];
|
|
1069
|
+
for (const author of authors) p.push(`<author>${escapeXml(author)}</author>`);
|
|
1070
|
+
p.push("</authors><commentList>");
|
|
1071
|
+
for (const entry of this.entries) {
|
|
1072
|
+
const authorId = authors.indexOf(entry.author);
|
|
1073
|
+
p.push(`<comment ref="${entry.cell}" authorId="${authorId}"><text><t>${escapeXml(entry.text)}</t></text></comment>`);
|
|
1074
|
+
}
|
|
1075
|
+
p.push("</commentList></comments>");
|
|
1076
|
+
return p.join("");
|
|
1077
|
+
}
|
|
1078
|
+
collectAuthors() {
|
|
1079
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1080
|
+
const result = [];
|
|
1081
|
+
for (const entry of this.entries) if (!seen.has(entry.author)) {
|
|
1082
|
+
seen.add(entry.author);
|
|
1083
|
+
result.push(entry.author);
|
|
1084
|
+
}
|
|
1085
|
+
return result.length > 0 ? result : [""];
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
//#endregion
|
|
782
1089
|
//#region src/file/drawing/drawing.ts
|
|
783
1090
|
/**
|
|
784
1091
|
* XLSX Drawing component — generates xl/drawings/drawing{n}.xml.
|
|
@@ -816,6 +1123,387 @@ var Drawing = class extends BaseXmlComponent {
|
|
|
816
1123
|
}
|
|
817
1124
|
};
|
|
818
1125
|
//#endregion
|
|
1126
|
+
//#region src/file/pivot/pivot-utils.ts
|
|
1127
|
+
/**
|
|
1128
|
+
* Extract unique values from source data for a given field index.
|
|
1129
|
+
*/
|
|
1130
|
+
function collectUniqueValues(records, fieldIdx) {
|
|
1131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1132
|
+
const result = [];
|
|
1133
|
+
for (const row of records) {
|
|
1134
|
+
const val = row[fieldIdx];
|
|
1135
|
+
const key = String(val);
|
|
1136
|
+
if (!seen.has(key)) {
|
|
1137
|
+
seen.add(key);
|
|
1138
|
+
result.push(val);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return result;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Check if a field is numeric (all non-empty values are numbers).
|
|
1145
|
+
*/
|
|
1146
|
+
function isNumericField(records, fieldIdx) {
|
|
1147
|
+
for (const row of records) {
|
|
1148
|
+
const val = row[fieldIdx];
|
|
1149
|
+
if (typeof val === "string" && val !== "") return false;
|
|
1150
|
+
}
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Aggregate values using the specified function.
|
|
1155
|
+
*/
|
|
1156
|
+
function aggregate(values, func) {
|
|
1157
|
+
if (values.length === 0) return 0;
|
|
1158
|
+
switch (func) {
|
|
1159
|
+
case "sum": return values.reduce((a, b) => a + b, 0);
|
|
1160
|
+
case "count":
|
|
1161
|
+
case "countNums": return values.length;
|
|
1162
|
+
case "average": return values.reduce((a, b) => a + b, 0) / values.length;
|
|
1163
|
+
case "max": return Math.max(...values);
|
|
1164
|
+
case "min": return Math.min(...values);
|
|
1165
|
+
case "product": return values.reduce((a, b) => a * b, 1);
|
|
1166
|
+
case "var": return sampleVariance(values);
|
|
1167
|
+
case "varp": return populationVariance(values);
|
|
1168
|
+
case "stdDev": return Math.sqrt(sampleVariance(values));
|
|
1169
|
+
case "stdDevp": return Math.sqrt(populationVariance(values));
|
|
1170
|
+
default: return values.reduce((a, b) => a + b, 0);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function populationVariance(values) {
|
|
1174
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
1175
|
+
return values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
|
|
1176
|
+
}
|
|
1177
|
+
function sampleVariance(values) {
|
|
1178
|
+
if (values.length < 2) return 0;
|
|
1179
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
1180
|
+
return values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / (values.length - 1);
|
|
1181
|
+
}
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region src/file/pivot/pivot-cache-definition-xml.ts
|
|
1184
|
+
/**
|
|
1185
|
+
* PivotCacheDefinition XML generator.
|
|
1186
|
+
*
|
|
1187
|
+
* Generates xl/pivotCache/pivotCacheDefinition{N}.xml.
|
|
1188
|
+
* Follows CT_PivotCacheDefinition from sml.xsd.
|
|
1189
|
+
*
|
|
1190
|
+
* @module
|
|
1191
|
+
*/
|
|
1192
|
+
var PivotCacheDefinitionXml = class extends BaseXmlComponent {
|
|
1193
|
+
sourceRef;
|
|
1194
|
+
sourceSheet;
|
|
1195
|
+
sourceData;
|
|
1196
|
+
recordsRid;
|
|
1197
|
+
constructor(_cacheIdx, sourceRef, sourceSheet, sourceData, recordsRid) {
|
|
1198
|
+
super("pivotCacheDefinition");
|
|
1199
|
+
this.sourceRef = sourceRef;
|
|
1200
|
+
this.sourceSheet = sourceSheet;
|
|
1201
|
+
this.sourceData = sourceData;
|
|
1202
|
+
this.recordsRid = recordsRid;
|
|
1203
|
+
}
|
|
1204
|
+
toXml(_context) {
|
|
1205
|
+
const p = [];
|
|
1206
|
+
p.push(`<pivotCacheDefinition xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="${escapeXml(this.recordsRid)}" recordCount="${this.sourceData.records.length}" createdVersion="6" refreshedVersion="6" minRefreshableVersion="3">`);
|
|
1207
|
+
p.push(`<cacheSource type="worksheet"><worksheetSource ref="${escapeXml(this.sourceRef)}" sheet="${escapeXml(this.sourceSheet)}"/></cacheSource>`);
|
|
1208
|
+
const fields = this.sourceData.fieldNames;
|
|
1209
|
+
p.push(`<cacheFields count="${fields.length}">`);
|
|
1210
|
+
for (let i = 0; i < fields.length; i++) {
|
|
1211
|
+
const fieldName = fields[i];
|
|
1212
|
+
const numeric = isNumericField(this.sourceData.records, i);
|
|
1213
|
+
const uniqueVals = collectUniqueValues(this.sourceData.records, i);
|
|
1214
|
+
if (numeric) {
|
|
1215
|
+
let min = Infinity;
|
|
1216
|
+
let max = -Infinity;
|
|
1217
|
+
for (const row of this.sourceData.records) {
|
|
1218
|
+
const v = row[i];
|
|
1219
|
+
if (typeof v === "number") {
|
|
1220
|
+
if (v < min) min = v;
|
|
1221
|
+
if (v > max) max = v;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (!isFinite(min)) {
|
|
1225
|
+
min = 0;
|
|
1226
|
+
max = 0;
|
|
1227
|
+
}
|
|
1228
|
+
const allInteger = this.sourceData.records.every((row) => typeof row[i] === "number" && Number.isInteger(row[i]));
|
|
1229
|
+
p.push(`<cacheField name="${escapeXml(fieldName)}" numFmtId="0"><sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="${allInteger ? "1" : "0"}" minValue="${min}" maxValue="${max}" count="${uniqueVals.length}"/></cacheField>`);
|
|
1230
|
+
} else {
|
|
1231
|
+
p.push(`<cacheField name="${escapeXml(fieldName)}" numFmtId="0"><sharedItems count="${uniqueVals.length}">`);
|
|
1232
|
+
for (const v of uniqueVals) p.push(`<s v="${escapeXml(String(v))}"/>`);
|
|
1233
|
+
p.push("</sharedItems></cacheField>");
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
p.push("</cacheFields>");
|
|
1237
|
+
p.push("</pivotCacheDefinition>");
|
|
1238
|
+
return p.join("");
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/file/pivot/pivot-cache-records-xml.ts
|
|
1243
|
+
/**
|
|
1244
|
+
* PivotCacheRecords XML generator.
|
|
1245
|
+
*
|
|
1246
|
+
* Generates xl/pivotCache/pivotCacheRecords{N}.xml.
|
|
1247
|
+
* Follows CT_PivotCacheRecords from sml.xsd.
|
|
1248
|
+
*
|
|
1249
|
+
* @module
|
|
1250
|
+
*/
|
|
1251
|
+
var PivotCacheRecordsXml = class extends BaseXmlComponent {
|
|
1252
|
+
sourceData;
|
|
1253
|
+
numericFields;
|
|
1254
|
+
/** For string fields: value → sharedItems index */
|
|
1255
|
+
fieldIndexMaps;
|
|
1256
|
+
constructor(sourceData) {
|
|
1257
|
+
super("pivotCacheRecords");
|
|
1258
|
+
this.sourceData = sourceData;
|
|
1259
|
+
this.numericFields = sourceData.fieldNames.map((_, i) => isNumericField(sourceData.records, i));
|
|
1260
|
+
this.fieldIndexMaps = sourceData.fieldNames.map((_, i) => {
|
|
1261
|
+
if (this.numericFields[i]) return /* @__PURE__ */ new Map();
|
|
1262
|
+
const unique = collectUniqueValues(sourceData.records, i);
|
|
1263
|
+
const map = /* @__PURE__ */ new Map();
|
|
1264
|
+
for (let j = 0; j < unique.length; j++) map.set(String(unique[j]), j);
|
|
1265
|
+
return map;
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
toXml(_context) {
|
|
1269
|
+
const p = [];
|
|
1270
|
+
p.push(`<pivotCacheRecords xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${this.sourceData.records.length}">`);
|
|
1271
|
+
for (const row of this.sourceData.records) {
|
|
1272
|
+
p.push("<r>");
|
|
1273
|
+
for (let i = 0; i < row.length; i++) {
|
|
1274
|
+
const val = row[i];
|
|
1275
|
+
if (this.numericFields[i]) p.push(`<n v="${val}"/>`);
|
|
1276
|
+
else {
|
|
1277
|
+
const idx = this.fieldIndexMaps[i].get(String(val)) ?? 0;
|
|
1278
|
+
p.push(`<x v="${idx}"/>`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
p.push("</r>");
|
|
1282
|
+
}
|
|
1283
|
+
p.push("</pivotCacheRecords>");
|
|
1284
|
+
return p.join("");
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/file/pivot/pivot-table-xml.ts
|
|
1289
|
+
/**
|
|
1290
|
+
* PivotTableDefinition XML generator.
|
|
1291
|
+
*
|
|
1292
|
+
* Generates xl/pivotTables/pivotTable{N}.xml.
|
|
1293
|
+
* Follows CT_pivotTableDefinition from sml.xsd.
|
|
1294
|
+
*
|
|
1295
|
+
* @module
|
|
1296
|
+
*/
|
|
1297
|
+
var PivotTableXml = class extends BaseXmlComponent {
|
|
1298
|
+
options;
|
|
1299
|
+
sourceData;
|
|
1300
|
+
cacheId;
|
|
1301
|
+
constructor(options, sourceData, cacheId) {
|
|
1302
|
+
super("pivotTableDefinition");
|
|
1303
|
+
this.options = options;
|
|
1304
|
+
this.sourceData = sourceData;
|
|
1305
|
+
this.cacheId = cacheId;
|
|
1306
|
+
}
|
|
1307
|
+
toXml(_context) {
|
|
1308
|
+
const o = this.options;
|
|
1309
|
+
const fields = this.sourceData.fieldNames;
|
|
1310
|
+
const rowFieldNames = o.rows;
|
|
1311
|
+
const colFieldNames = o.columns ?? [];
|
|
1312
|
+
const dataFields = o.data;
|
|
1313
|
+
const style = o.style ?? "PivotStyleLight16";
|
|
1314
|
+
const location = o.location ?? "A3";
|
|
1315
|
+
const name = o.name ?? "PivotTable1";
|
|
1316
|
+
const rowFieldIndices = rowFieldNames.map((n) => fields.indexOf(n));
|
|
1317
|
+
const colFieldIndices = colFieldNames.map((n) => fields.indexOf(n));
|
|
1318
|
+
const dataFieldIndices = dataFields.map((df) => fields.indexOf(df.field));
|
|
1319
|
+
const pivotFieldsXml = this.buildPivotFields(rowFieldIndices, colFieldIndices, dataFieldIndices);
|
|
1320
|
+
const rowFieldsXml = this.buildRowFields(rowFieldIndices);
|
|
1321
|
+
const rowItemsXml = this.buildRowItems(rowFieldIndices);
|
|
1322
|
+
const colFieldsXml = this.buildColFields(colFieldIndices);
|
|
1323
|
+
const colItemsXml = this.buildColItems(colFieldIndices, dataFields);
|
|
1324
|
+
const dataFieldsXml = this.buildDataFields(dataFields, dataFieldIndices);
|
|
1325
|
+
const locationRef = this.computeLocationRef(location, rowFieldIndices, colFieldIndices, dataFields);
|
|
1326
|
+
const p = [];
|
|
1327
|
+
p.push(`<pivotTableDefinition xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" name="${escapeXml(name)}" cacheId="${this.cacheId}" dataCaption="Values" updatedVersion="6" minRefreshableVersion="3" createdVersion="6" applyNumberFormats="0" applyBorderFormats="0" applyFontFormats="0" applyPatternFormats="0" applyAlignmentFormats="0" applyWidthHeightFormats="1" autoFormatId="0" useAutoFormatting="1" itemPrintTitles="1" indent="0" outline="1" outlineData="1" compact="1" compactData="1" rowGrandTotals="1" colGrandTotals="1">`);
|
|
1328
|
+
p.push(`<location ref="${escapeXml(locationRef)}" firstHeaderRow="1" firstDataRow="${colFieldIndices.length + 1}" firstDataCol="${rowFieldIndices.length}"/>`);
|
|
1329
|
+
p.push(pivotFieldsXml);
|
|
1330
|
+
p.push(rowFieldsXml);
|
|
1331
|
+
p.push(rowItemsXml);
|
|
1332
|
+
if (colFieldIndices.length > 0) p.push(colFieldsXml);
|
|
1333
|
+
p.push(colItemsXml);
|
|
1334
|
+
if (dataFields.length > 0) p.push(dataFieldsXml);
|
|
1335
|
+
p.push(`<pivotTableStyleInfo name="${escapeXml(style)}" showRowHeaders="1" showColHeaders="1" showRowStripes="0" showColStripes="0" showLastColumn="1"/>`);
|
|
1336
|
+
p.push("</pivotTableDefinition>");
|
|
1337
|
+
return p.join("");
|
|
1338
|
+
}
|
|
1339
|
+
buildPivotFields(rowIndices, colIndices, dataIndices) {
|
|
1340
|
+
const fields = this.sourceData.fieldNames;
|
|
1341
|
+
const parts = [`<pivotFields count="${fields.length}">`];
|
|
1342
|
+
for (let i = 0; i < fields.length; i++) {
|
|
1343
|
+
const isRow = rowIndices.includes(i);
|
|
1344
|
+
const isCol = colIndices.includes(i);
|
|
1345
|
+
if (dataIndices.includes(i)) parts.push(`<pivotField dataField="1" showAll="0"/>`);
|
|
1346
|
+
else if (isRow) {
|
|
1347
|
+
const uniqueVals = collectUniqueValues(this.sourceData.records, i);
|
|
1348
|
+
parts.push(`<pivotField axis="axisRow" showAll="0">`);
|
|
1349
|
+
parts.push(`<items count="${uniqueVals.length + 1}">`);
|
|
1350
|
+
for (let j = 0; j < uniqueVals.length; j++) parts.push(`<item x="${j}"/>`);
|
|
1351
|
+
parts.push(`<item t="default"/>`);
|
|
1352
|
+
parts.push("</items></pivotField>");
|
|
1353
|
+
} else if (isCol) {
|
|
1354
|
+
const uniqueVals = collectUniqueValues(this.sourceData.records, i);
|
|
1355
|
+
parts.push(`<pivotField axis="axisCol" showAll="0">`);
|
|
1356
|
+
parts.push(`<items count="${uniqueVals.length + 1}">`);
|
|
1357
|
+
for (let j = 0; j < uniqueVals.length; j++) parts.push(`<item x="${j}"/>`);
|
|
1358
|
+
parts.push(`<item t="default"/>`);
|
|
1359
|
+
parts.push("</items></pivotField>");
|
|
1360
|
+
} else parts.push(`<pivotField showAll="0"/>`);
|
|
1361
|
+
}
|
|
1362
|
+
parts.push("</pivotFields>");
|
|
1363
|
+
return parts.join("");
|
|
1364
|
+
}
|
|
1365
|
+
buildRowFields(rowIndices) {
|
|
1366
|
+
if (rowIndices.length === 0) return "<rowFields count=\"0\"/>";
|
|
1367
|
+
const parts = [`<rowFields count="${rowIndices.length}">`];
|
|
1368
|
+
for (const idx of rowIndices) parts.push(`<field x="${idx}"/>`);
|
|
1369
|
+
parts.push("</rowFields>");
|
|
1370
|
+
return parts.join("");
|
|
1371
|
+
}
|
|
1372
|
+
buildRowItems(rowIndices) {
|
|
1373
|
+
if (rowIndices.length === 0) return "<rowItems count=\"1\"><i/></rowItems>";
|
|
1374
|
+
const allUniqueCounts = [];
|
|
1375
|
+
for (const idx of rowIndices) {
|
|
1376
|
+
const vals = collectUniqueValues(this.sourceData.records, idx);
|
|
1377
|
+
allUniqueCounts.push(vals.length);
|
|
1378
|
+
}
|
|
1379
|
+
if (rowIndices.length === 1) {
|
|
1380
|
+
const count = allUniqueCounts[0];
|
|
1381
|
+
const parts = [`<rowItems count="${count + 1}">`];
|
|
1382
|
+
for (let i = 0; i < count; i++) parts.push(`<i><x v="${i}"/></i>`);
|
|
1383
|
+
parts.push(`<i t="grand"><x/></i>`);
|
|
1384
|
+
parts.push("</rowItems>");
|
|
1385
|
+
return parts.join("");
|
|
1386
|
+
}
|
|
1387
|
+
const combos = cartesianOfCounts(allUniqueCounts);
|
|
1388
|
+
const rowItems = [];
|
|
1389
|
+
for (const combo of combos) {
|
|
1390
|
+
const xParts = combo.map((v) => `<x v="${v}"/>`).join("");
|
|
1391
|
+
rowItems.push(`<i>${xParts}</i>`);
|
|
1392
|
+
}
|
|
1393
|
+
rowItems.push(`<i t="grand">${rowIndices.map(() => "<x/>").join("")}</i>`);
|
|
1394
|
+
return `<rowItems count="${rowItems.length}">${rowItems.join("")}</rowItems>`;
|
|
1395
|
+
}
|
|
1396
|
+
buildColFields(colIndices) {
|
|
1397
|
+
if (colIndices.length === 0) return "<colFields count=\"0\"/>";
|
|
1398
|
+
const parts = [`<colFields count="${colIndices.length}">`];
|
|
1399
|
+
for (const idx of colIndices) parts.push(`<field x="${idx}"/>`);
|
|
1400
|
+
parts.push("</colFields>");
|
|
1401
|
+
return parts.join("");
|
|
1402
|
+
}
|
|
1403
|
+
buildColItems(colIndices, dataFields) {
|
|
1404
|
+
if (colIndices.length > 0) {
|
|
1405
|
+
const allUniqueCounts = [];
|
|
1406
|
+
for (const idx of colIndices) {
|
|
1407
|
+
const vals = collectUniqueValues(this.sourceData.records, idx);
|
|
1408
|
+
allUniqueCounts.push(vals.length);
|
|
1409
|
+
}
|
|
1410
|
+
const combos = cartesianOfCounts(allUniqueCounts);
|
|
1411
|
+
const items = [];
|
|
1412
|
+
for (const combo of combos) {
|
|
1413
|
+
const xParts = combo.map((v) => `<x v="${v}"/>`).join("");
|
|
1414
|
+
items.push(`<i>${xParts}</i>`);
|
|
1415
|
+
}
|
|
1416
|
+
items.push(`<i t="grand">${colIndices.map(() => "<x/>").join("")}</i>`);
|
|
1417
|
+
return `<colItems count="${items.length}">${items.join("")}</colItems>`;
|
|
1418
|
+
}
|
|
1419
|
+
if (dataFields.length > 1) {
|
|
1420
|
+
const items = dataFields.map((_, i) => `<i><x v="${i}"/></i>`);
|
|
1421
|
+
return `<colItems count="${items.length}">${items.join("")}</colItems>`;
|
|
1422
|
+
}
|
|
1423
|
+
return "<colItems count=\"1\"><i/></colItems>";
|
|
1424
|
+
}
|
|
1425
|
+
buildDataFields(dataFields, dataFieldIndices) {
|
|
1426
|
+
if (dataFields.length === 0) return "<dataFields count=\"0\"/>";
|
|
1427
|
+
const parts = [`<dataFields count="${dataFields.length}">`];
|
|
1428
|
+
for (let i = 0; i < dataFields.length; i++) {
|
|
1429
|
+
const df = dataFields[i];
|
|
1430
|
+
const subtotal = df.summarize ?? "sum";
|
|
1431
|
+
const name = df.name ?? `${subtotal === "sum" ? "Sum" : subtotal} of ${df.field}`;
|
|
1432
|
+
parts.push(`<dataField name="${escapeXml(name)}" fld="${dataFieldIndices[i]}" subtotal="${subtotal}"/>`);
|
|
1433
|
+
}
|
|
1434
|
+
parts.push("</dataFields>");
|
|
1435
|
+
return parts.join("");
|
|
1436
|
+
}
|
|
1437
|
+
computeLocationRef(location, rowFieldIndices, colFieldIndices, dataFields) {
|
|
1438
|
+
const match = location.split(":")[0].match(/^([A-Z]+)(\d+)$/);
|
|
1439
|
+
if (!match) return location;
|
|
1440
|
+
const startCol = match[1];
|
|
1441
|
+
const startRow = parseInt(match[2], 10);
|
|
1442
|
+
let rowCount = 1;
|
|
1443
|
+
if (rowFieldIndices.length > 0) rowCount += collectUniqueValues(this.sourceData.records, rowFieldIndices[0]).length;
|
|
1444
|
+
rowCount += 1;
|
|
1445
|
+
let colCount = Math.max(rowFieldIndices.length, 1);
|
|
1446
|
+
if (colFieldIndices.length > 0) colCount += collectUniqueValues(this.sourceData.records, colFieldIndices[0]).length;
|
|
1447
|
+
else if (dataFields.length > 1) colCount += dataFields.length - 1;
|
|
1448
|
+
colCount += 1;
|
|
1449
|
+
return `${startCol}${startRow}:${colIndexToLetter(letterToColIndex(startCol) + colCount - 1)}${startRow + rowCount - 1}`;
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
function letterToColIndex(letters) {
|
|
1453
|
+
let col = 0;
|
|
1454
|
+
for (let i = 0; i < letters.length; i++) col = col * 26 + (letters.charCodeAt(i) - 64);
|
|
1455
|
+
return col;
|
|
1456
|
+
}
|
|
1457
|
+
function colIndexToLetter(col) {
|
|
1458
|
+
let result = "";
|
|
1459
|
+
let n = col;
|
|
1460
|
+
while (n > 0) {
|
|
1461
|
+
n--;
|
|
1462
|
+
result = String.fromCharCode(65 + n % 26) + result;
|
|
1463
|
+
n = Math.floor(n / 26);
|
|
1464
|
+
}
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
/** Cartesian product from counts: e.g. [2, 3] → [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2]] */
|
|
1468
|
+
function cartesianOfCounts(counts) {
|
|
1469
|
+
if (counts.length === 0) return [[]];
|
|
1470
|
+
let result = [[]];
|
|
1471
|
+
for (const count of counts) {
|
|
1472
|
+
const next = [];
|
|
1473
|
+
for (const prefix of result) for (let i = 0; i < count; i++) next.push([...prefix, i]);
|
|
1474
|
+
result = next;
|
|
1475
|
+
}
|
|
1476
|
+
return result;
|
|
1477
|
+
}
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region src/file/vml-notes.ts
|
|
1480
|
+
var VmlNotes = class {
|
|
1481
|
+
comments;
|
|
1482
|
+
constructor(comments) {
|
|
1483
|
+
this.comments = comments;
|
|
1484
|
+
}
|
|
1485
|
+
toXml() {
|
|
1486
|
+
const p = [
|
|
1487
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>",
|
|
1488
|
+
"<xml xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:x=\"urn:schemas-microsoft-com:office:excel\">",
|
|
1489
|
+
"<o:shapelayout v:ext=\"edit\"><o:idmap v:ext=\"edit\" data=\"1\"/></o:shapelayout>",
|
|
1490
|
+
"<v:shapetype id=\"_x0000_t202\" coordsize=\"21600,21600\" o:spt=\"202\" path=\"m,l,21600r21600,l21600,xe\">",
|
|
1491
|
+
"<v:stroke joinstyle=\"miter\"/>",
|
|
1492
|
+
"<v:path gradientshapeok=\"t\" o:connecttype=\"rect\"/>",
|
|
1493
|
+
"</v:shapetype>"
|
|
1494
|
+
];
|
|
1495
|
+
for (let i = 0; i < this.comments.length; i++) {
|
|
1496
|
+
const c = this.comments[i];
|
|
1497
|
+
const col = c.cell.charCodeAt(0) - 65;
|
|
1498
|
+
const row = parseInt(c.cell.slice(1), 10) - 1;
|
|
1499
|
+
const anchor = `${col}, 0, ${row}, 0, ${col + 2}, 0, ${row + 2}, 0`;
|
|
1500
|
+
p.push(`<v:shape id="_x0000_s${1025 + i}" type="#_x0000_t202" style="position:absolute;margin-left:59.25pt;margin-top:1.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden" fillcolor="infoBackground [80]" strokecolor="none [81]" o:insetmode="auto">`, `<v:fill color2="infoBackground [80]"/>`, `<v:shadow color="none [81]" obscured="t"/>`, `<v:path o:connecttype="none"/>`, `<v:textbox style="mso-direction-alt:auto"><div style="text-align:left"/></v:textbox>`, `<x:ClientData ObjectType="Note"><x:MoveWithCells/><x:SizeWithCells/>`, `<x:Anchor>${anchor}</x:Anchor>`, `<x:AutoFill>False</x:AutoFill>`, `<x:Row>${row}</x:Row>`, `<x:Column>${col}</x:Column>`, `</x:ClientData>`, `</v:shape>`);
|
|
1501
|
+
}
|
|
1502
|
+
p.push("</xml>");
|
|
1503
|
+
return p.join("");
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
//#endregion
|
|
819
1507
|
//#region src/export/packer/next-compiler.ts
|
|
820
1508
|
/**
|
|
821
1509
|
* XLSX Compiler — compiles a File object into a Zippable structure.
|
|
@@ -844,23 +1532,34 @@ var Compiler = class {
|
|
|
844
1532
|
data: fmt(file.fileRelationships),
|
|
845
1533
|
path: "_rels/.rels"
|
|
846
1534
|
};
|
|
847
|
-
mapping["Workbook"] = {
|
|
848
|
-
data: fmt(file.workbookXml),
|
|
849
|
-
path: "xl/workbook.xml"
|
|
850
|
-
};
|
|
851
|
-
mapping["WorkbookRelationships"] = {
|
|
852
|
-
data: fmt(file.workbookRelationships),
|
|
853
|
-
path: "xl/_rels/workbook.xml.rels"
|
|
854
|
-
};
|
|
855
1535
|
const worksheets = file.worksheets;
|
|
856
1536
|
let globalMediaIdx = 0;
|
|
857
1537
|
let globalChartIdx = 0;
|
|
1538
|
+
let globalPivotIdx = 0;
|
|
1539
|
+
let globalPivotCacheIdx = 0;
|
|
1540
|
+
const pivotCacheDataMap = /* @__PURE__ */ new Map();
|
|
858
1541
|
for (let i = 0; i < worksheets.length; i++) {
|
|
859
1542
|
const ws = worksheets[i];
|
|
860
1543
|
const imgOpts = ws.imageOptions;
|
|
861
1544
|
const chartOpts = ws.charts;
|
|
1545
|
+
const hlOpts = ws.hyperlinkOptions;
|
|
1546
|
+
const sheetName = file.worksheetConfigs[i]?.name ?? `Sheet${i + 1}`;
|
|
862
1547
|
let sheetXml = fmt(ws);
|
|
863
|
-
|
|
1548
|
+
const hasMedia = imgOpts.length > 0 || chartOpts.length > 0;
|
|
1549
|
+
const hasExternalHyperlinks = hlOpts.some((h) => h.target.type === "external");
|
|
1550
|
+
const commentOpts = ws.commentOptions;
|
|
1551
|
+
const hasComments = commentOpts.length > 0;
|
|
1552
|
+
const pivotOpts = ws.pivotTables;
|
|
1553
|
+
const hasPivots = pivotOpts.length > 0;
|
|
1554
|
+
let wsRels;
|
|
1555
|
+
let nextRid = 0;
|
|
1556
|
+
if (hasMedia || hasExternalHyperlinks || hasComments || hasPivots) wsRels = new Relationships();
|
|
1557
|
+
if (hasExternalHyperlinks) for (const hl of hlOpts) {
|
|
1558
|
+
if (hl.target.type !== "external") continue;
|
|
1559
|
+
const rid = ++nextRid;
|
|
1560
|
+
wsRels.addRelationship(rid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", hl.target.url, "External");
|
|
1561
|
+
}
|
|
1562
|
+
if (hasMedia) {
|
|
864
1563
|
const drawingImages = [];
|
|
865
1564
|
const drawingCharts = [];
|
|
866
1565
|
const drawingRels = new Relationships();
|
|
@@ -909,25 +1608,121 @@ var Compiler = class {
|
|
|
909
1608
|
data: fmt(drawingRels),
|
|
910
1609
|
path: `xl/drawings/_rels/drawing${drawingIdx}.xml.rels`
|
|
911
1610
|
};
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
wsRels.addRelationship(
|
|
915
|
-
mapping[`WorksheetRels${i}`] = {
|
|
916
|
-
data: fmt(wsRels),
|
|
917
|
-
path: `xl/worksheets/_rels/sheet${i + 1}.xml.rels`
|
|
918
|
-
};
|
|
1611
|
+
const drawingRid = ++nextRid;
|
|
1612
|
+
sheetXml = sheetXml.slice(0, -12) + `<drawing r:id="rId${drawingRid}"/></worksheet>`;
|
|
1613
|
+
wsRels.addRelationship(drawingRid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing", `../drawings/drawing${drawingIdx}.xml`);
|
|
919
1614
|
file.contentTypes.addDrawing(drawingIdx);
|
|
920
1615
|
}
|
|
1616
|
+
if (hasComments) {
|
|
1617
|
+
const commentsIdx = i + 1;
|
|
1618
|
+
const commentsXml = new Comments(commentOpts);
|
|
1619
|
+
mapping[`Comments${i}`] = {
|
|
1620
|
+
data: fmt(commentsXml),
|
|
1621
|
+
path: `xl/comments${commentsIdx}.xml`
|
|
1622
|
+
};
|
|
1623
|
+
const vmlNotes = new VmlNotes(commentOpts);
|
|
1624
|
+
mapping[`VmlDrawing${i}`] = {
|
|
1625
|
+
data: vmlNotes.toXml(),
|
|
1626
|
+
path: `xl/drawings/vmlDrawing${commentsIdx}.vml`
|
|
1627
|
+
};
|
|
1628
|
+
const commentsRid = ++nextRid;
|
|
1629
|
+
wsRels.addRelationship(commentsRid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", `../comments${commentsIdx}.xml`);
|
|
1630
|
+
const vmlRid = ++nextRid;
|
|
1631
|
+
wsRels.addRelationship(vmlRid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing", `../drawings/vmlDrawing${commentsIdx}.vml`);
|
|
1632
|
+
sheetXml = sheetXml.slice(0, -12) + `<legacyDrawing r:id="rId${vmlRid}"/></worksheet>`;
|
|
1633
|
+
file.contentTypes.addComments(commentsIdx);
|
|
1634
|
+
file.contentTypes.addVmlDrawing();
|
|
1635
|
+
}
|
|
1636
|
+
if (hasPivots) for (const pt of pivotOpts) {
|
|
1637
|
+
globalPivotIdx++;
|
|
1638
|
+
const pivotIdx = globalPivotIdx;
|
|
1639
|
+
const sourceSheet = pt.sourceSheet ?? sheetName;
|
|
1640
|
+
const sourceWsIdx = file.worksheetConfigs.findIndex((ws) => (ws.name ?? `Sheet${file.worksheetConfigs.indexOf(ws) + 1}`) === sourceSheet);
|
|
1641
|
+
if (sourceWsIdx === -1) continue;
|
|
1642
|
+
const sourceWs = worksheets[sourceWsIdx];
|
|
1643
|
+
const sourceData = extractPivotSourceData(sourceWs.worksheetRows, pt.source);
|
|
1644
|
+
const cacheKey = `${sourceSheet}:${pt.source}`;
|
|
1645
|
+
let cacheId;
|
|
1646
|
+
let cacheIdx;
|
|
1647
|
+
const existing = pivotCacheDataMap.get(cacheKey);
|
|
1648
|
+
if (existing) {
|
|
1649
|
+
cacheId = existing.cacheId;
|
|
1650
|
+
cacheIdx = existing.cacheIdx;
|
|
1651
|
+
} else {
|
|
1652
|
+
globalPivotCacheIdx++;
|
|
1653
|
+
cacheIdx = globalPivotCacheIdx;
|
|
1654
|
+
cacheId = cacheIdx - 1;
|
|
1655
|
+
pivotCacheDataMap.set(cacheKey, {
|
|
1656
|
+
cacheId,
|
|
1657
|
+
cacheIdx
|
|
1658
|
+
});
|
|
1659
|
+
const cacheDefRels = new Relationships();
|
|
1660
|
+
cacheDefRels.addRelationship(1, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords", "pivotCacheRecords1.xml");
|
|
1661
|
+
const cacheDef = new PivotCacheDefinitionXml(cacheIdx, pt.source.split(":")[0] ? pt.source : "A1", sourceSheet, sourceData, "rId1");
|
|
1662
|
+
mapping[`PivotCacheDef${cacheIdx}`] = {
|
|
1663
|
+
data: fmt(cacheDef),
|
|
1664
|
+
path: `xl/pivotCache/pivotCacheDefinition${cacheIdx}.xml`
|
|
1665
|
+
};
|
|
1666
|
+
mapping[`PivotCacheDefRels${cacheIdx}`] = {
|
|
1667
|
+
data: fmt(cacheDefRels),
|
|
1668
|
+
path: `xl/pivotCache/_rels/pivotCacheDefinition${cacheIdx}.xml.rels`
|
|
1669
|
+
};
|
|
1670
|
+
const cacheRecords = new PivotCacheRecordsXml(sourceData);
|
|
1671
|
+
mapping[`PivotCacheRecords${cacheIdx}`] = {
|
|
1672
|
+
data: fmt(cacheRecords),
|
|
1673
|
+
path: `xl/pivotCache/pivotCacheRecords${cacheIdx}.xml`
|
|
1674
|
+
};
|
|
1675
|
+
file.contentTypes.addPivotCacheDefinition(cacheIdx);
|
|
1676
|
+
file.contentTypes.addPivotCacheRecords(cacheIdx);
|
|
1677
|
+
const wbPivotRid = file.workbookRelationships.relationshipCount + 1;
|
|
1678
|
+
file.workbookRelationships.addRelationship(wbPivotRid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition", `pivotCache/pivotCacheDefinition${cacheIdx}.xml`);
|
|
1679
|
+
file.registerPivotCache(cacheId, `rId${wbPivotRid}`);
|
|
1680
|
+
}
|
|
1681
|
+
const pivotTable = new PivotTableXml(pt, sourceData, cacheId);
|
|
1682
|
+
mapping[`PivotTable${pivotIdx}`] = {
|
|
1683
|
+
data: fmt(pivotTable),
|
|
1684
|
+
path: `xl/pivotTables/pivotTable${pivotIdx}.xml`
|
|
1685
|
+
};
|
|
1686
|
+
const ptRels = new Relationships();
|
|
1687
|
+
ptRels.addRelationship(1, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition", `../pivotCache/pivotCacheDefinition${cacheIdx}.xml`);
|
|
1688
|
+
mapping[`PivotTableRels${pivotIdx}`] = {
|
|
1689
|
+
data: fmt(ptRels),
|
|
1690
|
+
path: `xl/pivotTables/_rels/pivotTable${pivotIdx}.xml.rels`
|
|
1691
|
+
};
|
|
1692
|
+
const ptRid = ++nextRid;
|
|
1693
|
+
wsRels.addRelationship(ptRid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable", `../pivotTables/pivotTable${pivotIdx}.xml`);
|
|
1694
|
+
file.contentTypes.addPivotTable(pivotIdx);
|
|
1695
|
+
}
|
|
1696
|
+
if (hasPivots) {
|
|
1697
|
+
const rendered = renderPivotSheetData(pivotOpts, worksheets, file.sharedStrings, file.worksheetConfigs, sheetName);
|
|
1698
|
+
if (rendered.sheetData.length > 0) {
|
|
1699
|
+
sheetXml = sheetXml.replace(/<sheetData\/>|<sheetData><\/sheetData>/, rendered.sheetData);
|
|
1700
|
+
if (!sheetXml.includes("<dimension")) sheetXml = sheetXml.replace("<sheetViews", `<dimension ref="${rendered.dimensionRef}"/><sheetViews`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (wsRels) mapping[`WorksheetRels${i}`] = {
|
|
1704
|
+
data: fmt(wsRels),
|
|
1705
|
+
path: `xl/worksheets/_rels/sheet${i + 1}.xml.rels`
|
|
1706
|
+
};
|
|
921
1707
|
mapping[`Worksheet${i}`] = {
|
|
922
1708
|
data: sheetXml,
|
|
923
1709
|
path: `xl/worksheets/sheet${i + 1}.xml`
|
|
924
1710
|
};
|
|
925
1711
|
}
|
|
1712
|
+
mapping["Workbook"] = {
|
|
1713
|
+
data: fmt(file.workbookXml),
|
|
1714
|
+
path: "xl/workbook.xml"
|
|
1715
|
+
};
|
|
1716
|
+
mapping["WorkbookRelationships"] = {
|
|
1717
|
+
data: fmt(file.workbookRelationships),
|
|
1718
|
+
path: "xl/_rels/workbook.xml.rels"
|
|
1719
|
+
};
|
|
926
1720
|
const sharedStrings = file.sharedStrings;
|
|
927
1721
|
if (sharedStrings.count > 0) mapping["SharedStrings"] = {
|
|
928
1722
|
data: fmt(sharedStrings),
|
|
929
1723
|
path: "xl/sharedStrings.xml"
|
|
930
1724
|
};
|
|
1725
|
+
for (const dxf of file.dxfEntries) file.registerDxf(dxf);
|
|
931
1726
|
mapping["Styles"] = {
|
|
932
1727
|
data: fmt(file.styles),
|
|
933
1728
|
path: "xl/styles.xml"
|
|
@@ -964,6 +1759,174 @@ var Compiler = class {
|
|
|
964
1759
|
return compileMapping(mapping, overrides, mediaFiles, mediaLevel);
|
|
965
1760
|
}
|
|
966
1761
|
};
|
|
1762
|
+
/**
|
|
1763
|
+
* Extract source data from worksheet rows for pivot cache generation.
|
|
1764
|
+
*/
|
|
1765
|
+
function extractPivotSourceData(rows, sourceRef) {
|
|
1766
|
+
const parts = sourceRef.split(":");
|
|
1767
|
+
const startMatch = parts[0]?.match(/^([A-Z]+)(\d+)$/);
|
|
1768
|
+
const endMatch = parts[1]?.match(/^([A-Z]+)(\d+)$/);
|
|
1769
|
+
if (!startMatch) return {
|
|
1770
|
+
fieldNames: [],
|
|
1771
|
+
records: []
|
|
1772
|
+
};
|
|
1773
|
+
const startRow = parseInt(startMatch[2], 10) - 1;
|
|
1774
|
+
const endRow = endMatch ? parseInt(endMatch[2], 10) - 1 : startRow;
|
|
1775
|
+
const startCol = colLetterToIndex(startMatch[1]);
|
|
1776
|
+
const endCol = endMatch ? colLetterToIndex(endMatch[1]) : startCol;
|
|
1777
|
+
const colCount = endCol - startCol + 1;
|
|
1778
|
+
const headerRow = rows[startRow];
|
|
1779
|
+
const fieldNames = [];
|
|
1780
|
+
if (headerRow?.cells) for (let c = startCol; c <= endCol && c < headerRow.cells.length; c++) fieldNames.push(String(headerRow.cells[c]?.value ?? `Col${c}`));
|
|
1781
|
+
const records = [];
|
|
1782
|
+
for (let r = startRow + 1; r <= endRow; r++) {
|
|
1783
|
+
const row = rows[r];
|
|
1784
|
+
if (!row?.cells) continue;
|
|
1785
|
+
const record = [];
|
|
1786
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
1787
|
+
const val = row.cells[c]?.value;
|
|
1788
|
+
if (typeof val === "number") record.push(val);
|
|
1789
|
+
else if (val instanceof Date) record.push(val.getTime());
|
|
1790
|
+
else record.push(String(val ?? ""));
|
|
1791
|
+
}
|
|
1792
|
+
if (record.length === colCount) records.push(record);
|
|
1793
|
+
}
|
|
1794
|
+
return {
|
|
1795
|
+
fieldNames,
|
|
1796
|
+
records
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
function colLetterToIndex(letters) {
|
|
1800
|
+
let col = 0;
|
|
1801
|
+
for (let i = 0; i < letters.length; i++) col = col * 26 + (letters.charCodeAt(i) - 64);
|
|
1802
|
+
return col - 1;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Pre-render pivot table output as sheetData cells.
|
|
1806
|
+
* Excel requires this so the pivot table is visible on open without manual refresh.
|
|
1807
|
+
*/
|
|
1808
|
+
function renderPivotSheetData(pivotOpts, worksheets, sharedStrings, worksheetConfigs, currentSheetName) {
|
|
1809
|
+
const rowCells = /* @__PURE__ */ new Map();
|
|
1810
|
+
let maxRow = 0;
|
|
1811
|
+
let maxCol = 0;
|
|
1812
|
+
let minRow = Infinity;
|
|
1813
|
+
let minCol = Infinity;
|
|
1814
|
+
for (const pt of pivotOpts) {
|
|
1815
|
+
const locMatch = (pt.location ?? "A3").match(/^([A-Z]+)(\d+)$/);
|
|
1816
|
+
if (!locMatch) continue;
|
|
1817
|
+
const startCol = colLetterToIndex(locMatch[1]);
|
|
1818
|
+
const startRow = parseInt(locMatch[2], 10);
|
|
1819
|
+
const rowFieldNames = pt.rows;
|
|
1820
|
+
const dataFields = pt.data;
|
|
1821
|
+
const sourceSheetName = pt.sourceSheet ?? currentSheetName;
|
|
1822
|
+
const sourceWsIdx = worksheetConfigs.findIndex((ws) => (ws.name ?? `Sheet${worksheetConfigs.indexOf(ws) + 1}`) === sourceSheetName);
|
|
1823
|
+
if (sourceWsIdx === -1) continue;
|
|
1824
|
+
const sourceData = extractPivotSourceData(worksheets[sourceWsIdx]?.worksheetRows ?? [], pt.source);
|
|
1825
|
+
if (sourceData.fieldNames.length === 0) continue;
|
|
1826
|
+
const fields = sourceData.fieldNames;
|
|
1827
|
+
const rowFieldIndices = rowFieldNames.map((n) => fields.indexOf(n));
|
|
1828
|
+
const dataFieldIndices = dataFields.map((df) => fields.indexOf(df.field));
|
|
1829
|
+
if (rowFieldIndices.some((idx) => idx === -1)) continue;
|
|
1830
|
+
if (dataFieldIndices.some((idx) => idx === -1)) continue;
|
|
1831
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
1832
|
+
for (const record of sourceData.records) {
|
|
1833
|
+
const groupKey = rowFieldIndices.map((fi) => String(record[fi])).join("|");
|
|
1834
|
+
let group = groupMap.get(groupKey);
|
|
1835
|
+
if (!group) {
|
|
1836
|
+
group = {
|
|
1837
|
+
keys: rowFieldIndices.map((fi) => record[fi]),
|
|
1838
|
+
values: dataFieldIndices.map(() => [])
|
|
1839
|
+
};
|
|
1840
|
+
groupMap.set(groupKey, group);
|
|
1841
|
+
}
|
|
1842
|
+
for (let di = 0; di < dataFieldIndices.length; di++) {
|
|
1843
|
+
const val = record[dataFieldIndices[di]];
|
|
1844
|
+
if (typeof val === "number") group.values[di].push(val);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
const endCol = startCol + rowFieldNames.length + dataFields.length - 1;
|
|
1848
|
+
minCol = Math.min(minCol, startCol);
|
|
1849
|
+
maxCol = Math.max(maxCol, endCol);
|
|
1850
|
+
const addCells = (rowIdx, cells) => {
|
|
1851
|
+
let arr = rowCells.get(rowIdx);
|
|
1852
|
+
if (!arr) {
|
|
1853
|
+
arr = [];
|
|
1854
|
+
rowCells.set(rowIdx, arr);
|
|
1855
|
+
}
|
|
1856
|
+
arr.push(...cells);
|
|
1857
|
+
minRow = Math.min(minRow, rowIdx);
|
|
1858
|
+
maxRow = Math.max(maxRow, rowIdx);
|
|
1859
|
+
};
|
|
1860
|
+
const headerCells = [];
|
|
1861
|
+
for (const rfName of rowFieldNames) {
|
|
1862
|
+
const cellRef = colIndexToLetterCompiler(startCol + headerCells.length) + startRow;
|
|
1863
|
+
const strIdx = sharedStrings.register(rfName);
|
|
1864
|
+
headerCells.push(`<c r="${cellRef}" t="s"><v>${strIdx}</v></c>`);
|
|
1865
|
+
}
|
|
1866
|
+
for (const df of dataFields) {
|
|
1867
|
+
const cellRef = colIndexToLetterCompiler(startCol + headerCells.length) + startRow;
|
|
1868
|
+
const subtotal = df.summarize ?? "sum";
|
|
1869
|
+
const dfName = df.name ?? `${subtotal === "sum" ? "Sum" : subtotal} of ${df.field}`;
|
|
1870
|
+
const strIdx = sharedStrings.register(dfName);
|
|
1871
|
+
headerCells.push(`<c r="${cellRef}" t="s"><v>${strIdx}</v></c>`);
|
|
1872
|
+
}
|
|
1873
|
+
addCells(startRow, headerCells);
|
|
1874
|
+
let currentRow = startRow + 1;
|
|
1875
|
+
for (const [, group] of groupMap) {
|
|
1876
|
+
const cells = [];
|
|
1877
|
+
for (let ri = 0; ri < group.keys.length; ri++) {
|
|
1878
|
+
const cellRef = colIndexToLetterCompiler(startCol + ri) + currentRow;
|
|
1879
|
+
const strIdx = sharedStrings.register(String(group.keys[ri]));
|
|
1880
|
+
cells.push(`<c r="${cellRef}" t="s"><v>${strIdx}</v></c>`);
|
|
1881
|
+
}
|
|
1882
|
+
for (let di = 0; di < dataFields.length; di++) {
|
|
1883
|
+
const cellRef = colIndexToLetterCompiler(startCol + (rowFieldNames.length + di)) + currentRow;
|
|
1884
|
+
const subtotal = dataFields[di].summarize ?? "sum";
|
|
1885
|
+
const result = aggregate(group.values[di], subtotal);
|
|
1886
|
+
cells.push(`<c r="${cellRef}"><v>${result}</v></c>`);
|
|
1887
|
+
}
|
|
1888
|
+
addCells(currentRow, cells);
|
|
1889
|
+
currentRow++;
|
|
1890
|
+
}
|
|
1891
|
+
const gtCells = [];
|
|
1892
|
+
const gtStrIdx = sharedStrings.register("Grand Total");
|
|
1893
|
+
gtCells.push(`<c r="${colIndexToLetterCompiler(startCol)}${currentRow}" t="s"><v>${gtStrIdx}</v></c>`);
|
|
1894
|
+
for (let di = 0; di < dataFields.length; di++) {
|
|
1895
|
+
const cellRef = colIndexToLetterCompiler(startCol + (rowFieldNames.length + di)) + currentRow;
|
|
1896
|
+
const subtotal = dataFields[di].summarize ?? "sum";
|
|
1897
|
+
const result = aggregate(sourceData.records.map((r) => r[dataFieldIndices[di]]).filter((v) => typeof v === "number"), subtotal);
|
|
1898
|
+
gtCells.push(`<c r="${cellRef}"><v>${result}</v></c>`);
|
|
1899
|
+
}
|
|
1900
|
+
addCells(currentRow, gtCells);
|
|
1901
|
+
}
|
|
1902
|
+
if (rowCells.size === 0) return {
|
|
1903
|
+
sheetData: "",
|
|
1904
|
+
dimensionRef: ""
|
|
1905
|
+
};
|
|
1906
|
+
const parts = ["<sheetData>"];
|
|
1907
|
+
const sortedRows = [...rowCells.entries()].sort((a, b) => a[0] - b[0]);
|
|
1908
|
+
for (const [rowIdx, cells] of sortedRows) {
|
|
1909
|
+
parts.push(`<row r="${rowIdx}" x14ac:dyDescent="0.25">`);
|
|
1910
|
+
parts.push(...cells);
|
|
1911
|
+
parts.push("</row>");
|
|
1912
|
+
}
|
|
1913
|
+
parts.push("</sheetData>");
|
|
1914
|
+
const dimensionRef = `${colIndexToLetterCompiler(minCol === Infinity ? 0 : minCol)}${minRow === Infinity ? 1 : minRow}:${colIndexToLetterCompiler(maxCol)}${maxRow}`;
|
|
1915
|
+
return {
|
|
1916
|
+
sheetData: parts.join(""),
|
|
1917
|
+
dimensionRef
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function colIndexToLetterCompiler(col) {
|
|
1921
|
+
let result = "";
|
|
1922
|
+
let n = col + 1;
|
|
1923
|
+
while (n > 0) {
|
|
1924
|
+
n--;
|
|
1925
|
+
result = String.fromCharCode(65 + n % 26) + result;
|
|
1926
|
+
n = Math.floor(n / 26);
|
|
1927
|
+
}
|
|
1928
|
+
return result;
|
|
1929
|
+
}
|
|
967
1930
|
//#endregion
|
|
968
1931
|
//#region src/export/packer/packer.ts
|
|
969
1932
|
/**
|