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.
@@ -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>`) plus the visible state persisted
84
- * in the property bag — `caption`, `value` (checked/text/number, as a string)
85
- * and OptionButton `groupName`. Render-only.
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 = 1024;
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
- let activeXControls;
238
- if (worksheet.oleObjects && worksheet.oleObjects.length > 0) {
239
- const wsRels = pkg.getPartRelationships(resolved.path);
240
- const resolvedAx = [];
241
- for (const ole of worksheet.oleObjects) {
242
- const rel = wsRels.find((r) => r.id === ole.relId);
243
- const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
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",
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>",