reamkit 1.12.0 → 1.13.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.
@@ -275,6 +275,7 @@ export interface CellProperties {
275
275
  readonly dataBar?: CellDataBar;
276
276
  readonly icon?: CellIcon;
277
277
  readonly sparkline?: CellSparkline;
278
+ readonly dropdown?: boolean;
278
279
  }
279
280
  export interface RowProperties {
280
281
  readonly height?: Pt;
@@ -6,10 +6,24 @@ export interface SheetChartRef {
6
6
  readonly widthPt: number;
7
7
  readonly heightPt: number;
8
8
  }
9
+ export interface SheetSlicerItem {
10
+ readonly label: string;
11
+ readonly selected: boolean;
12
+ }
13
+ export interface SheetSlicer {
14
+ readonly caption: string;
15
+ readonly columnCount: number;
16
+ readonly items: ReadonlyArray<SheetSlicerItem>;
17
+ readonly headerHex?: string;
18
+ readonly selectedHex?: string;
19
+ readonly headerTextHex?: string;
20
+ readonly selectedTextHex?: string;
21
+ }
9
22
  export interface Sheet {
10
23
  readonly name: string;
11
24
  readonly grid: ParsedWorksheet;
12
25
  readonly charts?: ReadonlyArray<SheetChartRef>;
26
+ readonly slicers?: ReadonlyArray<SheetSlicer>;
13
27
  }
14
28
  export interface SheetDoc {
15
29
  readonly kind: 'sheet';
@@ -1 +1 @@
1
- export type { CellType, WorksheetCell, ColumnWidth, MergedRange, RowHeight, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, ParsedWorksheet, XlsxFont, XlsxFill, XlsxBorderStyleName, XlsxBorderEdge, XlsxBorder, XlsxHorizontalAlign, XlsxVerticalAlign, XlsxCellAlignment, XlsxCellXf, XlsxStyles, DefinedName, Dxf, CfOperator, CfRuleCellIs, CfvoType, Cfvo, CfRuleColorScale, CfRuleDataBar, CfRuleIconSet, CfRule, ConditionalFormat, SparklineKind, ParsedSparkline, ExcelTable, PivotTable, SheetPane, } from './types.js';
1
+ export type { CellType, WorksheetCell, ColumnWidth, MergedRange, RowHeight, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, ParsedWorksheet, XlsxFont, XlsxFill, XlsxBorderStyleName, XlsxBorderEdge, XlsxBorder, XlsxHorizontalAlign, XlsxVerticalAlign, XlsxCellAlignment, XlsxCellXf, XlsxStyles, DefinedName, Dxf, CfOperator, CfRuleCellIs, CfvoType, Cfvo, CfRuleColorScale, CfRuleDataBar, CfRuleIconSet, CfRule, ConditionalFormat, DataValidationType, DataValidation, SparklineKind, ParsedSparkline, ExcelTable, PivotTable, SheetPane, } from './types.js';
@@ -58,6 +58,7 @@ export interface ParsedWorksheet {
58
58
  readonly colBreaks?: ReadonlyArray<number>;
59
59
  readonly drawingRelId?: string;
60
60
  readonly conditionalFormats?: ReadonlyArray<ConditionalFormat>;
61
+ readonly dataValidations?: ReadonlyArray<DataValidation>;
61
62
  readonly sparklines?: ReadonlyArray<ParsedSparkline>;
62
63
  readonly tablePartRelIds?: ReadonlyArray<string>;
63
64
  readonly tables?: ReadonlyArray<ExcelTable>;
@@ -203,3 +204,20 @@ export interface ConditionalFormat {
203
204
  readonly ranges: ReadonlyArray<MergedRange>;
204
205
  readonly rules: ReadonlyArray<CfRule>;
205
206
  }
207
+ export type DataValidationType = 'none' | 'whole' | 'decimal' | 'list' | 'date' | 'time' | 'textLength' | 'custom';
208
+ export interface DataValidation {
209
+ readonly type: DataValidationType;
210
+ readonly ranges: ReadonlyArray<MergedRange>;
211
+ readonly operator?: string;
212
+ readonly allowBlank?: boolean;
213
+ readonly showDropDown?: boolean;
214
+ readonly showInputMessage?: boolean;
215
+ readonly showErrorMessage?: boolean;
216
+ readonly errorStyle?: string;
217
+ readonly formula1?: string;
218
+ readonly formula2?: string;
219
+ readonly promptTitle?: string;
220
+ readonly prompt?: string;
221
+ readonly errorTitle?: string;
222
+ readonly error?: string;
223
+ }
@@ -7,7 +7,7 @@ export { parseWorkbook } from './workbook-parser.js';
7
7
  export type { ParsedWorkbook, SheetReference } from './workbook-parser.js';
8
8
  export type { DefinedName } from '../core/spreadsheet-model/index.js';
9
9
  export { parseWorksheet } from './worksheet-parser.js';
10
- export type { CellType, ColumnWidth, ExcelTable, MergedRange, ParsedWorksheet, RowHeight, WorksheetCell, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, } from '../core/spreadsheet-model/index.js';
10
+ export type { CellType, ColumnWidth, DataValidation, DataValidationType, ExcelTable, MergedRange, ParsedWorksheet, RowHeight, WorksheetCell, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, } from '../core/spreadsheet-model/index.js';
11
11
  export { parseXlsxStyles, EMPTY_XLSX_STYLES } from './styles-parser.js';
12
12
  export type { XlsxBorder, XlsxBorderEdge, XlsxBorderStyleName, XlsxCellAlignment, XlsxCellXf, XlsxFill, XlsxFont, XlsxHorizontalAlign, XlsxStyles, XlsxVerticalAlign, } from '../core/spreadsheet-model/index.js';
13
13
  export { applyNumberFormat } from './number-format.js';
@@ -1,5 +1,6 @@
1
- import { BodyElement, SectionProperties } from '../core/document-model/index.js';
2
- import { CellRange, DefinedName, ParsedWorksheet, XlsxStyles } from './index.js';
1
+ import { BodyElement, SectionProperties, Table } from '../core/document-model/index.js';
2
+ import { CellRange, DefinedName, ParsedWorksheet, WorksheetCell, XlsxStyles } from './index.js';
3
+ import { SheetSlicer } from '../core/ir/sheet.js';
3
4
  export declare const TWIPS_PER_EXCEL_CHAR = 105;
4
5
  export declare const DEFAULT_COL_TWIPS = 960;
5
6
  export declare const DEFAULT_ROW_TWIPS = 300;
@@ -19,4 +20,6 @@ interface PrintModelOptions {
19
20
  readonly sheetGrids?: ReadonlyMap<string, ParsedWorksheet>;
20
21
  }
21
22
  export declare function worksheetToBody(worksheet: ParsedWorksheet, sharedStrings: ReadonlyArray<string>, styles: XlsxStyles, date1904: boolean, print: PrintModelOptions): Array<BodyElement>;
23
+ export declare function resolveCellText(cell: WorksheetCell, sharedStrings: ReadonlyArray<string>, styles: XlsxStyles, date1904: boolean): string;
24
+ export declare function slicerTable(slicer: SheetSlicer): Table;
22
25
  export {};
@@ -1,4 +1,4 @@
1
- import { eighthPtToPt, halfPtToPt, twipsToPt } from "../core/ir/units.js";
1
+ import { eighthPtToPt, halfPtToPt, pt, twipsToPt } from "../core/ir/units.js";
2
2
  import { parseAreaRef, parseTitleRowRange } from "./defined-name-ref.js";
3
3
  import { applyNumberFormat } from "./number-format.js";
4
4
  import "./index.js";
@@ -195,6 +195,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
195
195
  const cfFormatter = buildConditionalFormatter(worksheet.conditionalFormats, styles, worksheet.cells);
196
196
  const sparklineByCell = buildSparklineLookup(worksheet, print);
197
197
  const tableFormatByCell = buildTableFormatLookup(worksheet);
198
+ const dropdownRanges = listDropdownRanges(worksheet.dataValidations);
198
199
  const rows = [];
199
200
  for (let r = 0; r < rowCount; r++) {
200
201
  const absR = r + rowStart;
@@ -246,6 +247,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
246
247
  if (text.length > charsFit) text = text.slice(0, charsFit);
247
248
  }
248
249
  }
250
+ const dropdown = dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, absC);
249
251
  const visibleEndCol = merge ? Math.min(merge.endColumn, colWindowEnd) : 0;
250
252
  const properties = {
251
253
  ...merge && visibleEndCol > merge.startColumn ? { colSpan: visibleEndCol - merge.startColumn + 1 } : {},
@@ -254,7 +256,8 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
254
256
  ...dataBar ? { dataBar } : {},
255
257
  ...icon ? { icon } : {},
256
258
  ...sparkline ? { sparkline } : {},
257
- ...borders ? { borders } : {}
259
+ ...borders ? { borders } : {},
260
+ ...dropdown ? { dropdown: true } : {}
258
261
  };
259
262
  const paragraphProps = cellParaProps(alignment);
260
263
  cells.push({
@@ -474,6 +477,19 @@ function mapBorderStyle(style) {
474
477
  function key(row, col) {
475
478
  return `${row},${col}`;
476
479
  }
480
+ function listDropdownRanges(dvs) {
481
+ if (!dvs || dvs.length === 0) return [];
482
+ const out = [];
483
+ for (const dv of dvs) {
484
+ if (dv.type !== "list" || dv.showDropDown) continue;
485
+ out.push(...dv.ranges);
486
+ }
487
+ return out;
488
+ }
489
+ function rangesCover(ranges, row, col) {
490
+ for (const r of ranges) if (row >= r.startRow && row <= r.endRow && col >= r.startColumn && col <= r.endColumn) return true;
491
+ return false;
492
+ }
477
493
  function buildSparklineLookup(worksheet, print) {
478
494
  const out = /* @__PURE__ */ new Map();
479
495
  for (const sp of worksheet.sparklines ?? []) {
@@ -572,5 +588,81 @@ function buildTableFormatLookup(worksheet) {
572
588
  function cellHasContent(cell) {
573
589
  return !!cell && (cell.rawValue !== "" || cell.inlineText !== void 0);
574
590
  }
591
+ var SLICER_WIDTH_PT = 108;
592
+ var SLICER_ROW_PT = 16;
593
+ var SLICER_UNSELECTED_HEX = "F2F2F2";
594
+ function slicerTable(slicer) {
595
+ const cols = Math.max(1, slicer.columnCount);
596
+ const colWidthPt = SLICER_WIDTH_PT / cols;
597
+ const thin = {
598
+ style: "single",
599
+ width: eighthPtToPt(4)
600
+ };
601
+ const rowProps = {
602
+ height: pt(SLICER_ROW_PT),
603
+ heightRule: "atLeast"
604
+ };
605
+ const rows = [];
606
+ rows.push({
607
+ properties: rowProps,
608
+ cells: [{
609
+ properties: {
610
+ ...cols > 1 ? { colSpan: cols } : {},
611
+ ...slicer.headerHex ? { shading: { colorHex: slicer.headerHex } } : {}
612
+ },
613
+ content: [slicerParagraph(slicer.caption, {
614
+ bold: true,
615
+ ...slicer.headerTextHex ? { colorHex: slicer.headerTextHex } : {}
616
+ })]
617
+ }]
618
+ });
619
+ for (let i = 0; i < slicer.items.length; i += cols) {
620
+ const cells = [];
621
+ for (let c = 0; c < cols; c++) {
622
+ const item = slicer.items[i + c];
623
+ if (!item) {
624
+ cells.push({
625
+ properties: {},
626
+ content: [slicerParagraph("", {})]
627
+ });
628
+ continue;
629
+ }
630
+ const fill = item.selected ? slicer.selectedHex : SLICER_UNSELECTED_HEX;
631
+ const textHex = item.selected ? slicer.selectedTextHex : void 0;
632
+ cells.push({
633
+ properties: fill ? { shading: { colorHex: fill } } : {},
634
+ content: [slicerParagraph(item.label, textHex ? { colorHex: textHex } : {})]
635
+ });
636
+ }
637
+ rows.push({
638
+ properties: rowProps,
639
+ cells
640
+ });
641
+ }
642
+ return {
643
+ properties: { borders: {
644
+ top: thin,
645
+ bottom: thin,
646
+ left: thin,
647
+ right: thin,
648
+ insideH: thin,
649
+ insideV: thin
650
+ } },
651
+ grid: Array.from({ length: cols }, () => pt(colWidthPt)),
652
+ rows
653
+ };
654
+ }
655
+ function slicerParagraph(text, runProps) {
656
+ return {
657
+ kind: "paragraph",
658
+ paragraph: {
659
+ properties: {},
660
+ runs: text.length > 0 ? [{
661
+ text,
662
+ properties: runProps
663
+ }] : []
664
+ }
665
+ };
666
+ }
575
667
  //#endregion
576
- export { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, worksheetToBody };
668
+ export { resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
@@ -1,7 +1,7 @@
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
5
  //#region src/excel/sheet-to-flow.ts
6
6
  function projectSheetDoc(sheet) {
7
7
  const body = [];
@@ -35,6 +35,10 @@ function projectSheetDoc(sheet) {
35
35
  paragraphProperties: {}
36
36
  }
37
37
  });
38
+ for (const slicer of ws.slicers ?? []) body.push({
39
+ kind: "table",
40
+ table: slicerTable(slicer)
41
+ });
38
42
  }
39
43
  return {
40
44
  kind: "flow",
@@ -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 };
@@ -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,7 @@ 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);
40
41
  const sparklines = parseSparklines(wsObj);
41
42
  const tablePartRelIds = parseTableParts(wsObj);
42
43
  const printModel = {
@@ -49,6 +50,7 @@ function parseWorksheet(data) {
49
50
  ...pane ? { pane } : {},
50
51
  ...drawingRelId !== void 0 ? { drawingRelId } : {},
51
52
  ...conditionalFormats.length > 0 ? { conditionalFormats } : {},
53
+ ...dataValidations.length > 0 ? { dataValidations } : {},
52
54
  ...sparklines.length > 0 ? { sparklines } : {},
53
55
  ...tablePartRelIds.length > 0 ? { tablePartRelIds } : {}
54
56
  };
@@ -298,6 +300,60 @@ function parseConditionalFormatting(ws) {
298
300
  }
299
301
  return out;
300
302
  }
303
+ var DV_TYPES = new Set([
304
+ "none",
305
+ "whole",
306
+ "decimal",
307
+ "list",
308
+ "date",
309
+ "time",
310
+ "textLength",
311
+ "custom"
312
+ ]);
313
+ function parseDataValidations(ws) {
314
+ const node = asObjectNode(ws["dataValidations"]);
315
+ if (!node) return [];
316
+ const out = [];
317
+ for (const dv of toArray(node["dataValidation"])) {
318
+ const obj = asObjectNode(dv);
319
+ if (!obj) continue;
320
+ const sqref = strAttr(obj, "sqref");
321
+ if (!sqref) continue;
322
+ const ranges = parseSqref(sqref);
323
+ if (ranges.length === 0) continue;
324
+ const typeRaw = strAttr(obj, "type");
325
+ const type = typeRaw && DV_TYPES.has(typeRaw) ? typeRaw : "none";
326
+ const operator = strAttr(obj, "operator");
327
+ const errorStyle = strAttr(obj, "errorStyle");
328
+ const formula1 = formulaText(obj["formula1"]);
329
+ const formula2 = formulaText(obj["formula2"]);
330
+ const promptTitle = strAttr(obj, "promptTitle");
331
+ const prompt = strAttr(obj, "prompt");
332
+ const errorTitle = strAttr(obj, "errorTitle");
333
+ const error = strAttr(obj, "error");
334
+ out.push({
335
+ type,
336
+ ranges,
337
+ ...operator !== void 0 ? { operator } : {},
338
+ ...boolAttr(obj, "allowBlank") ? { allowBlank: true } : {},
339
+ ...boolAttr(obj, "showDropDown") ? { showDropDown: true } : {},
340
+ ...boolAttr(obj, "showInputMessage") ? { showInputMessage: true } : {},
341
+ ...boolAttr(obj, "showErrorMessage") ? { showErrorMessage: true } : {},
342
+ ...errorStyle !== void 0 ? { errorStyle } : {},
343
+ ...formula1 !== void 0 ? { formula1 } : {},
344
+ ...formula2 !== void 0 ? { formula2 } : {},
345
+ ...promptTitle !== void 0 ? { promptTitle } : {},
346
+ ...prompt !== void 0 ? { prompt } : {},
347
+ ...errorTitle !== void 0 ? { errorTitle } : {},
348
+ ...error !== void 0 ? { error } : {}
349
+ });
350
+ }
351
+ return out;
352
+ }
353
+ function boolAttr(obj, key) {
354
+ const raw = strAttr(obj, key);
355
+ return raw === "1" || raw === "true";
356
+ }
301
357
  function parseSqref(sqref) {
302
358
  const out = [];
303
359
  for (const token of sqref.split(/\s+/)) {
@@ -13,15 +13,20 @@ import { parseWorkbook } from "./workbook-parser.js";
13
13
  import { parseWorksheet } from "./worksheet-parser.js";
14
14
  import { EMPTY_XLSX_STYLES, parseXlsxStyles } from "./styles-parser.js";
15
15
  import "./index.js";
16
+ import { resolveCellText } from "./print-model.js";
16
17
  import { parseSheetDrawing } from "./sheet-drawing.js";
17
- import { parseTablePart } from "./table-parser.js";
18
+ import { parseTablePartFull } from "./table-parser.js";
18
19
  import { parsePivotTablePart } from "./pivot-table-parser.js";
20
+ import { parseSlicerCachePart, parseSlicerPart } from "./slicer-parser.js";
19
21
  import { projectSheetDoc } from "./sheet-to-flow.js";
20
22
  //#region src/excel/xlsx-reader.ts
21
23
  var WORKBOOK_PART = "xl/workbook.xml";
22
24
  var SHARED_STRINGS_PART = "xl/sharedStrings.xml";
23
25
  var STYLES_PART = "xl/styles.xml";
24
26
  var CORE_PROPS_PART = "docProps/core.xml";
27
+ var SLICER_REL_TAIL = "/slicer";
28
+ var SLICER_CACHE_REL_TAIL = "/slicerCache";
29
+ var MAX_SLICER_ITEMS = 256;
25
30
  function readXlsx(xlsx) {
26
31
  return {
27
32
  doc: projectSheetDoc(readXlsxToSheetDoc(xlsx)),
@@ -43,6 +48,8 @@ function readXlsxToSheetDoc(xlsx) {
43
48
  const palette = buildThemePalette(pkg, workbookRels);
44
49
  const resolveColor = makeColorResolver(palette);
45
50
  const sheetsOut = [];
51
+ const tableIndex = /* @__PURE__ */ new Map();
52
+ const pendingSlicers = [];
46
53
  for (let sheetIdx = 0; sheetIdx < sheets.length; sheetIdx++) {
47
54
  const sheet = sheets[sheetIdx];
48
55
  const sheetRel = workbookRels.find((r) => r.id === sheet.relationshipId);
@@ -75,11 +82,31 @@ function readXlsxToSheetDoc(xlsx) {
75
82
  for (const rid of worksheet.tablePartRelIds) {
76
83
  const rel = wsRels.find((r) => r.id === rid);
77
84
  const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
78
- const parsed = part ? parseTablePart(part.data) : void 0;
79
- if (parsed) resolvedTables.push(resolveTableStyle(parsed, palette));
85
+ const full = part ? parseTablePartFull(part.data) : void 0;
86
+ if (!full) continue;
87
+ resolvedTables.push(resolveTableStyle(full.table, palette));
88
+ if (full.id !== void 0) tableIndex.set(full.id, {
89
+ cells: worksheet.cells,
90
+ ref: full.table.ref,
91
+ headerRows: full.table.headerRowCount,
92
+ filters: full.filters
93
+ });
80
94
  }
81
95
  if (resolvedTables.length > 0) tables = resolvedTables;
82
96
  }
97
+ {
98
+ const wsRels = pkg.getPartRelationships(resolved.path);
99
+ const defs = [];
100
+ for (const rel of wsRels) {
101
+ if (!rel.type.endsWith(SLICER_REL_TAIL)) continue;
102
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
103
+ if (part) defs.push(...parseSlicerPart(part.data));
104
+ }
105
+ if (defs.length > 0) pendingSlicers.push({
106
+ outIdx: sheetsOut.length,
107
+ defs
108
+ });
109
+ }
83
110
  let pivotTables;
84
111
  {
85
112
  const resolvedPivots = [];
@@ -102,6 +129,24 @@ function readXlsxToSheetDoc(xlsx) {
102
129
  ...charts.length > 0 ? { charts } : {}
103
130
  });
104
131
  }
132
+ if (pendingSlicers.length > 0) {
133
+ const cacheByName = /* @__PURE__ */ new Map();
134
+ for (const rel of workbookRels) {
135
+ if (!rel.type.endsWith(SLICER_CACHE_REL_TAIL)) continue;
136
+ const part = pkg.resolveRelatedPart(WORKBOOK_PART, rel);
137
+ const cache = part ? parseSlicerCachePart(part.data) : void 0;
138
+ if (cache) cacheByName.set(cache.name, cache);
139
+ }
140
+ for (const { outIdx, defs } of pendingSlicers) {
141
+ const sheet = sheetsOut[outIdx];
142
+ if (!sheet) continue;
143
+ const slicers = defs.map((def) => resolveSlicer(def, cacheByName.get(def.cacheName), tableIndex, styles, sharedStrings, date1904, palette));
144
+ sheetsOut[outIdx] = {
145
+ ...sheet,
146
+ slicers
147
+ };
148
+ }
149
+ }
105
150
  const coreData = pkg.getPart(CORE_PROPS_PART);
106
151
  const info = infoFromCore(coreData ? parseCoreProperties(coreData) : void 0);
107
152
  return {
@@ -163,6 +208,54 @@ function resolvePivotStyle(p, palette) {
163
208
  headerTextHex: "FFFFFF"
164
209
  };
165
210
  }
211
+ function resolveSlicer(def, cache, tableIndex, styles, sharedStrings, date1904, palette) {
212
+ const items = cache ? resolveTableSlicerItems(cache, tableIndex, styles, sharedStrings, date1904) : [];
213
+ return {
214
+ caption: def.caption,
215
+ columnCount: def.columnCount,
216
+ items,
217
+ ...resolveSlicerStyle(def.styleName, palette)
218
+ };
219
+ }
220
+ function resolveTableSlicerItems(cache, tableIndex, styles, sharedStrings, date1904) {
221
+ const loc = cache.tableId !== void 0 ? tableIndex.get(cache.tableId) : void 0;
222
+ if (!loc || cache.columnId === void 0) return [];
223
+ const colOffset = cache.columnId - 1;
224
+ const absCol = loc.ref.startColumn + colOffset;
225
+ if (absCol < loc.ref.startColumn || absCol > loc.ref.endColumn) return [];
226
+ const firstDataRow = loc.ref.startRow + Math.max(1, loc.headerRows);
227
+ const filter = loc.filters.find((f) => f.colId === colOffset);
228
+ const kept = filter ? new Set(filter.values) : void 0;
229
+ const byRow = /* @__PURE__ */ new Map();
230
+ for (const c of loc.cells) {
231
+ if (c.column !== absCol || c.row < firstDataRow || c.row > loc.ref.endRow) continue;
232
+ byRow.set(c.row, resolveCellText(c, sharedStrings, styles, date1904));
233
+ }
234
+ const seen = /* @__PURE__ */ new Set();
235
+ const out = [];
236
+ for (const row of [...byRow.keys()].sort((a, b) => a - b)) {
237
+ const label = byRow.get(row);
238
+ if (label === "" || seen.has(label)) continue;
239
+ seen.add(label);
240
+ out.push({
241
+ label,
242
+ selected: kept ? kept.has(label) : true
243
+ });
244
+ if (out.length >= MAX_SLICER_ITEMS) break;
245
+ }
246
+ return out;
247
+ }
248
+ function resolveSlicerStyle(styleName, palette) {
249
+ const m = styleName ? /SlicerStyle[A-Za-z]*?(\d+)/i.exec(styleName) : null;
250
+ const column = m ? (Number(m[1]) - 1) % 7 : 1;
251
+ const base = column === 0 ? "7F7F7F" : palette.get(`accent${column}`) ?? "4472C4";
252
+ return {
253
+ headerHex: base,
254
+ headerTextHex: "FFFFFF",
255
+ selectedHex: base,
256
+ selectedTextHex: "FFFFFF"
257
+ };
258
+ }
166
259
  function lighten(hex, amount) {
167
260
  const m = /^#?([0-9a-fA-F]{6})$/.exec(hex);
168
261
  const n = m ? parseInt(m[1], 16) : 4485828;
@@ -200,7 +200,7 @@ function worksheetXml(grid, tableRelIds = [], drawingRelId) {
200
200
  return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
201
201
  }).join("");
202
202
  const mergesXml = grid.merges.length > 0 ? `<mergeCells count="${grid.merges.length}">${grid.merges.map((m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`).join("")}</mergeCells>` : "";
203
- return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
203
+ return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + dataValidationsXml(grid.dataValidations) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
204
204
  }
205
205
  function sheetViewsXml(pane) {
206
206
  if (!pane || pane.frozenRows <= 0 && pane.frozenCols <= 0) return "";
@@ -235,6 +235,14 @@ function cfRuleXml(rule) {
235
235
  function cfvoXml(cfvo) {
236
236
  return `<cfvo type="${cfvo.type}"${cfvo.val !== void 0 ? ` val="${escapeAttr(cfvo.val)}"` : ""}/>`;
237
237
  }
238
+ function dataValidationsXml(dvs) {
239
+ if (!dvs || dvs.length === 0) return "";
240
+ return `<dataValidations count="${dvs.length}">${dvs.map(dataValidationXml).join("")}</dataValidations>`;
241
+ }
242
+ function dataValidationXml(dv) {
243
+ const sqref = dv.ranges.map(rangeRef).join(" ");
244
+ return `<dataValidation${(dv.type !== "none" ? ` type="${dv.type}"` : "") + (dv.operator !== void 0 ? ` operator="${escapeAttr(dv.operator)}"` : "") + (dv.allowBlank ? " allowBlank=\"1\"" : "") + (dv.showDropDown ? " showDropDown=\"1\"" : "") + (dv.showInputMessage ? " showInputMessage=\"1\"" : "") + (dv.showErrorMessage ? " showErrorMessage=\"1\"" : "") + (dv.errorStyle !== void 0 ? ` errorStyle="${escapeAttr(dv.errorStyle)}"` : "") + (dv.promptTitle !== void 0 ? ` promptTitle="${escapeAttr(dv.promptTitle)}"` : "") + (dv.prompt !== void 0 ? ` prompt="${escapeAttr(dv.prompt)}"` : "") + (dv.errorTitle !== void 0 ? ` errorTitle="${escapeAttr(dv.errorTitle)}"` : "") + (dv.error !== void 0 ? ` error="${escapeAttr(dv.error)}"` : "") + ` sqref="${sqref}"`}>${(dv.formula1 !== void 0 ? `<formula1>${escapeText(dv.formula1)}</formula1>` : "") + (dv.formula2 !== void 0 ? `<formula2>${escapeText(dv.formula2)}</formula2>` : "")}</dataValidation>`;
245
+ }
238
246
  var X14_NS = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
239
247
  var XM_NS = "http://schemas.microsoft.com/office/excel/2006/main";
240
248
  function sparklineExtXml(sparklines) {
@@ -496,6 +496,7 @@ function emitCell(out, cell, table, ctx, pos) {
496
496
  if (!cell.properties.shading) css.push("background-color:#fff");
497
497
  }
498
498
  out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
499
+ if (cell.properties.dropdown) out.push(cellDropdownSvg());
499
500
  if (cell.properties.icon) out.push(cellIconSvg(cell.properties.icon));
500
501
  if (cell.properties.sparkline) out.push(cellSparklineSvg(cell.properties.sparkline));
501
502
  for (const child of cell.content) emitBlock(out, child, ctx);
@@ -514,6 +515,9 @@ function cellSparklineSvg(sp) {
514
515
  }
515
516
  return `<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" style="vertical-align:middle"><g transform="translate(0,${H}) scale(1,-1)">${parts.join("")}</g></svg>`;
516
517
  }
518
+ function cellDropdownSvg() {
519
+ return "<svg width=\"13\" height=\"13\" viewBox=\"0 0 13 13\" style=\"float:right;margin-left:3px\"><rect x=\"0.5\" y=\"0.5\" width=\"12\" height=\"12\" fill=\"#F0F0F0\" stroke=\"#B0B0B0\"/><polygon points=\"3.4,5 9.6,5 6.5,8.6\" fill=\"#595959\"/></svg>";
520
+ }
517
521
  var ICON_EMPTY_HEX = "BFBFBF";
518
522
  function cellIconSvg(icon) {
519
523
  const fill = `#${icon.colorHex}`;
@@ -668,6 +668,24 @@ function rectAtPath(x, y, w, h) {
668
668
  var CF_ICON_SIZE_PT = 9;
669
669
  var CF_ICON_GUTTER_PT = 12;
670
670
  var CF_ICON_EMPTY_HEX = "BFBFBF";
671
+ var DROPDOWN_BUTTON_PT = 11;
672
+ var DROPDOWN_GUTTER_PT = 13;
673
+ var DROPDOWN_FILL_HEX = "F0F0F0";
674
+ var DROPDOWN_BORDER_HEX = "B0B0B0";
675
+ var DROPDOWN_ARROW_HEX = "595959";
676
+ function buildDropdownPrims(s) {
677
+ return [{
678
+ paths: [rectAtPath(0, 0, s, s)],
679
+ fillColorHex: DROPDOWN_FILL_HEX,
680
+ stroke: {
681
+ colorHex: DROPDOWN_BORDER_HEX,
682
+ widthPt: .5
683
+ }
684
+ }, {
685
+ paths: [trianglePath([s / 2, s * .34], [s * .26, s * .64], [s * .74, s * .64])],
686
+ fillColorHex: DROPDOWN_ARROW_HEX
687
+ }];
688
+ }
671
689
  function buildCellIconShape(icon, s) {
672
690
  const color = icon.colorHex;
673
691
  switch (icon.shape) {
@@ -1671,8 +1689,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
1671
1689
  const padTopPt = cellMar?.top ?? tableMar?.top ?? 0;
1672
1690
  const padBottomPt = cellMar?.bottom ?? tableMar?.bottom ?? 0;
1673
1691
  const padLeftBase = cellMar?.left ?? tableMar?.left ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1674
- const padRightPt = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1692
+ const padRightBase = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1675
1693
  const padLeftPt = padLeftBase + (cell.properties.icon ? CF_ICON_GUTTER_PT : 0);
1694
+ const padRightPt = padRightBase + (cell.properties.dropdown ? DROPDOWN_GUTTER_PT : 0);
1676
1695
  const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
1677
1696
  const lines = [];
1678
1697
  const nestedTables = [];
@@ -1705,6 +1724,7 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
1705
1724
  ...cell.properties.dataBar ? { dataBar: cell.properties.dataBar } : {},
1706
1725
  ...cell.properties.icon ? { icon: cell.properties.icon } : {},
1707
1726
  ...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
1727
+ ...cell.properties.dropdown ? { dropdown: true } : {},
1708
1728
  lines,
1709
1729
  ...nestedTables.length > 0 ? { nestedTables } : {},
1710
1730
  contentHeightPt,
@@ -2362,6 +2382,28 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
2362
2382
  });
2363
2383
  }
2364
2384
  }
2385
+ if (cell.dropdown && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2386
+ const btn = Math.min(DROPDOWN_BUTTON_PT, Math.max(0, row.heightPt - 2));
2387
+ if (btn > 1) {
2388
+ const btnTransform = flipTransform([
2389
+ 1,
2390
+ 0,
2391
+ 0,
2392
+ 1,
2393
+ cellX + cell.widthPt - btn - 1.5,
2394
+ rowBottom + (row.heightPt - btn) / 2
2395
+ ], pageHeight);
2396
+ for (const prim of buildDropdownPrims(btn)) out.push({
2397
+ type: "shape",
2398
+ shape: {
2399
+ paths: prim.paths,
2400
+ ...prim.fillColorHex ? { fillColorHex: prim.fillColorHex } : {},
2401
+ ...prim.stroke ? { stroke: prim.stroke } : {},
2402
+ transform: btnTransform
2403
+ }
2404
+ });
2405
+ }
2406
+ }
2365
2407
  if (cell.sparkline && cell.sparkline.values.length > 0 && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2366
2408
  const inset = 1.5;
2367
2409
  const sw = cell.widthPt - 2 * inset;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
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>",