reamkit 1.15.3 → 1.15.4
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/esm/core/ir/sheet.d.ts +6 -3
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +15 -1
- package/dist/esm/excel/print-model.js +4 -1
- package/dist/esm/excel/worksheet-parser.js +33 -1
- package/dist/esm/excel/xlsx-reader.js +31 -24
- package/package.json +1 -1
|
@@ -80,12 +80,15 @@ export interface SheetFormControl {
|
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
82
|
* An ActiveX control resolved against its activeX part (E-SHEET W10): the control
|
|
83
|
-
* class (`type`, from the `<oleObject progId>`
|
|
84
|
-
*
|
|
85
|
-
* and OptionButton
|
|
83
|
+
* class (`type`, from the `<oleObject progId>` or, for a §18.3.1.19 `<control>`,
|
|
84
|
+
* its class id) plus the visible state persisted in the property bag —
|
|
85
|
+
* `caption`, `value` (checked/text/number, as a string) and OptionButton
|
|
86
|
+
* `groupName`. Render-only.
|
|
86
87
|
*/
|
|
87
88
|
export interface SheetActiveXControl {
|
|
88
89
|
readonly type: string;
|
|
90
|
+
/** §18.3.1.19 `<control name>` — the control's identifier, when it came that way. */
|
|
91
|
+
readonly name?: string;
|
|
89
92
|
readonly caption?: string;
|
|
90
93
|
readonly value?: string;
|
|
91
94
|
readonly groupName?: string;
|
|
@@ -22,6 +22,15 @@ export declare function parseActiveX(data: Uint8Array): ActiveXProps;
|
|
|
22
22
|
* unknown progId falls back to a generic `'control'`.
|
|
23
23
|
*/
|
|
24
24
|
export declare function activeXType(progId: string | undefined): string;
|
|
25
|
+
/**
|
|
26
|
+
* The affordance key for an `<ax:ocx ax:classid>`, for controls reached through
|
|
27
|
+
* §18.3.1.19 `<control>` rather than `<oleObject progId>` — the `<control>`
|
|
28
|
+
* element carries no progId, so the class id is all there is to type it by.
|
|
29
|
+
*
|
|
30
|
+
* @param xmlData The `activeX#.xml` part bytes.
|
|
31
|
+
* @returns The affordance key, or `'control'` when the class id is unknown.
|
|
32
|
+
*/
|
|
33
|
+
export declare function activeXTypeFromPart(xmlData: Uint8Array): string;
|
|
25
34
|
/**
|
|
26
35
|
* The `<ax:ocx r:id>` of a control whose state is persisted to a binary stream
|
|
27
36
|
* (`persistStreamInit` / `persistStream` / `persistStorage`) rather than to
|
|
@@ -62,6 +62,20 @@ function strAttr(obj, key) {
|
|
|
62
62
|
const v = obj[`@_${key}`];
|
|
63
63
|
return typeof v === "string" ? v : void 0;
|
|
64
64
|
}
|
|
65
|
+
var ACTIVEX_CLASS_TYPES = new Map([["8BD21D40-EC42-11CE-9E0D-00AA006002F3", "checkbox"], ["8BD21D50-EC42-11CE-9E0D-00AA006002F3", "option"]]);
|
|
66
|
+
/**
|
|
67
|
+
* The affordance key for an `<ax:ocx ax:classid>`, for controls reached through
|
|
68
|
+
* §18.3.1.19 `<control>` rather than `<oleObject progId>` — the `<control>`
|
|
69
|
+
* element carries no progId, so the class id is all there is to type it by.
|
|
70
|
+
*
|
|
71
|
+
* @param xmlData The `activeX#.xml` part bytes.
|
|
72
|
+
* @returns The affordance key, or `'control'` when the class id is unknown.
|
|
73
|
+
*/
|
|
74
|
+
function activeXTypeFromPart(xmlData) {
|
|
75
|
+
const ocx = asObject(parser.parse(decoder.decode(xmlData))["ocx"]);
|
|
76
|
+
const key = (ocx ? strAttr(ocx, "classid") : void 0)?.replace(/[{}]/g, "").toUpperCase() ?? "";
|
|
77
|
+
return ACTIVEX_CLASS_TYPES.get(key) ?? "control";
|
|
78
|
+
}
|
|
65
79
|
/**
|
|
66
80
|
* The `<ax:ocx r:id>` of a control whose state is persisted to a binary stream
|
|
67
81
|
* (`persistStreamInit` / `persistStream` / `persistStorage`) rather than to
|
|
@@ -209,4 +223,4 @@ function parseActiveXBin(data) {
|
|
|
209
223
|
};
|
|
210
224
|
}
|
|
211
225
|
//#endregion
|
|
212
|
-
export { activeXBinRelId, activeXType, parseActiveX, parseActiveXBin };
|
|
226
|
+
export { activeXBinRelId, activeXType, activeXTypeFromPart, parseActiveX, parseActiveXBin };
|
|
@@ -160,11 +160,14 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
160
160
|
if (worksheet.maxRow < 0 || worksheet.maxColumn < 0) return [];
|
|
161
161
|
let usedRow = -1;
|
|
162
162
|
let usedCol = -1;
|
|
163
|
+
const contentAt = /* @__PURE__ */ new Set();
|
|
163
164
|
for (const c of worksheet.cells) if (c.rawValue !== "" || c.inlineText !== void 0) {
|
|
165
|
+
contentAt.add(key(c.row, c.column));
|
|
164
166
|
if (c.row > usedRow) usedRow = c.row;
|
|
165
167
|
if (c.column > usedCol) usedCol = c.column;
|
|
166
168
|
}
|
|
167
169
|
for (const m of worksheet.merges) {
|
|
170
|
+
if (!contentAt.has(key(m.startRow, m.startColumn))) continue;
|
|
168
171
|
if (m.endRow > usedRow) usedRow = m.endRow;
|
|
169
172
|
if (m.endColumn > usedCol) usedCol = m.endColumn;
|
|
170
173
|
}
|
|
@@ -186,7 +189,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
186
189
|
colEnd = Math.min(usedCol, print.printArea.endColumn);
|
|
187
190
|
}
|
|
188
191
|
if (rowEnd < rowStart || colEnd < colStart) return [];
|
|
189
|
-
const MAX_GRID_COLS =
|
|
192
|
+
const MAX_GRID_COLS = 16384;
|
|
190
193
|
const MAX_GRID_ROWS = 5e4;
|
|
191
194
|
const MAX_GRID_CELLS = 1e6;
|
|
192
195
|
const wantRows = rowEnd - rowStart + 1;
|
|
@@ -792,7 +792,7 @@ function parseCell(c, fallbackRow, fallbackCol) {
|
|
|
792
792
|
if (ref) try {
|
|
793
793
|
address = parseCellRef(ref);
|
|
794
794
|
} catch {
|
|
795
|
-
address = implied;
|
|
795
|
+
address = numericRef(ref, fallbackRow) ?? implied;
|
|
796
796
|
}
|
|
797
797
|
else address = implied;
|
|
798
798
|
const type = validateCellType(strAttr(obj, "t") ?? "n");
|
|
@@ -822,6 +822,38 @@ function parseCell(c, fallbackRow, fallbackCol) {
|
|
|
822
822
|
...Number.isFinite(styleIndex) ? { styleIndex } : {}
|
|
823
823
|
};
|
|
824
824
|
}
|
|
825
|
+
var NUMERIC_REF = /^(\d+)_(\d+)$/;
|
|
826
|
+
/**
|
|
827
|
+
* Decode a numeric `"4_2"`-style reference, but only when the file corroborates
|
|
828
|
+
* the reading.
|
|
829
|
+
*
|
|
830
|
+
* This is not a spec spelling and there is no obligation to understand it. What
|
|
831
|
+
* makes it safe to act on is that the file states its own convention and can be
|
|
832
|
+
* checked against itself: every ref inside `<row r="2">` ends in `_2`. So the
|
|
833
|
+
* row half must agree with the row the cell actually sits in — and when it does
|
|
834
|
+
* not, the reference has told us nothing and document order (§18.3.1.4) remains
|
|
835
|
+
* the answer. A guess that cannot be checked is worse than the fallback; this
|
|
836
|
+
* one can be.
|
|
837
|
+
*
|
|
838
|
+
* The alternative is not harmless: these cells are sparse (columns 1, 4, 5, 7,
|
|
839
|
+
* 11, 13, 22 …), so packing them consecutively files every value under the
|
|
840
|
+
* wrong heading.
|
|
841
|
+
*
|
|
842
|
+
* @param ref The unparseable `r` attribute.
|
|
843
|
+
* @param rowIndex The 0-based row the cell sits in.
|
|
844
|
+
* @returns The address, or undefined when the reading is not corroborated.
|
|
845
|
+
*/
|
|
846
|
+
function numericRef(ref, rowIndex) {
|
|
847
|
+
const m = NUMERIC_REF.exec(ref);
|
|
848
|
+
if (!m) return void 0;
|
|
849
|
+
const column = Number(m[1]);
|
|
850
|
+
const row = Number(m[2]);
|
|
851
|
+
if (column < 1 || row !== rowIndex + 1) return void 0;
|
|
852
|
+
return {
|
|
853
|
+
column: column - 1,
|
|
854
|
+
row: rowIndex
|
|
855
|
+
};
|
|
856
|
+
}
|
|
825
857
|
function validateCellType(t) {
|
|
826
858
|
if (t === "n" || t === "s" || t === "str" || t === "b" || t === "d" || t === "e" || t === "inlineStr") return t;
|
|
827
859
|
return "n";
|
|
@@ -21,7 +21,7 @@ import { parsePivotTablePart } from "./pivot-table-parser.js";
|
|
|
21
21
|
import { parseSlicerCachePart, parseSlicerPart } from "./slicer-parser.js";
|
|
22
22
|
import { parseLegacyComments, parsePersons, parseThreadedComments } from "./comments-parser.js";
|
|
23
23
|
import { parseFormControlProps } from "./form-control-parser.js";
|
|
24
|
-
import { activeXBinRelId, activeXType, parseActiveX, parseActiveXBin } from "./activex-parser.js";
|
|
24
|
+
import { activeXBinRelId, activeXType, activeXTypeFromPart, parseActiveX, parseActiveXBin } from "./activex-parser.js";
|
|
25
25
|
import { parseSheetShapes } from "./sheet-shape-parser.js";
|
|
26
26
|
import { projectSheetDoc } from "./sheet-to-flow.js";
|
|
27
27
|
//#region src/excel/xlsx-reader.ts
|
|
@@ -34,6 +34,7 @@ var SLICER_CACHE_REL_TAIL = "/slicerCache";
|
|
|
34
34
|
var THREADED_COMMENTS_REL_TAIL = "/threadedComment";
|
|
35
35
|
var PERSON_REL_TAIL = "/person";
|
|
36
36
|
var MAX_SLICER_ITEMS = 256;
|
|
37
|
+
var ACTIVEX_PART = /^xl\/activeX\//i;
|
|
37
38
|
/**
|
|
38
39
|
* Read a `.xlsx` and project it to a {@link FlowDoc} in one step:
|
|
39
40
|
* {@link readXlsxToSheetDoc} then {@link projectSheetDoc}.
|
|
@@ -219,13 +220,33 @@ function readXlsxToSheetDoc(xlsx) {
|
|
|
219
220
|
}
|
|
220
221
|
if (resolvedComments.length > 0) comments = resolvedComments;
|
|
221
222
|
}
|
|
223
|
+
const wsRels = pkg.getPartRelationships(resolved.path);
|
|
224
|
+
const resolvedAx = [];
|
|
225
|
+
const activeXState = (part) => {
|
|
226
|
+
const props = parseActiveX(part.data);
|
|
227
|
+
const binRelId = activeXBinRelId(part.data);
|
|
228
|
+
const binRel = binRelId ? pkg.getPartRelationships(part.path).find((r) => r.id === binRelId) : void 0;
|
|
229
|
+
const binPart = binRel ? pkg.resolveRelatedPart(part.path, binRel) : void 0;
|
|
230
|
+
return {
|
|
231
|
+
type: "control",
|
|
232
|
+
...binPart ? parseActiveXBin(binPart.data) : {},
|
|
233
|
+
...props
|
|
234
|
+
};
|
|
235
|
+
};
|
|
222
236
|
let formControls;
|
|
223
237
|
if (worksheet.formControls && worksheet.formControls.length > 0) {
|
|
224
|
-
const wsRels = pkg.getPartRelationships(resolved.path);
|
|
225
238
|
const resolvedControls = [];
|
|
226
239
|
for (const fc of worksheet.formControls) {
|
|
227
240
|
const rel = wsRels.find((r) => r.id === fc.relId);
|
|
228
241
|
const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
|
|
242
|
+
if (part && ACTIVEX_PART.test(part.path)) {
|
|
243
|
+
resolvedAx.push({
|
|
244
|
+
...activeXState(part),
|
|
245
|
+
type: activeXTypeFromPart(part.data),
|
|
246
|
+
...fc.name ? { name: fc.name } : {}
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
229
250
|
const props = part ? parseFormControlProps(part.data) : {};
|
|
230
251
|
resolvedControls.push({
|
|
231
252
|
...fc.name ? { name: fc.name } : {},
|
|
@@ -234,29 +255,15 @@ function readXlsxToSheetDoc(xlsx) {
|
|
|
234
255
|
}
|
|
235
256
|
if (resolvedControls.length > 0) formControls = resolvedControls;
|
|
236
257
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const props = part ? parseActiveX(part.data) : {};
|
|
245
|
-
let binProps = {};
|
|
246
|
-
if (part) {
|
|
247
|
-
const binRelId = activeXBinRelId(part.data);
|
|
248
|
-
const binRel = binRelId ? pkg.getPartRelationships(part.path).find((r) => r.id === binRelId) : void 0;
|
|
249
|
-
const binPart = binRel ? pkg.resolveRelatedPart(part.path, binRel) : void 0;
|
|
250
|
-
if (binPart) binProps = parseActiveXBin(binPart.data);
|
|
251
|
-
}
|
|
252
|
-
resolvedAx.push({
|
|
253
|
-
type: activeXType(ole.progId),
|
|
254
|
-
...binProps,
|
|
255
|
-
...props
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
if (resolvedAx.length > 0) activeXControls = resolvedAx;
|
|
258
|
+
if (worksheet.oleObjects && worksheet.oleObjects.length > 0) for (const ole of worksheet.oleObjects) {
|
|
259
|
+
const rel = wsRels.find((r) => r.id === ole.relId);
|
|
260
|
+
const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
|
|
261
|
+
resolvedAx.push({
|
|
262
|
+
...part ? activeXState(part) : {},
|
|
263
|
+
type: activeXType(ole.progId)
|
|
264
|
+
});
|
|
259
265
|
}
|
|
266
|
+
const activeXControls = resolvedAx.length > 0 ? resolvedAx : void 0;
|
|
260
267
|
const grid = tables || pivotTables ? {
|
|
261
268
|
...worksheet,
|
|
262
269
|
...tables ? { tables } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reamkit",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.4",
|
|
4
4
|
"description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alex Krassavin <info@reamkit.dev>",
|