@shbernal/ts-xlsx 1.1.0 → 1.2.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 +21 -0
- package/dist/core/address.js +0 -33
- package/dist/core/autofilter.js +0 -9
- package/dist/core/cell.js +0 -131
- package/dist/core/color-resolution.js +0 -60
- package/dist/core/column.js +0 -60
- package/dist/core/comment-thread.js +0 -45
- package/dist/core/conditional-formatting-overlay.js +0 -11
- package/dist/core/conditional-formatting.js +0 -10
- package/dist/core/containers.js +0 -15
- package/dist/core/data-validation-overlay.js +0 -25
- package/dist/core/data-validation.js +0 -7
- package/dist/core/date.js +0 -39
- package/dist/core/formula.js +0 -117
- package/dist/core/grid-edits.js +0 -38
- package/dist/core/image.js +0 -25
- package/dist/core/internal.js +0 -27
- package/dist/core/limits.js +0 -45
- package/dist/core/merge.d.ts +18 -0
- package/dist/core/merge.js +22 -7
- package/dist/core/modern-functions.js +0 -17
- package/dist/core/page-setup.js +0 -5
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +0 -48
- package/dist/core/preserved.js +0 -4
- package/dist/core/protection.js +0 -26
- package/dist/core/range.js +0 -112
- package/dist/core/row-input.d.ts +24 -0
- package/dist/core/row-input.js +29 -0
- package/dist/core/row.js +0 -66
- package/dist/core/style.js +0 -26
- package/dist/core/table-style.js +0 -28
- package/dist/core/table.js +0 -97
- package/dist/core/text-metrics.js +0 -40
- package/dist/core/theme.js +0 -85
- package/dist/core/value.js +0 -94
- package/dist/core/workbook-protection.js +0 -13
- package/dist/core/workbook.js +0 -380
- package/dist/core/worksheet-model.js +2 -23
- package/dist/core/worksheet.js +7 -506
- package/dist/customui/errors.js +0 -10
- package/dist/customui/index.js +0 -5
- package/dist/customui/ribbon.js +0 -43
- package/dist/entries/core.js +0 -6
- package/dist/entries/csv.js +0 -4
- package/dist/entries/customui.js +0 -2
- package/dist/entries/errors.js +0 -13
- package/dist/entries/vba.js +0 -5
- package/dist/entries/xlsb.js +0 -6
- package/dist/entries/xlsx.js +0 -13
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +5 -56
- package/dist/index.js +0 -12
- package/dist/io/csv/read.js +0 -18
- package/dist/io/csv/write.js +0 -18
- package/dist/io/opc/errors.js +0 -30
- package/dist/io/opc/inflate.js +0 -35
- package/dist/io/opc/namespaces.js +0 -15
- package/dist/io/opc/part-paths.js +0 -11
- package/dist/io/opc/read-opc.js +0 -31
- package/dist/io/opc/read-options.js +0 -6
- package/dist/io/opc/rels.js +0 -13
- package/dist/io/opc/sniff-format.js +0 -50
- package/dist/io/style/xf-style.js +0 -31
- package/dist/io/xlsb/errors.js +0 -10
- package/dist/io/xlsb/formula.js +1 -99
- package/dist/io/xlsb/primitives.js +1 -83
- package/dist/io/xlsb/ptg-functions.js +0 -34
- package/dist/io/xlsb/read-shared-strings.js +0 -7
- package/dist/io/xlsb/read-styles.js +3 -66
- package/dist/io/xlsb/read-worksheet.js +4 -87
- package/dist/io/xlsb/read.js +2 -60
- package/dist/io/xlsb/record-stream.js +0 -28
- package/dist/io/xlsb/record-types.js +0 -19
- package/dist/io/xlsx/cell-accumulator.js +0 -32
- package/dist/io/xlsx/cell-value.js +0 -29
- package/dist/io/xlsx/color-xml.d.ts +6 -0
- package/dist/io/xlsx/color-xml.js +42 -0
- package/dist/io/xlsx/comments.js +0 -84
- package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
- package/dist/io/xlsx/conditional-formatting.js +1 -97
- package/dist/io/xlsx/data-validation.js +0 -56
- package/dist/io/xlsx/edit-vba.js +0 -54
- package/dist/io/xlsx/errors.js +0 -12
- package/dist/io/xlsx/hyperlinks.js +0 -40
- package/dist/io/xlsx/images.js +0 -32
- package/dist/io/xlsx/namespaces.js +0 -42
- package/dist/io/xlsx/package-plan.js +0 -47
- package/dist/io/xlsx/pivot.js +0 -22
- package/dist/io/xlsx/{pivot-read.js → read-pivot.js} +0 -30
- package/dist/io/xlsx/read-rows.js +1 -83
- package/dist/io/xlsx/{shared-strings-read.js → read-shared-strings.js} +0 -11
- package/dist/io/xlsx/read-styles.d.ts +25 -1
- package/dist/io/xlsx/read-styles.js +61 -108
- package/dist/io/xlsx/read-worksheet.js +1 -88
- package/dist/io/xlsx/read.js +3 -211
- package/dist/io/xlsx/relationships.js +0 -11
- package/dist/io/xlsx/rich-runs.js +0 -16
- package/dist/io/xlsx/rich-text.js +0 -12
- package/dist/io/xlsx/shared-formulas.js +0 -7
- package/dist/io/xlsx/shared-strings.js +0 -21
- package/dist/io/xlsx/sheet-properties.js +1 -64
- package/dist/io/xlsx/styles.d.ts +1 -29
- package/dist/io/xlsx/styles.js +2 -366
- package/dist/io/xlsx/tables.js +5 -52
- package/dist/io/xlsx/threaded-comments.js +0 -103
- package/dist/io/xlsx/workbook-xml.js +0 -94
- package/dist/io/xlsx/worksheet-xml.js +0 -174
- package/dist/io/xlsx/write-stream.js +0 -137
- package/dist/io/xlsx/write.js +1 -134
- package/dist/io/xlsx/x14-ext.js +0 -8
- package/dist/vba/cfb-writer.js +12 -59
- package/dist/vba/cfb.js +2 -32
- package/dist/vba/codepage.js +1 -23
- package/dist/vba/errors.js +0 -15
- package/dist/vba/index.js +0 -10
- package/dist/vba/ms-ovba.js +3 -46
- package/dist/vba/project-editor.js +4 -89
- package/dist/vba/project.js +2 -34
- package/dist/vba/vba-encoding.js +1 -17
- package/dist/xml/errors.js +0 -10
- package/dist/xml/xml-read.js +3 -82
- package/dist/xml/xml.js +0 -39
- package/package.json +8 -9
- package/skills/ts-xlsx-upstream/SKILL.md +178 -0
- /package/dist/io/xlsx/{pivot-read.d.ts → read-pivot.d.ts} +0 -0
- /package/dist/io/xlsx/{shared-strings-read.d.ts → read-shared-strings.d.ts} +0 -0
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
// The style-table reader: `xl/styles.xml` in, a flat table of resolved cell formats out. It is a
|
|
2
|
-
// single streaming pass over the shared sub-tables (`<numFmts>`, `<fills>`, `<fonts>`, `<borders>`)
|
|
3
|
-
// and the two xf tables (`<cellXfs>`, `<cellStyleXfs>`), flattening the id-indirection so a cell's
|
|
4
|
-
// `s` index maps straight to its facets. A construct it does not recognise is skipped, never guessed.
|
|
5
|
-
//
|
|
6
|
-
// Only the parsing is XML-specific. What an xf resolves *to* — `XfStyle`, the built-in number
|
|
7
|
-
// formats, applying an xf to a cell — is a property of the OOXML style model rather than of its
|
|
8
|
-
// spelling, and lives above both codecs in `../style/xf-style.ts`; the `.xlsb` style reader builds
|
|
9
|
-
// the same table from BIFF12 records.
|
|
10
1
|
import { assignStyleFacets, isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isHorizontalAlignment, isNamedUnderlineStyle, isVerticalAlignment, } from '../../core/style.js';
|
|
11
|
-
import { boolPresent, boolStrict, closeEmptyElements, localName, xmlEvents, } from '../../xml/xml-read.js';
|
|
2
|
+
import { boolPresent, boolStrict, closeEmptyElements, localName, openElements, xmlEvents, } from '../../xml/xml-read.js';
|
|
12
3
|
import { numFmtCodeFor } from '../style/xf-style.js';
|
|
13
|
-
import { parseColor } from './
|
|
14
|
-
// The four sides plus the diagonal — the edge elements a <border> can hold, in the order the
|
|
15
|
-
// schema lists them. This one tuple drives the edge-name union, the membership set (which drives
|
|
16
|
-
// edge parsing without a per-name branch), and the "does any edge carry a style" scan below.
|
|
4
|
+
import { parseColor } from './color-xml.js';
|
|
17
5
|
const BORDER_EDGE_NAMES = ['left', 'right', 'top', 'bottom', 'diagonal'];
|
|
18
6
|
const BORDER_EDGES = new Set(BORDER_EDGE_NAMES);
|
|
19
|
-
// Style-table elements that commit on their close: a bare <font/>/<border/>/<patternFill/>/
|
|
20
|
-
// <gradientFill/>/<xf/> or a self-closing border edge is expanded to open+close so each commits
|
|
21
|
-
// exactly once in onClose, never in a duplicated (and easily-forgotten) self-closing branch.
|
|
22
7
|
const STYLE_EMPTY_CLOSES = new Set([
|
|
23
8
|
'font',
|
|
24
9
|
'border',
|
|
@@ -27,11 +12,6 @@ const STYLE_EMPTY_CLOSES = new Set([
|
|
|
27
12
|
'xf',
|
|
28
13
|
...BORDER_EDGES,
|
|
29
14
|
]);
|
|
30
|
-
// styles.xml is a shared table: <numFmts> defines custom format codes by id, <fills> lists
|
|
31
|
-
// the fills, and <cellXfs> lists the cell formats, each naming a fill and a number format by
|
|
32
|
-
// id. We flatten that indirection into one array — cellXfs index → resolved {fill, numFmt} —
|
|
33
|
-
// so a cell/row/column style index maps straight to its facets. The schema orders <numFmts>
|
|
34
|
-
// and <fills> before <cellXfs>, so both lookups are complete before an xf references them.
|
|
35
15
|
export function parseStyleTable(xml) {
|
|
36
16
|
if (xml === '')
|
|
37
17
|
return { cellXfs: [], namedStyles: [] };
|
|
@@ -40,16 +20,8 @@ export function parseStyleTable(xml) {
|
|
|
40
20
|
let borders = [];
|
|
41
21
|
let numFmtCodes = new Map();
|
|
42
22
|
const xfStyles = [];
|
|
43
|
-
// The named-style layer: <cellStyleXfs> holds the base formats a cell's xfId links to; <cellStyles>
|
|
44
|
-
// labels them by name/builtinId. Parsed in parallel with cellXfs, then zipped and merged below.
|
|
45
23
|
const namedXfs = [];
|
|
46
24
|
let cellStyleNames = [];
|
|
47
|
-
// One streaming pass, but each top-level sub-table drives its own focused sub-parser over the slice
|
|
48
|
-
// of events between its open and close. The schema orders the shared tables (<numFmts>, <fonts>,
|
|
49
|
-
// <fills>, <borders>) before the xf tables, so their results are complete before an <xf> resolves
|
|
50
|
-
// against them. Every recognised container name is plural and unique to the styleSheet root — none
|
|
51
|
-
// appears inside a <dxf>'s singular <font>/<fill>/<border> children — so skipping an unrecognised
|
|
52
|
-
// section here drops exactly what the old flat pass gated off with its `in*` flags.
|
|
53
25
|
const events = closeEmptyElements(xmlEvents(xml), STYLE_EMPTY_CLOSES);
|
|
54
26
|
let next = events.next();
|
|
55
27
|
while (next.done !== true) {
|
|
@@ -81,18 +53,12 @@ export function parseStyleTable(xml) {
|
|
|
81
53
|
}
|
|
82
54
|
next = events.next();
|
|
83
55
|
}
|
|
84
|
-
// Layer each cellXfs entry over the named style its xfId links to: a facet the direct format sets
|
|
85
|
-
// wins; one it leaves unset falls through to the named style. The xfId is carried through so the
|
|
86
|
-
// link survives a re-write. A draft only holds keys for facets it actually set, so the spread merge
|
|
87
|
-
// takes the named base and lets the direct entry override exactly what it names.
|
|
88
56
|
const cellXfs = xfStyles.map((xf) => {
|
|
89
57
|
if (xf.xfId === undefined)
|
|
90
58
|
return xf;
|
|
91
59
|
const named = namedXfs[xf.xfId];
|
|
92
60
|
return named === undefined ? xf : { ...named, ...xf };
|
|
93
61
|
});
|
|
94
|
-
// Zip the resolved cellStyleXfs facets with their cellStyles name/builtinId into the model's named
|
|
95
|
-
// styles, index for index (a cellStyle's xfId is its cellStyleXfs index).
|
|
96
62
|
const namedStyles = namedXfs.map((xf, index) => {
|
|
97
63
|
const label = cellStyleNames.find((entry) => entry.xfId === index);
|
|
98
64
|
const style = {};
|
|
@@ -103,15 +69,9 @@ export function parseStyleTable(xml) {
|
|
|
103
69
|
style.builtinId = label.builtinId;
|
|
104
70
|
return style;
|
|
105
71
|
});
|
|
106
|
-
// Font 0 is the workbook's declared default, so it is carried out whole as well as flattened onto
|
|
107
|
-
// the xfs that name it — see {@link StyleTable.defaultFont}.
|
|
108
72
|
const defaultFont = fonts[0];
|
|
109
73
|
return defaultFont === undefined ? { cellXfs, namedStyles } : { cellXfs, namedStyles, defaultFont };
|
|
110
74
|
}
|
|
111
|
-
// Pull events off the shared stream up to — and consuming — the close of `container`, yielding only
|
|
112
|
-
// those strictly inside it. A sub-table parser loops this to completion (never breaking), so it drives
|
|
113
|
-
// its own small state machine over exactly its section without closing the underlying generator, and
|
|
114
|
-
// the outer pass resumes at the element after the container's close.
|
|
115
75
|
function* until(events, container) {
|
|
116
76
|
let next = events.next();
|
|
117
77
|
while (next.done !== true) {
|
|
@@ -122,8 +82,6 @@ function* until(events, container) {
|
|
|
122
82
|
next = events.next();
|
|
123
83
|
}
|
|
124
84
|
}
|
|
125
|
-
// <numFmts> entries are self-closing, so they are read on open. A code with no id, or the General
|
|
126
|
-
// id 0, contributes nothing.
|
|
127
85
|
function parseNumFmts(events) {
|
|
128
86
|
const codes = new Map();
|
|
129
87
|
for (const event of until(events, 'numFmts')) {
|
|
@@ -142,7 +100,6 @@ function parseFonts(events) {
|
|
|
142
100
|
for (const event of until(events, 'fonts')) {
|
|
143
101
|
if (event.kind === 'open') {
|
|
144
102
|
const local = localName(event.name);
|
|
145
|
-
// A <font>'s children are self-closing, so they are read here on open.
|
|
146
103
|
if (local === 'font')
|
|
147
104
|
fontDraft = {};
|
|
148
105
|
else if (fontDraft !== null)
|
|
@@ -160,9 +117,6 @@ function parseFills(events) {
|
|
|
160
117
|
let pattern = '';
|
|
161
118
|
let fgColor;
|
|
162
119
|
let bgColor;
|
|
163
|
-
// A gradient fill accumulates from <gradientFill> open to close; its stops fill in as <stop>/<color>
|
|
164
|
-
// pairs arrive. `fillSlotAt` marks where in `fills` the current <fill> began, so its close can keep a
|
|
165
|
-
// slot even when the fill body was neither a pattern nor a gradient — index alignment is load-bearing.
|
|
166
120
|
let gradientDraft = null;
|
|
167
121
|
let fillSlotAt = -1;
|
|
168
122
|
for (const event of until(events, 'fills')) {
|
|
@@ -170,9 +124,6 @@ function parseFills(events) {
|
|
|
170
124
|
const attrs = event.attrs;
|
|
171
125
|
switch (localName(event.name)) {
|
|
172
126
|
case 'fill':
|
|
173
|
-
// Mark where this <fill> starts so its close can guarantee exactly one slot — a fill body
|
|
174
|
-
// that is neither <patternFill> nor <gradientFill> (or a gradient we could not parse) must
|
|
175
|
-
// still consume an id, or every later fill index shifts and cells mis-resolve their fill.
|
|
176
127
|
fillSlotAt = fills.length;
|
|
177
128
|
break;
|
|
178
129
|
case 'patternFill':
|
|
@@ -206,7 +157,6 @@ function parseFills(events) {
|
|
|
206
157
|
}
|
|
207
158
|
break;
|
|
208
159
|
case 'color':
|
|
209
|
-
// The colour of the open <stop>; committed to a GradientStop when the stop closes.
|
|
210
160
|
if (gradientDraft !== null)
|
|
211
161
|
gradientDraft.stopColor = parseColor(attrs);
|
|
212
162
|
break;
|
|
@@ -235,8 +185,6 @@ function parseFills(events) {
|
|
|
235
185
|
}
|
|
236
186
|
break;
|
|
237
187
|
case 'fill':
|
|
238
|
-
// Backstop the slot: if this <fill>'s body pushed nothing (unparsed/unknown content), keep an
|
|
239
|
-
// empty slot so id alignment holds and later fills still resolve to the right cells.
|
|
240
188
|
if (fills.length === fillSlotAt)
|
|
241
189
|
fills.push(undefined);
|
|
242
190
|
break;
|
|
@@ -248,7 +196,6 @@ function parseFills(events) {
|
|
|
248
196
|
function parseBorders(events) {
|
|
249
197
|
const borders = [];
|
|
250
198
|
let borderDraft = null;
|
|
251
|
-
// Which edge of the current border a nested <color> belongs to; null between edges.
|
|
252
199
|
let currentEdge = null;
|
|
253
200
|
for (const event of until(events, 'borders')) {
|
|
254
201
|
if (event.kind === 'open') {
|
|
@@ -263,9 +210,6 @@ function parseBorders(events) {
|
|
|
263
210
|
borderDraft.diagonalDown = true;
|
|
264
211
|
}
|
|
265
212
|
else if (borderDraft !== null) {
|
|
266
|
-
// A border's edges and their <color> children are all read on open (each is self-closing bar a
|
|
267
|
-
// coloured edge, whose colour child is itself self-closing). An edge whose style is absent or
|
|
268
|
-
// an unrecognised token is dropped — the side simply carries no border.
|
|
269
213
|
if (BORDER_EDGES.has(local)) {
|
|
270
214
|
if (attrs.style !== undefined && isBorderStyle(attrs.style)) {
|
|
271
215
|
currentEdge = local;
|
|
@@ -292,39 +236,28 @@ function parseBorders(events) {
|
|
|
292
236
|
}
|
|
293
237
|
}
|
|
294
238
|
else if (borderDraft !== null && BORDER_EDGES.has(local)) {
|
|
295
|
-
// A coloured edge closes after its <color> child; drop the edge context so a stray later
|
|
296
|
-
// <color> cannot attach to it.
|
|
297
239
|
currentEdge = null;
|
|
298
240
|
}
|
|
299
241
|
}
|
|
300
242
|
}
|
|
301
243
|
return borders;
|
|
302
244
|
}
|
|
303
|
-
// Both <cellXfs> and <cellStyleXfs> hold <xf> with identical structure; they differ only in which
|
|
304
|
-
// table the result lands in and whether an xfId link is meaningful (only cellXfs entries link to a
|
|
305
|
-
// named style). One parser serves both, told by `container` which it is reading.
|
|
306
245
|
function parseXfTable(events, container, deps) {
|
|
307
246
|
const xfs = [];
|
|
308
247
|
const captureXfId = container === 'cellXfs';
|
|
309
|
-
// The xf being read; held from open to close so its <alignment>/<protection> children can attach
|
|
310
|
-
// before it is committed. null outside an <xf>.
|
|
311
248
|
let pendingXf = null;
|
|
312
249
|
for (const event of until(events, container)) {
|
|
313
250
|
if (event.kind === 'open') {
|
|
314
251
|
const local = localName(event.name);
|
|
315
252
|
if (local === 'xf') {
|
|
316
|
-
// Hold the xf open until its close so an <alignment>/<protection> child can attach first; a
|
|
317
|
-
// self-closing <xf/> is expanded to a close, so it commits there too, child-free.
|
|
318
253
|
pendingXf = resolveXf(event.attrs, deps, captureXfId);
|
|
319
254
|
}
|
|
320
255
|
else if (pendingXf !== null && local === 'alignment') {
|
|
321
|
-
// An xf's <alignment> child arrives before the xf closes; attach it to the pending xf.
|
|
322
256
|
const alignment = parseAlignment(event.attrs);
|
|
323
257
|
if (alignment !== undefined)
|
|
324
258
|
pendingXf.alignment = alignment;
|
|
325
259
|
}
|
|
326
260
|
else if (pendingXf !== null && local === 'protection') {
|
|
327
|
-
// An xf's <protection> child likewise arrives before the xf closes.
|
|
328
261
|
const protection = parseProtection(event.attrs);
|
|
329
262
|
if (protection !== undefined)
|
|
330
263
|
pendingXf.protection = protection;
|
|
@@ -337,18 +270,12 @@ function parseXfTable(events, container, deps) {
|
|
|
337
270
|
}
|
|
338
271
|
return xfs;
|
|
339
272
|
}
|
|
340
|
-
// Resolve an <xf>'s facet ids against the shared sub-tables into a draft. `captureXfId` is set only
|
|
341
|
-
// for cellXfs entries, the sole table whose xfId links to a named style.
|
|
342
273
|
function resolveXf(attrs, deps, captureXfId) {
|
|
343
274
|
const fillId = Number(attrs.fillId);
|
|
344
275
|
const fill = Number.isInteger(fillId) ? deps.fills[fillId] : undefined;
|
|
345
276
|
const fontId = Number(attrs.fontId);
|
|
346
|
-
// Font id 0 is the workbook default font (a real Calibri-11-style face), not an absence — unlike
|
|
347
|
-
// border id 0, which is a genuinely empty border. So an xf naming font 0 resolves to that default
|
|
348
|
-
// face, giving every cell a concrete font to render.
|
|
349
277
|
const font = Number.isInteger(fontId) ? deps.fonts[fontId] : undefined;
|
|
350
278
|
const borderId = Number(attrs.borderId);
|
|
351
|
-
// Border id 0 is the empty default; only a custom border (id > 0) is an explicit one.
|
|
352
279
|
const border = Number.isInteger(borderId) && borderId > 0 ? deps.borders[borderId] : undefined;
|
|
353
280
|
const numFmt = resolveNumFmt(attrs.numFmtId, deps.numFmtCodes);
|
|
354
281
|
const draft = {};
|
|
@@ -360,12 +287,8 @@ function resolveXf(attrs, deps, captureXfId) {
|
|
|
360
287
|
draft.font = font;
|
|
361
288
|
if (border)
|
|
362
289
|
draft.border = border;
|
|
363
|
-
// The quote-prefix flag is an attribute on the xf itself (no shared sub-table); carry it only when
|
|
364
|
-
// set so an ordinary cell does not gain a spurious `quotePrefix: false`.
|
|
365
290
|
if (boolStrict(attrs.quotePrefix))
|
|
366
291
|
draft.quotePrefix = true;
|
|
367
|
-
// A cellXfs entry's xfId links it to a named style; capture it only when it points beyond the Normal
|
|
368
|
-
// default (0), so an ordinary cell carries no spurious named-style link.
|
|
369
292
|
if (captureXfId && attrs.xfId !== undefined) {
|
|
370
293
|
const xfId = Number(attrs.xfId);
|
|
371
294
|
if (Number.isInteger(xfId) && xfId > 0)
|
|
@@ -373,8 +296,6 @@ function resolveXf(attrs, deps, captureXfId) {
|
|
|
373
296
|
}
|
|
374
297
|
return draft;
|
|
375
298
|
}
|
|
376
|
-
// A <cellStyle> (inside <cellStyles>) names a cellStyleXfs entry by xfId; it is self-closing, so it
|
|
377
|
-
// is read on open.
|
|
378
299
|
function parseCellStyles(events) {
|
|
379
300
|
const names = [];
|
|
380
301
|
for (const event of until(events, 'cellStyles')) {
|
|
@@ -396,9 +317,6 @@ function parseCellStyles(events) {
|
|
|
396
317
|
}
|
|
397
318
|
return names;
|
|
398
319
|
}
|
|
399
|
-
// A <font> child element sets one facet on the draft. Boolean flags honour their `val`: a
|
|
400
|
-
// bare tag or val="1"/"true" is on, val="0"/"false" is off (an explicit-false flag is not
|
|
401
|
-
// truthy merely because the tag is present). An unrecognised child is ignored.
|
|
402
320
|
export function applyFontChild(draft, local, attrs) {
|
|
403
321
|
switch (local) {
|
|
404
322
|
case 'b':
|
|
@@ -414,10 +332,6 @@ export function applyFontChild(draft, local, attrs) {
|
|
|
414
332
|
draft.outline = boolPresent(attrs.val);
|
|
415
333
|
break;
|
|
416
334
|
case 'u':
|
|
417
|
-
// A bare <u/> is a single underline; a named style (single/double/…) carries through; but
|
|
418
|
-
// val="none" is the explicit ABSENCE of an underline, so it must read back falsy — not the
|
|
419
|
-
// truthy string "none" that a consumer's `if (font.underline)` would mistake for underlined. An
|
|
420
|
-
// unrecognised token keeps the "is underlined" fact but drops the unknown style (a plain true).
|
|
421
335
|
draft.underline =
|
|
422
336
|
attrs.val === undefined
|
|
423
337
|
? true
|
|
@@ -440,7 +354,6 @@ export function applyFontChild(draft, local, attrs) {
|
|
|
440
354
|
case 'color':
|
|
441
355
|
draft.color = parseColor(attrs);
|
|
442
356
|
break;
|
|
443
|
-
// `<name>` in a styles `<font>`, `<rFont>` in a rich-text run's `<rPr>` — the same font face.
|
|
444
357
|
case 'name':
|
|
445
358
|
case 'rFont':
|
|
446
359
|
if (attrs.val !== undefined)
|
|
@@ -466,15 +379,10 @@ export function applyFontChild(draft, local, attrs) {
|
|
|
466
379
|
break;
|
|
467
380
|
}
|
|
468
381
|
}
|
|
469
|
-
// An xf's numFmtId resolves against the custom codes first, then the built-in table; the
|
|
470
|
-
// General format (id 0) and any unrecognised id mean the cell carries no explicit format.
|
|
471
382
|
function resolveNumFmt(raw, custom) {
|
|
472
383
|
return raw === undefined ? undefined : numFmtCodeFor(Number(raw), custom);
|
|
473
384
|
}
|
|
474
385
|
function toFill(pattern, fgColor, bgColor) {
|
|
475
|
-
// `none` (and an absent patternType) is the absence of a fill; an unrecognised token is dropped the
|
|
476
|
-
// same way — like the border-edge style above — so a foreign pattern we do not model leaves the cell
|
|
477
|
-
// unfilled rather than propagating a token the writer would later re-emit unvalidated.
|
|
478
386
|
if (!isFillPatternType(pattern) || pattern === 'none')
|
|
479
387
|
return undefined;
|
|
480
388
|
return {
|
|
@@ -484,8 +392,6 @@ function toFill(pattern, fgColor, bgColor) {
|
|
|
484
392
|
...(bgColor ? { bgColor } : {}),
|
|
485
393
|
};
|
|
486
394
|
}
|
|
487
|
-
// Copy the numeric <gradientFill> attributes (degree; the path insets) onto a gradient draft, keeping
|
|
488
|
-
// only the finite ones so an absent or malformed attribute leaves the field its OOXML default (unset).
|
|
489
395
|
function assignGradientNumbers(fill, attrs) {
|
|
490
396
|
for (const key of ['degree', 'left', 'right', 'top', 'bottom']) {
|
|
491
397
|
const value = Number(attrs[key]);
|
|
@@ -493,22 +399,14 @@ function assignGradientNumbers(fill, attrs) {
|
|
|
493
399
|
fill[key] = value;
|
|
494
400
|
}
|
|
495
401
|
}
|
|
496
|
-
// An accumulated border with no styled edge and no diagonal direction is the empty default:
|
|
497
|
-
// it carries nothing, so it resolves to undefined rather than an all-empty Border object.
|
|
498
402
|
function borderToStyle(draft) {
|
|
499
403
|
const hasEdge = BORDER_EDGE_NAMES.some((edge) => draft[edge] !== undefined);
|
|
500
404
|
if (!hasEdge && draft.diagonalUp === undefined && draft.diagonalDown === undefined)
|
|
501
405
|
return undefined;
|
|
502
406
|
return draft;
|
|
503
407
|
}
|
|
504
|
-
// Read an <alignment> element's attributes into an Alignment, keeping only facets that differ
|
|
505
|
-
// from the default. Boolean flags honour their parsed value — wrapText="0" is off, so it must
|
|
506
|
-
// not fabricate a { wrapText: false } alignment — and an element carrying only defaults yields
|
|
507
|
-
// undefined rather than an empty alignment object.
|
|
508
408
|
function parseAlignment(attrs) {
|
|
509
409
|
const out = {};
|
|
510
|
-
// `general` is the default and reads back as no explicit horizontal alignment; an unrecognised
|
|
511
|
-
// token (like an out-of-enum vertical one) is dropped rather than trusted into the model.
|
|
512
410
|
if (attrs.horizontal !== undefined &&
|
|
513
411
|
attrs.horizontal !== 'general' &&
|
|
514
412
|
isHorizontalAlignment(attrs.horizontal)) {
|
|
@@ -537,10 +435,6 @@ function parseAlignment(attrs) {
|
|
|
537
435
|
}
|
|
538
436
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
539
437
|
}
|
|
540
|
-
// Read a <protection> element into a Protection, keeping only facets that differ from the OOXML
|
|
541
|
-
// default. `locked` defaults to TRUE, so only an explicit `locked="0"` carries information (an
|
|
542
|
-
// unlocked cell) — a default or explicit-true cell must not read back as { locked: true }; `hidden`
|
|
543
|
-
// defaults to false, so only `hidden="1"` is carried. An element with only defaults yields undefined.
|
|
544
438
|
function parseProtection(attrs) {
|
|
545
439
|
const out = {};
|
|
546
440
|
if (attrs.locked === '0' || attrs.locked === 'false')
|
|
@@ -549,3 +443,62 @@ function parseProtection(attrs) {
|
|
|
549
443
|
out.hidden = true;
|
|
550
444
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
551
445
|
}
|
|
446
|
+
export function parseIndexedColors(stylesXml) {
|
|
447
|
+
return elementFragments(stylesXml, 'indexedColors', 'rgbColor');
|
|
448
|
+
}
|
|
449
|
+
export function parseMruColors(stylesXml) {
|
|
450
|
+
return elementFragments(stylesXml, 'mruColors', 'color');
|
|
451
|
+
}
|
|
452
|
+
export function parseTableStyles(stylesXml) {
|
|
453
|
+
const styles = elementFragments(stylesXml, 'tableStyles', 'tableStyle');
|
|
454
|
+
const table = { styles };
|
|
455
|
+
for (const { attrs } of openElements(stylesXml, 'tableStyles')) {
|
|
456
|
+
if (attrs.defaultTableStyle !== undefined)
|
|
457
|
+
table.defaultTableStyle = attrs.defaultTableStyle;
|
|
458
|
+
if (attrs.defaultPivotStyle !== undefined)
|
|
459
|
+
table.defaultPivotStyle = attrs.defaultPivotStyle;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
const namespaces = fragmentNamespaces(stylesXml, styles);
|
|
463
|
+
if (namespaces.length > 0)
|
|
464
|
+
table.namespaces = namespaces;
|
|
465
|
+
return table;
|
|
466
|
+
}
|
|
467
|
+
function fragmentNamespaces(stylesXml, fragments) {
|
|
468
|
+
if (fragments.length === 0)
|
|
469
|
+
return [];
|
|
470
|
+
const declared = new Map();
|
|
471
|
+
const ignorable = new Set();
|
|
472
|
+
for (const { attrs } of openElements(stylesXml, 'styleSheet')) {
|
|
473
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
474
|
+
if (name.startsWith('xmlns:'))
|
|
475
|
+
declared.set(name.slice('xmlns:'.length), value);
|
|
476
|
+
}
|
|
477
|
+
for (const prefix of (attrs['mc:Ignorable'] ?? '').split(/\s+/)) {
|
|
478
|
+
if (prefix !== '')
|
|
479
|
+
ignorable.add(prefix);
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
const used = new Set();
|
|
484
|
+
for (const fragment of fragments) {
|
|
485
|
+
for (const match of fragment.matchAll(/[\s</]([A-Za-z_][\w.-]*):[A-Za-z_]/g)) {
|
|
486
|
+
used.add(match[1]);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return [...used]
|
|
490
|
+
.filter((prefix) => declared.has(prefix))
|
|
491
|
+
.map((prefix) => ({
|
|
492
|
+
prefix,
|
|
493
|
+
uri: declared.get(prefix),
|
|
494
|
+
ignorable: ignorable.has(prefix),
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
function elementFragments(xml, container, child) {
|
|
498
|
+
const block = new RegExp(`<${container}\\b[^>]*>([\\s\\S]*?)</${container}>`).exec(xml);
|
|
499
|
+
if (block === null)
|
|
500
|
+
return [];
|
|
501
|
+
const inner = block[1] ?? '';
|
|
502
|
+
const pattern = new RegExp(`<${child}\\b[^>]*/>|<${child}\\b[^>]*>[\\s\\S]*?</${child}>`, 'g');
|
|
503
|
+
return [...inner.matchAll(pattern)].map((m) => m[0] ?? '');
|
|
504
|
+
}
|