reamkit 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/core/document-model/types.d.ts +3 -0
- package/dist/esm/core/ir/sheet.d.ts +49 -3
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +80 -1
- package/dist/esm/excel/comments-parser.d.ts +4 -0
- package/dist/esm/excel/comments-parser.js +104 -0
- package/dist/esm/excel/conditional-format.d.ts +2 -2
- package/dist/esm/excel/conditional-format.js +113 -11
- package/dist/esm/excel/form-control-parser.d.ts +6 -0
- package/dist/esm/excel/form-control-parser.js +29 -0
- package/dist/esm/excel/header-footer.d.ts +2 -0
- package/dist/esm/excel/header-footer.js +127 -0
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/print-model.d.ts +7 -2
- package/dist/esm/excel/print-model.js +218 -19
- package/dist/esm/excel/shared-strings-parser.d.ts +6 -1
- package/dist/esm/excel/shared-strings-parser.js +91 -12
- package/dist/esm/excel/sheet-drawing.d.ts +13 -1
- package/dist/esm/excel/sheet-drawing.js +47 -14
- package/dist/esm/excel/sheet-shape-parser.d.ts +4 -0
- package/dist/esm/excel/sheet-shape-parser.js +96 -0
- package/dist/esm/excel/sheet-to-flow.js +130 -3
- package/dist/esm/excel/slicer-parser.d.ts +15 -0
- package/dist/esm/excel/slicer-parser.js +76 -0
- package/dist/esm/excel/styles-parser.js +37 -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 +181 -1
- package/dist/esm/excel/xlsx-reader.js +203 -17
- package/dist/esm/excel/xlsx-writer.js +19 -3
- package/dist/esm/html/html-writer.js +4 -0
- package/dist/esm/layout/styled-layout.js +72 -1
- package/dist/esm/pptx/slide-parser.d.ts +4 -2
- package/dist/esm/pptx/slide-parser.js +1 -1
- package/package.json +1 -1
|
@@ -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, SheetRichRun, WorksheetCell, XlsxStyles } from './index.js';
|
|
3
|
+
import { SheetHyperlink, 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;
|
|
@@ -17,6 +18,10 @@ interface PrintModelOptions {
|
|
|
17
18
|
readonly endRow: number;
|
|
18
19
|
};
|
|
19
20
|
readonly sheetGrids?: ReadonlyMap<string, ParsedWorksheet>;
|
|
21
|
+
readonly hyperlinks?: ReadonlyArray<SheetHyperlink>;
|
|
22
|
+
readonly sharedStringRuns?: ReadonlyArray<ReadonlyArray<SheetRichRun> | undefined>;
|
|
20
23
|
}
|
|
21
24
|
export declare function worksheetToBody(worksheet: ParsedWorksheet, sharedStrings: ReadonlyArray<string>, styles: XlsxStyles, date1904: boolean, print: PrintModelOptions): Array<BodyElement>;
|
|
25
|
+
export declare function resolveCellText(cell: WorksheetCell, sharedStrings: ReadonlyArray<string>, styles: XlsxStyles, date1904: boolean): string;
|
|
26
|
+
export declare function slicerTable(slicer: SheetSlicer): Table;
|
|
22
27
|
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";
|
|
@@ -192,9 +192,10 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
192
192
|
}
|
|
193
193
|
return props;
|
|
194
194
|
};
|
|
195
|
-
const cfFormatter = buildConditionalFormatter(worksheet.conditionalFormats, styles, worksheet.cells);
|
|
195
|
+
const cfFormatter = buildConditionalFormatter(worksheet.conditionalFormats, styles, worksheet.cells, (cell) => resolveCellText(cell, sharedStrings, styles, date1904));
|
|
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;
|
|
@@ -209,6 +210,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
209
210
|
}
|
|
210
211
|
const ws = cellMatrix[r]?.[c];
|
|
211
212
|
let text = ws ? resolveCellText(ws, sharedStrings, styles, date1904) : "";
|
|
213
|
+
const cfText = text.length > 0 ? text : void 0;
|
|
212
214
|
if (text.length > textBudget) text = text.slice(0, Math.max(0, textBudget));
|
|
213
215
|
textBudget -= text.length;
|
|
214
216
|
const xf = ws && ws.styleIndex !== void 0 ? styles.cellXfs[ws.styleIndex] : void 0;
|
|
@@ -226,7 +228,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
226
228
|
let icon;
|
|
227
229
|
const sparkline = sparklineByCell.get(key(absR, absC));
|
|
228
230
|
if (cfFormatter && ws) {
|
|
229
|
-
const over = cfFormatter(absR, absC, ws.type === "n" && Number.isFinite(Number(ws.rawValue)) ? Number(ws.rawValue) : void 0);
|
|
231
|
+
const over = cfFormatter(absR, absC, ws.type === "n" && Number.isFinite(Number(ws.rawValue)) ? Number(ws.rawValue) : void 0, cfText);
|
|
230
232
|
if (over) {
|
|
231
233
|
if (over.fillHex) shading = { colorHex: over.fillHex };
|
|
232
234
|
if (over.dataBar) dataBar = over.dataBar;
|
|
@@ -234,7 +236,11 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
234
236
|
runProps = applyCfOverride(runProps, over);
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
const wrapText = xf?.alignment?.wrapText === true;
|
|
240
|
+
const rotation = xf?.alignment?.textRotation;
|
|
241
|
+
const rotated = rotation !== void 0 && rotation !== 0 && text.length > 0;
|
|
242
|
+
const shrinkToFit = xf?.alignment?.shrinkToFit === true && !wrapText && !rotated;
|
|
243
|
+
if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && ws && (ws.type === "s" || ws.type === "str" || ws.type === "inlineStr") && (alignment === void 0 || alignment === "left")) {
|
|
238
244
|
let availTwips = columnWidths[c];
|
|
239
245
|
let cc = c + 1;
|
|
240
246
|
while (cc < colCount && !cellHasContent(cellMatrix[r]?.[cc])) {
|
|
@@ -246,6 +252,19 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
246
252
|
if (text.length > charsFit) text = text.slice(0, charsFit);
|
|
247
253
|
}
|
|
248
254
|
}
|
|
255
|
+
if (shrinkToFit && ws && text.length > 0) {
|
|
256
|
+
const charsFit = Math.max(1, columnWidths[c] / 105);
|
|
257
|
+
if (text.length > charsFit) {
|
|
258
|
+
const baseHalfPt = (styles.fonts[xf.fontId]?.sizePt ?? 11) * 2;
|
|
259
|
+
const scaledHalfPt = Math.max(2, Math.round(baseHalfPt * charsFit / text.length));
|
|
260
|
+
runProps = {
|
|
261
|
+
...runProps,
|
|
262
|
+
fontSizePt: halfPtToPt(scaledHalfPt)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const dropdown = dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, absC);
|
|
267
|
+
const href = print.hyperlinks && print.hyperlinks.length > 0 ? hyperlinkUrlAt(print.hyperlinks, absR, absC) : void 0;
|
|
249
268
|
const visibleEndCol = merge ? Math.min(merge.endColumn, colWindowEnd) : 0;
|
|
250
269
|
const properties = {
|
|
251
270
|
...merge && visibleEndCol > merge.startColumn ? { colSpan: visibleEndCol - merge.startColumn + 1 } : {},
|
|
@@ -254,21 +273,35 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
254
273
|
...dataBar ? { dataBar } : {},
|
|
255
274
|
...icon ? { icon } : {},
|
|
256
275
|
...sparkline ? { sparkline } : {},
|
|
257
|
-
...borders ? { borders } : {}
|
|
276
|
+
...borders ? { borders } : {},
|
|
277
|
+
...dropdown ? { dropdown: true } : {}
|
|
258
278
|
};
|
|
259
|
-
const
|
|
279
|
+
const indentLevels = xf?.alignment?.indent ?? 0;
|
|
280
|
+
const baseParaProps = cellParaProps(alignment);
|
|
281
|
+
const paragraphProps = indentLevels > 0 ? {
|
|
282
|
+
...baseParaProps,
|
|
283
|
+
indentLeft: twipsToPt(indentLevels * 3 * 105)
|
|
284
|
+
} : baseParaProps;
|
|
285
|
+
const richRuns = ws && ws.type === "s" && print.sharedStringRuns ? print.sharedStringRuns[Number(ws.rawValue)] : void 0;
|
|
286
|
+
const cellRuns = richRuns && richRuns.length > 0 ? richRuns.map((rr) => ({
|
|
287
|
+
text: rr.text,
|
|
288
|
+
properties: richRunProps(runProps, rr),
|
|
289
|
+
...href ? { href } : {}
|
|
290
|
+
})) : text.length > 0 ? [{
|
|
291
|
+
text,
|
|
292
|
+
properties: runProps,
|
|
293
|
+
...href ? { href } : {}
|
|
294
|
+
}] : [];
|
|
295
|
+
const content = rotated ? stackedVerticalContent(text, runProps, href) : [{
|
|
296
|
+
kind: "paragraph",
|
|
297
|
+
paragraph: {
|
|
298
|
+
properties: paragraphProps,
|
|
299
|
+
runs: cellRuns
|
|
300
|
+
}
|
|
301
|
+
}];
|
|
260
302
|
cells.push({
|
|
261
303
|
properties,
|
|
262
|
-
content
|
|
263
|
-
kind: "paragraph",
|
|
264
|
-
paragraph: {
|
|
265
|
-
properties: paragraphProps,
|
|
266
|
-
runs: text.length > 0 ? [{
|
|
267
|
-
text,
|
|
268
|
-
properties: runProps
|
|
269
|
-
}] : []
|
|
270
|
-
}
|
|
271
|
-
}]
|
|
304
|
+
content
|
|
272
305
|
});
|
|
273
306
|
}
|
|
274
307
|
const baseRowProps = rowHeightMap.get(r);
|
|
@@ -374,6 +407,31 @@ function runPropsFromXf(xf, styles) {
|
|
|
374
407
|
if (font.colorHex) props.colorHex = font.colorHex;
|
|
375
408
|
return props;
|
|
376
409
|
}
|
|
410
|
+
function stackedVerticalContent(text, runProps, href) {
|
|
411
|
+
const out = [];
|
|
412
|
+
for (const ch of text) out.push({
|
|
413
|
+
kind: "paragraph",
|
|
414
|
+
paragraph: {
|
|
415
|
+
properties: { alignment: "center" },
|
|
416
|
+
runs: [{
|
|
417
|
+
text: ch,
|
|
418
|
+
properties: runProps,
|
|
419
|
+
...href ? { href } : {}
|
|
420
|
+
}]
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
function richRunProps(base, rr) {
|
|
426
|
+
const out = { ...base };
|
|
427
|
+
if (rr.bold) out.bold = true;
|
|
428
|
+
if (rr.italic) out.italic = true;
|
|
429
|
+
if (rr.underline) out.underline = "single";
|
|
430
|
+
if (rr.colorHex) out.colorHex = rr.colorHex;
|
|
431
|
+
if (rr.sizePt !== void 0) out.fontSizePt = halfPtToPt(Math.round(rr.sizePt * 2));
|
|
432
|
+
if (rr.vertAlign) out.verticalAlign = rr.vertAlign;
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
377
435
|
function applyCfOverride(base, o) {
|
|
378
436
|
return {
|
|
379
437
|
...base,
|
|
@@ -395,14 +453,55 @@ function mapAlignment(h) {
|
|
|
395
453
|
if (h === "justify") return "both";
|
|
396
454
|
if (h === "distributed") return "distribute";
|
|
397
455
|
}
|
|
456
|
+
var PATTERN_DENSITY = {
|
|
457
|
+
darkGray: .75,
|
|
458
|
+
mediumGray: .5,
|
|
459
|
+
lightGray: .25,
|
|
460
|
+
gray125: .125,
|
|
461
|
+
gray0625: .0625,
|
|
462
|
+
darkHorizontal: .5,
|
|
463
|
+
darkVertical: .5,
|
|
464
|
+
darkDown: .5,
|
|
465
|
+
darkUp: .5,
|
|
466
|
+
darkGrid: .62,
|
|
467
|
+
darkTrellis: .75,
|
|
468
|
+
lightHorizontal: .25,
|
|
469
|
+
lightVertical: .25,
|
|
470
|
+
lightDown: .25,
|
|
471
|
+
lightUp: .25,
|
|
472
|
+
lightGrid: .37,
|
|
473
|
+
lightTrellis: .5
|
|
474
|
+
};
|
|
398
475
|
function shadingFromXf(xf, styles) {
|
|
399
476
|
if (xf.applyFill === false) return void 0;
|
|
400
477
|
if (xf.fillId === 0 || xf.fillId === 1) {
|
|
401
478
|
if (!xf.applyFill) return void 0;
|
|
402
479
|
}
|
|
403
480
|
const fill = styles.fills[xf.fillId];
|
|
404
|
-
if (!fill
|
|
405
|
-
|
|
481
|
+
if (!fill) return void 0;
|
|
482
|
+
const pattern = fill.patternType;
|
|
483
|
+
if (!pattern || pattern === "none") return void 0;
|
|
484
|
+
if (pattern === "solid") return fill.fgColorHex ? { colorHex: fill.fgColorHex } : void 0;
|
|
485
|
+
const density = PATTERN_DENSITY[pattern];
|
|
486
|
+
if (density === void 0 || !fill.fgColorHex) return void 0;
|
|
487
|
+
const blended = blendHex(fill.fgColorHex, fill.bgColorHex ?? "FFFFFF", density);
|
|
488
|
+
return blended ? { colorHex: blended } : void 0;
|
|
489
|
+
}
|
|
490
|
+
function blendHex(fg, bg, fraction) {
|
|
491
|
+
const a = hexToRgb(fg);
|
|
492
|
+
const b = hexToRgb(bg);
|
|
493
|
+
if (!a || !b) return void 0;
|
|
494
|
+
const mix = (x, y) => Math.max(0, Math.min(255, Math.round(x * fraction + y * (1 - fraction)))).toString(16).padStart(2, "0").toUpperCase();
|
|
495
|
+
return `${mix(a[0], b[0])}${mix(a[1], b[1])}${mix(a[2], b[2])}`;
|
|
496
|
+
}
|
|
497
|
+
function hexToRgb(hex) {
|
|
498
|
+
const h = hex.length === 8 ? hex.slice(2) : hex;
|
|
499
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(h)) return void 0;
|
|
500
|
+
return [
|
|
501
|
+
parseInt(h.slice(0, 2), 16),
|
|
502
|
+
parseInt(h.slice(2, 4), 16),
|
|
503
|
+
parseInt(h.slice(4, 6), 16)
|
|
504
|
+
];
|
|
406
505
|
}
|
|
407
506
|
function bordersFromXf(xf, styles) {
|
|
408
507
|
if (xf.applyBorder === false) return void 0;
|
|
@@ -418,6 +517,11 @@ function bordersFromXf(xf, styles) {
|
|
|
418
517
|
if (right) out.right = right;
|
|
419
518
|
if (bottom) out.bottom = bottom;
|
|
420
519
|
if (left) out.left = left;
|
|
520
|
+
const diagonal = border.diagonal ? mapBorderEdge(border.diagonal) : void 0;
|
|
521
|
+
if (diagonal) {
|
|
522
|
+
if (border.diagonalDown) out.diagonalDown = diagonal;
|
|
523
|
+
if (border.diagonalUp) out.diagonalUp = diagonal;
|
|
524
|
+
}
|
|
421
525
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
422
526
|
}
|
|
423
527
|
function mapBorderEdge(edge) {
|
|
@@ -474,6 +578,25 @@ function mapBorderStyle(style) {
|
|
|
474
578
|
function key(row, col) {
|
|
475
579
|
return `${row},${col}`;
|
|
476
580
|
}
|
|
581
|
+
function listDropdownRanges(dvs) {
|
|
582
|
+
if (!dvs || dvs.length === 0) return [];
|
|
583
|
+
const out = [];
|
|
584
|
+
for (const dv of dvs) {
|
|
585
|
+
if (dv.type !== "list" || dv.showDropDown) continue;
|
|
586
|
+
out.push(...dv.ranges);
|
|
587
|
+
}
|
|
588
|
+
return out;
|
|
589
|
+
}
|
|
590
|
+
function rangesCover(ranges, row, col) {
|
|
591
|
+
for (const r of ranges) if (row >= r.startRow && row <= r.endRow && col >= r.startColumn && col <= r.endColumn) return true;
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
function hyperlinkUrlAt(links, row, col) {
|
|
595
|
+
for (const l of links) {
|
|
596
|
+
const r = l.ref;
|
|
597
|
+
if (row >= r.startRow && row <= r.endRow && col >= r.startColumn && col <= r.endColumn) return l.url;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
477
600
|
function buildSparklineLookup(worksheet, print) {
|
|
478
601
|
const out = /* @__PURE__ */ new Map();
|
|
479
602
|
for (const sp of worksheet.sparklines ?? []) {
|
|
@@ -572,5 +695,81 @@ function buildTableFormatLookup(worksheet) {
|
|
|
572
695
|
function cellHasContent(cell) {
|
|
573
696
|
return !!cell && (cell.rawValue !== "" || cell.inlineText !== void 0);
|
|
574
697
|
}
|
|
698
|
+
var SLICER_WIDTH_PT = 108;
|
|
699
|
+
var SLICER_ROW_PT = 16;
|
|
700
|
+
var SLICER_UNSELECTED_HEX = "F2F2F2";
|
|
701
|
+
function slicerTable(slicer) {
|
|
702
|
+
const cols = Math.max(1, slicer.columnCount);
|
|
703
|
+
const colWidthPt = SLICER_WIDTH_PT / cols;
|
|
704
|
+
const thin = {
|
|
705
|
+
style: "single",
|
|
706
|
+
width: eighthPtToPt(4)
|
|
707
|
+
};
|
|
708
|
+
const rowProps = {
|
|
709
|
+
height: pt(SLICER_ROW_PT),
|
|
710
|
+
heightRule: "atLeast"
|
|
711
|
+
};
|
|
712
|
+
const rows = [];
|
|
713
|
+
rows.push({
|
|
714
|
+
properties: rowProps,
|
|
715
|
+
cells: [{
|
|
716
|
+
properties: {
|
|
717
|
+
...cols > 1 ? { colSpan: cols } : {},
|
|
718
|
+
...slicer.headerHex ? { shading: { colorHex: slicer.headerHex } } : {}
|
|
719
|
+
},
|
|
720
|
+
content: [slicerParagraph(slicer.caption, {
|
|
721
|
+
bold: true,
|
|
722
|
+
...slicer.headerTextHex ? { colorHex: slicer.headerTextHex } : {}
|
|
723
|
+
})]
|
|
724
|
+
}]
|
|
725
|
+
});
|
|
726
|
+
for (let i = 0; i < slicer.items.length; i += cols) {
|
|
727
|
+
const cells = [];
|
|
728
|
+
for (let c = 0; c < cols; c++) {
|
|
729
|
+
const item = slicer.items[i + c];
|
|
730
|
+
if (!item) {
|
|
731
|
+
cells.push({
|
|
732
|
+
properties: {},
|
|
733
|
+
content: [slicerParagraph("", {})]
|
|
734
|
+
});
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const fill = item.selected ? slicer.selectedHex : SLICER_UNSELECTED_HEX;
|
|
738
|
+
const textHex = item.selected ? slicer.selectedTextHex : void 0;
|
|
739
|
+
cells.push({
|
|
740
|
+
properties: fill ? { shading: { colorHex: fill } } : {},
|
|
741
|
+
content: [slicerParagraph(item.label, textHex ? { colorHex: textHex } : {})]
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
rows.push({
|
|
745
|
+
properties: rowProps,
|
|
746
|
+
cells
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
properties: { borders: {
|
|
751
|
+
top: thin,
|
|
752
|
+
bottom: thin,
|
|
753
|
+
left: thin,
|
|
754
|
+
right: thin,
|
|
755
|
+
insideH: thin,
|
|
756
|
+
insideV: thin
|
|
757
|
+
} },
|
|
758
|
+
grid: Array.from({ length: cols }, () => pt(colWidthPt)),
|
|
759
|
+
rows
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function slicerParagraph(text, runProps) {
|
|
763
|
+
return {
|
|
764
|
+
kind: "paragraph",
|
|
765
|
+
paragraph: {
|
|
766
|
+
properties: {},
|
|
767
|
+
runs: text.length > 0 ? [{
|
|
768
|
+
text,
|
|
769
|
+
properties: runProps
|
|
770
|
+
}] : []
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
}
|
|
575
774
|
//#endregion
|
|
576
|
-
export { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, worksheetToBody };
|
|
775
|
+
export { resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { SheetRichRun } from '../core/spreadsheet-model/index.js';
|
|
2
|
+
export interface SharedStrings {
|
|
3
|
+
readonly texts: ReadonlyArray<string>;
|
|
4
|
+
readonly runs: ReadonlyArray<ReadonlyArray<SheetRichRun> | undefined>;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseSharedStrings(data: Uint8Array): SharedStrings;
|
|
@@ -17,22 +17,101 @@ function capLen(s) {
|
|
|
17
17
|
function parseSharedStrings(data) {
|
|
18
18
|
const xml = decoder.decode(data);
|
|
19
19
|
const sst = parser.parse(xml)["sst"];
|
|
20
|
-
if (!sst || typeof sst !== "object") return
|
|
20
|
+
if (!sst || typeof sst !== "object") return {
|
|
21
|
+
texts: [],
|
|
22
|
+
runs: []
|
|
23
|
+
};
|
|
21
24
|
const siRaw = sst["si"];
|
|
22
|
-
|
|
25
|
+
const items = Array.isArray(siRaw) ? siRaw : siRaw !== void 0 ? [siRaw] : [];
|
|
26
|
+
const texts = [];
|
|
27
|
+
const runs = [];
|
|
28
|
+
for (const item of items) {
|
|
29
|
+
const parsed = extractSi(item);
|
|
30
|
+
texts.push(capLen(parsed.text));
|
|
31
|
+
runs.push(parsed.runs);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
texts,
|
|
35
|
+
runs
|
|
36
|
+
};
|
|
23
37
|
}
|
|
24
|
-
function
|
|
25
|
-
if (typeof si === "string") return
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
function extractSi(si) {
|
|
39
|
+
if (typeof si === "string") return {
|
|
40
|
+
text: si,
|
|
41
|
+
runs: void 0
|
|
42
|
+
};
|
|
43
|
+
if (typeof si === "number") return {
|
|
44
|
+
text: String(si),
|
|
45
|
+
runs: void 0
|
|
46
|
+
};
|
|
47
|
+
if (!si || typeof si !== "object") return {
|
|
48
|
+
text: "",
|
|
49
|
+
runs: void 0
|
|
50
|
+
};
|
|
28
51
|
const obj = si;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
const direct = textOf(obj["t"]);
|
|
53
|
+
if (direct) return {
|
|
54
|
+
text: direct,
|
|
55
|
+
runs: void 0
|
|
56
|
+
};
|
|
32
57
|
const r = obj["r"];
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
58
|
+
const rs = Array.isArray(r) ? r : r && typeof r === "object" ? [r] : [];
|
|
59
|
+
if (rs.length === 0) return {
|
|
60
|
+
text: "",
|
|
61
|
+
runs: void 0
|
|
62
|
+
};
|
|
63
|
+
const runs = [];
|
|
64
|
+
let anyFormatting = false;
|
|
65
|
+
for (const rr of rs) {
|
|
66
|
+
const ro = rr;
|
|
67
|
+
const text = textOf(ro?.["t"]);
|
|
68
|
+
if (text.length === 0) continue;
|
|
69
|
+
const run = richRun(text, ro?.["rPr"]);
|
|
70
|
+
if (Object.keys(run).length > 1) anyFormatting = true;
|
|
71
|
+
runs.push(run);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
text: runs.map((x) => x.text).join(""),
|
|
75
|
+
runs: anyFormatting && runs.length > 0 ? runs : void 0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function richRun(text, rPr) {
|
|
79
|
+
const p = rPr && typeof rPr === "object" ? rPr : void 0;
|
|
80
|
+
if (!p) return { text };
|
|
81
|
+
const out = { text };
|
|
82
|
+
if (has(p, "b")) out.bold = true;
|
|
83
|
+
if (has(p, "i")) out.italic = true;
|
|
84
|
+
if (has(p, "u")) out.underline = true;
|
|
85
|
+
const color = colorRgb(p["color"]);
|
|
86
|
+
if (color) out.colorHex = color;
|
|
87
|
+
const sz = attrNum(p["sz"], "val");
|
|
88
|
+
if (sz !== void 0) out.sizePt = sz;
|
|
89
|
+
const vert = attrStr(p["vertAlign"], "val");
|
|
90
|
+
if (vert === "superscript" || vert === "subscript") out.vertAlign = vert;
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function has(p, key) {
|
|
94
|
+
if (!(key in p)) return false;
|
|
95
|
+
const node = p[key];
|
|
96
|
+
const val = attrStr(node, "val");
|
|
97
|
+
return val !== "0" && val !== "false";
|
|
98
|
+
}
|
|
99
|
+
function attrStr(node, key) {
|
|
100
|
+
if (!node || typeof node !== "object") return void 0;
|
|
101
|
+
const v = node[`@_${key}`];
|
|
102
|
+
return typeof v === "string" ? v : void 0;
|
|
103
|
+
}
|
|
104
|
+
function attrNum(node, key) {
|
|
105
|
+
const s = attrStr(node, key);
|
|
106
|
+
if (s === void 0) return void 0;
|
|
107
|
+
const n = Number(s);
|
|
108
|
+
return Number.isFinite(n) ? n : void 0;
|
|
109
|
+
}
|
|
110
|
+
function colorRgb(node) {
|
|
111
|
+
const rgb = attrStr(node, "rgb");
|
|
112
|
+
if (!rgb) return void 0;
|
|
113
|
+
const hex = rgb.length === 8 ? rgb.slice(2) : rgb;
|
|
114
|
+
return /^[0-9A-Fa-f]{6}$/.test(hex) ? hex.toUpperCase() : void 0;
|
|
36
115
|
}
|
|
37
116
|
function textOf(node) {
|
|
38
117
|
if (typeof node === "string") return node;
|
|
@@ -6,4 +6,16 @@ export interface SheetChartRef {
|
|
|
6
6
|
readonly heightPt: number;
|
|
7
7
|
readonly anchorRow: number;
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export interface SheetPicture {
|
|
10
|
+
readonly imagePartPath: string;
|
|
11
|
+
readonly widthPt: number;
|
|
12
|
+
readonly heightPt: number;
|
|
13
|
+
readonly anchorRow: number;
|
|
14
|
+
}
|
|
15
|
+
export interface SheetDrawing {
|
|
16
|
+
readonly charts: Array<SheetChartRef>;
|
|
17
|
+
readonly pictures: Array<SheetPicture>;
|
|
18
|
+
}
|
|
19
|
+
export declare function parseSheetDrawing(drawingXml: Uint8Array, drawingPartPath: string, pkg: OpcPackage, worksheet: ParsedWorksheet): SheetDrawing;
|
|
20
|
+
export declare function makeColWidthPt(ws: ParsedWorksheet): (col: number) => number;
|
|
21
|
+
export declare function makeRowHeightPt(ws: ParsedWorksheet): (row: number) => number;
|
|
@@ -12,12 +12,20 @@ var parser = new XMLParser({
|
|
|
12
12
|
var TWIPS_PER_PT = 20;
|
|
13
13
|
function parseSheetDrawing(drawingXml, drawingPartPath, pkg, worksheet) {
|
|
14
14
|
const root = parser.parse(new TextDecoder().decode(drawingXml))["wsDr"];
|
|
15
|
-
if (!root || typeof root !== "object") return
|
|
15
|
+
if (!root || typeof root !== "object") return {
|
|
16
|
+
charts: [],
|
|
17
|
+
pictures: []
|
|
18
|
+
};
|
|
16
19
|
const rootObj = root;
|
|
17
20
|
const colWidthPt = makeColWidthPt(worksheet);
|
|
18
21
|
const rowHeightPt = makeRowHeightPt(worksheet);
|
|
19
22
|
const rels = pkg.getPartRelationships(drawingPartPath);
|
|
20
|
-
const
|
|
23
|
+
const partPathOf = (relId) => {
|
|
24
|
+
const rel = rels.find((r) => r.id === relId);
|
|
25
|
+
return rel ? pkg.resolveRelatedPart(drawingPartPath, rel)?.path : void 0;
|
|
26
|
+
};
|
|
27
|
+
const charts = [];
|
|
28
|
+
const pictures = [];
|
|
21
29
|
for (const kind of [
|
|
22
30
|
"twoCellAnchor",
|
|
23
31
|
"oneCellAnchor",
|
|
@@ -26,10 +34,8 @@ function parseSheetDrawing(drawingXml, drawingPartPath, pkg, worksheet) {
|
|
|
26
34
|
if (!anchor || typeof anchor !== "object") continue;
|
|
27
35
|
const a = anchor;
|
|
28
36
|
const chartRelId = chartRelIdOf(a);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const resolved = rel ? pkg.resolveRelatedPart(drawingPartPath, rel) : void 0;
|
|
32
|
-
if (!resolved) continue;
|
|
37
|
+
const picRelId = chartRelId ? void 0 : picRelIdOf(a);
|
|
38
|
+
if (!chartRelId && !picRelId) continue;
|
|
33
39
|
const from = cellMarker(a["from"]);
|
|
34
40
|
let widthPt = 0;
|
|
35
41
|
let heightPt = 0;
|
|
@@ -46,14 +52,41 @@ function parseSheetDrawing(drawingXml, drawingPartPath, pkg, worksheet) {
|
|
|
46
52
|
heightPt = emuToPt(num(e["@_cy"]) ?? 0);
|
|
47
53
|
}
|
|
48
54
|
if (widthPt <= 0 || heightPt <= 0) continue;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const anchorRow = from?.row ?? 0;
|
|
56
|
+
if (chartRelId) {
|
|
57
|
+
const path = partPathOf(chartRelId);
|
|
58
|
+
if (path) charts.push({
|
|
59
|
+
chartPartPath: path,
|
|
60
|
+
widthPt,
|
|
61
|
+
heightPt,
|
|
62
|
+
anchorRow
|
|
63
|
+
});
|
|
64
|
+
} else if (picRelId) {
|
|
65
|
+
const path = partPathOf(picRelId);
|
|
66
|
+
if (path) pictures.push({
|
|
67
|
+
imagePartPath: path,
|
|
68
|
+
widthPt,
|
|
69
|
+
heightPt,
|
|
70
|
+
anchorRow
|
|
71
|
+
});
|
|
72
|
+
}
|
|
55
73
|
}
|
|
56
|
-
|
|
74
|
+
charts.sort((x, y) => x.anchorRow - y.anchorRow);
|
|
75
|
+
pictures.sort((x, y) => x.anchorRow - y.anchorRow);
|
|
76
|
+
return {
|
|
77
|
+
charts,
|
|
78
|
+
pictures
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function picRelIdOf(anchor) {
|
|
82
|
+
const pic = anchor["pic"];
|
|
83
|
+
if (!pic || typeof pic !== "object") return void 0;
|
|
84
|
+
const blipFill = pic["blipFill"];
|
|
85
|
+
if (!blipFill || typeof blipFill !== "object") return void 0;
|
|
86
|
+
const blip = blipFill["blip"];
|
|
87
|
+
if (!blip || typeof blip !== "object") return void 0;
|
|
88
|
+
const embed = blip["@_embed"];
|
|
89
|
+
return typeof embed === "string" && embed !== "" ? embed : void 0;
|
|
57
90
|
}
|
|
58
91
|
function chartRelIdOf(anchor) {
|
|
59
92
|
const frame = anchor["graphicFrame"];
|
|
@@ -110,4 +143,4 @@ function asArray(v) {
|
|
|
110
143
|
return Array.isArray(v) ? v : v !== void 0 ? [v] : [];
|
|
111
144
|
}
|
|
112
145
|
//#endregion
|
|
113
|
-
export { parseSheetDrawing };
|
|
146
|
+
export { makeColWidthPt, makeRowHeightPt, parseSheetDrawing };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ShapeBlock } from '../core/document-model/index.js';
|
|
2
|
+
import { ColorResolver } from '../core/drawingml/colors.js';
|
|
3
|
+
import { ParsedWorksheet } from '../core/spreadsheet-model/index.js';
|
|
4
|
+
export declare function parseSheetShapes(drawingXml: Uint8Array, worksheet: ParsedWorksheet, colors: ColorResolver): Array<ShapeBlock>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { emuToPt, pt } from "../core/ir/units.js";
|
|
2
|
+
import { poChildren, poFirstChild, poIntAttr, poIs, poText } from "../core/po-helpers.js";
|
|
3
|
+
import { parseFill, parseLine } from "../word/drawing-parser.js";
|
|
4
|
+
import { parseGeometry, parseTxBody } from "../pptx/slide-parser.js";
|
|
5
|
+
import { parseXml } from "../pptx/pptx-reader.js";
|
|
6
|
+
import { makeColWidthPt, makeRowHeightPt } from "./sheet-drawing.js";
|
|
7
|
+
//#region src/excel/sheet-shape-parser.ts
|
|
8
|
+
var ANCHOR_KINDS = [
|
|
9
|
+
"xdr:twoCellAnchor",
|
|
10
|
+
"xdr:oneCellAnchor",
|
|
11
|
+
"xdr:absoluteAnchor"
|
|
12
|
+
];
|
|
13
|
+
function parseSheetShapes(drawingXml, worksheet, colors) {
|
|
14
|
+
const wsDr = parseXml(drawingXml).find((n) => poIs(n, "xdr:wsDr"));
|
|
15
|
+
if (!wsDr) return [];
|
|
16
|
+
const colWidthPt = makeColWidthPt(worksheet);
|
|
17
|
+
const rowHeightPt = makeRowHeightPt(worksheet);
|
|
18
|
+
const shapes = [];
|
|
19
|
+
for (const anchor of poChildren(wsDr)) {
|
|
20
|
+
if (!ANCHOR_KINDS.some((k) => poIs(anchor, k))) continue;
|
|
21
|
+
const sp = poChildren(anchor).find((c) => poIs(c, "xdr:sp"));
|
|
22
|
+
if (!sp) continue;
|
|
23
|
+
const box = anchorBox(anchor, colWidthPt, rowHeightPt);
|
|
24
|
+
if (!box) continue;
|
|
25
|
+
const spPr = poChildren(sp).find((c) => poIs(c, "xdr:spPr"));
|
|
26
|
+
const geometry = parseGeometry(spPr);
|
|
27
|
+
const fill = spPr ? parseFill(spPr, colors) : { kind: "none" };
|
|
28
|
+
const line = spPr ? parseLine(spPr, colors) : void 0;
|
|
29
|
+
const txBody = poChildren(sp).find((c) => poIs(c, "xdr:txBody"));
|
|
30
|
+
const text = txBody ? parseTxBody(txBody, void 0, void 0, colors, void 0) : void 0;
|
|
31
|
+
const visibleLine = line !== void 0 && line.fill !== "none";
|
|
32
|
+
if (!text && fill.kind === "none" && !visibleLine) continue;
|
|
33
|
+
shapes.push({
|
|
34
|
+
shape: {
|
|
35
|
+
width: pt(box.widthPt),
|
|
36
|
+
height: pt(box.heightPt),
|
|
37
|
+
geometry,
|
|
38
|
+
fill,
|
|
39
|
+
...line ? { line } : {},
|
|
40
|
+
...text ? { text } : {},
|
|
41
|
+
paragraphProperties: {}
|
|
42
|
+
},
|
|
43
|
+
anchorRow: box.anchorRow
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
shapes.sort((a, b) => a.anchorRow - b.anchorRow);
|
|
47
|
+
return shapes.map((s) => s.shape);
|
|
48
|
+
}
|
|
49
|
+
function anchorBox(anchor, colWidthPt, rowHeightPt) {
|
|
50
|
+
const from = marker(poChildren(anchor).find((c) => poIs(c, "xdr:from")));
|
|
51
|
+
if (poIs(anchor, "xdr:twoCellAnchor")) {
|
|
52
|
+
const to = marker(poChildren(anchor).find((c) => poIs(c, "xdr:to")));
|
|
53
|
+
if (!from || !to) return void 0;
|
|
54
|
+
const widthPt = span(from.col, from.colOffPt, to.col, to.colOffPt, colWidthPt);
|
|
55
|
+
const heightPt = span(from.row, from.rowOffPt, to.row, to.rowOffPt, rowHeightPt);
|
|
56
|
+
return widthPt > 0 && heightPt > 0 ? {
|
|
57
|
+
widthPt,
|
|
58
|
+
heightPt,
|
|
59
|
+
anchorRow: from.row
|
|
60
|
+
} : void 0;
|
|
61
|
+
}
|
|
62
|
+
const ext = poChildren(anchor).find((c) => poIs(c, "xdr:ext"));
|
|
63
|
+
if (!ext) return void 0;
|
|
64
|
+
const widthPt = emuToPt(poIntAttr(ext, "cx") ?? 0);
|
|
65
|
+
const heightPt = emuToPt(poIntAttr(ext, "cy") ?? 0);
|
|
66
|
+
return widthPt > 0 && heightPt > 0 ? {
|
|
67
|
+
widthPt,
|
|
68
|
+
heightPt,
|
|
69
|
+
anchorRow: from?.row ?? 0
|
|
70
|
+
} : void 0;
|
|
71
|
+
}
|
|
72
|
+
function marker(node) {
|
|
73
|
+
if (!node) return void 0;
|
|
74
|
+
const col = childInt(node, "xdr:col");
|
|
75
|
+
const row = childInt(node, "xdr:row");
|
|
76
|
+
if (col === void 0 || row === void 0) return void 0;
|
|
77
|
+
return {
|
|
78
|
+
col,
|
|
79
|
+
row,
|
|
80
|
+
colOffPt: emuToPt(childInt(node, "xdr:colOff") ?? 0),
|
|
81
|
+
rowOffPt: emuToPt(childInt(node, "xdr:rowOff") ?? 0)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function childInt(parent, tag) {
|
|
85
|
+
const child = poFirstChild(parent, tag);
|
|
86
|
+
if (!child) return void 0;
|
|
87
|
+
const n = Number(poText(child));
|
|
88
|
+
return Number.isFinite(n) ? n : void 0;
|
|
89
|
+
}
|
|
90
|
+
function span(from, fromOffPt, to, toOffPt, trackPt) {
|
|
91
|
+
let total = 0;
|
|
92
|
+
for (let i = from; i < to; i++) total += trackPt(i);
|
|
93
|
+
return total - fromOffPt + toOffPt;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { parseSheetShapes };
|