@rowsncolumns/toolkit 24.4.2 → 24.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/cjs/excel-parser/cfb-decryptor.d.ts +6 -0
  2. package/dist/cjs/excel-parser/cfb-decryptor.d.ts.map +1 -1
  3. package/dist/cjs/excel-parser/cfb-decryptor.js +6 -0
  4. package/dist/cjs/excel-parser/helpers.d.ts +9 -1
  5. package/dist/cjs/excel-parser/helpers.d.ts.map +1 -1
  6. package/dist/cjs/excel-parser/helpers.js +37 -0
  7. package/dist/cjs/excel-parser/interface.d.ts +10 -1
  8. package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
  9. package/dist/cjs/excel-parser/plugins/ods/index.d.ts +19 -0
  10. package/dist/cjs/excel-parser/plugins/ods/index.d.ts.map +1 -1
  11. package/dist/cjs/excel-parser/plugins/ods/index.js +676 -304
  12. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts +43 -1
  13. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts.map +1 -1
  14. package/dist/cjs/excel-parser/plugins/ods/styles.js +343 -45
  15. package/dist/cjs/excel-parser/plugins/xls/index.d.ts +11 -3
  16. package/dist/cjs/excel-parser/plugins/xls/index.d.ts.map +1 -1
  17. package/dist/cjs/excel-parser/plugins/xls/index.js +55 -6
  18. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts +4 -0
  19. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
  20. package/dist/cjs/excel-parser/plugins/xlsb/index.js +21 -9
  21. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts +10 -0
  22. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
  23. package/dist/cjs/excel-parser/plugins/xlsx/charts.js +68 -9
  24. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts +28 -1
  25. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
  26. package/dist/cjs/excel-parser/plugins/xlsx/drawing.js +155 -20
  27. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +26 -1
  28. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  29. package/dist/cjs/excel-parser/plugins/xlsx/index.js +567 -234
  30. package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
  31. package/dist/cjs/excel-parser/plugins/xlsx/pivots.js +125 -37
  32. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts +3 -0
  33. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -1
  34. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.js +4 -1
  35. package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  36. package/dist/cjs/excel-parser/plugins/xlsx/styles.js +12 -2
  37. package/dist/cjs/excel-parser/registry.d.ts.map +1 -1
  38. package/dist/cjs/excel-parser/registry.js +2 -0
  39. package/dist/cjs/excel-parser/types.d.ts +7 -1
  40. package/dist/cjs/excel-parser/types.d.ts.map +1 -1
  41. package/dist/cjs/excel-parser/workbook.d.ts +1 -0
  42. package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
  43. package/dist/cjs/excel-parser/workbook.js +4 -0
  44. package/dist/cjs/excel-parser/worker.js +3 -1
  45. package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -1
  46. package/dist/cjs/exporter/ods/ods-export.js +432 -175
  47. package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
  48. package/dist/cjs/exporter/xlsx/chart-builder.js +210 -6
  49. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts +8 -0
  50. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts.map +1 -1
  51. package/dist/cjs/exporter/xlsx/pivot-builder.js +141 -42
  52. package/dist/cjs/exporter/xlsx/style-builder.js +9 -3
  53. package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts +7 -1
  54. package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -1
  55. package/dist/cjs/exporter/xlsx/threaded-comments-builder.js +15 -3
  56. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +11 -2
  57. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  58. package/dist/cjs/exporter/xlsx/xlsx-export.js +678 -222
  59. package/dist/cjs/helpers/implicit-intersection.d.ts +16 -0
  60. package/dist/cjs/helpers/implicit-intersection.d.ts.map +1 -0
  61. package/dist/cjs/helpers/implicit-intersection.js +197 -0
  62. package/dist/cjs/print-settings.d.ts +105 -0
  63. package/dist/cjs/print-settings.d.ts.map +1 -0
  64. package/dist/cjs/print-settings.js +494 -0
  65. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  66. package/dist/esm/excel-parser/cfb-decryptor.d.ts +6 -0
  67. package/dist/esm/excel-parser/cfb-decryptor.d.ts.map +1 -1
  68. package/dist/esm/excel-parser/cfb-decryptor.js +6 -0
  69. package/dist/esm/excel-parser/helpers.d.ts +9 -1
  70. package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
  71. package/dist/esm/excel-parser/helpers.js +34 -0
  72. package/dist/esm/excel-parser/interface.d.ts +10 -1
  73. package/dist/esm/excel-parser/interface.d.ts.map +1 -1
  74. package/dist/esm/excel-parser/plugins/ods/index.d.ts +19 -0
  75. package/dist/esm/excel-parser/plugins/ods/index.d.ts.map +1 -1
  76. package/dist/esm/excel-parser/plugins/ods/index.js +347 -33
  77. package/dist/esm/excel-parser/plugins/ods/styles.d.ts +43 -1
  78. package/dist/esm/excel-parser/plugins/ods/styles.d.ts.map +1 -1
  79. package/dist/esm/excel-parser/plugins/ods/styles.js +276 -2
  80. package/dist/esm/excel-parser/plugins/xls/index.d.ts +11 -3
  81. package/dist/esm/excel-parser/plugins/xls/index.d.ts.map +1 -1
  82. package/dist/esm/excel-parser/plugins/xls/index.js +37 -5
  83. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts +4 -0
  84. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
  85. package/dist/esm/excel-parser/plugins/xlsb/index.js +20 -6
  86. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts +10 -0
  87. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
  88. package/dist/esm/excel-parser/plugins/xlsx/charts.js +69 -6
  89. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts +28 -1
  90. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
  91. package/dist/esm/excel-parser/plugins/xlsx/drawing.js +99 -0
  92. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +26 -1
  93. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  94. package/dist/esm/excel-parser/plugins/xlsx/index.js +362 -26
  95. package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
  96. package/dist/esm/excel-parser/plugins/xlsx/pivots.js +86 -6
  97. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts +3 -0
  98. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -1
  99. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.js +2 -0
  100. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  101. package/dist/esm/excel-parser/plugins/xlsx/styles.js +12 -2
  102. package/dist/esm/excel-parser/registry.d.ts.map +1 -1
  103. package/dist/esm/excel-parser/registry.js +2 -0
  104. package/dist/esm/excel-parser/types.d.ts +7 -1
  105. package/dist/esm/excel-parser/types.d.ts.map +1 -1
  106. package/dist/esm/excel-parser/workbook.d.ts +1 -0
  107. package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
  108. package/dist/esm/excel-parser/workbook.js +3 -0
  109. package/dist/esm/excel-parser/worker.js +2 -0
  110. package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -1
  111. package/dist/esm/exporter/ods/ods-export.js +306 -35
  112. package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
  113. package/dist/esm/exporter/xlsx/chart-builder.js +161 -0
  114. package/dist/esm/exporter/xlsx/pivot-builder.d.ts +8 -0
  115. package/dist/esm/exporter/xlsx/pivot-builder.d.ts.map +1 -1
  116. package/dist/esm/exporter/xlsx/pivot-builder.js +104 -19
  117. package/dist/esm/exporter/xlsx/style-builder.js +9 -3
  118. package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts +7 -1
  119. package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -1
  120. package/dist/esm/exporter/xlsx/threaded-comments-builder.js +15 -3
  121. package/dist/esm/exporter/xlsx/xlsx-export.d.ts +11 -2
  122. package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  123. package/dist/esm/exporter/xlsx/xlsx-export.js +449 -47
  124. package/dist/esm/helpers/implicit-intersection.d.ts +16 -0
  125. package/dist/esm/helpers/implicit-intersection.d.ts.map +1 -0
  126. package/dist/esm/helpers/implicit-intersection.js +184 -0
  127. package/dist/esm/print-settings.d.ts +105 -0
  128. package/dist/esm/print-settings.d.ts.map +1 -0
  129. package/dist/esm/print-settings.js +422 -0
  130. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  131. package/package.json +3 -3
@@ -2,9 +2,10 @@ import { addressToCell, addressToSelection, alpha2number, checkIfStringIsReferen
2
2
  import { XMLParser } from "fast-xml-parser";
3
3
  import { get } from "lodash-es";
4
4
  import { Parser as HtmlParser } from "htmlparser2";
5
- import { createFilterSpecsFromAutoFilter, excelColumnWidthToPixels, findMax, getRangeFromAddressRef, } from "../../helpers";
5
+ import { createFilterSpecsFromAutoFilter, excelColumnWidthToPixels, findMax, getRangeFromAddressRef, parseTableColumnTotals, } from "../../helpers";
6
6
  import { defaultSpreadsheetTheme } from "../../theme-default";
7
7
  import { Unescape } from "../../utils";
8
+ import { compactPrintSettings, isBuiltinPrintName, isDefaultPageMargins, parsePrintAreaFormula, parsePrintTitlesFormula, PRINT_AREA_NAME, } from "../../../print-settings";
8
9
  import { StreamingZipParser } from "../../zip-parser-streaming";
9
10
  import { SheetDataValidationStamper } from "../../dv-cell-stamp";
10
11
  import { BaseWorkbookPlugin } from "../base/index";
@@ -12,7 +13,7 @@ import { processChart } from "./charts";
12
13
  import { customFiltersToCondition, dynamicFilterToCondition, } from "../../../autofilter-condition";
13
14
  import { getConditionalFormattingFromSheet } from "./conditional-formatting";
14
15
  import { getDataValidationsForSheet as parseDataValidationsFromSheetData } from "./data-validations";
15
- import { processAnchors } from "./drawing";
16
+ import { anchorsInDocumentOrder, processAnchors, } from "./drawing";
16
17
  import { getRelationsById } from "./relationships";
17
18
  import { hasPowerQueryParts, } from "../../../power-query";
18
19
  import { extractSheetViewProperties } from "./sheet-views";
@@ -739,6 +740,18 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
739
740
  * `getDataValidations()` aggregates these per-sheet caches, keeping ids
740
741
  * stable across per-sheet and workbook-wide callers.
741
742
  */
743
+ /**
744
+ * `<protectedRanges>` per sheet, populated while `getSheets` builds each
745
+ * `Sheet` (the element sits after `<sheetData>` in the worksheet part, so it
746
+ * rides the same geometry pass). Read back via `getProtectedRanges`.
747
+ */
748
+ protectedRangesBySheet = new Map();
749
+ /**
750
+ * The builtin `_xlnm.Print_Area` / `_xlnm.Print_Titles` defined names,
751
+ * hoisted out of `namedRanges` (they are sheet-scoped facets, not user
752
+ * names) and merged into each sheet's `printSettings` by `getSheets()`.
753
+ */
754
+ printNamesBySheetId = new Map();
742
755
  dataValidationsBySheetCache = new Map();
743
756
  /** Per-sheet memo for conditional formatting, mirroring the DV cache. */
744
757
  conditionalFormattingBySheetCache = new Map();
@@ -784,6 +797,26 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
784
797
  let paneAttrs;
785
798
  let tabColor;
786
799
  let autoFilterRef;
800
+ // `<sheetProtection>` + `<protectedRanges>/<protectedRange>` — the sheet's
801
+ // protection facets, read here because both sit after `<sheetData>`.
802
+ let sheetProtection;
803
+ const protectedRangeEntries = [];
804
+ // Print facets (`CT_Worksheet` tail: printOptions / pageMargins /
805
+ // pageSetup / headerFooter / rowBreaks / colBreaks) plus the
806
+ // `<sheetPr><pageSetUpPr fitToPage>` flag. `_xlnm.Print_Area` /
807
+ // `_xlnm.Print_Titles` come from the workbook's defined names and are
808
+ // merged in by `getSheets()`.
809
+ let pageSetup;
810
+ let pageMargins;
811
+ let printOptions;
812
+ let fitToPage = false;
813
+ let headerFooter;
814
+ // The `<oddHeader>`-style element whose text is streaming in.
815
+ let headerFooterTextTarget;
816
+ const rowBreaks = [];
817
+ const columnBreaks = [];
818
+ // Inside `<rowBreaks>` ("row") / `<colBreaks>` ("column"); routes `<brk>`.
819
+ let breakAxis;
787
820
  const filterColumns = new Map();
788
821
  const getOrCreateFilterColumn = (colId) => {
789
822
  let entry = filterColumns.get(colId);
@@ -800,10 +833,21 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
800
833
  // r-less sheets still report real extents instead of 0.
801
834
  let lastScannedRowIndex = 0;
802
835
  let scanSeqColIndex = 1;
836
+ // `<customSheetViews>` (Excel's View → Custom Views) carry their own
837
+ // pane / selection / autoFilter / print setup / breaks. They describe a
838
+ // saved view, not the sheet, so the whole subtree is skipped — a custom
839
+ // view's `<oddHeader>` or `<brk>` must not become the sheet's.
840
+ let inCustomSheetViews = false;
803
841
  const normalizeTag = (name) => name.includes(":") ? name.split(":")[1] : name;
804
842
  const parser = new HtmlParser({
805
843
  onopentag(tagName, attrs) {
806
844
  const name = normalizeTag(tagName);
845
+ if (name === "customSheetViews") {
846
+ inCustomSheetViews = true;
847
+ return;
848
+ }
849
+ if (inCustomSheetViews)
850
+ return;
807
851
  if (name === "dimension") {
808
852
  const ref = attrs?.ref;
809
853
  if (typeof ref === "string") {
@@ -849,6 +893,50 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
849
893
  if (name === "tabColor") {
850
894
  tabColor = { a$: { ...attrs } };
851
895
  }
896
+ if (name === "sheetProtection") {
897
+ sheetProtection = readSheetProtectionAttrs(attrs);
898
+ }
899
+ if (name === "protectedRange") {
900
+ const sqref = attrs.sqref;
901
+ if (typeof sqref === "string" && sqref) {
902
+ protectedRangeEntries.push({
903
+ sqref,
904
+ name: attrs.name,
905
+ securityDescriptor: attrs.securityDescriptor,
906
+ });
907
+ }
908
+ }
909
+ if (name === "pageSetUpPr") {
910
+ fitToPage = isXmlTrue(attrs?.fitToPage);
911
+ }
912
+ if (name === "pageSetup") {
913
+ pageSetup = readPageSetupAttrs(attrs);
914
+ }
915
+ if (name === "pageMargins") {
916
+ pageMargins = readPageMarginsAttrs(attrs);
917
+ }
918
+ if (name === "printOptions") {
919
+ printOptions = readPrintOptionsAttrs(attrs);
920
+ }
921
+ if (name === "headerFooter") {
922
+ headerFooter = readHeaderFooterAttrs(attrs);
923
+ }
924
+ if (isHeaderFooterTextKey(name)) {
925
+ headerFooterTextTarget = name;
926
+ headerFooter ??= {};
927
+ }
928
+ if (name === "rowBreaks")
929
+ breakAxis = "row";
930
+ if (name === "colBreaks")
931
+ breakAxis = "column";
932
+ if (name === "brk" && breakAxis) {
933
+ // Only manual breaks are user state; Excel drops automatic ones
934
+ // (`man` absent) on save as well.
935
+ const id = parseInt(attrs?.id ?? "", 10);
936
+ if (Number.isFinite(id) && id > 0 && isXmlTrue(attrs?.man)) {
937
+ (breakAxis === "row" ? rowBreaks : columnBreaks).push(id);
938
+ }
939
+ }
852
940
  if (name === "sheetView" && !sheetViewAttrs) {
853
941
  sheetViewAttrs = { ...attrs };
854
942
  }
@@ -1037,11 +1125,29 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1037
1125
  }
1038
1126
  }
1039
1127
  },
1128
+ ontext(text) {
1129
+ if (headerFooterTextTarget && headerFooter) {
1130
+ headerFooter[headerFooterTextTarget] =
1131
+ `${headerFooter[headerFooterTextTarget] ?? ""}${text}`;
1132
+ }
1133
+ },
1040
1134
  onclosetag(tagName) {
1041
1135
  const name = normalizeTag(tagName);
1136
+ if (name === "customSheetViews") {
1137
+ inCustomSheetViews = false;
1138
+ return;
1139
+ }
1140
+ if (inCustomSheetViews)
1141
+ return;
1042
1142
  if (name === "filterColumn") {
1043
1143
  currentFilterColumn = null;
1044
1144
  }
1145
+ if (isHeaderFooterTextKey(name)) {
1146
+ headerFooterTextTarget = undefined;
1147
+ }
1148
+ if (name === "rowBreaks" || name === "colBreaks") {
1149
+ breakAxis = undefined;
1150
+ }
1045
1151
  },
1046
1152
  onerror(err) {
1047
1153
  console.error("Streaming sheet metadata parse error", err);
@@ -1114,6 +1220,18 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1114
1220
  tablePartIds,
1115
1221
  outlineSummaryBelow,
1116
1222
  outlineSummaryRight,
1223
+ sheetProtection,
1224
+ protectedRangeEntries,
1225
+ printSettings: compactPrintSettings({
1226
+ pageSetup: fitToPage ? { ...pageSetup, fitToPage: true } : pageSetup,
1227
+ // Every Excel worksheet carries `<pageMargins>`; only non-default
1228
+ // margins are user state worth modelling.
1229
+ pageMargins: isDefaultPageMargins(pageMargins) ? undefined : pageMargins,
1230
+ printOptions,
1231
+ headerFooter,
1232
+ rowBreaks,
1233
+ columnBreaks,
1234
+ }),
1117
1235
  };
1118
1236
  }
1119
1237
  /**
@@ -1816,6 +1934,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1816
1934
  this.sheetMetadataCache.clear();
1817
1935
  this.dataValidationsBySheetCache.clear();
1818
1936
  this.conditionalFormattingBySheetCache.clear();
1937
+ this.protectedRangesBySheet.clear();
1819
1938
  await this.loadWorkbookXml();
1820
1939
  this.relations = await this.getRelations();
1821
1940
  this.workbookStyles = await WorkbookStyles.load(this.zipParser, this.xmlParser);
@@ -1835,6 +1954,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1835
1954
  this.sheetMetadataCache.clear();
1836
1955
  this.dataValidationsBySheetCache.clear();
1837
1956
  this.conditionalFormattingBySheetCache.clear();
1957
+ this.protectedRangesBySheet.clear();
1838
1958
  this.worksheetBySheetId.clear();
1839
1959
  }
1840
1960
  clearSharedFormula() {
@@ -1924,7 +2044,10 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1924
2044
  : [];
1925
2045
  const columns = columnDefsArray.map((colDef) => {
1926
2046
  const name = colDef.a$.name || "";
1927
- const columnObj = { name };
2047
+ const columnObj = {
2048
+ name,
2049
+ ...parseTableColumnTotals(colDef),
2050
+ };
1928
2051
  // Check for calculated column
1929
2052
  // Handle both with and without namespace prefix
1930
2053
  const calculatedColumnFormula = get(colDef, "calculatedColumnFormula") ||
@@ -2878,28 +3001,17 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2878
3001
  ? drawingPath.substring(1)
2879
3002
  : `xl/${drawingPath}`;
2880
3003
  }
2881
- const drawingContent = await this.zipParser.getText(drawingPath);
3004
+ const drawingContent = (await this.zipParser.getText(drawingPath)) ?? "";
2882
3005
  // Parse drawing XML
2883
- const drawing = this.xmlParser.parse(drawingContent ?? "");
2884
- // Extract image anchors - both absolute and cell anchors
2885
- // Handle different XML namespaces that might be used
2886
- const twoCellAnchors = get(drawing, "xdr:wsDr.xdr:twoCellAnchor") ||
2887
- get(drawing, "wsDr.twoCellAnchor");
2888
- const oneCellAnchors = get(drawing, "xdr:wsDr.xdr:oneCellAnchor") ||
2889
- get(drawing, "wsDr.oneCellAnchor");
2890
- const absoluteAnchors = get(drawing, "xdr:wsDr.xdr:absoluteAnchor") ||
2891
- get(drawing, "wsDr.absoluteAnchor");
2892
- // One cell
2893
- for (const image of await processAnchors(oneCellAnchors, "oneCellAnchor", drawingPath, sheetId, this.zipParser, this.xmlParser)) {
2894
- images.push(image);
2895
- }
2896
- // Two cell
2897
- for (const image of await processAnchors(twoCellAnchors, "twoCellAnchors", drawingPath, sheetId, this.zipParser, this.xmlParser)) {
2898
- images.push(image);
2899
- }
2900
- // absoluteAnchors
2901
- for (const image of await processAnchors(absoluteAnchors, "absoluteAnchors", drawingPath, sheetId, this.zipParser, this.xmlParser)) {
2902
- images.push(image);
3006
+ const drawing = this.xmlParser.parse(drawingContent);
3007
+ // Anchor order in the drawing part is Excel's z-order (first = back).
3008
+ // The parse groups anchors by element name, so the document order
3009
+ // across `oneCellAnchor` / `twoCellAnchor` / `absoluteAnchor` is
3010
+ // recovered from the raw part and each anchor processed in that order.
3011
+ for (const anchor of anchorsInDocumentOrder(drawing, drawingContent)) {
3012
+ for (const image of await processAnchors(anchor.node, anchor.kind, drawingPath, sheetId, this.zipParser, this.xmlParser)) {
3013
+ images.push(image);
3014
+ }
2903
3015
  }
2904
3016
  }
2905
3017
  }
@@ -2949,7 +3061,43 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2949
3061
  _getSheets() {
2950
3062
  return this.workSheets;
2951
3063
  }
3064
+ /**
3065
+ * Resolve a builtin print name to its sheet (`localSheetId` indexes the
3066
+ * `<sheets>` order; a name without scope falls back to the sheet named in
3067
+ * the formula) and record the parsed print area / titles for `getSheets()`.
3068
+ */
3069
+ stashBuiltinPrintName(name, expression, localSheetId, sheetIdBySheetName) {
3070
+ if (!expression || expression.includes("#REF!"))
3071
+ return;
3072
+ const parsed = name === PRINT_AREA_NAME
3073
+ ? parsePrintAreaFormula(expression)
3074
+ : parsePrintTitlesFormula(expression);
3075
+ if (!parsed)
3076
+ return;
3077
+ let sheetId;
3078
+ if (localSheetId !== undefined) {
3079
+ const index = parseInt(localSheetId, 10);
3080
+ const sheetEl = this._getSheets()[index];
3081
+ const id = Number(sheetEl?.a$?.sheetId);
3082
+ if (Number.isFinite(id))
3083
+ sheetId = id;
3084
+ }
3085
+ if (sheetId === undefined && parsed.sheetName) {
3086
+ sheetId = sheetIdBySheetName[parsed.sheetName];
3087
+ }
3088
+ if (sheetId === undefined)
3089
+ return;
3090
+ const entry = this.printNamesBySheetId.get(sheetId) ?? {};
3091
+ if ("ranges" in parsed) {
3092
+ entry.printArea = parsed.ranges;
3093
+ }
3094
+ else {
3095
+ entry.printTitles = parsed.titles;
3096
+ }
3097
+ this.printNamesBySheetId.set(sheetId, entry);
3098
+ }
2952
3099
  _getNamedRanges() {
3100
+ this.printNamesBySheetId.clear();
2953
3101
  const xml = this.workbookXml;
2954
3102
  const sheetIdBySheetName = this._getSheets().reduce((acc, element) => {
2955
3103
  if (element) {
@@ -2971,6 +3119,14 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2971
3119
  const name = defined_name.a$?.name;
2972
3120
  const expression = defined_name.t$ || "";
2973
3121
  const hidden = defined_name.a$.hidden === "1";
3122
+ // `_xlnm.Print_Area` / `_xlnm.Print_Titles` are sheet-scoped print
3123
+ // facets, not user names: they land on `sheet.printSettings` (see
3124
+ // `getSheets()`), never in `namedRanges` — a global copy would make
3125
+ // Excel repair the file on the next export.
3126
+ if (isBuiltinPrintName(name)) {
3127
+ this.stashBuiltinPrintName(name, typeof expression === "string" ? expression : "", defined_name.a$?.localSheetId, sheetIdBySheetName);
3128
+ continue;
3129
+ }
2974
3130
  // Empty defined names are noise; nothing useful to round-trip.
2975
3131
  // Hidden defined names ARE preserved — Excel uses them as scratch
2976
3132
  // names for features like print areas and add-ins, and dropping
@@ -3162,7 +3318,21 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3162
3318
  sheet.visibility = visibility;
3163
3319
  }
3164
3320
  const metadata = await this.getSheetMetadata(rid);
3165
- const { merges, rowEntries, columnMetadata, maxRowIndex, maxColumnIndex, autoFilterRange, filterSpecs, sheetViewData, tabColor, sheetHasFilterMode, zeroHeight, defaultRowHeightPx, defaultColumnWidthPx, tablePartIds, outlineSummaryBelow, outlineSummaryRight, } = metadata;
3321
+ const { merges, rowEntries, columnMetadata, maxRowIndex, maxColumnIndex, autoFilterRange, filterSpecs, sheetViewData, tabColor, sheetHasFilterMode, zeroHeight, defaultRowHeightPx, defaultColumnWidthPx, tablePartIds, outlineSummaryBelow, outlineSummaryRight, sheetProtection, protectedRangeEntries, printSettings, } = metadata;
3322
+ if (sheetProtection) {
3323
+ sheet.sheetProtection = sheetProtection;
3324
+ }
3325
+ {
3326
+ const printNames = this.printNamesBySheetId.get(sheetId);
3327
+ const merged = compactPrintSettings({
3328
+ ...printSettings,
3329
+ printArea: printNames?.printArea,
3330
+ printTitles: printNames?.printTitles,
3331
+ });
3332
+ if (merged)
3333
+ sheet.printSettings = merged;
3334
+ }
3335
+ this.protectedRangesBySheet.set(sheetId, protectedRangesFromEntries(sheetId, protectedRangeEntries));
3166
3336
  if (defaultRowHeightPx !== undefined) {
3167
3337
  sheet.defaultRowHeight = defaultRowHeightPx;
3168
3338
  }
@@ -3203,7 +3373,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3203
3373
  // counts
3204
3374
  let columnCount = 0;
3205
3375
  // gridlines / headers / zeros / zoom
3206
- const { frozenRowCount, frozenColumnCount, paneTopLeftCell, showGridLines, showRowColHeaders, showZeros, zoomScale, } = extractSheetViewProperties(sheetViewData);
3376
+ const { frozenRowCount, frozenColumnCount, paneTopLeftCell, showGridLines, showRowColHeaders, showZeros, zoomScale, rightToLeft, } = extractSheetViewProperties(sheetViewData);
3207
3377
  sheet.frozenRowCount = frozenRowCount;
3208
3378
  sheet.frozenColumnCount = frozenColumnCount;
3209
3379
  if (paneTopLeftCell) {
@@ -3219,6 +3389,8 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3219
3389
  if (zoomScale !== undefined && zoomScale !== 100) {
3220
3390
  sheet.zoomScale = zoomScale;
3221
3391
  }
3392
+ if (rightToLeft)
3393
+ sheet.rightToLeft = true;
3222
3394
  // tab color
3223
3395
  if (tabColor) {
3224
3396
  sheet.tabColor = extractColor(tabColor, this.workbookStyles.indexedColors);
@@ -4117,6 +4289,18 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4117
4289
  const sheet_data = await this.getSheetData(rid);
4118
4290
  return parseDataValidationsFromSheetData(this, sheetId, sheet_data, sheetName);
4119
4291
  }
4292
+ /**
4293
+ * Allow-edit ranges (`<protectedRanges>/<protectedRange>`) in the model the
4294
+ * `protect-range` command writes: `protectedRangeId` = the source `name`,
4295
+ * `range` = the first `sqref` area plus `sheetId`. Available after
4296
+ * `getSheets()`; sheet-level `<sheetProtection>` rides `Sheet.sheetProtection`.
4297
+ */
4298
+ getProtectedRanges(sheetId) {
4299
+ if (sheetId !== undefined) {
4300
+ return this.protectedRangesBySheet.get(sheetId) ?? [];
4301
+ }
4302
+ return Array.from(this.protectedRangesBySheet.values()).flat();
4303
+ }
4120
4304
  getNamedRanges() {
4121
4305
  return this.namedRanges;
4122
4306
  }
@@ -4652,3 +4836,155 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4652
4836
  return externalLinks;
4653
4837
  }
4654
4838
  }
4839
+ /** `CT_SheetProtection` boolean attributes, in OOXML order (camelCase = model key). */
4840
+ const SHEET_PROTECTION_FLAGS = [
4841
+ "sheet",
4842
+ "objects",
4843
+ "scenarios",
4844
+ "formatCells",
4845
+ "formatColumns",
4846
+ "formatRows",
4847
+ "insertColumns",
4848
+ "insertRows",
4849
+ "insertHyperlinks",
4850
+ "deleteColumns",
4851
+ "deleteRows",
4852
+ "selectLockedCells",
4853
+ "sort",
4854
+ "autoFilter",
4855
+ "pivotTables",
4856
+ "selectUnlockedCells",
4857
+ ];
4858
+ /**
4859
+ * `<sheetProtection …>` attributes → `Sheet.sheetProtection`. Only attributes
4860
+ * present in the source are set (`"1"|"true"` → true, anything else → false),
4861
+ * so an explicit flag round-trips without inventing schema defaults; the
4862
+ * password hash (legacy `password` or the agile quartet) rides along verbatim.
4863
+ * Mirrors `rnc-import`'s `read_sheet_protection`.
4864
+ */
4865
+ const readSheetProtectionAttrs = (attrs) => {
4866
+ const protection = {};
4867
+ for (const flag of SHEET_PROTECTION_FLAGS) {
4868
+ const value = attrs[flag];
4869
+ if (value !== undefined) {
4870
+ protection[flag] = value === "1" || value === "true";
4871
+ }
4872
+ }
4873
+ if (attrs.password)
4874
+ protection.password = attrs.password;
4875
+ if (attrs.algorithmName)
4876
+ protection.algorithmName = attrs.algorithmName;
4877
+ if (attrs.hashValue)
4878
+ protection.hashValue = attrs.hashValue;
4879
+ if (attrs.saltValue)
4880
+ protection.saltValue = attrs.saltValue;
4881
+ if (attrs.spinCount !== undefined) {
4882
+ const spinCount = parseInt(attrs.spinCount, 10);
4883
+ if (Number.isFinite(spinCount))
4884
+ protection.spinCount = spinCount;
4885
+ }
4886
+ return protection;
4887
+ };
4888
+ /**
4889
+ * `<protectedRange sqref name>` entries → `ProtectedRange[]` for one sheet. The
4890
+ * `name` becomes the `protectedRangeId` so it keys the stored facet and
4891
+ * re-exports under the same name; only the first `sqref` area is modeled
4892
+ * (Excel writes one per element in practice).
4893
+ */
4894
+ const protectedRangesFromEntries = (sheetId, entries) => {
4895
+ const ranges = [];
4896
+ for (const entry of entries) {
4897
+ const first = entry.sqref.trim().split(/\s+/)[0];
4898
+ const range = first ? getRangeFromAddressRef(first) : undefined;
4899
+ if (!range)
4900
+ continue;
4901
+ const protectedRange = {
4902
+ range: { sheetId, ...range },
4903
+ };
4904
+ if (entry.name)
4905
+ protectedRange.protectedRangeId = entry.name;
4906
+ if (entry.securityDescriptor) {
4907
+ protectedRange.securityDescriptor = entry.securityDescriptor;
4908
+ }
4909
+ ranges.push(protectedRange);
4910
+ }
4911
+ return ranges;
4912
+ };
4913
+ const isXmlTrue = (value) => value === "1" || value === "true";
4914
+ /** `"1"|"true"|"0"|"false"` → boolean; absent stays undefined. */
4915
+ const xmlBoolOpt = (value) => value === undefined ? undefined : isXmlTrue(value);
4916
+ const xmlIntOpt = (value) => {
4917
+ if (value === undefined)
4918
+ return undefined;
4919
+ const n = parseInt(value, 10);
4920
+ return Number.isFinite(n) ? n : undefined;
4921
+ };
4922
+ const xmlFloatOpt = (value) => {
4923
+ if (value === undefined)
4924
+ return undefined;
4925
+ const n = parseFloat(value);
4926
+ return Number.isFinite(n) ? n : undefined;
4927
+ };
4928
+ const stripUndefined = (value) => {
4929
+ for (const key of Object.keys(value)) {
4930
+ if (value[key] === undefined)
4931
+ delete value[key];
4932
+ }
4933
+ return value;
4934
+ };
4935
+ /** `<headerFooter>` children whose text is a header/footer code string. */
4936
+ const HEADER_FOOTER_TEXT_ELEMENTS = [
4937
+ "oddHeader",
4938
+ "oddFooter",
4939
+ "evenHeader",
4940
+ "evenFooter",
4941
+ "firstHeader",
4942
+ "firstFooter",
4943
+ ];
4944
+ const isHeaderFooterTextKey = (name) => HEADER_FOOTER_TEXT_ELEMENTS.includes(name);
4945
+ /**
4946
+ * `<pageSetup …>` → `PageSetup` (present attributes only; `fitToPage` is
4947
+ * filled from `<sheetPr><pageSetUpPr>` by the caller). Mirrors
4948
+ * `rnc-import`'s `read_page_setup`.
4949
+ */
4950
+ const readPageSetupAttrs = (attrs) => {
4951
+ const orientation = attrs.orientation;
4952
+ const pageOrder = attrs.pageOrder;
4953
+ return stripUndefined({
4954
+ orientation: orientation === "landscape" || orientation === "portrait"
4955
+ ? orientation
4956
+ : undefined,
4957
+ paperSize: xmlIntOpt(attrs.paperSize),
4958
+ scale: xmlIntOpt(attrs.scale),
4959
+ fitToWidth: xmlIntOpt(attrs.fitToWidth),
4960
+ fitToHeight: xmlIntOpt(attrs.fitToHeight),
4961
+ firstPageNumber: xmlIntOpt(attrs.firstPageNumber),
4962
+ useFirstPageNumber: xmlBoolOpt(attrs.useFirstPageNumber),
4963
+ pageOrder: pageOrder === "overThenDown" || pageOrder === "downThenOver"
4964
+ ? pageOrder
4965
+ : undefined,
4966
+ blackAndWhite: xmlBoolOpt(attrs.blackAndWhite),
4967
+ draft: xmlBoolOpt(attrs.draft),
4968
+ });
4969
+ };
4970
+ const readPageMarginsAttrs = (attrs) => stripUndefined({
4971
+ left: xmlFloatOpt(attrs.left),
4972
+ right: xmlFloatOpt(attrs.right),
4973
+ top: xmlFloatOpt(attrs.top),
4974
+ bottom: xmlFloatOpt(attrs.bottom),
4975
+ header: xmlFloatOpt(attrs.header),
4976
+ footer: xmlFloatOpt(attrs.footer),
4977
+ });
4978
+ const readPrintOptionsAttrs = (attrs) => stripUndefined({
4979
+ gridLines: xmlBoolOpt(attrs.gridLines),
4980
+ headings: xmlBoolOpt(attrs.headings),
4981
+ horizontalCentered: xmlBoolOpt(attrs.horizontalCentered),
4982
+ verticalCentered: xmlBoolOpt(attrs.verticalCentered),
4983
+ });
4984
+ /** `<headerFooter …>` attributes; the child texts stream in via `ontext`. */
4985
+ const readHeaderFooterAttrs = (attrs) => stripUndefined({
4986
+ differentOddEven: xmlBoolOpt(attrs.differentOddEven),
4987
+ differentFirst: xmlBoolOpt(attrs.differentFirst),
4988
+ scaleWithDoc: xmlBoolOpt(attrs.scaleWithDoc),
4989
+ alignWithMargins: xmlBoolOpt(attrs.alignWithMargins),
4990
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"pivots.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsx/pivots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AASrE,KAAK,aAAa,GAAG;IACnB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA6OF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS;IACP,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,mEAAmE;IACnE,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,kDAAkD;IAClD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,cAAc,EAAE,MAAM,CAAC;CACxB,KACA,OAAO,CAAC,UAAU,EAAE,CAoEtB,CAAC"}
1
+ {"version":3,"file":"pivots.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsx/pivots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAEV,UAAU,EAGX,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAoErE,KAAK,aAAa,GAAG;IACnB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAkQF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS;IACP,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,mEAAmE;IACnE,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,kDAAkD;IAClD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,cAAc,EAAE,MAAM,CAAC;CACxB,KACA,OAAO,CAAC,UAAU,EAAE,CAsEtB,CAAC"}
@@ -1,3 +1,62 @@
1
+ const attr = (node, name) => {
2
+ const v = node?.a$?.[name] ?? node?.[`@_${name}`];
3
+ return v === undefined || v === null ? undefined : String(v);
4
+ };
5
+ /**
6
+ * `dataField@showDataAs` (ECMA-376) + the `x14:dataField@pivotShowAs`
7
+ * extension Excel uses for the newer modes → `PivotValue.showAs`.
8
+ * Modes without an engine equivalent (difference, % of parent, index…)
9
+ * degrade to the plain aggregate.
10
+ */
11
+ const readShowAs = (dataField) => {
12
+ const extLst = dataField?.extLst ?? dataField?.["x:extLst"];
13
+ const exts = asArray(extLst?.ext ?? extLst?.["x:ext"]);
14
+ for (const ext of exts) {
15
+ const x14 = ext?.["x14:dataField"];
16
+ const pivotShowAs = attr(x14, "pivotShowAs");
17
+ if (pivotShowAs === "rankAscending")
18
+ return "rank_asc";
19
+ if (pivotShowAs === "rankDescending")
20
+ return "rank_desc";
21
+ }
22
+ switch (attr(dataField, "showDataAs")) {
23
+ case "percentOfTotal":
24
+ return "pct_of_total";
25
+ case "percentOfRow":
26
+ return "pct_of_row";
27
+ case "percentOfCol":
28
+ return "pct_of_column";
29
+ case "runTotal":
30
+ return "running_total";
31
+ default:
32
+ return undefined;
33
+ }
34
+ };
35
+ const readStyleInfo = (pt) => {
36
+ const node = pt?.pivotTableStyleInfo ?? pt?.["x:pivotTableStyleInfo"];
37
+ if (!node)
38
+ return undefined;
39
+ const flag = (name) => {
40
+ const v = attr(node, name);
41
+ return v === undefined ? undefined : v !== "0" && v !== "false";
42
+ };
43
+ const info = {};
44
+ const name = attr(node, "name");
45
+ if (name)
46
+ info.name = name;
47
+ for (const key of [
48
+ "showRowHeaders",
49
+ "showColHeaders",
50
+ "showRowStripes",
51
+ "showColStripes",
52
+ "showLastColumn",
53
+ ]) {
54
+ const v = flag(key);
55
+ if (v !== undefined)
56
+ info[key] = v;
57
+ }
58
+ return Object.keys(info).length ? info : undefined;
59
+ };
1
60
  const asArray = (value) => {
2
61
  if (value === undefined || value === null)
3
62
  return [];
@@ -69,6 +128,9 @@ const parsePivotCacheDefinition = (cacheXml) => {
69
128
  const cacheSource = cacheDef.cacheSource ?? cacheDef["x:cacheSource"];
70
129
  const wsSource = cacheSource?.worksheetSource ?? cacheSource?.["x:worksheetSource"];
71
130
  const sourceRef = wsSource?.a$?.ref || wsSource?.["@_ref"];
131
+ // Excel writes the source sheet as `worksheetSource@sheet` (the ref is
132
+ // then sheet-less); a `Sheet!A1:D9` prefix on the ref also occurs.
133
+ const sourceSheet = attr(wsSource, "sheet");
72
134
  const cacheFields = cacheDef.cacheFields ?? cacheDef["x:cacheFields"];
73
135
  const rawFieldList = asArray(cacheFields?.cacheField ?? cacheFields?.["x:cacheField"]);
74
136
  const fields = rawFieldList.map((cf) => {
@@ -86,9 +148,14 @@ const parsePivotCacheDefinition = (cacheXml) => {
86
148
  items.push(null);
87
149
  }
88
150
  }
89
- return { name, sharedItems: items.length > 0 ? items : undefined };
151
+ const formula = attr(cf, "formula");
152
+ return {
153
+ name,
154
+ sharedItems: items.length > 0 ? items : undefined,
155
+ ...(formula !== undefined ? { formula } : {}),
156
+ };
90
157
  });
91
- return { sourceRef, fields };
158
+ return { sourceRef, sourceSheet, fields };
92
159
  };
93
160
  /**
94
161
  * Parse a single pivotTable.xml into a PivotTable record. Needs the
@@ -124,7 +191,8 @@ const parsePivotTableXml = (pivotXml, cacheDef, getSheetId, defaultSheetId, pivo
124
191
  // Source range from cacheDef
125
192
  let source;
126
193
  if (cacheDef.sourceRef) {
127
- const { sheet, a1 } = stripSheetPrefix(cacheDef.sourceRef);
194
+ const { sheet: prefixSheet, a1 } = stripSheetPrefix(cacheDef.sourceRef);
195
+ const sheet = prefixSheet ?? cacheDef.sourceSheet;
128
196
  const sourceRange = parseA1Range(a1);
129
197
  if (sourceRange) {
130
198
  const resolved = sheet ? getSheetId(sheet) : undefined;
@@ -169,11 +237,19 @@ const parsePivotTableXml = (pivotXml, cacheDef, getSheetId, defaultSheetId, pivo
169
237
  const field = fieldNameAt(idx);
170
238
  if (!field)
171
239
  return null;
172
- const aggFunc = normalizeAggFunc(d.a$?.subtotal || d["@_subtotal"]);
240
+ // Excel always summarizes a calculated field with Sum.
241
+ const aggFunc = cacheDef.fields[idx]?.formula !== undefined
242
+ ? "sum"
243
+ : normalizeAggFunc(d.a$?.subtotal || d["@_subtotal"]);
173
244
  const displayName = d.a$?.name || d["@_name"] || `${aggFunc} of ${field}`;
174
- return { field, displayName, aggFunc };
245
+ const showAs = readShowAs(d);
246
+ return { field, displayName, aggFunc, ...(showAs ? { showAs } : {}) };
175
247
  })
176
248
  .filter((v) => v !== null);
249
+ const calculatedFields = cacheDef.fields
250
+ .filter((f) => f.formula !== undefined && f.name !== "")
251
+ .map((f) => ({ name: f.name, formula: f.formula }));
252
+ const styleInfo = readStyleInfo(pt);
177
253
  // Display toggles (Phase A1 — round-trip these so the editor
178
254
  // checkboxes pick up the imported values).
179
255
  // XLSX: rowGrandTotals / colGrandTotals default to "1" (true).
@@ -192,6 +268,8 @@ const parsePivotTableXml = (pivotXml, cacheDef, getSheetId, defaultSheetId, pivo
192
268
  showColumnGrandTotals: showColGT,
193
269
  // Subtotal default on individual fields → leave true.
194
270
  showSubtotals: true,
271
+ ...(calculatedFields.length ? { calculatedFields } : {}),
272
+ ...(styleInfo ? { styleInfo } : {}),
195
273
  };
196
274
  };
197
275
  /**
@@ -219,7 +297,9 @@ export const parsePivotTablesForSheet = async (options) => {
219
297
  const fileName = pivotTablePath.split("/").pop();
220
298
  const pivotRelsPath = `${pivotDir}/_rels/${fileName}.rels`;
221
299
  const pivotRelsText = await zipParser.getText(pivotRelsPath);
222
- let cacheDef = { fields: [] };
300
+ let cacheDef = {
301
+ fields: [],
302
+ };
223
303
  if (pivotRelsText) {
224
304
  const pivotRels = xmlParser.parse(pivotRelsText);
225
305
  const relList = asArray(pivotRels?.Relationships?.Relationship);