@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.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
- p.push("<dxfs count=\"0\"/>");
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
- constructor(sheets) {
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><calcPr calcId=\"191029\"/></workbook>");
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
- p.push("<sheetViews><sheetView tabSelected=\"1\" workbookViewId=\"0\">", `<pane ySplit="${ySplit}" xSplit="${xSplit}" topLeftCell="${topLeftCell}" activePane="${activePane}" state="frozen"/>`, "</sheetView></sheetViews>");
519
- } else p.push("<sheetViews><sheetView tabSelected=\"1\" workbookViewId=\"0\"/></sheetViews>");
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.autoFilter) p.push(selfCloseElement("autoFilter", attrs({ ref: this.autoFilter })));
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
- if (imgOpts.length > 0 || chartOpts.length > 0) {
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
- sheetXml = sheetXml.slice(0, -12) + `<drawing r:id="rId${rid}"/></worksheet>`;
913
- const wsRels = new Relationships();
914
- wsRels.addRelationship(rid, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing", `../drawings/drawing${drawingIdx}.xml`);
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
  /**