@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.
- package/dist/cjs/excel-parser/cfb-decryptor.d.ts +6 -0
- package/dist/cjs/excel-parser/cfb-decryptor.d.ts.map +1 -1
- package/dist/cjs/excel-parser/cfb-decryptor.js +6 -0
- package/dist/cjs/excel-parser/helpers.d.ts +9 -1
- package/dist/cjs/excel-parser/helpers.d.ts.map +1 -1
- package/dist/cjs/excel-parser/helpers.js +37 -0
- package/dist/cjs/excel-parser/interface.d.ts +10 -1
- package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/ods/index.d.ts +19 -0
- package/dist/cjs/excel-parser/plugins/ods/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/ods/index.js +676 -304
- package/dist/cjs/excel-parser/plugins/ods/styles.d.ts +43 -1
- package/dist/cjs/excel-parser/plugins/ods/styles.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/ods/styles.js +343 -45
- package/dist/cjs/excel-parser/plugins/xls/index.d.ts +11 -3
- package/dist/cjs/excel-parser/plugins/xls/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xls/index.js +55 -6
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts +4 -0
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsb/index.js +21 -9
- package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts +10 -0
- package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/charts.js +68 -9
- package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts +28 -1
- package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/drawing.js +155 -20
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +26 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.js +567 -234
- package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/pivots.js +125 -37
- package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts +3 -0
- package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.js +4 -1
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/styles.js +12 -2
- package/dist/cjs/excel-parser/registry.d.ts.map +1 -1
- package/dist/cjs/excel-parser/registry.js +2 -0
- package/dist/cjs/excel-parser/types.d.ts +7 -1
- package/dist/cjs/excel-parser/types.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.d.ts +1 -0
- package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.js +4 -0
- package/dist/cjs/excel-parser/worker.js +3 -1
- package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -1
- package/dist/cjs/exporter/ods/ods-export.js +432 -175
- package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/chart-builder.js +210 -6
- package/dist/cjs/exporter/xlsx/pivot-builder.d.ts +8 -0
- package/dist/cjs/exporter/xlsx/pivot-builder.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/pivot-builder.js +141 -42
- package/dist/cjs/exporter/xlsx/style-builder.js +9 -3
- package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts +7 -1
- package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/threaded-comments-builder.js +15 -3
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +11 -2
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/xlsx-export.js +678 -222
- package/dist/cjs/helpers/implicit-intersection.d.ts +16 -0
- package/dist/cjs/helpers/implicit-intersection.d.ts.map +1 -0
- package/dist/cjs/helpers/implicit-intersection.js +197 -0
- package/dist/cjs/print-settings.d.ts +105 -0
- package/dist/cjs/print-settings.d.ts.map +1 -0
- package/dist/cjs/print-settings.js +494 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/excel-parser/cfb-decryptor.d.ts +6 -0
- package/dist/esm/excel-parser/cfb-decryptor.d.ts.map +1 -1
- package/dist/esm/excel-parser/cfb-decryptor.js +6 -0
- package/dist/esm/excel-parser/helpers.d.ts +9 -1
- package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
- package/dist/esm/excel-parser/helpers.js +34 -0
- package/dist/esm/excel-parser/interface.d.ts +10 -1
- package/dist/esm/excel-parser/interface.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/ods/index.d.ts +19 -0
- package/dist/esm/excel-parser/plugins/ods/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/ods/index.js +347 -33
- package/dist/esm/excel-parser/plugins/ods/styles.d.ts +43 -1
- package/dist/esm/excel-parser/plugins/ods/styles.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/ods/styles.js +276 -2
- package/dist/esm/excel-parser/plugins/xls/index.d.ts +11 -3
- package/dist/esm/excel-parser/plugins/xls/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xls/index.js +37 -5
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts +4 -0
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsb/index.js +20 -6
- package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts +10 -0
- package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/charts.js +69 -6
- package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts +28 -1
- package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/drawing.js +99 -0
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +26 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.js +362 -26
- package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/pivots.js +86 -6
- package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts +3 -0
- package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/sheet-views.js +2 -0
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/styles.js +12 -2
- package/dist/esm/excel-parser/registry.d.ts.map +1 -1
- package/dist/esm/excel-parser/registry.js +2 -0
- package/dist/esm/excel-parser/types.d.ts +7 -1
- package/dist/esm/excel-parser/types.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.d.ts +1 -0
- package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.js +3 -0
- package/dist/esm/excel-parser/worker.js +2 -0
- package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -1
- package/dist/esm/exporter/ods/ods-export.js +306 -35
- package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/chart-builder.js +161 -0
- package/dist/esm/exporter/xlsx/pivot-builder.d.ts +8 -0
- package/dist/esm/exporter/xlsx/pivot-builder.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/pivot-builder.js +104 -19
- package/dist/esm/exporter/xlsx/style-builder.js +9 -3
- package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts +7 -1
- package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/threaded-comments-builder.js +15 -3
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts +11 -2
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/xlsx-export.js +449 -47
- package/dist/esm/helpers/implicit-intersection.d.ts +16 -0
- package/dist/esm/helpers/implicit-intersection.d.ts.map +1 -0
- package/dist/esm/helpers/implicit-intersection.js +184 -0
- package/dist/esm/print-settings.d.ts +105 -0
- package/dist/esm/print-settings.d.ts.map +1 -0
- package/dist/esm/print-settings.js +422 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- 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 = {
|
|
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
|
-
//
|
|
2885
|
-
//
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = {
|
|
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);
|