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.
- package/dist/esm/core/document-model/types.d.ts +1 -0
- package/dist/esm/core/ir/sheet.d.ts +14 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +18 -0
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/print-model.d.ts +5 -2
- package/dist/esm/excel/print-model.js +95 -3
- package/dist/esm/excel/sheet-to-flow.js +5 -1
- package/dist/esm/excel/slicer-parser.d.ts +15 -0
- package/dist/esm/excel/slicer-parser.js +76 -0
- package/dist/esm/excel/table-parser.d.ts +10 -0
- package/dist/esm/excel/table-parser.js +36 -4
- package/dist/esm/excel/worksheet-parser.js +56 -0
- package/dist/esm/excel/xlsx-reader.js +96 -3
- package/dist/esm/excel/xlsx-writer.js +9 -1
- package/dist/esm/html/html-writer.js +4 -0
- package/dist/esm/layout/styled-layout.js +43 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
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 {
|
|
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
|
|
79
|
-
if (
|
|
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
|
|
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.
|
|
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>",
|