reamkit 1.4.0 → 1.5.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/README.md +8 -6
- package/dist/esm/core/converter/facade.d.ts +12 -6
- package/dist/esm/core/converter/facade.js +28 -4
- package/dist/esm/core/converter/ream.d.ts +5 -3
- package/dist/esm/core/converter/ream.js +16 -3
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +18 -0
- package/dist/esm/core/drawingml/sparkline-geometry.d.ts +8 -0
- package/dist/esm/core/drawingml/sparkline-geometry.js +111 -0
- package/dist/esm/core/ir/adapters.d.ts +5 -5
- package/dist/esm/core/ir/sheet.d.ts +31 -0
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -0
- package/dist/esm/core/spreadsheet-model/types.d.ts +184 -0
- package/dist/esm/excel/conditional-format.d.ts +16 -0
- package/dist/esm/excel/conditional-format.js +305 -0
- package/dist/esm/excel/index.d.ts +4 -3
- package/dist/esm/excel/print-model.d.ts +2 -1
- package/dist/esm/excel/print-model.js +114 -2
- package/dist/esm/excel/sheet-drawing.d.ts +1 -1
- package/dist/esm/excel/sheet-to-flow.d.ts +3 -0
- package/dist/esm/excel/sheet-to-flow.js +51 -0
- package/dist/esm/excel/styles-parser.d.ts +1 -50
- package/dist/esm/excel/styles-parser.js +38 -1
- package/dist/esm/excel/table-parser.d.ts +2 -0
- package/dist/esm/excel/table-parser.js +51 -0
- package/dist/esm/excel/workbook-parser.d.ts +1 -5
- package/dist/esm/excel/worksheet-parser.d.ts +1 -60
- package/dist/esm/excel/worksheet-parser.js +239 -1
- package/dist/esm/excel/xlsx-reader.d.ts +3 -1
- package/dist/esm/excel/xlsx-reader.js +86 -51
- package/dist/esm/excel/xlsx-writer.d.ts +4 -0
- package/dist/esm/excel/xlsx-writer.js +271 -0
- package/dist/esm/html/html-writer.js +27 -0
- package/dist/esm/layout/styled-layout.js +91 -1
- package/package.json +4 -3
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
//#region src/excel/conditional-format.ts
|
|
2
|
+
function buildConditionalFormatter(conditionalFormats, styles, cells) {
|
|
3
|
+
if (!conditionalFormats || conditionalFormats.length === 0) return void 0;
|
|
4
|
+
const dxfs = styles.dxfs ?? [];
|
|
5
|
+
const flat = [];
|
|
6
|
+
for (const cf of conditionalFormats) for (const rule of cf.rules) if (rule.type === "colorScale") flat.push({
|
|
7
|
+
ranges: cf.ranges,
|
|
8
|
+
rule,
|
|
9
|
+
scale: resolveColorScale(rule, cf.ranges, cells)
|
|
10
|
+
});
|
|
11
|
+
else if (rule.type === "dataBar") flat.push({
|
|
12
|
+
ranges: cf.ranges,
|
|
13
|
+
rule,
|
|
14
|
+
bar: resolveDataBar(rule, cf.ranges, cells)
|
|
15
|
+
});
|
|
16
|
+
else if (rule.type === "iconSet") flat.push({
|
|
17
|
+
ranges: cf.ranges,
|
|
18
|
+
rule,
|
|
19
|
+
iconThresholds: resolveIconThresholds(rule, cf.ranges, cells)
|
|
20
|
+
});
|
|
21
|
+
else flat.push({
|
|
22
|
+
ranges: cf.ranges,
|
|
23
|
+
rule
|
|
24
|
+
});
|
|
25
|
+
flat.sort((a, b) => a.rule.priority - b.rule.priority);
|
|
26
|
+
return (row, col, value) => {
|
|
27
|
+
if (value === void 0) return void 0;
|
|
28
|
+
let textFmt;
|
|
29
|
+
let textClaimed = false;
|
|
30
|
+
let bar;
|
|
31
|
+
let icon;
|
|
32
|
+
for (const { ranges, rule, scale, bar: resolved, iconThresholds } of flat) {
|
|
33
|
+
if (!coversCell(ranges, row, col)) continue;
|
|
34
|
+
switch (rule.type) {
|
|
35
|
+
case "cellIs":
|
|
36
|
+
if (!textClaimed && cellIsMatches(rule.operator, value, rule.formulas)) {
|
|
37
|
+
textFmt = dxfToOverride(dxfs[rule.dxfId]);
|
|
38
|
+
textClaimed = true;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
case "colorScale":
|
|
42
|
+
if (!textClaimed && scale) {
|
|
43
|
+
textFmt = { fillHex: rgbHex(colorScaleColor(scale, value)) };
|
|
44
|
+
textClaimed = true;
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case "dataBar":
|
|
48
|
+
if (!bar && resolved) bar = dataBarBar(resolved, value);
|
|
49
|
+
break;
|
|
50
|
+
case "iconSet":
|
|
51
|
+
if (!icon && iconThresholds) {
|
|
52
|
+
const bucket = iconBucket(iconThresholds, value);
|
|
53
|
+
icon = iconToCell(rule.iconSet, iconThresholds.length, bucket, rule.reverse ?? false);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!bar && !icon) return textFmt;
|
|
59
|
+
return {
|
|
60
|
+
...textFmt ?? {},
|
|
61
|
+
...bar ? { dataBar: bar } : {},
|
|
62
|
+
...icon ? { icon } : {}
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function coversCell(ranges, row, col) {
|
|
67
|
+
for (const r of ranges) if (row >= r.startRow && row <= r.endRow && col >= r.startColumn && col <= r.endColumn) return true;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function cellIsMatches(operator, value, formulas) {
|
|
71
|
+
const a = Number(formulas[0]);
|
|
72
|
+
if (!Number.isFinite(a)) return false;
|
|
73
|
+
switch (operator) {
|
|
74
|
+
case "lessThan": return value < a;
|
|
75
|
+
case "lessThanOrEqual": return value <= a;
|
|
76
|
+
case "equal": return value === a;
|
|
77
|
+
case "notEqual": return value !== a;
|
|
78
|
+
case "greaterThanOrEqual": return value >= a;
|
|
79
|
+
case "greaterThan": return value > a;
|
|
80
|
+
case "between":
|
|
81
|
+
case "notBetween": {
|
|
82
|
+
const b = Number(formulas[1]);
|
|
83
|
+
if (!Number.isFinite(b)) return false;
|
|
84
|
+
const inside = value >= Math.min(a, b) && value <= Math.max(a, b);
|
|
85
|
+
return operator === "between" ? inside : !inside;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function dxfToOverride(dxf) {
|
|
90
|
+
if (!dxf) return void 0;
|
|
91
|
+
const fillHex = dxf.fill?.bgColorHex ?? dxf.fill?.fgColorHex;
|
|
92
|
+
const out = {};
|
|
93
|
+
if (fillHex) out.fillHex = fillHex;
|
|
94
|
+
if (dxf.font?.colorHex) out.fontColorHex = dxf.font.colorHex;
|
|
95
|
+
if (dxf.font?.bold !== void 0) out.bold = dxf.font.bold;
|
|
96
|
+
if (dxf.font?.italic !== void 0) out.italic = dxf.font.italic;
|
|
97
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
98
|
+
}
|
|
99
|
+
function collectRangeValues(cells, ranges) {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const cell of cells) {
|
|
102
|
+
if (cell.type !== "n") continue;
|
|
103
|
+
const n = Number(cell.rawValue);
|
|
104
|
+
if (!Number.isFinite(n)) continue;
|
|
105
|
+
if (coversCell(ranges, cell.row, cell.column)) out.push(n);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function resolveColorScale(rule, ranges, cells) {
|
|
110
|
+
const vals = collectRangeValues(cells, ranges);
|
|
111
|
+
if (vals.length === 0) return void 0;
|
|
112
|
+
const sorted = [...vals].sort((a, b) => a - b);
|
|
113
|
+
const vmin = sorted[0];
|
|
114
|
+
const vmax = sorted[sorted.length - 1];
|
|
115
|
+
const thresholds = [];
|
|
116
|
+
for (const cfvo of rule.cfvos) {
|
|
117
|
+
const t = resolveCfvo(cfvo, vmin, vmax, sorted);
|
|
118
|
+
if (t === void 0) return void 0;
|
|
119
|
+
thresholds.push(t);
|
|
120
|
+
}
|
|
121
|
+
const colors = [];
|
|
122
|
+
for (const hex of rule.colorsHex) {
|
|
123
|
+
const rgb = parseRgb(hex);
|
|
124
|
+
if (!rgb) return void 0;
|
|
125
|
+
colors.push(rgb);
|
|
126
|
+
}
|
|
127
|
+
if (thresholds.length < 2 || thresholds.length !== colors.length) return void 0;
|
|
128
|
+
for (let i = 1; i < thresholds.length; i++) if (thresholds[i] < thresholds[i - 1]) thresholds[i] = thresholds[i - 1];
|
|
129
|
+
return {
|
|
130
|
+
thresholds,
|
|
131
|
+
colors
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function resolveCfvo(cfvo, vmin, vmax, sorted) {
|
|
135
|
+
switch (cfvo.type) {
|
|
136
|
+
case "min":
|
|
137
|
+
case "autoMin": return vmin;
|
|
138
|
+
case "max":
|
|
139
|
+
case "autoMax": return vmax;
|
|
140
|
+
case "num":
|
|
141
|
+
case "formula": {
|
|
142
|
+
const n = Number(cfvo.val);
|
|
143
|
+
return Number.isFinite(n) ? n : void 0;
|
|
144
|
+
}
|
|
145
|
+
case "percent": {
|
|
146
|
+
const p = Number(cfvo.val);
|
|
147
|
+
return Number.isFinite(p) ? vmin + (vmax - vmin) * (p / 100) : void 0;
|
|
148
|
+
}
|
|
149
|
+
case "percentile": {
|
|
150
|
+
const p = Number(cfvo.val);
|
|
151
|
+
return Number.isFinite(p) ? percentile(sorted, p) : void 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function percentile(sorted, p) {
|
|
156
|
+
const n = sorted.length;
|
|
157
|
+
if (n === 1) return sorted[0];
|
|
158
|
+
const rank = Math.min(100, Math.max(0, p)) / 100 * (n - 1);
|
|
159
|
+
const lo = Math.floor(rank);
|
|
160
|
+
const frac = rank - lo;
|
|
161
|
+
const a = sorted[lo];
|
|
162
|
+
return a + frac * (sorted[Math.min(lo + 1, n - 1)] - a);
|
|
163
|
+
}
|
|
164
|
+
function colorScaleColor(scale, value) {
|
|
165
|
+
const { thresholds: t, colors } = scale;
|
|
166
|
+
const last = t.length - 1;
|
|
167
|
+
if (value <= t[0]) return colors[0];
|
|
168
|
+
if (value >= t[last]) return colors[last];
|
|
169
|
+
let i = 0;
|
|
170
|
+
while (i < last && value > t[i + 1]) i++;
|
|
171
|
+
const a = t[i];
|
|
172
|
+
const b = t[i + 1];
|
|
173
|
+
const f = b > a ? (value - a) / (b - a) : 0;
|
|
174
|
+
return lerpRgb(colors[i], colors[i + 1], f);
|
|
175
|
+
}
|
|
176
|
+
function lerpRgb(c0, c1, f) {
|
|
177
|
+
return [
|
|
178
|
+
c0[0] + (c1[0] - c0[0]) * f,
|
|
179
|
+
c0[1] + (c1[1] - c0[1]) * f,
|
|
180
|
+
c0[2] + (c1[2] - c0[2]) * f
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
function parseRgb(hex) {
|
|
184
|
+
const h = hex.length === 8 ? hex.slice(2) : hex;
|
|
185
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(h)) return void 0;
|
|
186
|
+
return [
|
|
187
|
+
parseInt(h.slice(0, 2), 16),
|
|
188
|
+
parseInt(h.slice(2, 4), 16),
|
|
189
|
+
parseInt(h.slice(4, 6), 16)
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
function rgbHex([r, g, b]) {
|
|
193
|
+
const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0").toUpperCase();
|
|
194
|
+
return `${h(r)}${h(g)}${h(b)}`;
|
|
195
|
+
}
|
|
196
|
+
function resolveDataBar(rule, ranges, cells) {
|
|
197
|
+
const vals = collectRangeValues(cells, ranges);
|
|
198
|
+
if (vals.length === 0) return void 0;
|
|
199
|
+
const sorted = [...vals].sort((a, b) => a - b);
|
|
200
|
+
const vmin = sorted[0];
|
|
201
|
+
const vmax = sorted[sorted.length - 1];
|
|
202
|
+
const lower = resolveCfvo(rule.cfvos[0], vmin, vmax, sorted);
|
|
203
|
+
const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted);
|
|
204
|
+
if (lower === void 0 || upper === void 0) return void 0;
|
|
205
|
+
return {
|
|
206
|
+
lower,
|
|
207
|
+
upper,
|
|
208
|
+
colorHex: rule.colorHex,
|
|
209
|
+
negativeColorHex: "FF0000",
|
|
210
|
+
minLen: (rule.minLength ?? 0) / 100,
|
|
211
|
+
maxLen: (rule.maxLength ?? 100) / 100
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function dataBarBar(bar, value) {
|
|
215
|
+
if (bar.lower < 0 && bar.upper > 0) {
|
|
216
|
+
const axis = -bar.lower / (bar.upper - bar.lower);
|
|
217
|
+
if (value >= 0) return {
|
|
218
|
+
fraction: value / bar.upper * (1 - axis),
|
|
219
|
+
colorHex: bar.colorHex,
|
|
220
|
+
startFraction: axis
|
|
221
|
+
};
|
|
222
|
+
const w = Math.abs(value) / Math.abs(bar.lower) * axis;
|
|
223
|
+
return {
|
|
224
|
+
fraction: w,
|
|
225
|
+
colorHex: bar.negativeColorHex,
|
|
226
|
+
startFraction: axis - w
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
fraction: dataBarFraction(bar, value),
|
|
231
|
+
colorHex: bar.colorHex
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function dataBarFraction(bar, value) {
|
|
235
|
+
const t = bar.upper > bar.lower ? Math.min(1, Math.max(0, (value - bar.lower) / (bar.upper - bar.lower))) : value >= bar.upper ? 1 : 0;
|
|
236
|
+
return bar.minLen + t * (bar.maxLen - bar.minLen);
|
|
237
|
+
}
|
|
238
|
+
function resolveIconThresholds(rule, ranges, cells) {
|
|
239
|
+
const vals = collectRangeValues(cells, ranges);
|
|
240
|
+
if (vals.length === 0) return void 0;
|
|
241
|
+
const sorted = [...vals].sort((a, b) => a - b);
|
|
242
|
+
const vmin = sorted[0];
|
|
243
|
+
const vmax = sorted[sorted.length - 1];
|
|
244
|
+
const out = [];
|
|
245
|
+
for (const cfvo of rule.cfvos) {
|
|
246
|
+
const t = resolveCfvo(cfvo, vmin, vmax, sorted);
|
|
247
|
+
if (t === void 0) return void 0;
|
|
248
|
+
out.push(t);
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
function iconBucket(thresholds, value) {
|
|
253
|
+
let idx = 0;
|
|
254
|
+
for (let i = 1; i < thresholds.length; i++) if (value >= thresholds[i]) idx++;
|
|
255
|
+
return idx;
|
|
256
|
+
}
|
|
257
|
+
function iconToCell(setName, count, index, reverse) {
|
|
258
|
+
const e = reverse ? count - 1 - index : index;
|
|
259
|
+
const colorHex = /Gray/i.test(setName) ? GRAY_HEX : iconColor(count, e);
|
|
260
|
+
return {
|
|
261
|
+
shape: iconShape(setName, e, count),
|
|
262
|
+
colorHex
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
var GRAY_HEX = "808080";
|
|
266
|
+
function iconShape(setName, e, count) {
|
|
267
|
+
if (setName.includes("Arrows")) {
|
|
268
|
+
if (e <= 0) return "triangleDown";
|
|
269
|
+
if (e >= count - 1) return "triangleUp";
|
|
270
|
+
return "triangleRight";
|
|
271
|
+
}
|
|
272
|
+
if (setName.includes("Signs")) {
|
|
273
|
+
if (e <= 0) return "diamond";
|
|
274
|
+
if (e >= count - 1) return "circle";
|
|
275
|
+
return "triangleUp";
|
|
276
|
+
}
|
|
277
|
+
if (setName.includes("Flags")) return "square";
|
|
278
|
+
return "circle";
|
|
279
|
+
}
|
|
280
|
+
var ICON_RAMPS = {
|
|
281
|
+
3: [
|
|
282
|
+
"FF0000",
|
|
283
|
+
"FFC000",
|
|
284
|
+
"00B050"
|
|
285
|
+
],
|
|
286
|
+
4: [
|
|
287
|
+
"FF0000",
|
|
288
|
+
"FF8C00",
|
|
289
|
+
"FFC000",
|
|
290
|
+
"00B050"
|
|
291
|
+
],
|
|
292
|
+
5: [
|
|
293
|
+
"FF0000",
|
|
294
|
+
"FF8C00",
|
|
295
|
+
"FFC000",
|
|
296
|
+
"92D050",
|
|
297
|
+
"00B050"
|
|
298
|
+
]
|
|
299
|
+
};
|
|
300
|
+
function iconColor(count, e) {
|
|
301
|
+
const ramp = ICON_RAMPS[count] ?? ICON_RAMPS[3];
|
|
302
|
+
return ramp[Math.max(0, Math.min(ramp.length - 1, e))];
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
export { buildConditionalFormatter };
|
|
@@ -4,9 +4,10 @@ export { parseAreaRef, parseTitleRowRange } from './defined-name-ref.js';
|
|
|
4
4
|
export type { CellRange } from './defined-name-ref.js';
|
|
5
5
|
export { parseSharedStrings } from './shared-strings-parser.js';
|
|
6
6
|
export { parseWorkbook } from './workbook-parser.js';
|
|
7
|
-
export type {
|
|
7
|
+
export type { ParsedWorkbook, SheetReference } from './workbook-parser.js';
|
|
8
|
+
export type { DefinedName } from '../core/spreadsheet-model/index.js';
|
|
8
9
|
export { parseWorksheet } from './worksheet-parser.js';
|
|
9
|
-
export type { CellType, ColumnWidth, MergedRange, ParsedWorksheet, RowHeight, WorksheetCell, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, } from '
|
|
10
|
+
export type { CellType, ColumnWidth, ExcelTable, MergedRange, ParsedWorksheet, RowHeight, WorksheetCell, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, } from '../core/spreadsheet-model/index.js';
|
|
10
11
|
export { parseXlsxStyles, EMPTY_XLSX_STYLES } from './styles-parser.js';
|
|
11
|
-
export type { XlsxBorder, XlsxBorderEdge, XlsxBorderStyleName, XlsxCellAlignment, XlsxCellXf, XlsxFill, XlsxFont, XlsxHorizontalAlign, XlsxStyles, XlsxVerticalAlign, } from '
|
|
12
|
+
export type { XlsxBorder, XlsxBorderEdge, XlsxBorderStyleName, XlsxCellAlignment, XlsxCellXf, XlsxFill, XlsxFont, XlsxHorizontalAlign, XlsxStyles, XlsxVerticalAlign, } from '../core/spreadsheet-model/index.js';
|
|
12
13
|
export { applyNumberFormat } from './number-format.js';
|
|
@@ -16,6 +16,7 @@ interface PrintModelOptions {
|
|
|
16
16
|
readonly startRow: number;
|
|
17
17
|
readonly endRow: number;
|
|
18
18
|
};
|
|
19
|
+
readonly sheetGrids?: ReadonlyMap<string, ParsedWorksheet>;
|
|
19
20
|
}
|
|
20
|
-
export declare function worksheetToBody(worksheet: ParsedWorksheet, sharedStrings:
|
|
21
|
+
export declare function worksheetToBody(worksheet: ParsedWorksheet, sharedStrings: ReadonlyArray<string>, styles: XlsxStyles, date1904: boolean, print: PrintModelOptions): Array<BodyElement>;
|
|
21
22
|
export {};
|
|
@@ -2,6 +2,7 @@ import { eighthPtToPt, halfPtToPt, 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";
|
|
5
|
+
import { buildConditionalFormatter } from "./conditional-format.js";
|
|
5
6
|
var TWIPS_PER_POINT = 20;
|
|
6
7
|
var TWIPS_PER_INCH = 1440;
|
|
7
8
|
var PAPER_SIZES_TWIPS = new Map([
|
|
@@ -107,6 +108,12 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
107
108
|
if (m.endRow > usedRow) usedRow = m.endRow;
|
|
108
109
|
if (m.endColumn > usedCol) usedCol = m.endColumn;
|
|
109
110
|
}
|
|
111
|
+
for (const sp of worksheet.sparklines ?? []) {
|
|
112
|
+
const host = parseAreaRef(sp.sqref);
|
|
113
|
+
if (!host) continue;
|
|
114
|
+
if (host.startRow > usedRow) usedRow = host.startRow;
|
|
115
|
+
if (host.startColumn > usedCol) usedCol = host.startColumn;
|
|
116
|
+
}
|
|
110
117
|
if (usedRow < 0 || usedCol < 0) return [];
|
|
111
118
|
let rowStart = 0;
|
|
112
119
|
let colStart = 0;
|
|
@@ -184,6 +191,9 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
184
191
|
}
|
|
185
192
|
return props;
|
|
186
193
|
};
|
|
194
|
+
const cfFormatter = buildConditionalFormatter(worksheet.conditionalFormats, styles, worksheet.cells);
|
|
195
|
+
const sparklineByCell = buildSparklineLookup(worksheet, print);
|
|
196
|
+
const tableFormatByCell = buildTableFormatLookup(worksheet);
|
|
187
197
|
const rows = [];
|
|
188
198
|
for (let r = 0; r < rowCount; r++) {
|
|
189
199
|
const absR = r + rowStart;
|
|
@@ -201,10 +211,28 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
201
211
|
if (text.length > textBudget) text = text.slice(0, Math.max(0, textBudget));
|
|
202
212
|
textBudget -= text.length;
|
|
203
213
|
const xf = ws && ws.styleIndex !== void 0 ? styles.cellXfs[ws.styleIndex] : void 0;
|
|
204
|
-
|
|
214
|
+
let runProps = cellRunProps(xf);
|
|
205
215
|
const alignment = xf ? alignmentFromXf(xf) : void 0;
|
|
206
|
-
|
|
216
|
+
let shading = xf ? shadingFromXf(xf, styles) : void 0;
|
|
217
|
+
const tableFmt = tableFormatByCell.get(key(absR, absC));
|
|
218
|
+
if (!shading && tableFmt?.shading) shading = tableFmt.shading;
|
|
219
|
+
if (tableFmt?.fontColorHex) runProps = {
|
|
220
|
+
...runProps,
|
|
221
|
+
colorHex: tableFmt.fontColorHex
|
|
222
|
+
};
|
|
207
223
|
const borders = xf ? bordersFromXf(xf, styles) : void 0;
|
|
224
|
+
let dataBar;
|
|
225
|
+
let icon;
|
|
226
|
+
const sparkline = sparklineByCell.get(key(absR, absC));
|
|
227
|
+
if (cfFormatter && ws) {
|
|
228
|
+
const over = cfFormatter(absR, absC, ws.type === "n" && Number.isFinite(Number(ws.rawValue)) ? Number(ws.rawValue) : void 0);
|
|
229
|
+
if (over) {
|
|
230
|
+
if (over.fillHex) shading = { colorHex: over.fillHex };
|
|
231
|
+
if (over.dataBar) dataBar = over.dataBar;
|
|
232
|
+
if (over.icon) icon = over.icon;
|
|
233
|
+
runProps = applyCfOverride(runProps, over);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
208
236
|
if (text.length > 0 && !merge && ws && (ws.type === "s" || ws.type === "str" || ws.type === "inlineStr") && (alignment === void 0 || alignment === "left")) {
|
|
209
237
|
let availTwips = columnWidths[c];
|
|
210
238
|
let cc = c + 1;
|
|
@@ -222,6 +250,9 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
222
250
|
...merge && visibleEndCol > merge.startColumn ? { colSpan: visibleEndCol - merge.startColumn + 1 } : {},
|
|
223
251
|
...merge && Math.min(merge.endRow, rowWindowEnd) > merge.startRow ? { merge: "start" } : {},
|
|
224
252
|
...shading ? { shading } : {},
|
|
253
|
+
...dataBar ? { dataBar } : {},
|
|
254
|
+
...icon ? { icon } : {},
|
|
255
|
+
...sparkline ? { sparkline } : {},
|
|
225
256
|
...borders ? { borders } : {}
|
|
226
257
|
};
|
|
227
258
|
const paragraphProps = cellParaProps(alignment);
|
|
@@ -321,6 +352,14 @@ function runPropsFromXf(xf, styles) {
|
|
|
321
352
|
if (font.colorHex) props.colorHex = font.colorHex;
|
|
322
353
|
return props;
|
|
323
354
|
}
|
|
355
|
+
function applyCfOverride(base, o) {
|
|
356
|
+
return {
|
|
357
|
+
...base,
|
|
358
|
+
...o.fontColorHex ? { colorHex: o.fontColorHex } : {},
|
|
359
|
+
...o.bold !== void 0 ? { bold: o.bold } : {},
|
|
360
|
+
...o.italic !== void 0 ? { italic: o.italic } : {}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
324
363
|
function alignmentFromXf(xf) {
|
|
325
364
|
const align = xf.alignment;
|
|
326
365
|
if (!align) return void 0;
|
|
@@ -413,6 +452,79 @@ function mapBorderStyle(style) {
|
|
|
413
452
|
function key(row, col) {
|
|
414
453
|
return `${row},${col}`;
|
|
415
454
|
}
|
|
455
|
+
function buildSparklineLookup(worksheet, print) {
|
|
456
|
+
const out = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const sp of worksheet.sparklines ?? []) {
|
|
458
|
+
const host = parseAreaRef(sp.sqref);
|
|
459
|
+
const area = parseAreaRef(sp.dataRange);
|
|
460
|
+
if (!host || !area) continue;
|
|
461
|
+
const values = collectSeriesValues(resolveSeriesGrid(sp.dataRange, worksheet, print.sheetGrids).cells, area);
|
|
462
|
+
if (values.length === 0 || values.every((v) => v === null)) continue;
|
|
463
|
+
out.set(key(host.startRow, host.startColumn), {
|
|
464
|
+
kind: sp.kind,
|
|
465
|
+
values,
|
|
466
|
+
...sp.colorHex ? { colorHex: sp.colorHex } : {}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
function resolveSeriesGrid(dataRange, current, sheetGrids) {
|
|
472
|
+
if (!sheetGrids) return current;
|
|
473
|
+
const firstToken = dataRange.split(",")[0] ?? dataRange;
|
|
474
|
+
const bang = firstToken.lastIndexOf("!");
|
|
475
|
+
if (bang < 0) return current;
|
|
476
|
+
let name = firstToken.slice(0, bang).trim();
|
|
477
|
+
if (name.startsWith("'") && name.endsWith("'")) name = name.slice(1, -1).replace(/''/g, "'");
|
|
478
|
+
return sheetGrids.get(name) ?? current;
|
|
479
|
+
}
|
|
480
|
+
var MAX_SPARKLINE_POINTS = 1e3;
|
|
481
|
+
function collectSeriesValues(cells, area) {
|
|
482
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const c of cells) {
|
|
484
|
+
if (c.row < area.startRow || c.row > area.endRow) continue;
|
|
485
|
+
if (c.column < area.startColumn || c.column > area.endColumn) continue;
|
|
486
|
+
if (c.type !== "n") continue;
|
|
487
|
+
const v = Number(c.rawValue);
|
|
488
|
+
if (Number.isFinite(v)) byKey.set(key(c.row, c.column), v);
|
|
489
|
+
}
|
|
490
|
+
if ((area.endRow - area.startRow + 1) * (area.endColumn - area.startColumn + 1) > MAX_SPARKLINE_POINTS) {
|
|
491
|
+
const pts = [...byKey.entries()].map(([k, v]) => {
|
|
492
|
+
const [r, col] = k.split(",").map(Number);
|
|
493
|
+
return {
|
|
494
|
+
r,
|
|
495
|
+
col,
|
|
496
|
+
v
|
|
497
|
+
};
|
|
498
|
+
});
|
|
499
|
+
pts.sort((a, b) => a.r - b.r || a.col - b.col);
|
|
500
|
+
return pts.map((p) => p.v);
|
|
501
|
+
}
|
|
502
|
+
const out = [];
|
|
503
|
+
for (let r = area.startRow; r <= area.endRow; r++) for (let col = area.startColumn; col <= area.endColumn; col++) out.push(byKey.get(key(r, col)) ?? null);
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
function buildTableFormatLookup(worksheet) {
|
|
507
|
+
const out = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const t of worksheet.tables ?? []) {
|
|
509
|
+
const { ref } = t;
|
|
510
|
+
const firstDataRow = ref.startRow + t.headerRowCount;
|
|
511
|
+
for (let r = ref.startRow; r <= ref.endRow; r++) {
|
|
512
|
+
let colorHex;
|
|
513
|
+
let fontColorHex;
|
|
514
|
+
if (r < firstDataRow) {
|
|
515
|
+
colorHex = t.headerHex;
|
|
516
|
+
fontColorHex = t.headerTextHex;
|
|
517
|
+
} else if (t.showRowStripes && t.bandHex) colorHex = (r - firstDataRow) % 2 === 1 ? t.bandHex : void 0;
|
|
518
|
+
if (!colorHex && !fontColorHex) continue;
|
|
519
|
+
const fmt = {
|
|
520
|
+
...colorHex ? { shading: { colorHex } } : {},
|
|
521
|
+
...fontColorHex ? { fontColorHex } : {}
|
|
522
|
+
};
|
|
523
|
+
for (let c = ref.startColumn; c <= ref.endColumn; c++) out.set(key(r, c), fmt);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return out;
|
|
527
|
+
}
|
|
416
528
|
function cellHasContent(cell) {
|
|
417
529
|
return !!cell && (cell.rawValue !== "" || cell.inlineText !== void 0);
|
|
418
530
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OpcPackage } from '../core/opc/index.js';
|
|
2
|
-
import { ParsedWorksheet } from '
|
|
2
|
+
import { ParsedWorksheet } from '../core/spreadsheet-model/index.js';
|
|
3
3
|
export interface SheetChartRef {
|
|
4
4
|
readonly chartPartPath: string;
|
|
5
5
|
readonly widthPt: number;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
2
|
+
import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
|
|
3
|
+
import "../core/style-cascade/index.js";
|
|
4
|
+
import { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, worksheetToBody } from "./print-model.js";
|
|
5
|
+
//#region src/excel/sheet-to-flow.ts
|
|
6
|
+
function projectSheetDoc(sheet) {
|
|
7
|
+
const body = [];
|
|
8
|
+
let firstSheetSection;
|
|
9
|
+
const sheetGrids = new Map(sheet.sheets.map((s) => [s.name, s.grid]));
|
|
10
|
+
for (let sheetIdx = 0; sheetIdx < sheet.sheets.length; sheetIdx++) {
|
|
11
|
+
const ws = sheet.sheets[sheetIdx];
|
|
12
|
+
if (sheetIdx === 0) firstSheetSection = sectionFromWorksheet(ws.grid);
|
|
13
|
+
if (sheetIdx > 0) body.push({
|
|
14
|
+
kind: "paragraph",
|
|
15
|
+
paragraph: {
|
|
16
|
+
properties: { pageBreakBefore: true },
|
|
17
|
+
runs: []
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const printArea = resolvePrintArea(sheet.definedNames, sheetIdx);
|
|
21
|
+
const titleRows = resolvePrintTitleRows(sheet.definedNames, sheetIdx);
|
|
22
|
+
const gridLines = ws.grid.printOptions?.gridLines === true;
|
|
23
|
+
body.push(...worksheetToBody(ws.grid, sheet.sharedStrings, sheet.styles, sheet.date1904, {
|
|
24
|
+
...printArea ? { printArea } : {},
|
|
25
|
+
...titleRows ? { titleRows } : {},
|
|
26
|
+
gridLines,
|
|
27
|
+
sheetGrids
|
|
28
|
+
}));
|
|
29
|
+
for (const ref of ws.charts ?? []) body.push({
|
|
30
|
+
kind: "chart",
|
|
31
|
+
chart: {
|
|
32
|
+
chartRelId: ref.chartPartPath,
|
|
33
|
+
width: pt(ref.widthPt),
|
|
34
|
+
height: pt(ref.heightPt),
|
|
35
|
+
paragraphProperties: {}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
kind: "flow",
|
|
41
|
+
body: resolveBodyStyles(body, EMPTY_STYLE_SHEET),
|
|
42
|
+
sections: [],
|
|
43
|
+
...firstSheetSection ? { section: firstSheetSection } : {},
|
|
44
|
+
styles: EMPTY_STYLE_SHEET,
|
|
45
|
+
resources: sheet.resources,
|
|
46
|
+
...sheet.chartData && sheet.chartData.size > 0 ? { charts: sheet.chartData } : {},
|
|
47
|
+
...sheet.info ? { info: sheet.info } : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { projectSheetDoc };
|
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
readonly sizePt?: number;
|
|
3
|
-
readonly bold?: boolean;
|
|
4
|
-
readonly italic?: boolean;
|
|
5
|
-
readonly underline?: boolean;
|
|
6
|
-
readonly colorHex?: string;
|
|
7
|
-
readonly name?: string;
|
|
8
|
-
}
|
|
9
|
-
export interface XlsxFill {
|
|
10
|
-
readonly patternType?: string;
|
|
11
|
-
readonly fgColorHex?: string;
|
|
12
|
-
readonly bgColorHex?: string;
|
|
13
|
-
}
|
|
14
|
-
export type XlsxBorderStyleName = 'none' | 'thin' | 'medium' | 'thick' | 'hair' | 'dashed' | 'dotted' | 'double' | 'mediumDashed' | 'dashDot' | 'mediumDashDot' | 'dashDotDot' | 'mediumDashDotDot' | 'slantDashDot';
|
|
15
|
-
export interface XlsxBorderEdge {
|
|
16
|
-
readonly style?: XlsxBorderStyleName;
|
|
17
|
-
readonly colorHex?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface XlsxBorder {
|
|
20
|
-
readonly top?: XlsxBorderEdge;
|
|
21
|
-
readonly right?: XlsxBorderEdge;
|
|
22
|
-
readonly bottom?: XlsxBorderEdge;
|
|
23
|
-
readonly left?: XlsxBorderEdge;
|
|
24
|
-
}
|
|
25
|
-
export type XlsxHorizontalAlign = 'left' | 'center' | 'right' | 'fill' | 'justify' | 'centerContinuous' | 'distributed';
|
|
26
|
-
export type XlsxVerticalAlign = 'top' | 'center' | 'bottom' | 'justify' | 'distributed';
|
|
27
|
-
export interface XlsxCellAlignment {
|
|
28
|
-
readonly horizontal?: XlsxHorizontalAlign;
|
|
29
|
-
readonly vertical?: XlsxVerticalAlign;
|
|
30
|
-
readonly wrapText?: boolean;
|
|
31
|
-
}
|
|
32
|
-
export interface XlsxCellXf {
|
|
33
|
-
readonly numFmtId: number;
|
|
34
|
-
readonly fontId: number;
|
|
35
|
-
readonly fillId: number;
|
|
36
|
-
readonly borderId: number;
|
|
37
|
-
readonly applyNumberFormat?: boolean;
|
|
38
|
-
readonly applyFont?: boolean;
|
|
39
|
-
readonly applyFill?: boolean;
|
|
40
|
-
readonly applyBorder?: boolean;
|
|
41
|
-
readonly applyAlignment?: boolean;
|
|
42
|
-
readonly alignment?: XlsxCellAlignment;
|
|
43
|
-
}
|
|
44
|
-
export interface XlsxStyles {
|
|
45
|
-
readonly numFmts: ReadonlyMap<number, string>;
|
|
46
|
-
readonly fonts: ReadonlyArray<XlsxFont>;
|
|
47
|
-
readonly fills: ReadonlyArray<XlsxFill>;
|
|
48
|
-
readonly borders: ReadonlyArray<XlsxBorder>;
|
|
49
|
-
readonly cellXfs: ReadonlyArray<XlsxCellXf>;
|
|
50
|
-
}
|
|
1
|
+
import { XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
51
2
|
export declare const EMPTY_XLSX_STYLES: XlsxStyles;
|
|
52
3
|
export declare function parseXlsxStyles(data: Uint8Array): XlsxStyles;
|
|
@@ -21,14 +21,51 @@ function parseXlsxStyles(data) {
|
|
|
21
21
|
const xml = decoder.decode(data);
|
|
22
22
|
const root = asObject(parser.parse(xml)["styleSheet"]);
|
|
23
23
|
if (!root) return EMPTY_XLSX_STYLES;
|
|
24
|
+
const dxfs = parseDxfs(root);
|
|
24
25
|
return {
|
|
25
26
|
numFmts: parseNumFmts(root),
|
|
26
27
|
fonts: parseFonts(root),
|
|
27
28
|
fills: parseFills(root),
|
|
28
29
|
borders: parseBorders(root),
|
|
29
|
-
cellXfs: parseCellXfs(root)
|
|
30
|
+
cellXfs: parseCellXfs(root),
|
|
31
|
+
...dxfs.length > 0 ? { dxfs } : {}
|
|
30
32
|
};
|
|
31
33
|
}
|
|
34
|
+
function parseDxfs(root) {
|
|
35
|
+
const node = asObject(root["dxfs"]);
|
|
36
|
+
if (!node) return [];
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const item of asArray(node["dxf"])) {
|
|
39
|
+
const obj = asObject(item);
|
|
40
|
+
if (!obj) {
|
|
41
|
+
out.push({});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const dxf = {};
|
|
45
|
+
const fontObj = asObject(obj["font"]);
|
|
46
|
+
if (fontObj) {
|
|
47
|
+
const font = {};
|
|
48
|
+
if (hasChild(fontObj, "b")) font.bold = childToggle(fontObj, "b");
|
|
49
|
+
if (hasChild(fontObj, "i")) font.italic = childToggle(fontObj, "i");
|
|
50
|
+
const colorHex = colorOf(asObject(fontObj["color"]));
|
|
51
|
+
if (colorHex) font.colorHex = colorHex;
|
|
52
|
+
if (Object.keys(font).length > 0) dxf.font = font;
|
|
53
|
+
}
|
|
54
|
+
const pf = asObject(asObject(obj["fill"])?.["patternFill"]);
|
|
55
|
+
if (pf) {
|
|
56
|
+
const fill = {};
|
|
57
|
+
const pt = strAttr(pf, "patternType");
|
|
58
|
+
if (pt) fill.patternType = pt;
|
|
59
|
+
const fg = colorOf(asObject(pf["fgColor"]));
|
|
60
|
+
const bg = colorOf(asObject(pf["bgColor"]));
|
|
61
|
+
if (fg) fill.fgColorHex = fg;
|
|
62
|
+
if (bg) fill.bgColorHex = bg;
|
|
63
|
+
if (Object.keys(fill).length > 0) dxf.fill = fill;
|
|
64
|
+
}
|
|
65
|
+
out.push(dxf);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
32
69
|
var VALID_BORDER_STYLES = new Set([
|
|
33
70
|
"none",
|
|
34
71
|
"thin",
|