reamkit 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/esm/core/document-model/types.d.ts +3 -0
  2. package/dist/esm/core/ir/sheet.d.ts +49 -3
  3. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  4. package/dist/esm/core/spreadsheet-model/types.d.ts +80 -1
  5. package/dist/esm/excel/comments-parser.d.ts +4 -0
  6. package/dist/esm/excel/comments-parser.js +104 -0
  7. package/dist/esm/excel/conditional-format.d.ts +2 -2
  8. package/dist/esm/excel/conditional-format.js +113 -11
  9. package/dist/esm/excel/form-control-parser.d.ts +6 -0
  10. package/dist/esm/excel/form-control-parser.js +29 -0
  11. package/dist/esm/excel/header-footer.d.ts +2 -0
  12. package/dist/esm/excel/header-footer.js +127 -0
  13. package/dist/esm/excel/index.d.ts +1 -1
  14. package/dist/esm/excel/print-model.d.ts +7 -2
  15. package/dist/esm/excel/print-model.js +218 -19
  16. package/dist/esm/excel/shared-strings-parser.d.ts +6 -1
  17. package/dist/esm/excel/shared-strings-parser.js +91 -12
  18. package/dist/esm/excel/sheet-drawing.d.ts +13 -1
  19. package/dist/esm/excel/sheet-drawing.js +47 -14
  20. package/dist/esm/excel/sheet-shape-parser.d.ts +4 -0
  21. package/dist/esm/excel/sheet-shape-parser.js +96 -0
  22. package/dist/esm/excel/sheet-to-flow.js +130 -3
  23. package/dist/esm/excel/slicer-parser.d.ts +15 -0
  24. package/dist/esm/excel/slicer-parser.js +76 -0
  25. package/dist/esm/excel/styles-parser.js +37 -0
  26. package/dist/esm/excel/table-parser.d.ts +10 -0
  27. package/dist/esm/excel/table-parser.js +36 -4
  28. package/dist/esm/excel/worksheet-parser.js +181 -1
  29. package/dist/esm/excel/xlsx-reader.js +203 -17
  30. package/dist/esm/excel/xlsx-writer.js +19 -3
  31. package/dist/esm/html/html-writer.js +4 -0
  32. package/dist/esm/layout/styled-layout.js +72 -1
  33. package/dist/esm/pptx/slide-parser.d.ts +4 -2
  34. package/dist/esm/pptx/slide-parser.js +1 -1
  35. package/package.json +1 -1
@@ -1,15 +1,19 @@
1
1
  import { pt } from "../core/ir/units.js";
2
2
  import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
3
3
  import "../core/style-cascade/index.js";
4
- import { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, worksheetToBody } from "./print-model.js";
4
+ import { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody } from "./print-model.js";
5
+ import { buildHeaderFooterContent } from "./header-footer.js";
5
6
  //#region src/excel/sheet-to-flow.ts
7
+ var HEADER_REL = "_xlsxHeaderDefault";
8
+ var FOOTER_REL = "_xlsxFooterDefault";
6
9
  function projectSheetDoc(sheet) {
7
10
  const body = [];
8
11
  let firstSheetSection;
12
+ const headersFooters = /* @__PURE__ */ new Map();
9
13
  const sheetGrids = new Map(sheet.sheets.map((s) => [s.name, s.grid]));
10
14
  for (let sheetIdx = 0; sheetIdx < sheet.sheets.length; sheetIdx++) {
11
15
  const ws = sheet.sheets[sheetIdx];
12
- if (sheetIdx === 0) firstSheetSection = sectionFromWorksheet(ws.grid);
16
+ if (sheetIdx === 0) firstSheetSection = withHeaderFooter(sectionFromWorksheet(ws.grid), ws, headersFooters);
13
17
  if (sheetIdx > 0) body.push({
14
18
  kind: "paragraph",
15
19
  paragraph: {
@@ -24,7 +28,9 @@ function projectSheetDoc(sheet) {
24
28
  ...printArea ? { printArea } : {},
25
29
  ...titleRows ? { titleRows } : {},
26
30
  gridLines,
27
- sheetGrids
31
+ sheetGrids,
32
+ ...ws.hyperlinks ? { hyperlinks: ws.hyperlinks } : {},
33
+ ...sheet.sharedStringRuns ? { sharedStringRuns: sheet.sharedStringRuns } : {}
28
34
  }));
29
35
  for (const ref of ws.charts ?? []) body.push({
30
36
  kind: "chart",
@@ -35,6 +41,25 @@ function projectSheetDoc(sheet) {
35
41
  paragraphProperties: {}
36
42
  }
37
43
  });
44
+ for (const img of ws.images ?? []) body.push({
45
+ kind: "image",
46
+ image: {
47
+ resource: img.resourceId,
48
+ width: pt(img.widthPt),
49
+ height: pt(img.heightPt),
50
+ paragraphProperties: {}
51
+ }
52
+ });
53
+ for (const shape of ws.shapes ?? []) body.push({
54
+ kind: "shape",
55
+ shape
56
+ });
57
+ for (const slicer of ws.slicers ?? []) body.push({
58
+ kind: "table",
59
+ table: slicerTable(slicer)
60
+ });
61
+ if (ws.comments && ws.comments.length > 0) body.push(...commentBlocks(ws.comments));
62
+ if (ws.formControls && ws.formControls.length > 0) body.push(...formControlBlocks(ws.formControls));
38
63
  }
39
64
  return {
40
65
  kind: "flow",
@@ -44,8 +69,110 @@ function projectSheetDoc(sheet) {
44
69
  styles: EMPTY_STYLE_SHEET,
45
70
  resources: sheet.resources,
46
71
  ...sheet.chartData && sheet.chartData.size > 0 ? { charts: sheet.chartData } : {},
72
+ ...headersFooters.size > 0 ? { headersFooters } : {},
47
73
  ...sheet.info ? { info: sheet.info } : {}
48
74
  };
49
75
  }
76
+ function commentBlocks(comments) {
77
+ const out = [{
78
+ kind: "paragraph",
79
+ paragraph: {
80
+ properties: {},
81
+ runs: [{
82
+ text: "Comments",
83
+ properties: { bold: true }
84
+ }]
85
+ }
86
+ }];
87
+ for (const c of comments) {
88
+ const body = c.text.replace(/\s+/g, " ").trim();
89
+ const label = c.author ? `${c.ref} — ${c.author}: ` : `${c.ref}: `;
90
+ out.push({
91
+ kind: "paragraph",
92
+ paragraph: {
93
+ properties: {},
94
+ runs: [{
95
+ text: label,
96
+ properties: { bold: true }
97
+ }, ...body.length > 0 ? [{
98
+ text: body,
99
+ properties: {}
100
+ }] : []]
101
+ }
102
+ });
103
+ }
104
+ return out;
105
+ }
106
+ function formControlBlocks(controls) {
107
+ const out = [{
108
+ kind: "paragraph",
109
+ paragraph: {
110
+ properties: {},
111
+ runs: [{
112
+ text: "Form controls",
113
+ properties: { bold: true }
114
+ }]
115
+ }
116
+ }];
117
+ for (const c of controls) out.push({
118
+ kind: "paragraph",
119
+ paragraph: {
120
+ properties: {},
121
+ runs: [{
122
+ text: formControlLabel(c),
123
+ properties: {}
124
+ }]
125
+ }
126
+ });
127
+ return out;
128
+ }
129
+ function formControlLabel(c) {
130
+ const name = c.name ?? c.objectType ?? "Control";
131
+ switch ((c.objectType ?? "").toLowerCase()) {
132
+ case "checkbox": return `${c.checked ? "[x]" : "[ ]"} ${name}`;
133
+ case "radio": return `${c.checked ? "(o)" : "( )"} ${name}`;
134
+ case "buttons": return `[ ${name} ]`;
135
+ case "spin":
136
+ case "scroll": return c.value !== void 0 ? `${name} (value ${c.value})` : name;
137
+ case "drop":
138
+ case "list": return `${name} (list)`;
139
+ default: return c.objectType ? `${name} (${c.objectType})` : name;
140
+ }
141
+ }
142
+ function withHeaderFooter(section, ws, headersFooters) {
143
+ const hf = ws.grid.headerFooter;
144
+ if (!hf || !hf.oddHeader && !hf.oddFooter) return section;
145
+ const headers = [];
146
+ const footers = [];
147
+ if (hf.oddHeader) {
148
+ const content = buildHeaderFooterContent(hf.oddHeader, ws.name);
149
+ if (content.length > 0) {
150
+ headersFooters.set(HEADER_REL, resolveBodyStyles(content, EMPTY_STYLE_SHEET));
151
+ headers.push({
152
+ type: "default",
153
+ relationshipId: HEADER_REL
154
+ });
155
+ }
156
+ }
157
+ if (hf.oddFooter) {
158
+ const content = buildHeaderFooterContent(hf.oddFooter, ws.name);
159
+ if (content.length > 0) {
160
+ headersFooters.set(FOOTER_REL, resolveBodyStyles(content, EMPTY_STYLE_SHEET));
161
+ footers.push({
162
+ type: "default",
163
+ relationshipId: FOOTER_REL
164
+ });
165
+ }
166
+ }
167
+ if (headers.length === 0 && footers.length === 0) return section;
168
+ return {
169
+ ...section ?? {
170
+ headers: [],
171
+ footers: []
172
+ },
173
+ headers,
174
+ footers
175
+ };
176
+ }
50
177
  //#endregion
51
178
  export { projectSheetDoc };
@@ -0,0 +1,15 @@
1
+ export interface SlicerDef {
2
+ readonly name: string;
3
+ readonly cacheName: string;
4
+ readonly caption: string;
5
+ readonly columnCount: number;
6
+ readonly styleName?: string;
7
+ }
8
+ export interface SlicerCacheDef {
9
+ readonly name: string;
10
+ readonly sourceName?: string;
11
+ readonly tableId?: number;
12
+ readonly columnId?: number;
13
+ }
14
+ export declare function parseSlicerPart(data: Uint8Array): Array<SlicerDef>;
15
+ export declare function parseSlicerCachePart(data: Uint8Array): SlicerCacheDef | undefined;
@@ -0,0 +1,76 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ //#region src/excel/slicer-parser.ts
3
+ var decoder = new TextDecoder("utf-8");
4
+ var parser = new XMLParser({
5
+ ignoreAttributes: false,
6
+ attributeNamePrefix: "@_",
7
+ parseAttributeValue: false,
8
+ parseTagValue: false,
9
+ removeNSPrefix: true
10
+ });
11
+ function parseSlicerPart(data) {
12
+ const root = asObj(parser.parse(decoder.decode(data))["slicers"]);
13
+ if (!root) return [];
14
+ const out = [];
15
+ for (const s of asArray(root["slicer"])) {
16
+ const o = asObj(s);
17
+ if (!o) continue;
18
+ const name = strAttr(o, "name");
19
+ const cacheName = strAttr(o, "cache");
20
+ if (!name || !cacheName) continue;
21
+ const caption = strAttr(o, "caption") ?? name;
22
+ const styleName = strAttr(o, "style");
23
+ out.push({
24
+ name,
25
+ cacheName,
26
+ caption,
27
+ columnCount: Math.max(1, numAttr(o, "columnCount") ?? 1),
28
+ ...styleName ? { styleName } : {}
29
+ });
30
+ }
31
+ return out;
32
+ }
33
+ function parseSlicerCachePart(data) {
34
+ const root = asObj(parser.parse(decoder.decode(data))["slicerCacheDefinition"]);
35
+ if (!root) return void 0;
36
+ const name = strAttr(root, "name");
37
+ if (!name) return void 0;
38
+ const sourceName = strAttr(root, "sourceName");
39
+ let tableId;
40
+ let columnId;
41
+ const extLst = asObj(root["extLst"]);
42
+ if (extLst) for (const ext of asArray(extLst["ext"])) {
43
+ const e = asObj(ext);
44
+ const inner = e ? asObj(e["slicerCacheDefinition"]) : void 0;
45
+ const tsc = inner ? asObj(inner["tableSlicerCache"]) : void 0;
46
+ if (tsc) {
47
+ tableId = numAttr(tsc, "tableId");
48
+ columnId = numAttr(tsc, "column");
49
+ break;
50
+ }
51
+ }
52
+ return {
53
+ name,
54
+ ...sourceName ? { sourceName } : {},
55
+ ...tableId !== void 0 ? { tableId } : {},
56
+ ...columnId !== void 0 ? { columnId } : {}
57
+ };
58
+ }
59
+ function asObj(v) {
60
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
61
+ }
62
+ function asArray(v) {
63
+ return Array.isArray(v) ? v : v === void 0 || v === null ? [] : [v];
64
+ }
65
+ function strAttr(obj, name) {
66
+ const v = obj[`@_${name}`];
67
+ return typeof v === "string" ? v : void 0;
68
+ }
69
+ function numAttr(obj, name) {
70
+ const v = strAttr(obj, name);
71
+ if (v === void 0) return void 0;
72
+ const n = Number(v);
73
+ return Number.isFinite(n) ? n : void 0;
74
+ }
75
+ //#endregion
76
+ export { parseSlicerCachePart, parseSlicerPart };
@@ -101,6 +101,12 @@ function parseBorders(root) {
101
101
  if (right) border.right = right;
102
102
  if (bottom) border.bottom = bottom;
103
103
  if (left) border.left = left;
104
+ const diagonal = parseBorderEdge(asObject(obj["diagonal"]));
105
+ if (diagonal) {
106
+ border.diagonal = diagonal;
107
+ if (boolAttr(obj, "diagonalUp")) border.diagonalUp = true;
108
+ if (boolAttr(obj, "diagonalDown")) border.diagonalDown = true;
109
+ }
104
110
  out.push(border);
105
111
  }
106
112
  return out;
@@ -169,6 +175,13 @@ function parseFills(root) {
169
175
  if (fg) fill.fgColorHex = fg;
170
176
  if (bg) fill.bgColorHex = bg;
171
177
  }
178
+ if (!fill.patternType) {
179
+ const avg = averageGradientColor(asObject(obj["gradientFill"]));
180
+ if (avg) {
181
+ fill.patternType = "solid";
182
+ fill.fgColorHex = avg;
183
+ }
184
+ }
172
185
  out.push(fill);
173
186
  }
174
187
  return out;
@@ -205,6 +218,11 @@ function parseCellXfs(root) {
205
218
  if (h) a.horizontal = h;
206
219
  if (v) a.vertical = v;
207
220
  if (wrap !== void 0) a.wrapText = wrap;
221
+ const indent = numAttr(align, "indent");
222
+ if (indent !== void 0 && indent > 0) a.indent = indent;
223
+ const rotation = numAttr(align, "textRotation");
224
+ if (rotation !== void 0 && rotation !== 0) a.textRotation = rotation;
225
+ if (boolAttr(align, "shrinkToFit")) a.shrinkToFit = true;
208
226
  if (Object.keys(a).length > 0) xf.alignment = a;
209
227
  }
210
228
  out.push(xf);
@@ -250,6 +268,25 @@ function childToggle(obj, childName) {
250
268
  if (val === void 0) return true;
251
269
  return !(val === "false" || val === "0");
252
270
  }
271
+ function averageGradientColor(gf) {
272
+ if (!gf) return void 0;
273
+ let r = 0;
274
+ let g = 0;
275
+ let b = 0;
276
+ let n = 0;
277
+ for (const s of asArray(gf["stop"])) {
278
+ const so = asObject(s);
279
+ const hex = so ? colorOf(asObject(so["color"])) : void 0;
280
+ if (!hex) continue;
281
+ r += parseInt(hex.slice(0, 2), 16);
282
+ g += parseInt(hex.slice(2, 4), 16);
283
+ b += parseInt(hex.slice(4, 6), 16);
284
+ n++;
285
+ }
286
+ if (n === 0) return void 0;
287
+ const h = (x) => Math.round(x / n).toString(16).padStart(2, "0").toUpperCase();
288
+ return `${h(r)}${h(g)}${h(b)}`;
289
+ }
253
290
  function colorOf(node) {
254
291
  if (!node) return void 0;
255
292
  const rgb = strAttr(node, "rgb");
@@ -1,2 +1,12 @@
1
1
  import { ExcelTable } from '../core/spreadsheet-model/index.js';
2
+ export interface TableFilterColumn {
3
+ readonly colId: number;
4
+ readonly values: ReadonlyArray<string>;
5
+ }
6
+ export interface ParsedTablePart {
7
+ readonly table: ExcelTable;
8
+ readonly id?: number;
9
+ readonly filters: ReadonlyArray<TableFilterColumn>;
10
+ }
2
11
  export declare function parseTablePart(data: Uint8Array): ExcelTable | undefined;
12
+ export declare function parseTablePartFull(data: Uint8Array): ParsedTablePart | undefined;
@@ -9,7 +9,7 @@ var parser = new XMLParser({
9
9
  parseTagValue: false,
10
10
  removeNSPrefix: true
11
11
  });
12
- function parseTablePart(data) {
12
+ function parseTablePartFull(data) {
13
13
  const t = asObj(parser.parse(decoder.decode(data))["table"]);
14
14
  if (!t) return void 0;
15
15
  const refStr = strAttr(t, "ref");
@@ -18,7 +18,8 @@ function parseTablePart(data) {
18
18
  const si = asObj(t["tableStyleInfo"]);
19
19
  const name = strAttr(t, "name");
20
20
  const styleName = si ? strAttr(si, "name") : void 0;
21
- return {
21
+ const af = asObj(t["autoFilter"]);
22
+ const table = {
22
23
  ref,
23
24
  ...name ? { name } : {},
24
25
  ...styleName ? { styleName } : {},
@@ -27,9 +28,40 @@ function parseTablePart(data) {
27
28
  showColumnStripes: si ? boolAttr(si, "showColumnStripes") : false,
28
29
  showFirstColumn: si ? boolAttr(si, "showFirstColumn") : false,
29
30
  showLastColumn: si ? boolAttr(si, "showLastColumn") : false,
30
- autoFilter: asObj(t["autoFilter"]) !== void 0
31
+ autoFilter: af !== void 0
32
+ };
33
+ const id = numAttr(t, "id");
34
+ return {
35
+ table,
36
+ ...id !== void 0 ? { id } : {},
37
+ filters: parseFilterColumns(af)
31
38
  };
32
39
  }
40
+ function parseFilterColumns(af) {
41
+ if (!af) return [];
42
+ const out = [];
43
+ for (const fc of asArray(af["filterColumn"])) {
44
+ const col = asObj(fc);
45
+ if (!col) continue;
46
+ const colId = numAttr(col, "colId");
47
+ if (colId === void 0) continue;
48
+ const filters = asObj(col["filters"]);
49
+ if (!filters) continue;
50
+ const values = [];
51
+ for (const f of asArray(filters["filter"])) {
52
+ const v = asObj(f) ? strAttr(asObj(f), "val") : void 0;
53
+ if (v !== void 0) values.push(v);
54
+ }
55
+ out.push({
56
+ colId,
57
+ values
58
+ });
59
+ }
60
+ return out;
61
+ }
62
+ function asArray(v) {
63
+ return Array.isArray(v) ? v : v === void 0 || v === null ? [] : [v];
64
+ }
33
65
  function asObj(v) {
34
66
  return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
35
67
  }
@@ -48,4 +80,4 @@ function boolAttr(obj, name) {
48
80
  return v === "1" || v === "true";
49
81
  }
50
82
  //#endregion
51
- export { parseTablePart };
83
+ export { parseTablePartFull };
@@ -37,6 +37,10 @@ function parseWorksheet(data) {
37
37
  const drawingNode = wsObj["drawing"];
38
38
  const drawingRelId = drawingNode && typeof drawingNode === "object" ? strAttr(drawingNode, "id") : void 0;
39
39
  const conditionalFormats = parseConditionalFormatting(wsObj);
40
+ const dataValidations = parseDataValidations(wsObj);
41
+ const hyperlinks = parseHyperlinks(wsObj);
42
+ const headerFooter = parseHeaderFooter(wsObj);
43
+ const formControls = parseFormControls(wsObj);
40
44
  const sparklines = parseSparklines(wsObj);
41
45
  const tablePartRelIds = parseTableParts(wsObj);
42
46
  const printModel = {
@@ -49,6 +53,10 @@ function parseWorksheet(data) {
49
53
  ...pane ? { pane } : {},
50
54
  ...drawingRelId !== void 0 ? { drawingRelId } : {},
51
55
  ...conditionalFormats.length > 0 ? { conditionalFormats } : {},
56
+ ...dataValidations.length > 0 ? { dataValidations } : {},
57
+ ...hyperlinks.length > 0 ? { hyperlinks } : {},
58
+ ...headerFooter ? { headerFooter } : {},
59
+ ...formControls.length > 0 ? { formControls } : {},
52
60
  ...sparklines.length > 0 ? { sparklines } : {},
53
61
  ...tablePartRelIds.length > 0 ? { tablePartRelIds } : {}
54
62
  };
@@ -298,6 +306,95 @@ function parseConditionalFormatting(ws) {
298
306
  }
299
307
  return out;
300
308
  }
309
+ var DV_TYPES = new Set([
310
+ "none",
311
+ "whole",
312
+ "decimal",
313
+ "list",
314
+ "date",
315
+ "time",
316
+ "textLength",
317
+ "custom"
318
+ ]);
319
+ function parseDataValidations(ws) {
320
+ const node = asObjectNode(ws["dataValidations"]);
321
+ if (!node) return [];
322
+ const out = [];
323
+ for (const dv of toArray(node["dataValidation"])) {
324
+ const obj = asObjectNode(dv);
325
+ if (!obj) continue;
326
+ const sqref = strAttr(obj, "sqref");
327
+ if (!sqref) continue;
328
+ const ranges = parseSqref(sqref);
329
+ if (ranges.length === 0) continue;
330
+ const typeRaw = strAttr(obj, "type");
331
+ const type = typeRaw && DV_TYPES.has(typeRaw) ? typeRaw : "none";
332
+ const operator = strAttr(obj, "operator");
333
+ const errorStyle = strAttr(obj, "errorStyle");
334
+ const formula1 = formulaText(obj["formula1"]);
335
+ const formula2 = formulaText(obj["formula2"]);
336
+ const promptTitle = strAttr(obj, "promptTitle");
337
+ const prompt = strAttr(obj, "prompt");
338
+ const errorTitle = strAttr(obj, "errorTitle");
339
+ const error = strAttr(obj, "error");
340
+ out.push({
341
+ type,
342
+ ranges,
343
+ ...operator !== void 0 ? { operator } : {},
344
+ ...boolAttr(obj, "allowBlank") ? { allowBlank: true } : {},
345
+ ...boolAttr(obj, "showDropDown") ? { showDropDown: true } : {},
346
+ ...boolAttr(obj, "showInputMessage") ? { showInputMessage: true } : {},
347
+ ...boolAttr(obj, "showErrorMessage") ? { showErrorMessage: true } : {},
348
+ ...errorStyle !== void 0 ? { errorStyle } : {},
349
+ ...formula1 !== void 0 ? { formula1 } : {},
350
+ ...formula2 !== void 0 ? { formula2 } : {},
351
+ ...promptTitle !== void 0 ? { promptTitle } : {},
352
+ ...prompt !== void 0 ? { prompt } : {},
353
+ ...errorTitle !== void 0 ? { errorTitle } : {},
354
+ ...error !== void 0 ? { error } : {}
355
+ });
356
+ }
357
+ return out;
358
+ }
359
+ function boolAttr(obj, key) {
360
+ const raw = strAttr(obj, key);
361
+ return raw === "1" || raw === "true";
362
+ }
363
+ function parseHeaderFooter(ws) {
364
+ const node = asObjectNode(ws["headerFooter"]);
365
+ if (!node) return void 0;
366
+ const oddHeader = formulaText(node["oddHeader"]);
367
+ const oddFooter = formulaText(node["oddFooter"]);
368
+ if (!oddHeader && !oddFooter) return void 0;
369
+ return {
370
+ ...oddHeader ? { oddHeader } : {},
371
+ ...oddFooter ? { oddFooter } : {}
372
+ };
373
+ }
374
+ function parseHyperlinks(ws) {
375
+ const node = asObjectNode(ws["hyperlinks"]);
376
+ if (!node) return [];
377
+ const out = [];
378
+ for (const h of toArray(node["hyperlink"])) {
379
+ const obj = asObjectNode(h);
380
+ if (!obj) continue;
381
+ const ref = strAttr(obj, "ref");
382
+ if (!ref) continue;
383
+ const relId = strAttr(obj, "id");
384
+ const location = strAttr(obj, "location");
385
+ if (relId === void 0 && location === void 0) continue;
386
+ const display = strAttr(obj, "display");
387
+ const tooltip = strAttr(obj, "tooltip");
388
+ out.push({
389
+ ref,
390
+ ...relId !== void 0 ? { relId } : {},
391
+ ...location !== void 0 ? { location } : {},
392
+ ...display !== void 0 ? { display } : {},
393
+ ...tooltip !== void 0 ? { tooltip } : {}
394
+ });
395
+ }
396
+ return out;
397
+ }
301
398
  function parseSqref(sqref) {
302
399
  const out = [];
303
400
  for (const token of sqref.split(/\s+/)) {
@@ -324,14 +421,73 @@ var CF_OPERATORS = new Set([
324
421
  ]);
325
422
  function parseCfRule(obj) {
326
423
  const priority = parseNumericAttr(obj, "priority") ?? 0;
327
- switch (strAttr(obj, "type")) {
424
+ const type = strAttr(obj, "type");
425
+ switch (type) {
328
426
  case "cellIs": return parseCellIsRule(obj, priority);
329
427
  case "colorScale": return parseColorScaleRule(obj, priority);
330
428
  case "dataBar": return parseDataBarRule(obj, priority);
331
429
  case "iconSet": return parseIconSetRule(obj, priority);
430
+ case "top10": return parseTop10Rule(obj, priority);
431
+ case "aboveAverage": return parseAboveAverageRule(obj, priority);
432
+ case "duplicateValues":
433
+ case "uniqueValues": return parseDupUniqueRule(type, obj, priority);
434
+ case "containsText":
435
+ case "notContainsText":
436
+ case "beginsWith":
437
+ case "endsWith": return parseTextRule(type, obj, priority);
332
438
  default: return;
333
439
  }
334
440
  }
441
+ function parseTop10Rule(obj, priority) {
442
+ const dxfId = parseNumericAttr(obj, "dxfId");
443
+ if (dxfId === void 0) return void 0;
444
+ return {
445
+ type: "top10",
446
+ priority,
447
+ rank: parseNumericAttr(obj, "rank") ?? 10,
448
+ percent: boolAttr(obj, "percent"),
449
+ bottom: boolAttr(obj, "bottom"),
450
+ dxfId
451
+ };
452
+ }
453
+ function parseAboveAverageRule(obj, priority) {
454
+ const dxfId = parseNumericAttr(obj, "dxfId");
455
+ if (dxfId === void 0) return void 0;
456
+ const aboveRaw = strAttr(obj, "aboveAverage");
457
+ const aboveAverage = aboveRaw === void 0 ? true : aboveRaw === "1" || aboveRaw === "true";
458
+ const stdDev = parseNumericAttr(obj, "stdDev");
459
+ return {
460
+ type: "aboveAverage",
461
+ priority,
462
+ aboveAverage,
463
+ equalAverage: boolAttr(obj, "equalAverage"),
464
+ ...stdDev !== void 0 ? { stdDev } : {},
465
+ dxfId
466
+ };
467
+ }
468
+ function parseDupUniqueRule(type, obj, priority) {
469
+ const dxfId = parseNumericAttr(obj, "dxfId");
470
+ if (dxfId === void 0) return void 0;
471
+ return {
472
+ type,
473
+ priority,
474
+ dxfId
475
+ };
476
+ }
477
+ function parseTextRule(type, obj, priority) {
478
+ const dxfId = parseNumericAttr(obj, "dxfId");
479
+ if (dxfId === void 0) return void 0;
480
+ const text = strAttr(obj, "text");
481
+ if (text === void 0) return void 0;
482
+ const formula = formulaText(obj["formula"]);
483
+ return {
484
+ type,
485
+ priority,
486
+ text,
487
+ dxfId,
488
+ ...formula !== void 0 ? { formula } : {}
489
+ };
490
+ }
335
491
  function parseIconSetRule(obj, priority) {
336
492
  const is = obj["iconSet"];
337
493
  if (!is || typeof is !== "object") return void 0;
@@ -489,6 +645,30 @@ function parseSparklines(ws) {
489
645
  }
490
646
  return out;
491
647
  }
648
+ function parseFormControls(ws) {
649
+ const out = [];
650
+ const seen = /* @__PURE__ */ new Set();
651
+ collectControls(asObjectNode(ws["controls"]), out, seen);
652
+ for (const ext of toArray(asObjectNode(ws["extLst"])?.["ext"])) collectControls(asObjectNode(asObjectNode(ext)?.["controls"]), out, seen);
653
+ return out;
654
+ }
655
+ function collectControls(node, out, seen) {
656
+ if (!node) return;
657
+ const direct = toArray(node["control"]);
658
+ const fromChoice = toArray(asObjectNode(node["AlternateContent"])?.["Choice"]).flatMap((c) => toArray(asObjectNode(c)?.["control"]));
659
+ for (const c of [...direct, ...fromChoice]) {
660
+ const obj = asObjectNode(c);
661
+ if (!obj) continue;
662
+ const relId = strAttr(obj, "id");
663
+ if (!relId || seen.has(relId)) continue;
664
+ seen.add(relId);
665
+ const name = strAttr(obj, "name");
666
+ out.push({
667
+ relId,
668
+ ...name ? { name } : {}
669
+ });
670
+ }
671
+ }
492
672
  function asObjectNode(v) {
493
673
  return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
494
674
  }