reamkit 1.15.3 → 1.16.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/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +187 -1
- package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
- package/dist/esm/core/drawingml/chart-geometry.js +371 -88
- package/dist/esm/core/drawingml/chart-parser.js +262 -11
- package/dist/esm/core/drawingml/colors.d.ts +6 -6
- package/dist/esm/core/drawingml/colors.js +58 -18
- package/dist/esm/core/drawingml/preset-geometry.js +7 -6
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
- package/dist/esm/core/drawingml/theme-parser.js +74 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.js +172 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/sheet.d.ts +67 -3
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/vector.d.ts +13 -0
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +16 -1
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +902 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +551 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +149 -28
- package/dist/esm/excel/xlsx-reader.js +271 -33
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +11 -2
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -0
- package/dist/esm/layout/styled-layout.js +493 -64
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/styled-page-emitter.js +164 -32
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +3 -1
- package/dist/esm/pdf/vector-graphics.js +19 -2
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.js +12 -1
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.js +1 -0
- package/dist/esm/word/drawing-parser.d.ts +34 -1
- package/dist/esm/word/drawing-parser.js +52 -2
- package/dist/esm/word/numbering-parser.js +1 -0
- package/dist/esm/word/settings-parser.js +1 -0
- package/dist/esm/word/styles-parser.js +1 -0
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
import { XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
1
|
+
import { Dxf, XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
2
|
+
import { INDEXED_COLORS } from '../core/indexed-colors.js';
|
|
3
|
+
export { INDEXED_COLORS };
|
|
2
4
|
/** The empty style table, returned for a workbook with no (or a malformed) `styles.xml`. */
|
|
3
5
|
export declare const EMPTY_XLSX_STYLES: XlsxStyles;
|
|
6
|
+
/** A workbook theme's colour scheme, slot name → RRGGBB (see `parseTheme`). */
|
|
7
|
+
export type ThemePalette = ReadonlyMap<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Everything a `<color>` in this workbook can point AT: the theme's scheme
|
|
10
|
+
* (§18.8.3 `theme`) and the legacy palette (§18.8.27 `indexed`), which the
|
|
11
|
+
* workbook may replace with one of its own.
|
|
12
|
+
*/
|
|
13
|
+
export interface WorkbookColors {
|
|
14
|
+
readonly theme?: ThemePalette;
|
|
15
|
+
readonly indexed: ReadonlyArray<string>;
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Parse `xl/styles.xml` (§18.8) into the {@link XlsxStyles} table the renderer
|
|
6
19
|
* consumes: custom number formats, fonts, fills, borders, the cell formats
|
|
7
20
|
* (`cellXfs` — each indexing font/fill/border/numFmt + optional alignment) and the
|
|
8
21
|
* differential formats (`dxfs`) conditional formatting applies. Returns
|
|
9
22
|
* {@link EMPTY_XLSX_STYLES} when the root is absent or malformed.
|
|
23
|
+
*
|
|
24
|
+
* @param theme The workbook's theme palette, resolving `<color theme="N">`.
|
|
25
|
+
* Without it those colours are dropped, and a sheet styled from the
|
|
26
|
+
* theme loses every fill and font colour it has.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseXlsxStyles(data: Uint8Array, theme?: ThemePalette): XlsxStyles;
|
|
29
|
+
/**
|
|
30
|
+
* Read one §18.8.14 `<dxf>` — the differential format a conditional-format rule
|
|
31
|
+
* applies on match. Exported because the 2009 extension writes its dxf INSIDE
|
|
32
|
+
* the rule (`<x14:dxf>`) rather than into the workbook's `<dxfs>` table, and
|
|
33
|
+
* the sheet reader has to resolve it there.
|
|
34
|
+
*
|
|
35
|
+
* @param item The `<dxf>` node, as parsed.
|
|
36
|
+
* @param colors The workbook palette, resolving theme and indexed colours.
|
|
37
|
+
* @returns The format; empty when the node states nothing.
|
|
10
38
|
*/
|
|
11
|
-
export declare function
|
|
39
|
+
export declare function parseDxf(item: unknown, colors: WorkbookColors): Dxf;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { INDEXED_COLORS } from "../core/indexed-colors.js";
|
|
2
|
+
import { applyColorMods } from "../core/drawingml/colors.js";
|
|
3
|
+
import { resolveInternalEntities } from "../core/opc/xml-entities.js";
|
|
4
|
+
import { resolveAlternateContent } from "../core/opc/alternate-content.js";
|
|
1
5
|
import { XMLParser } from "fast-xml-parser";
|
|
2
6
|
//#region src/excel/styles-parser.ts
|
|
3
7
|
var decoder = new TextDecoder("utf-8");
|
|
4
8
|
var parser = new XMLParser({
|
|
9
|
+
htmlEntities: true,
|
|
5
10
|
ignoreAttributes: false,
|
|
6
11
|
attributeNamePrefix: "@_",
|
|
7
12
|
textNodeName: "#text",
|
|
@@ -24,38 +29,56 @@ var EMPTY_XLSX_STYLES = {
|
|
|
24
29
|
* (`cellXfs` — each indexing font/fill/border/numFmt + optional alignment) and the
|
|
25
30
|
* differential formats (`dxfs`) conditional formatting applies. Returns
|
|
26
31
|
* {@link EMPTY_XLSX_STYLES} when the root is absent or malformed.
|
|
32
|
+
*
|
|
33
|
+
* @param theme The workbook's theme palette, resolving `<color theme="N">`.
|
|
34
|
+
* Without it those colours are dropped, and a sheet styled from the
|
|
35
|
+
* theme loses every fill and font colour it has.
|
|
27
36
|
*/
|
|
28
|
-
function parseXlsxStyles(data) {
|
|
29
|
-
const xml = decoder.decode(data);
|
|
37
|
+
function parseXlsxStyles(data, theme) {
|
|
38
|
+
const xml = resolveAlternateContent(resolveInternalEntities(decoder.decode(data)));
|
|
30
39
|
const root = asObject(parser.parse(xml)["styleSheet"]);
|
|
31
40
|
if (!root) return EMPTY_XLSX_STYLES;
|
|
32
|
-
const
|
|
41
|
+
const colors = {
|
|
42
|
+
...theme ? { theme } : {},
|
|
43
|
+
indexed: parseIndexedColors(root)
|
|
44
|
+
};
|
|
45
|
+
const dxfs = parseDxfs(root, colors);
|
|
33
46
|
return {
|
|
34
47
|
numFmts: parseNumFmts(root),
|
|
35
|
-
fonts: parseFonts(root),
|
|
36
|
-
fills: parseFills(root),
|
|
37
|
-
borders: parseBorders(root),
|
|
48
|
+
fonts: parseFonts(root, colors),
|
|
49
|
+
fills: parseFills(root, colors),
|
|
50
|
+
borders: parseBorders(root, colors),
|
|
38
51
|
cellXfs: parseCellXfs(root),
|
|
39
52
|
...dxfs.length > 0 ? { dxfs } : {}
|
|
40
53
|
};
|
|
41
54
|
}
|
|
42
|
-
function parseDxfs(root) {
|
|
55
|
+
function parseDxfs(root, colors) {
|
|
43
56
|
const node = asObject(root["dxfs"]);
|
|
44
57
|
if (!node) return [];
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
return asArray(node["dxf"]).map((item) => parseDxf(item, colors));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read one §18.8.14 `<dxf>` — the differential format a conditional-format rule
|
|
62
|
+
* applies on match. Exported because the 2009 extension writes its dxf INSIDE
|
|
63
|
+
* the rule (`<x14:dxf>`) rather than into the workbook's `<dxfs>` table, and
|
|
64
|
+
* the sheet reader has to resolve it there.
|
|
65
|
+
*
|
|
66
|
+
* @param item The `<dxf>` node, as parsed.
|
|
67
|
+
* @param colors The workbook palette, resolving theme and indexed colours.
|
|
68
|
+
* @returns The format; empty when the node states nothing.
|
|
69
|
+
*/
|
|
70
|
+
function parseDxf(item, colors) {
|
|
71
|
+
{
|
|
47
72
|
const obj = asObject(item);
|
|
48
|
-
if (!obj) {
|
|
49
|
-
out.push({});
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
73
|
+
if (!obj) return {};
|
|
52
74
|
const dxf = {};
|
|
53
75
|
const fontObj = asObject(obj["font"]);
|
|
54
76
|
if (fontObj) {
|
|
55
77
|
const font = {};
|
|
56
78
|
if (hasChild(fontObj, "b")) font.bold = childToggle(fontObj, "b");
|
|
57
79
|
if (hasChild(fontObj, "i")) font.italic = childToggle(fontObj, "i");
|
|
58
|
-
|
|
80
|
+
if (hasChild(fontObj, "strike")) font.strike = childToggle(fontObj, "strike");
|
|
81
|
+
const colorHex = colorOf(asObject(fontObj["color"]), colors);
|
|
59
82
|
if (colorHex) font.colorHex = colorHex;
|
|
60
83
|
if (Object.keys(font).length > 0) dxf.font = font;
|
|
61
84
|
}
|
|
@@ -64,15 +87,31 @@ function parseDxfs(root) {
|
|
|
64
87
|
const fill = {};
|
|
65
88
|
const pt = strAttr(pf, "patternType");
|
|
66
89
|
if (pt) fill.patternType = pt;
|
|
67
|
-
const fg = colorOf(asObject(pf["fgColor"]));
|
|
68
|
-
const bg = colorOf(asObject(pf["bgColor"]));
|
|
90
|
+
const fg = colorOf(asObject(pf["fgColor"]), colors);
|
|
91
|
+
const bg = colorOf(asObject(pf["bgColor"]), colors);
|
|
69
92
|
if (fg) fill.fgColorHex = fg;
|
|
70
93
|
if (bg) fill.bgColorHex = bg;
|
|
71
94
|
if (Object.keys(fill).length > 0) dxf.fill = fill;
|
|
72
95
|
}
|
|
73
|
-
|
|
96
|
+
const numFmtObj = asObject(obj["numFmt"]);
|
|
97
|
+
const code = numFmtObj ? strAttr(numFmtObj, "formatCode") : void 0;
|
|
98
|
+
if (code !== void 0 && code.length > 0) dxf.numberFormat = code;
|
|
99
|
+
const borderObj = asObject(obj["border"]);
|
|
100
|
+
if (borderObj) {
|
|
101
|
+
const border = {};
|
|
102
|
+
for (const side of [
|
|
103
|
+
"top",
|
|
104
|
+
"right",
|
|
105
|
+
"bottom",
|
|
106
|
+
"left"
|
|
107
|
+
]) {
|
|
108
|
+
const edge = parseBorderEdge(asObject(borderObj[side]), colors);
|
|
109
|
+
if (edge) border[side] = edge;
|
|
110
|
+
}
|
|
111
|
+
if (Object.keys(border).length > 0) dxf.border = border;
|
|
112
|
+
}
|
|
113
|
+
return dxf;
|
|
74
114
|
}
|
|
75
|
-
return out;
|
|
76
115
|
}
|
|
77
116
|
var VALID_BORDER_STYLES = new Set([
|
|
78
117
|
"none",
|
|
@@ -90,7 +129,7 @@ var VALID_BORDER_STYLES = new Set([
|
|
|
90
129
|
"mediumDashDotDot",
|
|
91
130
|
"slantDashDot"
|
|
92
131
|
]);
|
|
93
|
-
function parseBorders(root) {
|
|
132
|
+
function parseBorders(root, colors) {
|
|
94
133
|
const node = asObject(root["borders"]);
|
|
95
134
|
if (!node) return [];
|
|
96
135
|
const out = [];
|
|
@@ -101,15 +140,15 @@ function parseBorders(root) {
|
|
|
101
140
|
continue;
|
|
102
141
|
}
|
|
103
142
|
const border = {};
|
|
104
|
-
const top = parseBorderEdge(asObject(obj["top"]));
|
|
105
|
-
const right = parseBorderEdge(asObject(obj["right"]));
|
|
106
|
-
const bottom = parseBorderEdge(asObject(obj["bottom"]));
|
|
107
|
-
const left = parseBorderEdge(asObject(obj["left"]));
|
|
143
|
+
const top = parseBorderEdge(asObject(obj["top"]), colors);
|
|
144
|
+
const right = parseBorderEdge(asObject(obj["right"]), colors);
|
|
145
|
+
const bottom = parseBorderEdge(asObject(obj["bottom"]), colors);
|
|
146
|
+
const left = parseBorderEdge(asObject(obj["left"]), colors);
|
|
108
147
|
if (top) border.top = top;
|
|
109
148
|
if (right) border.right = right;
|
|
110
149
|
if (bottom) border.bottom = bottom;
|
|
111
150
|
if (left) border.left = left;
|
|
112
|
-
const diagonal = parseBorderEdge(asObject(obj["diagonal"]));
|
|
151
|
+
const diagonal = parseBorderEdge(asObject(obj["diagonal"]), colors);
|
|
113
152
|
if (diagonal) {
|
|
114
153
|
border.diagonal = diagonal;
|
|
115
154
|
if (boolAttr(obj, "diagonalUp")) border.diagonalUp = true;
|
|
@@ -119,12 +158,12 @@ function parseBorders(root) {
|
|
|
119
158
|
}
|
|
120
159
|
return out;
|
|
121
160
|
}
|
|
122
|
-
function parseBorderEdge(node) {
|
|
161
|
+
function parseBorderEdge(node, colors) {
|
|
123
162
|
if (!node) return void 0;
|
|
124
163
|
const styleRaw = strAttr(node, "style");
|
|
125
164
|
if (!styleRaw) return void 0;
|
|
126
165
|
if (!VALID_BORDER_STYLES.has(styleRaw)) return void 0;
|
|
127
|
-
const colorHex = colorOf(asObject(node["color"]));
|
|
166
|
+
const colorHex = colorOf(asObject(node["color"]), colors);
|
|
128
167
|
const edge = { style: styleRaw };
|
|
129
168
|
if (colorHex) edge.colorHex = colorHex;
|
|
130
169
|
return edge;
|
|
@@ -142,13 +181,16 @@ function parseNumFmts(root) {
|
|
|
142
181
|
}
|
|
143
182
|
return result;
|
|
144
183
|
}
|
|
145
|
-
function parseFonts(root) {
|
|
184
|
+
function parseFonts(root, colors) {
|
|
146
185
|
const node = asObject(root["fonts"]);
|
|
147
186
|
if (!node) return [];
|
|
148
187
|
const out = [];
|
|
149
188
|
for (const item of asArray(node["font"])) {
|
|
150
189
|
const obj = asObject(item);
|
|
151
|
-
if (!obj)
|
|
190
|
+
if (!obj) {
|
|
191
|
+
out.push({});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
152
194
|
const font = {};
|
|
153
195
|
const sz = childValAttr(obj, "sz");
|
|
154
196
|
if (sz !== void 0) {
|
|
@@ -157,8 +199,9 @@ function parseFonts(root) {
|
|
|
157
199
|
}
|
|
158
200
|
if (hasChild(obj, "b")) font.bold = childToggle(obj, "b");
|
|
159
201
|
if (hasChild(obj, "i")) font.italic = childToggle(obj, "i");
|
|
160
|
-
if (hasChild(obj, "u")) font.underline =
|
|
161
|
-
|
|
202
|
+
if (hasChild(obj, "u")) font.underline = childUnderline(obj);
|
|
203
|
+
if (hasChild(obj, "strike")) font.strike = childToggle(obj, "strike");
|
|
204
|
+
const colorRgb = colorOf(asObject(obj["color"]), colors);
|
|
162
205
|
if (colorRgb) font.colorHex = colorRgb;
|
|
163
206
|
const nameVal = childValAttr(obj, "name");
|
|
164
207
|
if (nameVal) font.name = nameVal;
|
|
@@ -166,25 +209,28 @@ function parseFonts(root) {
|
|
|
166
209
|
}
|
|
167
210
|
return out;
|
|
168
211
|
}
|
|
169
|
-
function parseFills(root) {
|
|
212
|
+
function parseFills(root, colors) {
|
|
170
213
|
const node = asObject(root["fills"]);
|
|
171
214
|
if (!node) return [];
|
|
172
215
|
const out = [];
|
|
173
216
|
for (const item of asArray(node["fill"])) {
|
|
174
217
|
const obj = asObject(item);
|
|
175
|
-
if (!obj)
|
|
218
|
+
if (!obj) {
|
|
219
|
+
out.push({});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
176
222
|
const fill = {};
|
|
177
223
|
const pf = asObject(obj["patternFill"]);
|
|
178
224
|
if (pf) {
|
|
179
225
|
const pt = strAttr(pf, "patternType");
|
|
180
226
|
if (pt) fill.patternType = pt;
|
|
181
|
-
const fg = colorOf(asObject(pf["fgColor"]));
|
|
182
|
-
const bg = colorOf(asObject(pf["bgColor"]));
|
|
227
|
+
const fg = colorOf(asObject(pf["fgColor"]), colors);
|
|
228
|
+
const bg = colorOf(asObject(pf["bgColor"]), colors);
|
|
183
229
|
if (fg) fill.fgColorHex = fg;
|
|
184
230
|
if (bg) fill.bgColorHex = bg;
|
|
185
231
|
}
|
|
186
232
|
if (!fill.patternType) {
|
|
187
|
-
const avg = averageGradientColor(asObject(obj["gradientFill"]));
|
|
233
|
+
const avg = averageGradientColor(asObject(obj["gradientFill"]), colors);
|
|
188
234
|
if (avg) {
|
|
189
235
|
fill.patternType = "solid";
|
|
190
236
|
fill.fgColorHex = avg;
|
|
@@ -200,7 +246,15 @@ function parseCellXfs(root) {
|
|
|
200
246
|
const out = [];
|
|
201
247
|
for (const item of asArray(node["xf"])) {
|
|
202
248
|
const obj = asObject(item);
|
|
203
|
-
if (!obj)
|
|
249
|
+
if (!obj) {
|
|
250
|
+
out.push({
|
|
251
|
+
numFmtId: 0,
|
|
252
|
+
fontId: 0,
|
|
253
|
+
fillId: 0,
|
|
254
|
+
borderId: 0
|
|
255
|
+
});
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
204
258
|
const xf = {
|
|
205
259
|
numFmtId: numAttr(obj, "numFmtId") ?? 0,
|
|
206
260
|
fontId: numAttr(obj, "fontId") ?? 0,
|
|
@@ -268,6 +322,14 @@ function childValAttr(obj, childName) {
|
|
|
268
322
|
function hasChild(obj, childName) {
|
|
269
323
|
return childName in obj;
|
|
270
324
|
}
|
|
325
|
+
function childUnderline(obj) {
|
|
326
|
+
const child = obj["u"];
|
|
327
|
+
if (child === "" || child === null || child === void 0) return true;
|
|
328
|
+
if (typeof child !== "object") return true;
|
|
329
|
+
const val = strAttr(child, "val");
|
|
330
|
+
if (val === void 0) return true;
|
|
331
|
+
return !(val === "none" || val === "false" || val === "0");
|
|
332
|
+
}
|
|
271
333
|
function childToggle(obj, childName) {
|
|
272
334
|
const child = obj[childName];
|
|
273
335
|
if (child === "" || child === null || child === void 0) return true;
|
|
@@ -276,7 +338,7 @@ function childToggle(obj, childName) {
|
|
|
276
338
|
if (val === void 0) return true;
|
|
277
339
|
return !(val === "false" || val === "0");
|
|
278
340
|
}
|
|
279
|
-
function averageGradientColor(gf) {
|
|
341
|
+
function averageGradientColor(gf, colors) {
|
|
280
342
|
if (!gf) return void 0;
|
|
281
343
|
let r = 0;
|
|
282
344
|
let g = 0;
|
|
@@ -284,7 +346,7 @@ function averageGradientColor(gf) {
|
|
|
284
346
|
let n = 0;
|
|
285
347
|
for (const s of asArray(gf["stop"])) {
|
|
286
348
|
const so = asObject(s);
|
|
287
|
-
const hex = so ? colorOf(asObject(so["color"])) : void 0;
|
|
349
|
+
const hex = so ? colorOf(asObject(so["color"]), colors) : void 0;
|
|
288
350
|
if (!hex) continue;
|
|
289
351
|
r += parseInt(hex.slice(0, 2), 16);
|
|
290
352
|
g += parseInt(hex.slice(2, 4), 16);
|
|
@@ -295,13 +357,88 @@ function averageGradientColor(gf) {
|
|
|
295
357
|
const h = (x) => Math.round(x / n).toString(16).padStart(2, "0").toUpperCase();
|
|
296
358
|
return `${h(r)}${h(g)}${h(b)}`;
|
|
297
359
|
}
|
|
298
|
-
function colorOf(node) {
|
|
360
|
+
function colorOf(node, colors) {
|
|
299
361
|
if (!node) return void 0;
|
|
300
362
|
const rgb = strAttr(node, "rgb");
|
|
301
363
|
if (rgb) {
|
|
302
364
|
if (/^[0-9A-Fa-f]{8}$/.test(rgb)) return rgb.substring(2).toUpperCase();
|
|
303
365
|
if (/^[0-9A-Fa-f]{6}$/.test(rgb)) return rgb.toUpperCase();
|
|
304
366
|
}
|
|
367
|
+
const indexed = strAttr(node, "indexed");
|
|
368
|
+
if (indexed !== void 0) {
|
|
369
|
+
const i = Number(indexed);
|
|
370
|
+
if (Number.isInteger(i) && i >= 0 && i < colors.indexed.length) return colors.indexed[i];
|
|
371
|
+
}
|
|
372
|
+
const themeIdx = strAttr(node, "theme");
|
|
373
|
+
if (themeIdx !== void 0 && colors.theme) {
|
|
374
|
+
const slot = THEME_SLOTS[Number(themeIdx)];
|
|
375
|
+
const base = slot ? colors.theme.get(slot) : void 0;
|
|
376
|
+
if (base) return applyTint(base, Number(strAttr(node, "tint") ?? "0"));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** §18.8.27 — the indexed palette proper; 56 and 57+ are system colours. */
|
|
380
|
+
var PALETTE_ENTRIES = 56;
|
|
381
|
+
/**
|
|
382
|
+
* §18.8.27 `<colors><indexedColors>` — the workbook's OWN legacy palette. It
|
|
383
|
+
* replaces the built-in one wholesale (Excel writes all 56 entries), and a
|
|
384
|
+
* workbook that ships one means it: customIndexedColors.xlsx paints a cell from
|
|
385
|
+
* index 61, which is a plum in the default table and the light grey #E8E8E8 in
|
|
386
|
+
* its own.
|
|
387
|
+
*/
|
|
388
|
+
function parseIndexedColors(root) {
|
|
389
|
+
const colors = asObject(root["colors"]);
|
|
390
|
+
const node = colors ? asObject(colors["indexedColors"]) : void 0;
|
|
391
|
+
if (!node) return INDEXED_COLORS;
|
|
392
|
+
const out = [];
|
|
393
|
+
for (const item of asArray(node["rgbColor"])) {
|
|
394
|
+
const obj = asObject(item);
|
|
395
|
+
const rgb = obj ? strAttr(obj, "rgb") : void 0;
|
|
396
|
+
if (rgb === void 0) break;
|
|
397
|
+
const hex = rgb.length === 8 ? rgb.slice(2) : rgb;
|
|
398
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) break;
|
|
399
|
+
out.push(hex.toUpperCase());
|
|
400
|
+
}
|
|
401
|
+
if (out.length < PALETTE_ENTRIES) return INDEXED_COLORS;
|
|
402
|
+
return [...out, ...INDEXED_COLORS.slice(out.length)];
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* ECMA-376 §18.8.3 `<color theme>` — the index into the workbook theme's colour
|
|
406
|
+
* scheme. Excel's order is NOT the order the theme part declares them in: the
|
|
407
|
+
* first two slots are swapped, so `theme="0"` is the light background and
|
|
408
|
+
* `theme="1"` the dark text, while `theme1.xml` writes `dk1` before `lt1`.
|
|
409
|
+
*/
|
|
410
|
+
var THEME_SLOTS = [
|
|
411
|
+
"lt1",
|
|
412
|
+
"dk1",
|
|
413
|
+
"lt2",
|
|
414
|
+
"dk2",
|
|
415
|
+
"accent1",
|
|
416
|
+
"accent2",
|
|
417
|
+
"accent3",
|
|
418
|
+
"accent4",
|
|
419
|
+
"accent5",
|
|
420
|
+
"accent6",
|
|
421
|
+
"hlink",
|
|
422
|
+
"folHlink"
|
|
423
|
+
];
|
|
424
|
+
/**
|
|
425
|
+
* §18.8.19 `tint` — lighten (positive) or darken (negative) a theme colour by
|
|
426
|
+
* scaling its HSL luminance. Excel writes it on nearly every theme colour it
|
|
427
|
+
* uses, so ignoring it is not much better than ignoring the colour: a `theme="2"
|
|
428
|
+
* tint="-0.5"` band is half as light as its slot.
|
|
429
|
+
*/
|
|
430
|
+
function applyTint(hex, tint) {
|
|
431
|
+
if (!Number.isFinite(tint) || tint === 0) return hex;
|
|
432
|
+
return tint < 0 ? applyColorMods(hex, [{
|
|
433
|
+
kind: "lumMod",
|
|
434
|
+
val: 1 + tint
|
|
435
|
+
}]) : applyColorMods(hex, [{
|
|
436
|
+
kind: "lumMod",
|
|
437
|
+
val: 1 - tint
|
|
438
|
+
}, {
|
|
439
|
+
kind: "lumOff",
|
|
440
|
+
val: tint
|
|
441
|
+
}]);
|
|
305
442
|
}
|
|
306
443
|
//#endregion
|
|
307
|
-
export { EMPTY_XLSX_STYLES, parseXlsxStyles };
|
|
444
|
+
export { EMPTY_XLSX_STYLES, parseDxf, parseXlsxStyles };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a VML shape sits, in points from the sheet's top-left — read from the
|
|
3
|
+
* CSS `style` attribute (`margin-left`/`margin-top`/`width`/`height`).
|
|
4
|
+
*
|
|
5
|
+
* The same rectangle is also expressed by `<x:ClientData><x:Anchor>` as
|
|
6
|
+
* (column, offset px, row, offset px) pairs, which needs the sheet's column
|
|
7
|
+
* widths and row heights to resolve. The style is already absolute, and the two
|
|
8
|
+
* agree exactly in every file checked, so this reads the simpler one.
|
|
9
|
+
*/
|
|
10
|
+
export interface VmlShapeBox {
|
|
11
|
+
readonly xPt: number;
|
|
12
|
+
readonly yPt: number;
|
|
13
|
+
readonly widthPt: number;
|
|
14
|
+
readonly heightPt: number;
|
|
15
|
+
}
|
|
16
|
+
/** A form control declared by a legacy VML shape's `<x:ClientData>`. */
|
|
17
|
+
export interface VmlFormControl {
|
|
18
|
+
/** `ST_ObjectType` — `Radio`, `CheckBox`, `GBox`, `Button`, `Drop`, … */
|
|
19
|
+
readonly objectType: string;
|
|
20
|
+
/** The shape id (`o:spid` without its `_x0000_s` prefix), pairing with `<control shapeId>`. */
|
|
21
|
+
readonly shapeId?: string;
|
|
22
|
+
/** The visible label, from the shape's `<v:textbox>`. */
|
|
23
|
+
readonly caption?: string;
|
|
24
|
+
/** `<x:Checked>` — set on a checked check/option button, absent otherwise. */
|
|
25
|
+
readonly checked?: boolean;
|
|
26
|
+
/** `<x:FirstButton/>` — this option button starts a new group. */
|
|
27
|
+
readonly firstButton?: boolean;
|
|
28
|
+
/** Where the control sits, from the shape's `style` (§{@link VmlShapeBox}). */
|
|
29
|
+
readonly box?: VmlShapeBox;
|
|
30
|
+
/** `<v:textbox><font size>` — twentieths of a point, so 160 is 8pt. */
|
|
31
|
+
readonly fontSizePt?: number;
|
|
32
|
+
}
|
|
33
|
+
/** A parsed legacy VML drawing: its form controls plus every shape's box. */
|
|
34
|
+
export interface VmlDrawing {
|
|
35
|
+
readonly controls: ReadonlyArray<VmlFormControl>;
|
|
36
|
+
/**
|
|
37
|
+
* Shape id (`o:spid` without its `_x0000_s` prefix) → box, for EVERY shape in
|
|
38
|
+
* the part, controls or not. An ActiveX control's box lives nowhere else: its
|
|
39
|
+
* `activeX#.xml` carries only a class id and its `<control>` element carries
|
|
40
|
+
* no anchor, so the `ObjectType="Pict"` shape that shares its `shapeId` is
|
|
41
|
+
* the one thing that says where it goes.
|
|
42
|
+
*/
|
|
43
|
+
readonly boxes: ReadonlyMap<string, VmlShapeBox>;
|
|
44
|
+
/**
|
|
45
|
+
* Shape ids whose `<x:ClientData>` clears `<x:PrintObject>` — Excel's "Print
|
|
46
|
+
* object" checkbox, the legacy spelling of §18.3.1.20 `<controlPr print>`.
|
|
47
|
+
* Such a shape is on screen only, and the `<control>` that shares its id must
|
|
48
|
+
* not be drawn either.
|
|
49
|
+
*/
|
|
50
|
+
readonly nonPrinting: ReadonlySet<string>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read the form controls out of a legacy VML drawing part.
|
|
54
|
+
*
|
|
55
|
+
* Shapes with no `<x:ClientData>`, or whose object type is not a control (a
|
|
56
|
+
* comment, a text box, a picture), are skipped — this is not a general VML
|
|
57
|
+
* shape reader, only the control channel. See {@link CONTROL_TYPES}.
|
|
58
|
+
*
|
|
59
|
+
* @param data The raw `vmlDrawing#.vml` bytes.
|
|
60
|
+
* @returns One entry per control shape, in document order.
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseVmlFormControls(data: Uint8Array): Array<VmlFormControl>;
|
|
63
|
+
/**
|
|
64
|
+
* Read a legacy VML drawing part: its form controls and every shape's box.
|
|
65
|
+
*
|
|
66
|
+
* @param data The raw `vmlDrawing#.vml` bytes.
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseVmlDrawing(data: Uint8Array): VmlDrawing;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
//#region src/excel/vml-drawing.ts
|
|
3
|
+
var decoder = new TextDecoder("utf-8");
|
|
4
|
+
var parser = new XMLParser({
|
|
5
|
+
htmlEntities: true,
|
|
6
|
+
ignoreAttributes: false,
|
|
7
|
+
attributeNamePrefix: "@_",
|
|
8
|
+
textNodeName: "#text",
|
|
9
|
+
parseAttributeValue: false,
|
|
10
|
+
parseTagValue: false,
|
|
11
|
+
removeNSPrefix: true
|
|
12
|
+
});
|
|
13
|
+
var SPID_PREFIX = /^_x0000_s/;
|
|
14
|
+
/**
|
|
15
|
+
* The `x:ObjectType` values that name a FORM CONTROL.
|
|
16
|
+
*
|
|
17
|
+
* Not everything with an `<x:ClientData>` is one — a cell comment is a VML
|
|
18
|
+
* shape with `ObjectType="Note"`, and so are text boxes, pictures and movies.
|
|
19
|
+
* Taking the element's presence as the test listed every comment in the
|
|
20
|
+
* document as a form control.
|
|
21
|
+
*/
|
|
22
|
+
var CONTROL_TYPES = new Set([
|
|
23
|
+
"Button",
|
|
24
|
+
"Checkbox",
|
|
25
|
+
"CheckBox",
|
|
26
|
+
"Dialog",
|
|
27
|
+
"Drop",
|
|
28
|
+
"Edit",
|
|
29
|
+
"EditBox",
|
|
30
|
+
"GBox",
|
|
31
|
+
"Label",
|
|
32
|
+
"List",
|
|
33
|
+
"Radio",
|
|
34
|
+
"Scroll",
|
|
35
|
+
"Spin"
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Read a legacy VML drawing part: its form controls and every shape's box.
|
|
39
|
+
*
|
|
40
|
+
* @param data The raw `vmlDrawing#.vml` bytes.
|
|
41
|
+
*/
|
|
42
|
+
function parseVmlDrawing(data) {
|
|
43
|
+
const tree = parser.parse(decoder.decode(data));
|
|
44
|
+
const root = asObject(tree["xml"]) ?? tree;
|
|
45
|
+
const out = [];
|
|
46
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
47
|
+
const nonPrinting = /* @__PURE__ */ new Set();
|
|
48
|
+
for (const raw of asArray(root["shape"])) {
|
|
49
|
+
const shape = asObject(raw);
|
|
50
|
+
if (!shape) continue;
|
|
51
|
+
const idAttr = strAttr(shape, "id");
|
|
52
|
+
const shapeId = (strAttr(shape, "spid") ?? (idAttr !== void 0 && SPID_PREFIX.test(idAttr) ? idAttr : void 0))?.replace(SPID_PREFIX, "");
|
|
53
|
+
const box = shapeBox(strAttr(shape, "style"));
|
|
54
|
+
if (shapeId && box) boxes.set(shapeId, box);
|
|
55
|
+
const client = asObject(shape["ClientData"]);
|
|
56
|
+
if (!client) continue;
|
|
57
|
+
if (!(flatText(client["PrintObject"])?.toLowerCase() !== "false")) {
|
|
58
|
+
if (shapeId) nonPrinting.add(shapeId);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const objectType = strAttr(client, "ObjectType");
|
|
62
|
+
if (!objectType || !CONTROL_TYPES.has(objectType)) continue;
|
|
63
|
+
const control = { objectType };
|
|
64
|
+
if (shapeId) control.shapeId = shapeId;
|
|
65
|
+
if (box) control.box = box;
|
|
66
|
+
const caption = textboxText(shape["textbox"]);
|
|
67
|
+
if (caption) control.caption = caption;
|
|
68
|
+
const fontSizePt = textboxFontSizePt(shape["textbox"]);
|
|
69
|
+
if (fontSizePt !== void 0) control.fontSizePt = fontSizePt;
|
|
70
|
+
if (flatText(client["Checked"]) === "1") control.checked = true;
|
|
71
|
+
if ("FirstButton" in client) control.firstButton = true;
|
|
72
|
+
out.push(control);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
controls: out,
|
|
76
|
+
boxes,
|
|
77
|
+
nonPrinting
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** CSS length → points. A bare number is pixels, VML's implicit unit. */
|
|
81
|
+
var CSS_UNITS = new Map([
|
|
82
|
+
["pt", 1],
|
|
83
|
+
["px", .75],
|
|
84
|
+
["in", 72],
|
|
85
|
+
["cm", 72 / 2.54],
|
|
86
|
+
["mm", 7.2 / 2.54],
|
|
87
|
+
["pc", 12]
|
|
88
|
+
]);
|
|
89
|
+
function cssLengthPt(value) {
|
|
90
|
+
if (value === void 0) return void 0;
|
|
91
|
+
const m = /^\s*(-?[\d.]+)\s*([a-z]*)\s*$/i.exec(value);
|
|
92
|
+
if (!m) return void 0;
|
|
93
|
+
const n = Number(m[1]);
|
|
94
|
+
if (!Number.isFinite(n)) return void 0;
|
|
95
|
+
const unit = m[2].toLowerCase();
|
|
96
|
+
const factor = unit === "" ? .75 : CSS_UNITS.get(unit);
|
|
97
|
+
return factor === void 0 ? void 0 : n * factor;
|
|
98
|
+
}
|
|
99
|
+
/** The shape's rectangle from its `style` declaration; undefined if incomplete. */
|
|
100
|
+
function shapeBox(style) {
|
|
101
|
+
if (style === void 0) return void 0;
|
|
102
|
+
const props = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const decl of style.split(";")) {
|
|
104
|
+
const i = decl.indexOf(":");
|
|
105
|
+
if (i < 0) continue;
|
|
106
|
+
props.set(decl.slice(0, i).trim().toLowerCase(), decl.slice(i + 1).trim());
|
|
107
|
+
}
|
|
108
|
+
const widthPt = cssLengthPt(props.get("width"));
|
|
109
|
+
const heightPt = cssLengthPt(props.get("height"));
|
|
110
|
+
if (widthPt === void 0 || heightPt === void 0) return void 0;
|
|
111
|
+
return {
|
|
112
|
+
xPt: cssLengthPt(props.get("margin-left")) ?? 0,
|
|
113
|
+
yPt: cssLengthPt(props.get("margin-top")) ?? 0,
|
|
114
|
+
widthPt,
|
|
115
|
+
heightPt
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** `<v:textbox>`'s `<font size>` — twentieths of a point (160 ⇒ 8pt). */
|
|
119
|
+
function textboxFontSizePt(node) {
|
|
120
|
+
const box = asObject(node);
|
|
121
|
+
const font = asObject(((box ? asObject(box["div"]) : void 0) ?? box)?.["font"]);
|
|
122
|
+
const size = font ? strAttr(font, "size") : void 0;
|
|
123
|
+
const twips = size === void 0 ? NaN : Number(size);
|
|
124
|
+
return Number.isFinite(twips) && twips > 0 ? twips / 20 : void 0;
|
|
125
|
+
}
|
|
126
|
+
/** The visible text of a `<v:textbox>`, whose content is an HTML fragment. */
|
|
127
|
+
function textboxText(node) {
|
|
128
|
+
const text = flatText(node);
|
|
129
|
+
if (text === void 0) return void 0;
|
|
130
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
131
|
+
return collapsed.length > 0 ? collapsed : void 0;
|
|
132
|
+
}
|
|
133
|
+
/** Every text node under `node`, concatenated — the element tree flattened. */
|
|
134
|
+
function flatText(node) {
|
|
135
|
+
if (node === void 0 || node === null) return void 0;
|
|
136
|
+
if (typeof node === "string") return node;
|
|
137
|
+
if (typeof node === "number" || typeof node === "boolean") return String(node);
|
|
138
|
+
if (Array.isArray(node)) return node.map((n) => flatText(n) ?? "").join("");
|
|
139
|
+
if (typeof node !== "object") return void 0;
|
|
140
|
+
let out = "";
|
|
141
|
+
for (const [key, value] of Object.entries(node)) {
|
|
142
|
+
if (key.startsWith("@_")) continue;
|
|
143
|
+
out += flatText(value) ?? "";
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
function asObject(value) {
|
|
148
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
149
|
+
}
|
|
150
|
+
function asArray(value) {
|
|
151
|
+
return Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
152
|
+
}
|
|
153
|
+
function strAttr(obj, name) {
|
|
154
|
+
const v = obj[`@_${name}`];
|
|
155
|
+
return typeof v === "string" ? v : typeof v === "number" ? String(v) : void 0;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
export { parseVmlDrawing };
|
|
@@ -7,6 +7,11 @@ export interface SheetReference {
|
|
|
7
7
|
readonly sheetId: string;
|
|
8
8
|
/** The relationship id (`r:id`) resolving to the sheet's worksheet part. */
|
|
9
9
|
readonly relationshipId: string;
|
|
10
|
+
/**
|
|
11
|
+
* §18.2.19 `@state` — `hidden` or `veryHidden` when the tab is not shown.
|
|
12
|
+
* Excel and LibreOffice print only visible sheets.
|
|
13
|
+
*/
|
|
14
|
+
readonly hidden?: boolean;
|
|
10
15
|
}
|
|
11
16
|
/** The parsed `workbook.xml`: the ordered sheet list, the date epoch and defined names. */
|
|
12
17
|
export interface ParsedWorkbook {
|
|
@@ -2,6 +2,7 @@ import { XMLParser } from "fast-xml-parser";
|
|
|
2
2
|
//#region src/excel/workbook-parser.ts
|
|
3
3
|
var decoder = new TextDecoder("utf-8");
|
|
4
4
|
var parser = new XMLParser({
|
|
5
|
+
htmlEntities: true,
|
|
5
6
|
ignoreAttributes: false,
|
|
6
7
|
attributeNamePrefix: "@_",
|
|
7
8
|
textNodeName: "#text",
|
|
@@ -42,10 +43,13 @@ function parseWorkbook(data) {
|
|
|
42
43
|
const sheetId = strAttr(obj, "sheetId");
|
|
43
44
|
const rId = strAttr(obj, "r:id") ?? strAttr(obj, "id");
|
|
44
45
|
if (!name || !rId) continue;
|
|
46
|
+
const state = strAttr(obj, "state");
|
|
47
|
+
const hidden = state === "hidden" || state === "veryHidden";
|
|
45
48
|
out.push({
|
|
46
49
|
name,
|
|
47
50
|
sheetId: sheetId ?? "",
|
|
48
|
-
relationshipId: rId
|
|
51
|
+
relationshipId: rId,
|
|
52
|
+
...hidden ? { hidden } : {}
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
return {
|