@rowsncolumns/toolkit 24.4.3 → 24.4.5
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/interface.d.ts +5 -1
- package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/base/index.d.ts +2 -0
- package/dist/cjs/excel-parser/plugins/base/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/base/index.js +3 -0
- package/dist/cjs/excel-parser/plugins/xls/index.d.ts +55 -9
- package/dist/cjs/excel-parser/plugins/xls/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xls/index.js +487 -24
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsb/index.js +12 -5
- package/dist/cjs/excel-parser/plugins/xlsx/chartex.d.ts +37 -0
- package/dist/cjs/excel-parser/plugins/xlsx/chartex.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/chartex.js +244 -0
- package/dist/cjs/excel-parser/plugins/xlsx/form-controls.d.ts +70 -0
- package/dist/cjs/excel-parser/plugins/xlsx/form-controls.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/form-controls.js +295 -0
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +9 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.js +251 -104
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts +15 -1
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/styles.js +90 -4
- package/dist/cjs/excel-parser/registry.d.ts +7 -0
- package/dist/cjs/excel-parser/registry.d.ts.map +1 -1
- package/dist/cjs/excel-parser/registry.js +28 -1
- package/dist/cjs/excel-parser/types.d.ts +10 -1
- package/dist/cjs/excel-parser/types.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.d.ts +2 -0
- package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.js +20 -14
- package/dist/cjs/excel-parser/worker.js +128 -130
- package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/chart-builder.js +5 -33
- package/dist/cjs/exporter/xlsx/chartex-builder.d.ts +36 -0
- package/dist/cjs/exporter/xlsx/chartex-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/chartex-builder.js +109 -0
- package/dist/cjs/exporter/xlsx/form-controls-builder.d.ts +61 -0
- package/dist/cjs/exporter/xlsx/form-controls-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/form-controls-builder.js +197 -0
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/xlsx-export.js +431 -193
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/excel-parser/interface.d.ts +5 -1
- package/dist/esm/excel-parser/interface.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/base/index.d.ts +2 -0
- package/dist/esm/excel-parser/plugins/base/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/base/index.js +3 -0
- package/dist/esm/excel-parser/plugins/xls/index.d.ts +55 -9
- package/dist/esm/excel-parser/plugins/xls/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xls/index.js +429 -23
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsb/index.js +10 -3
- package/dist/esm/excel-parser/plugins/xlsx/chartex.d.ts +37 -0
- package/dist/esm/excel-parser/plugins/xlsx/chartex.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/chartex.js +199 -0
- package/dist/esm/excel-parser/plugins/xlsx/form-controls.d.ts +70 -0
- package/dist/esm/excel-parser/plugins/xlsx/form-controls.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/form-controls.js +246 -0
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +9 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.js +92 -6
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts +15 -1
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/styles.js +80 -0
- package/dist/esm/excel-parser/registry.d.ts +7 -0
- package/dist/esm/excel-parser/registry.d.ts.map +1 -1
- package/dist/esm/excel-parser/registry.js +18 -1
- package/dist/esm/excel-parser/types.d.ts +10 -1
- package/dist/esm/excel-parser/types.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.d.ts +2 -0
- package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.js +16 -10
- package/dist/esm/excel-parser/worker.js +10 -18
- package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/chart-builder.js +0 -28
- package/dist/esm/exporter/xlsx/chartex-builder.d.ts +36 -0
- package/dist/esm/exporter/xlsx/chartex-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/chartex-builder.js +97 -0
- package/dist/esm/exporter/xlsx/form-controls-builder.d.ts +61 -0
- package/dist/esm/exporter/xlsx/form-controls-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/form-controls-builder.js +173 -0
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/xlsx-export.js +228 -19
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/xlsx.d.ts +14 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsb/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EAEL,2BAA2B,EAE5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,WAAW,EAGX,qBAAqB,EACrB,wBAAwB,EAExB,aAAa,EACb,UAAU,EACV,KAAK,EACL,gBAAgB,EAChB,SAAS,EACV,MAAM,2BAA2B,CAAC;AAenC,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsb/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ import { processChart } from "../xlsx/charts";
|
|
|
11
11
|
import { processAnchors } from "../xlsx/drawing";
|
|
12
12
|
import { readAllRecordHeaders } from "./biff-record-reader";
|
|
13
13
|
import { standardExcelFormatCodeById } from "../xlsx/numfmt";
|
|
14
|
+
import { DATE_1904_OFFSET_DAYS, isEpochShiftedFormat } from "../xlsx/styles";
|
|
14
15
|
import { extractThemeColors, extractThemeFonts, generateDarkThemeColors, } from "../xlsx/theme";
|
|
15
16
|
const RT = {
|
|
16
17
|
BrtCellRk: 0x0002,
|
|
@@ -939,7 +940,7 @@ const resolveXf = (styles, xfIndex) => {
|
|
|
939
940
|
indent: xf.indent ?? parent.indent,
|
|
940
941
|
};
|
|
941
942
|
};
|
|
942
|
-
const toCellData = (parsed, styles, sharedStrings, enableCellXfsRegistry, enableSharedStrings, cellXfs) => {
|
|
943
|
+
const toCellData = (parsed, styles, sharedStrings, enableCellXfsRegistry, enableSharedStrings, cellXfs, use1904) => {
|
|
943
944
|
const xf = resolveXf(styles, parsed.styleRef);
|
|
944
945
|
const format = {};
|
|
945
946
|
const pattern = xf
|
|
@@ -1052,7 +1053,13 @@ const toCellData = (parsed, styles, sharedStrings, enableCellXfsRegistry, enable
|
|
|
1052
1053
|
};
|
|
1053
1054
|
}
|
|
1054
1055
|
if (parsed.kind === "n") {
|
|
1055
|
-
|
|
1056
|
+
// A 1904-system workbook (`BrtWbProp.fDate1904`) stores date serials
|
|
1057
|
+
// 1462 days below the engine's 1900 system; date-formatted numbers are
|
|
1058
|
+
// converted on the way in, like the XLSX importer does.
|
|
1059
|
+
const value = Number(parsed.value) +
|
|
1060
|
+
(use1904 && isEpochShiftedFormat(format.numberFormat?.type)
|
|
1061
|
+
? DATE_1904_OFFSET_DAYS
|
|
1062
|
+
: 0);
|
|
1056
1063
|
const pattern = format.numberFormat?.pattern ?? "GENERAL";
|
|
1057
1064
|
const fv = ssfFormat(pattern, value);
|
|
1058
1065
|
// If there's a formula, use it in ue.fv, otherwise use the numeric value
|
|
@@ -1861,7 +1868,7 @@ export class XlsbPlugin extends BaseWorkbookPlugin {
|
|
|
1861
1868
|
row = { values: [] };
|
|
1862
1869
|
rows.set(parsed.row, row);
|
|
1863
1870
|
}
|
|
1864
|
-
row.values[parsed.col] = toCellData(parsed, this.styles, this.sharedStrings, enableCellXfsRegistry, enableSharedStrings, this.cellXfs);
|
|
1871
|
+
row.values[parsed.col] = toCellData(parsed, this.styles, this.sharedStrings, enableCellXfsRegistry, enableSharedStrings, this.cellXfs, this.use1904);
|
|
1865
1872
|
}
|
|
1866
1873
|
// Apply merge cell formatting
|
|
1867
1874
|
// In Excel, merged cells should have formatting from the top-left (anchor) cell
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EmbeddedChart, SheetRange } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import type { StreamingZipParser } from "../../zip-parser-streaming";
|
|
3
|
+
import { type ResolveSheetId, type SheetDimensions } from "./charts";
|
|
4
|
+
/**
|
|
5
|
+
* Excel 2016+ "chartex" charts (`xl/charts/chartEx{N}.xml`, namespace
|
|
6
|
+
* `http://schemas.microsoft.com/office/drawing/2014/chartex`): treemap,
|
|
7
|
+
* sunburst, histogram / pareto, box & whisker, waterfall, funnel. They are
|
|
8
|
+
* anchored through an `mc:AlternateContent` whose `Choice` graphic frame
|
|
9
|
+
* carries `<cx:chart r:id>` instead of `<c:chart>`, and their data lives in
|
|
10
|
+
* `<cx:chartData><cx:data>` dimensions (`strDim type="cat"` for the
|
|
11
|
+
* hierarchy, `numDim type="size"|"val"` for the numbers) referenced by the
|
|
12
|
+
* series through `<cx:dataId>`.
|
|
13
|
+
*
|
|
14
|
+
* The exporter counterpart is `exporter/xlsx/chartex-builder.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const CHART_EX_REL_TYPE = "http://schemas.microsoft.com/office/2014/relationships/chartEx";
|
|
17
|
+
export declare const CHART_EX_GRAPHIC_URI = "http://schemas.microsoft.com/office/drawing/2014/chartex";
|
|
18
|
+
/**
|
|
19
|
+
* `<cx:chart r:id>` inside an anchor's `mc:AlternateContent` choice (Excel
|
|
20
|
+
* also writes a plain `xdr:graphicFrame` for some producers — both forms are
|
|
21
|
+
* checked). Returns the `cx:chart` node or undefined for non-chartex anchors.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getChartExReferenceFromAnchor(anchor: any): any;
|
|
24
|
+
/**
|
|
25
|
+
* Split a multi-column hierarchy range (Excel lays the treemap hierarchy out
|
|
26
|
+
* as columns, top level first, leaf last) into `parentLabels` (one entry per
|
|
27
|
+
* parent column) and `labels` (the leaf column).
|
|
28
|
+
*/
|
|
29
|
+
export declare const splitHierarchyRange: (range: SheetRange) => {
|
|
30
|
+
parentLabels: SheetRange[];
|
|
31
|
+
labels: SheetRange;
|
|
32
|
+
};
|
|
33
|
+
/** Parse a `cx:chartSpace` DOM into an `EmbeddedChart` spec (null when unsupported). */
|
|
34
|
+
export declare function parseChartExSpec(chartSpaceDom: any, sheetId: number, resolveSheetId?: ResolveSheetId): EmbeddedChart["spec"] | null;
|
|
35
|
+
/** Resolve + parse one chartex part referenced from a drawing anchor. */
|
|
36
|
+
export declare function processChartEx(chartTarget: string, drawingPath: string, anchor: any, sheetId: number, zipParser: StreamingZipParser, xmlParser: any, sheetDimensions?: SheetDimensions, resolveSheetId?: ResolveSheetId): Promise<EmbeddedChart | null>;
|
|
37
|
+
//# sourceMappingURL=chartex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chartex.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsx/chartex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,UAAU,EACX,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,iBAAiB,mEACoC,CAAC;AACnE,eAAO,MAAM,oBAAoB,6DAC2B,CAAC;AAyB7D;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAoB9D;AA8BD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,OAAO,UAAU,KAChB;IAAE,YAAY,EAAE,UAAU,EAAE,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAalD,CAAC;AAWF,wFAAwF;AACxF,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,GAAG,EAClB,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,cAAc,GAC9B,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,CA0D9B;AAED,yEAAyE;AACzE,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,GAAG,EACX,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,kBAAkB,EAC7B,SAAS,EAAE,GAAG,EACd,eAAe,CAAC,EAAE,eAAe,EACjC,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAsB/B"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { generateSelectionsFromFormula, uuid } from "@rowsncolumns/utils";
|
|
2
|
+
import { get } from "lodash-es";
|
|
3
|
+
import { extractChartPosition, } from "./charts";
|
|
4
|
+
/**
|
|
5
|
+
* Excel 2016+ "chartex" charts (`xl/charts/chartEx{N}.xml`, namespace
|
|
6
|
+
* `http://schemas.microsoft.com/office/drawing/2014/chartex`): treemap,
|
|
7
|
+
* sunburst, histogram / pareto, box & whisker, waterfall, funnel. They are
|
|
8
|
+
* anchored through an `mc:AlternateContent` whose `Choice` graphic frame
|
|
9
|
+
* carries `<cx:chart r:id>` instead of `<c:chart>`, and their data lives in
|
|
10
|
+
* `<cx:chartData><cx:data>` dimensions (`strDim type="cat"` for the
|
|
11
|
+
* hierarchy, `numDim type="size"|"val"` for the numbers) referenced by the
|
|
12
|
+
* series through `<cx:dataId>`.
|
|
13
|
+
*
|
|
14
|
+
* The exporter counterpart is `exporter/xlsx/chartex-builder.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export const CHART_EX_REL_TYPE = "http://schemas.microsoft.com/office/2014/relationships/chartEx";
|
|
17
|
+
export const CHART_EX_GRAPHIC_URI = "http://schemas.microsoft.com/office/drawing/2014/chartex";
|
|
18
|
+
const asArray = (value) => value == null ? [] : Array.isArray(value) ? value : [value];
|
|
19
|
+
/** Read a child by local name, tolerating any namespace prefix. */
|
|
20
|
+
const child = (node, localName) => {
|
|
21
|
+
if (!node || typeof node !== "object")
|
|
22
|
+
return undefined;
|
|
23
|
+
if (node[localName] !== undefined)
|
|
24
|
+
return node[localName];
|
|
25
|
+
for (const key of Object.keys(node)) {
|
|
26
|
+
const colon = key.indexOf(":");
|
|
27
|
+
if (colon >= 0 && key.slice(colon + 1) === localName)
|
|
28
|
+
return node[key];
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
};
|
|
32
|
+
const textOf = (node) => {
|
|
33
|
+
if (node == null)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (typeof node === "object") {
|
|
36
|
+
const inner = node.t$;
|
|
37
|
+
return inner == null ? undefined : String(inner);
|
|
38
|
+
}
|
|
39
|
+
return String(node);
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* `<cx:chart r:id>` inside an anchor's `mc:AlternateContent` choice (Excel
|
|
43
|
+
* also writes a plain `xdr:graphicFrame` for some producers — both forms are
|
|
44
|
+
* checked). Returns the `cx:chart` node or undefined for non-chartex anchors.
|
|
45
|
+
*/
|
|
46
|
+
export function getChartExReferenceFromAnchor(anchor) {
|
|
47
|
+
const frames = [];
|
|
48
|
+
for (const alternate of asArray(child(anchor, "AlternateContent"))) {
|
|
49
|
+
for (const choice of asArray(child(alternate, "Choice"))) {
|
|
50
|
+
frames.push(...asArray(child(choice, "graphicFrame")));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
frames.push(...asArray(child(anchor, "graphicFrame")));
|
|
54
|
+
for (const frame of frames) {
|
|
55
|
+
const graphicData = child(child(frame, "graphic"), "graphicData");
|
|
56
|
+
if (!graphicData)
|
|
57
|
+
continue;
|
|
58
|
+
const uri = get(graphicData, "a$.uri");
|
|
59
|
+
const cxChart = child(graphicData, "chart");
|
|
60
|
+
if (uri === CHART_EX_GRAPHIC_URI && cxChart)
|
|
61
|
+
return cxChart;
|
|
62
|
+
// A `cx:`-prefixed chart element without the uri attribute (defensive).
|
|
63
|
+
if (cxChart && Object.keys(graphicData).some((k) => k.startsWith("cx:"))) {
|
|
64
|
+
return cxChart;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/** `layoutId` → engine chart type. Unknown layouts are not imported. */
|
|
70
|
+
const CHART_TYPE_BY_LAYOUT = {
|
|
71
|
+
treemap: "treemap",
|
|
72
|
+
sunburst: "sunburst",
|
|
73
|
+
waterfall: "waterfall",
|
|
74
|
+
funnel: "funnel",
|
|
75
|
+
boxWhisker: "boxplot",
|
|
76
|
+
// `clusteredColumn` is a histogram / pareto only when it carries `<cx:binning>`.
|
|
77
|
+
};
|
|
78
|
+
const sourcesFromRef = (ref, hostSheetId, resolveSheetId) => {
|
|
79
|
+
if (!ref)
|
|
80
|
+
return [];
|
|
81
|
+
const selections = generateSelectionsFromFormula(`=${ref}`);
|
|
82
|
+
return selections.map((sel) => ({
|
|
83
|
+
...sel.range,
|
|
84
|
+
sheetId: (sel.sheetName ? resolveSheetId?.(sel.sheetName) : undefined) ??
|
|
85
|
+
hostSheetId,
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
const chartData = (sources) => ({ sources });
|
|
89
|
+
/**
|
|
90
|
+
* Split a multi-column hierarchy range (Excel lays the treemap hierarchy out
|
|
91
|
+
* as columns, top level first, leaf last) into `parentLabels` (one entry per
|
|
92
|
+
* parent column) and `labels` (the leaf column).
|
|
93
|
+
*/
|
|
94
|
+
export const splitHierarchyRange = (range) => {
|
|
95
|
+
const parentLabels = [];
|
|
96
|
+
for (let c = range.startColumnIndex; c < range.endColumnIndex; c++) {
|
|
97
|
+
parentLabels.push({ ...range, startColumnIndex: c, endColumnIndex: c });
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
parentLabels,
|
|
101
|
+
labels: {
|
|
102
|
+
...range,
|
|
103
|
+
startColumnIndex: range.endColumnIndex,
|
|
104
|
+
endColumnIndex: range.endColumnIndex,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const dimensionRef = (data, dimTag, type) => {
|
|
109
|
+
const dims = asArray(child(data, dimTag)).filter((dim) => get(dim, "a$.type") === type);
|
|
110
|
+
const dim = dims[0] ?? (dimTag === "numDim" ? asArray(child(data, dimTag))[0] : undefined);
|
|
111
|
+
const f = child(dim, "f");
|
|
112
|
+
return textOf(Array.isArray(f) ? f[0] : f)?.trim() || undefined;
|
|
113
|
+
};
|
|
114
|
+
/** Parse a `cx:chartSpace` DOM into an `EmbeddedChart` spec (null when unsupported). */
|
|
115
|
+
export function parseChartExSpec(chartSpaceDom, sheetId, resolveSheetId) {
|
|
116
|
+
const chartSpace = child(chartSpaceDom, "chartSpace") ?? chartSpaceDom;
|
|
117
|
+
const chart = child(chartSpace, "chart");
|
|
118
|
+
if (!chart)
|
|
119
|
+
return null;
|
|
120
|
+
const region = child(child(chart, "plotArea"), "plotAreaRegion");
|
|
121
|
+
const series = asArray(child(region, "series"))[0];
|
|
122
|
+
if (!series)
|
|
123
|
+
return null;
|
|
124
|
+
const layoutId = String(get(series, "a$.layoutId") ?? "");
|
|
125
|
+
// Excel nests `<cx:binning>` in `<cx:layoutPr>`; tolerate it directly on the series too.
|
|
126
|
+
const binning = child(child(series, "layoutPr"), "binning") ?? child(series, "binning");
|
|
127
|
+
const chartType = layoutId === "clusteredColumn" && binning
|
|
128
|
+
? "histogram"
|
|
129
|
+
: CHART_TYPE_BY_LAYOUT[layoutId];
|
|
130
|
+
if (!chartType)
|
|
131
|
+
return null;
|
|
132
|
+
const dataId = String(get(child(series, "dataId"), "a$.val") ?? "0");
|
|
133
|
+
const data = asArray(child(child(chartSpace, "chartData"), "data")).find((d) => String(get(d, "a$.id")) === dataId) ?? asArray(child(child(chartSpace, "chartData"), "data"))[0];
|
|
134
|
+
if (!data)
|
|
135
|
+
return null;
|
|
136
|
+
const title = textOf(child(child(child(child(chart, "title"), "tx"), "txData"), "v"));
|
|
137
|
+
const catRef = dimensionRef(data, "strDim", "cat");
|
|
138
|
+
const catSources = sourcesFromRef(catRef, sheetId, resolveSheetId);
|
|
139
|
+
if (chartType === "treemap" || chartType === "sunburst") {
|
|
140
|
+
const sizeRef = dimensionRef(data, "numDim", "size");
|
|
141
|
+
const sizeSources = sourcesFromRef(sizeRef, sheetId, resolveSheetId);
|
|
142
|
+
if (!catSources.length || !sizeSources.length)
|
|
143
|
+
return null;
|
|
144
|
+
const hierarchy = splitHierarchyRange(catSources[0]);
|
|
145
|
+
return {
|
|
146
|
+
chartType,
|
|
147
|
+
...(title ? { title } : {}),
|
|
148
|
+
labels: [chartData([hierarchy.labels])],
|
|
149
|
+
parentLabels: hierarchy.parentLabels.map((range) => chartData([range])),
|
|
150
|
+
sizeData: [chartData(sizeSources)],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const valRef = dimensionRef(data, "numDim", "val");
|
|
154
|
+
const valSources = sourcesFromRef(valRef, sheetId, resolveSheetId);
|
|
155
|
+
if (!valSources.length)
|
|
156
|
+
return null;
|
|
157
|
+
const spec = {
|
|
158
|
+
chartType,
|
|
159
|
+
...(title ? { title } : {}),
|
|
160
|
+
series: [chartData(valSources)],
|
|
161
|
+
...(catSources.length ? { domains: [chartData(catSources)] } : {}),
|
|
162
|
+
};
|
|
163
|
+
if (chartType === "histogram") {
|
|
164
|
+
const binCount = Number(get(child(binning, "binCount"), "a$.val"));
|
|
165
|
+
if (Number.isFinite(binCount) && binCount > 0)
|
|
166
|
+
spec.binCount = binCount;
|
|
167
|
+
}
|
|
168
|
+
return spec;
|
|
169
|
+
}
|
|
170
|
+
/** Resolve + parse one chartex part referenced from a drawing anchor. */
|
|
171
|
+
export async function processChartEx(chartTarget, drawingPath, anchor, sheetId, zipParser, xmlParser, sheetDimensions, resolveSheetId) {
|
|
172
|
+
let chartPath = chartTarget;
|
|
173
|
+
const drawingDir = drawingPath.substring(0, drawingPath.lastIndexOf("/"));
|
|
174
|
+
if (chartPath.startsWith("/")) {
|
|
175
|
+
chartPath = chartPath.slice(1);
|
|
176
|
+
}
|
|
177
|
+
else if (chartPath.startsWith("../")) {
|
|
178
|
+
chartPath = `xl/${chartPath.replace("../", "")}`;
|
|
179
|
+
}
|
|
180
|
+
else if (!chartPath.startsWith("xl/")) {
|
|
181
|
+
chartPath = `${drawingDir}/${chartPath}`;
|
|
182
|
+
}
|
|
183
|
+
let content;
|
|
184
|
+
try {
|
|
185
|
+
content = await zipParser.getText(chartPath);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (!content)
|
|
191
|
+
return null;
|
|
192
|
+
const spec = parseChartExSpec(xmlParser.parse(content), sheetId, resolveSheetId);
|
|
193
|
+
if (!spec)
|
|
194
|
+
return null;
|
|
195
|
+
const position = extractChartPosition(anchor, sheetId, sheetDimensions);
|
|
196
|
+
if (!position)
|
|
197
|
+
return null;
|
|
198
|
+
return { chartId: uuid(), spec, position };
|
|
199
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { EmbeddedObject } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
export declare const CTRL_PROP_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp";
|
|
3
|
+
export declare const VML_DRAWING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
|
|
4
|
+
type CellAnchor = {
|
|
5
|
+
col: number;
|
|
6
|
+
colOff: number;
|
|
7
|
+
row: number;
|
|
8
|
+
rowOff: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* `<controlPr><anchor><from>…</from><to>…</to></anchor>` → the engine's
|
|
12
|
+
* pixel overlay position (1-based anchor cell, EMU offsets → px, size from
|
|
13
|
+
* the cell span at the grid defaults — the same approximation the drawing
|
|
14
|
+
* importer uses for `twoCellAnchor`s).
|
|
15
|
+
*/
|
|
16
|
+
export declare const controlAnchorToOverlayPosition: (from: CellAnchor, to: CellAnchor | undefined) => {
|
|
17
|
+
anchorCell: {
|
|
18
|
+
rowIndex: number;
|
|
19
|
+
columnIndex: number;
|
|
20
|
+
};
|
|
21
|
+
offsetXPixels?: number;
|
|
22
|
+
offsetYPixels?: number;
|
|
23
|
+
widthPixels?: number;
|
|
24
|
+
heightPixels?: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Parse an `xl/ctrlProps/ctrlPropN.xml` part: `objectType`, every other
|
|
28
|
+
* `formControlPr` attribute verbatim, and the literal `itemLst` items.
|
|
29
|
+
*/
|
|
30
|
+
export declare const parseFormControlPr: (ctrlPropDom: any) => {
|
|
31
|
+
objectType: string;
|
|
32
|
+
properties: Record<string, string>;
|
|
33
|
+
listItems?: string[];
|
|
34
|
+
} | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Captions live only in the legacy VML part: `<v:shape id="_x0000_s1025">
|
|
37
|
+
* … <v:textbox><div>Check Box 1</div></v:textbox>`. Returns shapeId → text.
|
|
38
|
+
* VML is scanned textually — Excel's VML is not reliably well-formed XML.
|
|
39
|
+
*/
|
|
40
|
+
export declare const parseVmlControlCaptions: (vml: string | undefined) => Map<number, string>;
|
|
41
|
+
export type RawWorksheetControl = {
|
|
42
|
+
shapeId?: number;
|
|
43
|
+
name?: string;
|
|
44
|
+
relId?: string;
|
|
45
|
+
controlProperties: Record<string, string>;
|
|
46
|
+
moveWithCells?: boolean;
|
|
47
|
+
sizeWithCells?: boolean;
|
|
48
|
+
from?: CellAnchor;
|
|
49
|
+
to?: CellAnchor;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* `<controls>` from a worksheet DOM, whether wrapped in
|
|
53
|
+
* `<mc:AlternateContent><mc:Choice Requires="x14">` (Excel) or bare.
|
|
54
|
+
*/
|
|
55
|
+
export declare const readWorksheetControls: (sheetDom: any) => RawWorksheetControl[];
|
|
56
|
+
/**
|
|
57
|
+
* Assemble the sheet's form controls. `loadCtrlProp` resolves a control's
|
|
58
|
+
* `r:id` to its parsed `ctrlProp` DOM (undefined when the part is missing —
|
|
59
|
+
* the control is then skipped, Excel would repair it the same way).
|
|
60
|
+
*/
|
|
61
|
+
export declare const buildFormControlEmbeds: ({ controls, sheetId, ctrlProps, captions, }: {
|
|
62
|
+
controls: RawWorksheetControl[];
|
|
63
|
+
sheetId: number;
|
|
64
|
+
ctrlProps: Map<string, ReturnType<typeof parseFormControlPr>>;
|
|
65
|
+
captions: Map<number, string>;
|
|
66
|
+
}) => EmbeddedObject[];
|
|
67
|
+
/** The `r:id` of a worksheet's `<legacyDrawing>` (VML part), if any. */
|
|
68
|
+
export declare const readLegacyDrawingRelId: (sheetDom: any) => string | undefined;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=form-controls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-controls.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsx/form-controls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,2BAA2B,CAAC;AAgBnC,eAAO,MAAM,kBAAkB,iFACiD,CAAC;AACjF,eAAO,MAAM,oBAAoB,mFACiD,CAAC;AA6CnF,KAAK,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAe/E;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GACzC,MAAM,UAAU,EAChB,IAAI,UAAU,GAAG,SAAS;gBAKZ;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;oBACrC,MAAM;oBACN,MAAM;kBACR,MAAM;mBACL,MAAM;CAiBxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,aAAa,GAAG,KACf;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,SAkBH,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,KAAK,MAAM,GAAG,SAAS,KACtB,GAAG,CAAC,MAAM,EAAE,MAAM,CAwBpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,EAAE,CAAC,EAAE,UAAU,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAI,UAAU,GAAG,KAAG,mBAAmB,EA+BxE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,6CAKpC;IACD,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;IAC9D,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,KAAG,cAAc,EA+BjB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,GAAI,UAAU,GAAG,KAAG,MAAM,GAAG,SAK/D,CAAC"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel form controls (`<controls>` in the worksheet + `xl/ctrlProps/*` +
|
|
3
|
+
* the legacy VML shape). Parsed into `EmbeddedObject`s with an
|
|
4
|
+
* {@link EmbedFormControlSpec} so the exporter can write the same three
|
|
5
|
+
* parts back (`exporter/xlsx/form-controls-builder.ts`). The grid does not
|
|
6
|
+
* run the control; the spec is a faithful, opaque carrier of what Excel
|
|
7
|
+
* stored.
|
|
8
|
+
*/
|
|
9
|
+
const EMU_PER_PIXEL = 9525;
|
|
10
|
+
/** Grid defaults used to size a control from its cell-span anchor. */
|
|
11
|
+
const DEFAULT_COLUMN_WIDTH_PX = 100;
|
|
12
|
+
const DEFAULT_ROW_HEIGHT_PX = 20;
|
|
13
|
+
export const CTRL_PROP_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp";
|
|
14
|
+
export const VML_DRAWING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
|
|
15
|
+
const asArray = (value) => value == null ? [] : Array.isArray(value) ? value : [value];
|
|
16
|
+
/** Read a child by local name, tolerating any namespace prefix. */
|
|
17
|
+
const childByLocalName = (node, localName) => {
|
|
18
|
+
if (!node || typeof node !== "object")
|
|
19
|
+
return undefined;
|
|
20
|
+
if (node[localName] !== undefined)
|
|
21
|
+
return node[localName];
|
|
22
|
+
for (const key of Object.keys(node)) {
|
|
23
|
+
const colon = key.indexOf(":");
|
|
24
|
+
if (colon >= 0 && key.slice(colon + 1) === localName) {
|
|
25
|
+
return node[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
30
|
+
/** Text of a leaf element (`t$` when it carries attributes, else the value). */
|
|
31
|
+
const textOf = (node) => {
|
|
32
|
+
if (node == null)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (typeof node === "object") {
|
|
35
|
+
const inner = node.t$;
|
|
36
|
+
return inner == null ? undefined : String(inner);
|
|
37
|
+
}
|
|
38
|
+
return String(node);
|
|
39
|
+
};
|
|
40
|
+
const numberOf = (node) => {
|
|
41
|
+
const text = textOf(node);
|
|
42
|
+
const value = text == null ? Number.NaN : Number(text);
|
|
43
|
+
return Number.isFinite(value) ? value : 0;
|
|
44
|
+
};
|
|
45
|
+
const attrs = (node) => {
|
|
46
|
+
const raw = node?.a$;
|
|
47
|
+
const out = {};
|
|
48
|
+
if (!raw || typeof raw !== "object")
|
|
49
|
+
return out;
|
|
50
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
51
|
+
if (value == null)
|
|
52
|
+
continue;
|
|
53
|
+
out[key] = String(value);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
};
|
|
57
|
+
const readCellAnchor = (node) => {
|
|
58
|
+
if (!node)
|
|
59
|
+
return undefined;
|
|
60
|
+
const col = childByLocalName(node, "col");
|
|
61
|
+
const row = childByLocalName(node, "row");
|
|
62
|
+
if (col === undefined || row === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
return {
|
|
65
|
+
col: numberOf(col),
|
|
66
|
+
colOff: numberOf(childByLocalName(node, "colOff")),
|
|
67
|
+
row: numberOf(row),
|
|
68
|
+
rowOff: numberOf(childByLocalName(node, "rowOff")),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* `<controlPr><anchor><from>…</from><to>…</to></anchor>` → the engine's
|
|
73
|
+
* pixel overlay position (1-based anchor cell, EMU offsets → px, size from
|
|
74
|
+
* the cell span at the grid defaults — the same approximation the drawing
|
|
75
|
+
* importer uses for `twoCellAnchor`s).
|
|
76
|
+
*/
|
|
77
|
+
export const controlAnchorToOverlayPosition = (from, to) => {
|
|
78
|
+
const offsetXPixels = Math.round(from.colOff / EMU_PER_PIXEL);
|
|
79
|
+
const offsetYPixels = Math.round(from.rowOff / EMU_PER_PIXEL);
|
|
80
|
+
const overlay = {
|
|
81
|
+
anchorCell: { rowIndex: from.row + 1, columnIndex: from.col + 1 },
|
|
82
|
+
};
|
|
83
|
+
if (offsetXPixels)
|
|
84
|
+
overlay.offsetXPixels = offsetXPixels;
|
|
85
|
+
if (offsetYPixels)
|
|
86
|
+
overlay.offsetYPixels = offsetYPixels;
|
|
87
|
+
if (to) {
|
|
88
|
+
const width = (to.col - from.col) * DEFAULT_COLUMN_WIDTH_PX +
|
|
89
|
+
(to.colOff - from.colOff) / EMU_PER_PIXEL;
|
|
90
|
+
const height = (to.row - from.row) * DEFAULT_ROW_HEIGHT_PX +
|
|
91
|
+
(to.rowOff - from.rowOff) / EMU_PER_PIXEL;
|
|
92
|
+
if (width > 0)
|
|
93
|
+
overlay.widthPixels = Math.round(width);
|
|
94
|
+
if (height > 0)
|
|
95
|
+
overlay.heightPixels = Math.round(height);
|
|
96
|
+
}
|
|
97
|
+
return overlay;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Parse an `xl/ctrlProps/ctrlPropN.xml` part: `objectType`, every other
|
|
101
|
+
* `formControlPr` attribute verbatim, and the literal `itemLst` items.
|
|
102
|
+
*/
|
|
103
|
+
export const parseFormControlPr = (ctrlPropDom) => {
|
|
104
|
+
const root = childByLocalName(ctrlPropDom, "formControlPr");
|
|
105
|
+
if (!root)
|
|
106
|
+
return undefined;
|
|
107
|
+
const { objectType, ...properties } = attrs(root);
|
|
108
|
+
if (!objectType)
|
|
109
|
+
return undefined;
|
|
110
|
+
// Namespace declarations ride along as attributes in fast-xml-parser.
|
|
111
|
+
for (const key of Object.keys(properties)) {
|
|
112
|
+
if (key === "xmlns" || key.startsWith("xmlns:"))
|
|
113
|
+
delete properties[key];
|
|
114
|
+
}
|
|
115
|
+
const itemLst = childByLocalName(root, "itemLst");
|
|
116
|
+
const listItems = asArray(childByLocalName(itemLst, "item"))
|
|
117
|
+
.map((item) => attrs(item).val)
|
|
118
|
+
.filter((val) => typeof val === "string");
|
|
119
|
+
return {
|
|
120
|
+
objectType,
|
|
121
|
+
properties,
|
|
122
|
+
...(listItems.length ? { listItems } : {}),
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Captions live only in the legacy VML part: `<v:shape id="_x0000_s1025">
|
|
127
|
+
* … <v:textbox><div>Check Box 1</div></v:textbox>`. Returns shapeId → text.
|
|
128
|
+
* VML is scanned textually — Excel's VML is not reliably well-formed XML.
|
|
129
|
+
*/
|
|
130
|
+
export const parseVmlControlCaptions = (vml) => {
|
|
131
|
+
const captions = new Map();
|
|
132
|
+
if (!vml)
|
|
133
|
+
return captions;
|
|
134
|
+
const shapeRe = /<v:shape\b([^>]*)>([\s\S]*?)<\/v:shape>/g;
|
|
135
|
+
let match;
|
|
136
|
+
while ((match = shapeRe.exec(vml))) {
|
|
137
|
+
const idMatch = /\bid="_x0000_s(\d+)"/.exec(match[1]);
|
|
138
|
+
if (!idMatch)
|
|
139
|
+
continue;
|
|
140
|
+
const body = match[2];
|
|
141
|
+
if (!/<x:ClientData\b/.test(body))
|
|
142
|
+
continue;
|
|
143
|
+
const textbox = /<v:textbox\b[^>]*>([\s\S]*?)<\/v:textbox>/.exec(body);
|
|
144
|
+
if (!textbox)
|
|
145
|
+
continue;
|
|
146
|
+
const text = textbox[1]
|
|
147
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
148
|
+
.replace(/<[^>]+>/g, "")
|
|
149
|
+
.replace(/&/g, "&")
|
|
150
|
+
.replace(/</g, "<")
|
|
151
|
+
.replace(/>/g, ">")
|
|
152
|
+
.replace(/"/g, '"')
|
|
153
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)))
|
|
154
|
+
.trim();
|
|
155
|
+
if (text)
|
|
156
|
+
captions.set(Number(idMatch[1]), text);
|
|
157
|
+
}
|
|
158
|
+
return captions;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* `<controls>` from a worksheet DOM, whether wrapped in
|
|
162
|
+
* `<mc:AlternateContent><mc:Choice Requires="x14">` (Excel) or bare.
|
|
163
|
+
*/
|
|
164
|
+
export const readWorksheetControls = (sheetDom) => {
|
|
165
|
+
const worksheet = childByLocalName(sheetDom, "worksheet") ?? sheetDom;
|
|
166
|
+
const controls = childByLocalName(worksheet, "controls");
|
|
167
|
+
if (!controls)
|
|
168
|
+
return [];
|
|
169
|
+
const controlNodes = [...asArray(childByLocalName(controls, "control"))];
|
|
170
|
+
for (const alternate of asArray(childByLocalName(controls, "AlternateContent"))) {
|
|
171
|
+
for (const choice of asArray(childByLocalName(alternate, "Choice"))) {
|
|
172
|
+
controlNodes.push(...asArray(childByLocalName(choice, "control")));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return controlNodes.map((node) => {
|
|
176
|
+
const a = attrs(node);
|
|
177
|
+
const controlPr = childByLocalName(node, "controlPr");
|
|
178
|
+
const anchor = childByLocalName(controlPr, "anchor");
|
|
179
|
+
const anchorAttrs = attrs(anchor);
|
|
180
|
+
const shapeId = Number(a.shapeId);
|
|
181
|
+
return {
|
|
182
|
+
...(Number.isFinite(shapeId) && a.shapeId ? { shapeId } : {}),
|
|
183
|
+
...(a.name ? { name: a.name } : {}),
|
|
184
|
+
...(a["r:id"] ?? a.id ? { relId: a["r:id"] ?? a.id } : {}),
|
|
185
|
+
controlProperties: attrs(controlPr),
|
|
186
|
+
...(anchorAttrs.moveWithCells !== undefined
|
|
187
|
+
? { moveWithCells: anchorAttrs.moveWithCells === "1" || anchorAttrs.moveWithCells === "true" }
|
|
188
|
+
: {}),
|
|
189
|
+
...(anchorAttrs.sizeWithCells !== undefined
|
|
190
|
+
? { sizeWithCells: anchorAttrs.sizeWithCells === "1" || anchorAttrs.sizeWithCells === "true" }
|
|
191
|
+
: {}),
|
|
192
|
+
from: readCellAnchor(childByLocalName(anchor, "from")),
|
|
193
|
+
to: readCellAnchor(childByLocalName(anchor, "to")),
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Assemble the sheet's form controls. `loadCtrlProp` resolves a control's
|
|
199
|
+
* `r:id` to its parsed `ctrlProp` DOM (undefined when the part is missing —
|
|
200
|
+
* the control is then skipped, Excel would repair it the same way).
|
|
201
|
+
*/
|
|
202
|
+
export const buildFormControlEmbeds = ({ controls, sheetId, ctrlProps, captions, }) => {
|
|
203
|
+
const embeds = [];
|
|
204
|
+
controls.forEach((control, index) => {
|
|
205
|
+
const parsed = control.relId ? ctrlProps.get(control.relId) : undefined;
|
|
206
|
+
if (!parsed || !control.from)
|
|
207
|
+
return;
|
|
208
|
+
const spec = {
|
|
209
|
+
type: "formControl",
|
|
210
|
+
objectType: parsed.objectType,
|
|
211
|
+
properties: parsed.properties,
|
|
212
|
+
};
|
|
213
|
+
if (control.name)
|
|
214
|
+
spec.name = control.name;
|
|
215
|
+
if (control.shapeId !== undefined)
|
|
216
|
+
spec.shapeId = control.shapeId;
|
|
217
|
+
const caption = control.shapeId !== undefined ? captions.get(control.shapeId) : undefined;
|
|
218
|
+
if (caption)
|
|
219
|
+
spec.text = caption;
|
|
220
|
+
if (parsed.listItems)
|
|
221
|
+
spec.listItems = parsed.listItems;
|
|
222
|
+
if (Object.keys(control.controlProperties).length) {
|
|
223
|
+
spec.controlProperties = control.controlProperties;
|
|
224
|
+
}
|
|
225
|
+
if (control.moveWithCells !== undefined)
|
|
226
|
+
spec.moveWithCells = control.moveWithCells;
|
|
227
|
+
if (control.sizeWithCells !== undefined)
|
|
228
|
+
spec.sizeWithCells = control.sizeWithCells;
|
|
229
|
+
embeds.push({
|
|
230
|
+
embedId: `control-${sheetId}-${control.shapeId ?? index + 1}`,
|
|
231
|
+
spec,
|
|
232
|
+
position: {
|
|
233
|
+
sheetId,
|
|
234
|
+
overlayPosition: controlAnchorToOverlayPosition(control.from, control.to),
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
return embeds;
|
|
239
|
+
};
|
|
240
|
+
/** The `r:id` of a worksheet's `<legacyDrawing>` (VML part), if any. */
|
|
241
|
+
export const readLegacyDrawingRelId = (sheetDom) => {
|
|
242
|
+
const worksheet = childByLocalName(sheetDom, "worksheet") ?? sheetDom;
|
|
243
|
+
const legacy = childByLocalName(worksheet, "legacyDrawing");
|
|
244
|
+
const a = attrs(legacy);
|
|
245
|
+
return a["r:id"] ?? a.id;
|
|
246
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CellFormat, ConditionalFormatRule, DataValidationRuleRecord, EmbeddedChart, EmbeddedObject, NamedRange, ProtectedRange, Sheet, Slicer, SpreadsheetTheme, TableView } from "@rowsncolumns/spreadsheet";
|
|
2
|
-
import type { CommentThread, TextFormat, TextFormatRun } from "@rowsncolumns/common-types";
|
|
2
|
+
import type { NamedCellStyle, CommentThread, TextFormat, TextFormatRun } from "@rowsncolumns/common-types";
|
|
3
3
|
import { XMLParser } from "fast-xml-parser";
|
|
4
4
|
import type { IWorkbookPlugin } from "../../interface";
|
|
5
5
|
import type { ExternalLink, ProcessSheetDataForSheetOptions, ProcessSheetDataResult, SheetCatalogEntry, WorkbookProperties } from "../../types";
|
|
@@ -252,6 +252,8 @@ export declare class XlsxPlugin extends BaseWorkbookPlugin implements IWorkbookP
|
|
|
252
252
|
getSheetData(rid: string): Promise<any>;
|
|
253
253
|
getStyles(): any;
|
|
254
254
|
getTheme(): SpreadsheetTheme;
|
|
255
|
+
/** `<cellStyles>` resolved to cell formats (toolbar "Workbook styles" gallery). */
|
|
256
|
+
getNamedCellStyles(): NamedCellStyle[];
|
|
255
257
|
/**
|
|
256
258
|
* The workbook default font — styles.xml fonts[0], the font every cell
|
|
257
259
|
* without an explicit font inherits. Exporters pass this through so the
|
|
@@ -294,6 +296,12 @@ export declare class XlsxPlugin extends BaseWorkbookPlugin implements IWorkbookP
|
|
|
294
296
|
releaseSheetCaches(sheetId: number): void;
|
|
295
297
|
private processOneSheet;
|
|
296
298
|
getDrawings(): Promise<EmbeddedObjectWithPosition[]>;
|
|
299
|
+
/**
|
|
300
|
+
* Form controls: `<controls><control r:id>` → `xl/ctrlProps/ctrlPropN.xml`
|
|
301
|
+
* (+ the caption from the sheet's legacy VML part), as `formControl`
|
|
302
|
+
* embeds. Nothing is rendered interactively — they round-trip.
|
|
303
|
+
*/
|
|
304
|
+
_getFormControls(rid: string, sheetId: number, sheetData: any): Promise<EmbeddedObject[]>;
|
|
297
305
|
getCharts(): Promise<EmbeddedChart[]>;
|
|
298
306
|
/**
|
|
299
307
|
* Phase D1 — extract every pivot table across every worksheet.
|