@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,42 +1,11 @@
|
|
|
1
|
-
// The `Ftab` built-in function table ([MS-XLS] 2.5.198.17, shared verbatim by BIFF12): the index a
|
|
2
|
-
// `PtgFunc`/`PtgFuncVar` token carries, mapped to the function name that index means.
|
|
3
|
-
//
|
|
4
|
-
// A binary formula names no function — it cites one by number — so this table is the whole difference
|
|
5
|
-
// between `SUM(A1:A5)` and an unreadable stream. It is transcribed from the normative table rather
|
|
6
|
-
// than assembled from Excel's UI, which matters at both ends: the low indices include the XLM macro
|
|
7
|
-
// functions (`GOTO`, `ADD.BAR`, …) that no modern sheet uses but a legacy file may still carry, and
|
|
8
|
-
// the table simply stops at `RTD`. Everything Microsoft added after that — the whole modern library,
|
|
9
|
-
// `TEXTJOIN` through `LAMBDA` — is *not* here and never will be: a post-2007 function is called
|
|
10
|
-
// through index 255 ("user defined") with an `_xlfn.`-prefixed name as its first operand, which is
|
|
11
|
-
// the same indirection the XML form spells out in the function name itself.
|
|
12
|
-
//
|
|
13
|
-
// The four gaps (0x00CA–CB, 0x00D9–DA, 0x00F9–FA, 0x014D) are gaps in the specification: those
|
|
14
|
-
// indices name no function, so a formula citing one is not decodable rather than decodable as
|
|
15
|
-
// something adjacent.
|
|
16
|
-
/** The index [MS-XLS] reserves for a call whose name is given by the token stream's first operand. */
|
|
17
1
|
export const FTAB_USER_DEFINED = 0x00ff;
|
|
18
|
-
/** Look up a built-in function name by its `iftab` index, or `undefined` if the index names none. */
|
|
19
2
|
export function functionNameFor(index) {
|
|
20
3
|
return FTAB[index];
|
|
21
4
|
}
|
|
22
|
-
/**
|
|
23
|
-
* How many arguments a fixed-arity function takes, or `undefined` for one whose argument count varies.
|
|
24
|
-
*
|
|
25
|
-
* A `PtgFunc` token states only *which* function is called, never with how many arguments — it is the
|
|
26
|
-
* token Excel emits precisely because the count is implied by the function. So decoding `ROUND(A1,2)`
|
|
27
|
-
* back from its two operands and one call token is impossible without this: the arity is the only
|
|
28
|
-
* thing that says which operands on the stack belong to the call. (`PtgFuncVar` carries its own count
|
|
29
|
-
* and needs none of this; the two token forms partition the table between them.)
|
|
30
|
-
*
|
|
31
|
-
* Transcribed from the parameter grammar each function's [MS-XLS] 2.5.198.17 entry states.
|
|
32
|
-
*/
|
|
33
5
|
export function fixedArityFor(name) {
|
|
34
6
|
return FIXED_ARITY.get(name);
|
|
35
7
|
}
|
|
36
|
-
// Keyed by name rather than index: an arity belongs to the function, and a name is checkable at a
|
|
37
|
-
// glance where a hex index is not.
|
|
38
8
|
const FIXED_ARITY = new Map(Object.entries({
|
|
39
|
-
// "This function takes no parameters."
|
|
40
9
|
NA: 0,
|
|
41
10
|
PI: 0,
|
|
42
11
|
TRUE: 0,
|
|
@@ -248,8 +217,6 @@ const FIXED_ARITY = new Map(Object.entries({
|
|
|
248
217
|
TTEST: 4,
|
|
249
218
|
WEIBULL: 4,
|
|
250
219
|
}));
|
|
251
|
-
// The table as contiguous runs, each `[first index, names in order]`. Written this way so the four
|
|
252
|
-
// specification gaps are visible as run boundaries instead of hiding as miscounted array entries.
|
|
253
220
|
const RUNS = [
|
|
254
221
|
[
|
|
255
222
|
0x0000,
|
|
@@ -650,7 +617,6 @@ const RUNS = [
|
|
|
650
617
|
],
|
|
651
618
|
],
|
|
652
619
|
];
|
|
653
|
-
// Indexed by `iftab`; a hole is an index the specification does not define.
|
|
654
620
|
const FTAB = RUNS.reduce((table, [start, names]) => {
|
|
655
621
|
names.forEach((name, offset) => {
|
|
656
622
|
table[start + offset] = name;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
// The BIFF12 shared-string table: `xl/sharedStrings.bin` in, the pool a `BrtCellIsst` cell indexes.
|
|
2
|
-
//
|
|
3
|
-
// The binary table is a flat run of `BrtSSTItem` records between a Begin/End pair, each holding one
|
|
4
|
-
// `RichStr`. Only the text is taken: a pooled string's per-run formatting is not modelled in this cut,
|
|
5
|
-
// so the runs are left unread rather than half-decoded (see `primitives.ts`). Reading stops at the
|
|
6
|
-
// closing record, so trailing future-record blocks are never framed.
|
|
7
1
|
import { RecordReader } from './primitives.js';
|
|
8
2
|
import { readRecords } from './record-stream.js';
|
|
9
3
|
import { BRT } from './record-types.js';
|
|
10
|
-
/** Parse `xl/sharedStrings.bin` into the pool, in index order. An absent part is an empty pool. */
|
|
11
4
|
export function parseSharedStrings(part) {
|
|
12
5
|
if (part === undefined)
|
|
13
6
|
return [];
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
// The BIFF12 style-sheet reader: `xl/styles.bin` in, the same {@link StyleTable} the XML reader
|
|
2
|
-
// produces out. Every collection (`fmts`, `fonts`, `fills`, `borders`, `cellStyleXfs`, `cellXfs`,
|
|
3
|
-
// `styles`) is a Begin/End record pair around its entries, so the pass tracks which collection it is
|
|
4
|
-
// inside — `BrtXF` appears in two of them and is meaningless without that context.
|
|
5
|
-
//
|
|
6
|
-
// The resolution *rules* are deliberately not restated here: number-format ids go through the XML
|
|
7
|
-
// reader's `numFmtCodeFor`, and each facet is emitted with the same "only when it differs from the
|
|
8
|
-
// default" discipline the XML path uses. That is what makes the binary and XML readings of one
|
|
9
|
-
// workbook the same model rather than two similar ones — a bottom vertical alignment, a locked cell,
|
|
10
|
-
// or a General number format is written explicitly in BIFF12 and omitted in XML, so the binary side
|
|
11
|
-
// has to drop exactly what the XML side never had.
|
|
12
1
|
import { assignStyleFacets } from '../../core/style.js';
|
|
13
2
|
import { numFmtCodeFor } from '../style/xf-style.js';
|
|
14
3
|
import { RecordReader } from './primitives.js';
|
|
@@ -32,7 +21,6 @@ const COLLECTION_ENDS = new Set([
|
|
|
32
21
|
BRT.EndCellXFs,
|
|
33
22
|
BRT.EndStyles,
|
|
34
23
|
]);
|
|
35
|
-
/** Parse `xl/styles.bin` into the flat cell-format table a worksheet's style indices resolve against. */
|
|
36
24
|
export function parseStyleTable(part) {
|
|
37
25
|
if (part === undefined)
|
|
38
26
|
return { cellXfs: [], namedStyles: [] };
|
|
@@ -86,8 +74,6 @@ export function parseStyleTable(part) {
|
|
|
86
74
|
break;
|
|
87
75
|
}
|
|
88
76
|
}
|
|
89
|
-
// Layer each direct format over the named style its xfId links to, exactly as the XML reader does:
|
|
90
|
-
// a facet the cell's own xf sets wins, one it leaves unset falls through to the named base.
|
|
91
77
|
const cellXfs = directXfs.map((xf) => {
|
|
92
78
|
if (xf.xfId === undefined)
|
|
93
79
|
return xf;
|
|
@@ -104,22 +90,15 @@ export function parseStyleTable(part) {
|
|
|
104
90
|
style.builtinId = label.builtinId;
|
|
105
91
|
return style;
|
|
106
92
|
});
|
|
107
|
-
// Font 0 is the workbook's declared default, carried out whole as well as flattened onto the xfs
|
|
108
|
-
// that name it — see {@link StyleTable.defaultFont}. The XML reader does the same.
|
|
109
93
|
const defaultFont = fonts[0];
|
|
110
94
|
return defaultFont === undefined ? { cellXfs, namedStyles } : { cellXfs, namedStyles, defaultFont };
|
|
111
95
|
}
|
|
112
|
-
// `BrtXF` ([MS-XLSB] 2.4.876): five facet indices, the two inline alignment scalars, then two flag
|
|
113
|
-
// words. `isDirect` distinguishes a cellXfs entry (whose ixfeParent links to a named style) from a
|
|
114
|
-
// cellStyleXfs entry (whose ixfeParent is the 0xFFFF "I am the named style" marker).
|
|
115
96
|
function readXf(reader, deps, isDirect) {
|
|
116
97
|
const parent = reader.u16();
|
|
117
98
|
const numFmt = numFmtCodeFor(reader.u16(), deps.numFmtCodes);
|
|
118
99
|
const font = deps.fonts[reader.u16()];
|
|
119
100
|
const fill = deps.fills[reader.u16()];
|
|
120
101
|
const borderIndex = reader.u16();
|
|
121
|
-
// Border 0 is the genuinely empty default (font 0, by contrast, is the workbook's real default
|
|
122
|
-
// face), so only a custom index names an actual border — the same asymmetry the XML reader keeps.
|
|
123
102
|
const border = borderIndex > 0 ? deps.borders[borderIndex] : undefined;
|
|
124
103
|
const rotation = reader.u8();
|
|
125
104
|
const indent = reader.u8();
|
|
@@ -139,20 +118,13 @@ function readXf(reader, deps, isDirect) {
|
|
|
139
118
|
const protection = readProtection(flags);
|
|
140
119
|
if (protection !== undefined)
|
|
141
120
|
draft.protection = protection;
|
|
142
|
-
// f123Prefix is the binary spelling of the `quotePrefix` attribute: the cell's text is displayed
|
|
143
|
-
// with a leading apostrophe stripped.
|
|
144
121
|
if ((flags & 0x8000) !== 0)
|
|
145
122
|
draft.quotePrefix = true;
|
|
146
|
-
// Only a link beyond the Normal default (0) carries information, and only a direct format has one.
|
|
147
123
|
if (isDirect && parent !== NOT_A_CELL_XF && parent > 0)
|
|
148
124
|
draft.xfId = parent;
|
|
149
125
|
return draft;
|
|
150
126
|
}
|
|
151
|
-
// The `ixfeParent` value a cell *style* XF carries in place of a link, since it is itself the base.
|
|
152
127
|
const NOT_A_CELL_XF = 0xffff;
|
|
153
|
-
// BIFF12 states every alignment field on every xf, where XML omits the ones at their default. Keep
|
|
154
|
-
// only what the XML reader would have seen, so the two readings of one workbook agree: `general`
|
|
155
|
-
// horizontal, `bottom` vertical, and zero rotation/indent/reading-order are absences, not values.
|
|
156
128
|
function readAlignment(flags, rotation, indent) {
|
|
157
129
|
const out = {};
|
|
158
130
|
const horizontal = HORIZONTAL_ALIGNMENTS[flags & 0b111];
|
|
@@ -174,8 +146,6 @@ function readAlignment(flags, rotation, indent) {
|
|
|
174
146
|
out.readingOrder = readingOrder;
|
|
175
147
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
176
148
|
}
|
|
177
|
-
// `locked` defaults to TRUE in OOXML, so an *unlocked* cell is the state that carries information;
|
|
178
|
-
// `hidden` defaults to false, so only a set flag does. A default xf yields no protection at all.
|
|
179
149
|
function readProtection(flags) {
|
|
180
150
|
const out = {};
|
|
181
151
|
if ((flags & 0x1000) === 0)
|
|
@@ -184,8 +154,6 @@ function readProtection(flags) {
|
|
|
184
154
|
out.hidden = true;
|
|
185
155
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
186
156
|
}
|
|
187
|
-
// `alc` ([MS-XLSB] 2.4.876), indexed by its stored value. `general` is index 0 and is left out
|
|
188
|
-
// deliberately: it is the type-dependent default, which the model spells as no horizontal alignment.
|
|
189
157
|
const HORIZONTAL_ALIGNMENTS = [
|
|
190
158
|
undefined,
|
|
191
159
|
'left',
|
|
@@ -196,7 +164,6 @@ const HORIZONTAL_ALIGNMENTS = [
|
|
|
196
164
|
'centerContinuous',
|
|
197
165
|
'distributed',
|
|
198
166
|
];
|
|
199
|
-
// `alcv`, likewise — with `bottom` (index 2) left out as the default.
|
|
200
167
|
const VERTICAL_ALIGNMENTS = [
|
|
201
168
|
'top',
|
|
202
169
|
'center',
|
|
@@ -204,8 +171,6 @@ const VERTICAL_ALIGNMENTS = [
|
|
|
204
171
|
'justify',
|
|
205
172
|
'distributed',
|
|
206
173
|
];
|
|
207
|
-
// `BrtFont` ([MS-XLSB] 2.4.690). Weight is a numeric scale (400 normal, 700 bold) rather than a flag,
|
|
208
|
-
// and the italic/strike/outline bits live in a separate word from it.
|
|
209
174
|
function readFont(reader) {
|
|
210
175
|
const height = reader.u16();
|
|
211
176
|
const flags = reader.u16();
|
|
@@ -214,14 +179,13 @@ function readFont(reader) {
|
|
|
214
179
|
const underline = reader.u8();
|
|
215
180
|
const family = reader.u8();
|
|
216
181
|
const charset = reader.u8();
|
|
217
|
-
reader.skip(1);
|
|
182
|
+
reader.skip(1);
|
|
218
183
|
const color = reader.color();
|
|
219
184
|
const scheme = reader.u8();
|
|
220
185
|
const name = reader.wideString();
|
|
221
186
|
const font = {};
|
|
222
187
|
if (name !== '')
|
|
223
188
|
font.name = name;
|
|
224
|
-
// Stored in twips; the model (like the XML) carries points.
|
|
225
189
|
if (height > 0)
|
|
226
190
|
font.size = height / 20;
|
|
227
191
|
if (family !== 0)
|
|
@@ -234,8 +198,6 @@ function readFont(reader) {
|
|
|
234
198
|
font.scheme = 'minor';
|
|
235
199
|
if (color !== undefined)
|
|
236
200
|
font.color = color;
|
|
237
|
-
// Each boolean facet is recorded only when on, mirroring XML's present-or-absent `<b/>`/`<i/>`:
|
|
238
|
-
// a non-bold font must not read back as `bold: false`.
|
|
239
201
|
if (weight >= BOLD_WEIGHT)
|
|
240
202
|
font.bold = true;
|
|
241
203
|
if ((flags & 0b0000_0010) !== 0)
|
|
@@ -254,27 +216,16 @@ function readFont(reader) {
|
|
|
254
216
|
return font;
|
|
255
217
|
}
|
|
256
218
|
const BOLD_WEIGHT = 700;
|
|
257
|
-
// `uls` ([MS-XLSB] 2.4.690). A single underline is `true`, not `'single'`, because that is what XML's
|
|
258
|
-
// bare `<u/>` — which is what Excel writes for it — reads back as.
|
|
259
219
|
const UNDERLINE_STYLES = new Map([
|
|
260
220
|
[0x01, true],
|
|
261
221
|
[0x02, 'double'],
|
|
262
222
|
[0x21, 'singleAccounting'],
|
|
263
223
|
[0x22, 'doubleAccounting'],
|
|
264
224
|
]);
|
|
265
|
-
// `BrtFill` ([MS-XLSB] 2.4.681). The pattern code and OOXML's `ST_PatternType` enumerate the same
|
|
266
|
-
// patterns in the same order, so the code indexes the name list directly.
|
|
267
225
|
function readFill(reader) {
|
|
268
226
|
const pattern = FILL_PATTERNS[reader.u32()];
|
|
269
|
-
// `none` is the absence of a fill, and an unmodelled pattern (a gradient — see below) is dropped
|
|
270
|
-
// rather than guessed, so an unfilled cell reads back unfilled either way.
|
|
271
227
|
if (pattern === undefined)
|
|
272
228
|
return undefined;
|
|
273
|
-
// BIFF12 always states both colours; XML states only the ones the fill actually has, using the two
|
|
274
|
-
// legacy-palette sentinels for the rest — 64 is "automatic foreground", 65 "automatic background".
|
|
275
|
-
// Dropping each in its own slot reproduces exactly what the XML reader sees: an untouched hatch
|
|
276
|
-
// pattern carries no colours at all, while a solid fill keeps the explicit `bgColor indexed="64"`
|
|
277
|
-
// Excel writes beside its foreground.
|
|
278
229
|
const fgColor = notSentinel(reader.color(), AUTOMATIC_FOREGROUND);
|
|
279
230
|
const bgColor = notSentinel(reader.color(), AUTOMATIC_BACKGROUND);
|
|
280
231
|
return {
|
|
@@ -289,10 +240,6 @@ const AUTOMATIC_BACKGROUND = 65;
|
|
|
289
240
|
function notSentinel(color, sentinel) {
|
|
290
241
|
return color?.indexed === sentinel ? undefined : color;
|
|
291
242
|
}
|
|
292
|
-
// Indexed by the stored `fls` value. Index 0 (`none`) is deliberately absent: an unfilled cell
|
|
293
|
-
// carries no fill. Gradient fills (`fls` 0x28) are not decoded in this cut — the stop array's layout
|
|
294
|
-
// is the one piece of BrtFill this reader has no Excel-authored sample to check against, and a
|
|
295
|
-
// silently wrong gradient is worse than none.
|
|
296
243
|
const FILL_PATTERNS = [
|
|
297
244
|
undefined,
|
|
298
245
|
'solid',
|
|
@@ -314,9 +261,6 @@ const FILL_PATTERNS = [
|
|
|
314
261
|
'gray125',
|
|
315
262
|
'gray0625',
|
|
316
263
|
];
|
|
317
|
-
// `BrtBorder` ([MS-XLSB] 2.4.314): the two diagonal-direction bits, then five `Blxf` edges in the
|
|
318
|
-
// order top, bottom, left, right, diagonal — which is *not* the model's or the schema's order, so
|
|
319
|
-
// the edges are read positionally and named here.
|
|
320
264
|
function readBorder(reader) {
|
|
321
265
|
const flags = reader.u8();
|
|
322
266
|
const top = readEdge(reader);
|
|
@@ -339,21 +283,16 @@ function readBorder(reader) {
|
|
|
339
283
|
border.diagonalDown = true;
|
|
340
284
|
if ((flags & 0b10) !== 0)
|
|
341
285
|
border.diagonalUp = true;
|
|
342
|
-
// An all-default border is the empty one every unbordered cell shares; it carries nothing.
|
|
343
286
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
344
287
|
}
|
|
345
|
-
// A `Blxf` ([MS-XLSB] 2.5.5): a line style, a reserved byte, and a colour. Style 0 is "no edge",
|
|
346
|
-
// which the model spells by omitting the edge rather than by a `none` value.
|
|
347
288
|
function readEdge(reader) {
|
|
348
289
|
const style = BORDER_STYLES[reader.u8()];
|
|
349
|
-
reader.skip(1);
|
|
290
|
+
reader.skip(1);
|
|
350
291
|
const color = reader.color();
|
|
351
292
|
if (style === undefined)
|
|
352
293
|
return undefined;
|
|
353
294
|
return color === undefined ? { style } : { style, color };
|
|
354
295
|
}
|
|
355
|
-
// Indexed by the stored `dg` value; index 0 (`none`) is absent, as above. The order is the binary
|
|
356
|
-
// format's own and differs from `ST_BorderStyle`'s declaration order, so it cannot be shared.
|
|
357
296
|
const BORDER_STYLES = [
|
|
358
297
|
undefined,
|
|
359
298
|
'thin',
|
|
@@ -370,13 +309,11 @@ const BORDER_STYLES = [
|
|
|
370
309
|
'mediumDashDotDot',
|
|
371
310
|
'slantDashDot',
|
|
372
311
|
];
|
|
373
|
-
// `BrtStyle` ([MS-XLSB] 2.4.809): which cellStyleXfs entry this names, and how it is labelled. The
|
|
374
|
-
// gallery index is only meaningful for a built-in style, which the flag word declares.
|
|
375
312
|
function readStyleLabel(reader) {
|
|
376
313
|
const xfId = reader.u32();
|
|
377
314
|
const flags = reader.u16();
|
|
378
315
|
const builtinId = reader.u8();
|
|
379
|
-
reader.skip(1);
|
|
316
|
+
reader.skip(1);
|
|
380
317
|
const name = reader.wideString();
|
|
381
318
|
return {
|
|
382
319
|
xfId,
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
// The BIFF12 worksheet-body reader: one `xl/worksheets/sheetN.bin` in, one populated {@link Worksheet}
|
|
2
|
-
// out — the binary counterpart of `../xlsx/read-worksheet.ts`, filling the very same model.
|
|
3
|
-
//
|
|
4
|
-
// The cell table is a flat, row-major run of records: a `BrtRowHdr` opens a row and every cell record
|
|
5
|
-
// until the next one belongs to it, so the open row is a single variable rather than nested state.
|
|
6
|
-
// Each cell record is a `Cell` header followed by a value shaped by the record's own type — which is
|
|
7
|
-
// what makes the binary form quick to parse: there is no `t=` attribute to interpret, the record
|
|
8
|
-
// number *is* the type.
|
|
9
|
-
//
|
|
10
|
-
// A formula cell carries both halves of what the XML form spells in `<f>` and `<v>`: a `Ptg` token
|
|
11
|
-
// stream, decoded back to text by `./formula.ts`, and the result Excel last computed. The two are
|
|
12
|
-
// filled independently — a formula whose stream uses a token this reader does not decode still
|
|
13
|
-
// surfaces its cached value, which is exactly what the reader could see before the decoder existed.
|
|
14
|
-
//
|
|
15
|
-
// One shape needs a second look at the sheet. An array formula's member cells hold a `PtgExp`
|
|
16
|
-
// pointing at the group's top-left, and the `BrtArrFmla` record carrying the group's actual formula
|
|
17
|
-
// comes *after* those cells in the stream. Those cells are therefore parked and resolved once the
|
|
18
|
-
// whole part has been read.
|
|
19
1
|
import { encodeAddress, MAX_COLUMN } from '../../core/address.js';
|
|
20
2
|
import { isDateFormat, serialToDate } from '../../core/date.js';
|
|
21
3
|
import { unmangleFunctions } from '../../core/formula.js';
|
|
@@ -25,9 +7,6 @@ import { decodeFormula, formulaAnchor } from './formula.js';
|
|
|
25
7
|
import { errorCodeFor, RecordReader } from './primitives.js';
|
|
26
8
|
import { readRecords } from './record-stream.js';
|
|
27
9
|
import { BRT } from './record-types.js';
|
|
28
|
-
// Every record that carries a plain cell — one whose payload is a value and nothing else. Membership
|
|
29
|
-
// drives the dispatch below, so a record type absent from both this set and {@link FORMULA_RECORDS}
|
|
30
|
-
// is skipped whole rather than being mistaken for a cell and consuming the reader.
|
|
31
10
|
const CELL_RECORDS = new Set([
|
|
32
11
|
BRT.CellBlank,
|
|
33
12
|
BRT.CellRk,
|
|
@@ -38,40 +17,23 @@ const CELL_RECORDS = new Set([
|
|
|
38
17
|
BRT.CellIsst,
|
|
39
18
|
BRT.CellRString,
|
|
40
19
|
]);
|
|
41
|
-
// Every record that carries a formula: a cached result of the record's own kind, then the token
|
|
42
|
-
// stream that produced it.
|
|
43
20
|
const FORMULA_RECORDS = new Set([
|
|
44
21
|
BRT.FmlaString,
|
|
45
22
|
BRT.FmlaNum,
|
|
46
23
|
BRT.FmlaBool,
|
|
47
24
|
BRT.FmlaError,
|
|
48
25
|
]);
|
|
49
|
-
/**
|
|
50
|
-
* Read a worksheet part into `sheet`: its column and row geometry, its merged ranges, and every
|
|
51
|
-
* non-empty cell with the style its index resolves to in `xfStyles` and, for a formula cell, the text
|
|
52
|
-
* its token stream decodes to through `scope`.
|
|
53
|
-
*/
|
|
54
26
|
export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
55
|
-
// The open row, one-based as the model counts them. -1 means none is open, which a cell record
|
|
56
|
-
// arriving before any row header (a malformed sheet) is dropped against rather than guessed at.
|
|
57
27
|
let row = -1;
|
|
58
|
-
// A row that declares a format supplies the default for its cells that carry none, as a column
|
|
59
|
-
// does; the next row header replaces it.
|
|
60
28
|
let rowStyle = -1;
|
|
61
|
-
// A column's format is the last fallback. Column records always precede the cell table.
|
|
62
29
|
const columnStyle = new Map();
|
|
63
|
-
// The sheet's default row height, in twips. Every row header restates its height whether or not the
|
|
64
|
-
// row has one of its own, so the default is what tells the two apart — see {@link applyRow}.
|
|
65
|
-
// `BrtWsFmtInfo` precedes the cell table, so it is always known by the time a row is read.
|
|
66
30
|
let defaultRowHeight = -1;
|
|
67
|
-
// The formula of each array-formula group, keyed by the group's top-left cell, and the member cells
|
|
68
|
-
// waiting on one. Both are needed because `BrtArrFmla` follows the cells it speaks for.
|
|
69
31
|
const groups = new Map();
|
|
70
32
|
const deferred = [];
|
|
71
33
|
for (const record of readRecords(part)) {
|
|
72
34
|
const reader = new RecordReader(record.data);
|
|
73
35
|
if (record.type === BRT.WsFmtInfo) {
|
|
74
|
-
reader.skip(6);
|
|
36
|
+
reader.skip(6);
|
|
75
37
|
defaultRowHeight = reader.u16();
|
|
76
38
|
}
|
|
77
39
|
else if (record.type === BRT.ColInfo) {
|
|
@@ -90,7 +52,7 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
90
52
|
}
|
|
91
53
|
else if (record.type === BRT.ArrFmla) {
|
|
92
54
|
const { rowFirst, colFirst } = reader.range();
|
|
93
|
-
reader.skip(1);
|
|
55
|
+
reader.skip(1);
|
|
94
56
|
groups.set(groupKey(rowFirst, colFirst), {
|
|
95
57
|
rgce: reader.bytes(reader.u32()),
|
|
96
58
|
rgcb: reader.bytes(reader.u32()),
|
|
@@ -100,9 +62,6 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
100
62
|
const { column, styleIndex } = reader.cell();
|
|
101
63
|
if (!inGrid(column, row - 1))
|
|
102
64
|
continue;
|
|
103
|
-
// A cell's own format wins, then its row's, then its column's — the order Excel applies.
|
|
104
|
-
// Index 0 is the default xf, which BIFF12 writes where XML simply omits `s`, so it means
|
|
105
|
-
// "no format of my own" and lets the row/column default through.
|
|
106
65
|
const resolved = styleIndex > 0 ? styleIndex : rowStyle >= 0 ? rowStyle : (columnStyle.get(column) ?? -1);
|
|
107
66
|
const style = resolved >= 0 ? xfStyles[resolved] : xfStyles[0];
|
|
108
67
|
const cell = sheet.getCell(encodeAddress(column + 1, row));
|
|
@@ -112,7 +71,7 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
112
71
|
continue;
|
|
113
72
|
}
|
|
114
73
|
const result = cachedResult(record.type, reader, style?.numFmt);
|
|
115
|
-
reader.skip(2);
|
|
74
|
+
reader.skip(2);
|
|
116
75
|
const rgce = reader.bytes(reader.u32());
|
|
117
76
|
const rgcb = reader.bytes(reader.u32());
|
|
118
77
|
const anchor = formulaAnchor(rgce, rgcb);
|
|
@@ -132,8 +91,6 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
132
91
|
}
|
|
133
92
|
}
|
|
134
93
|
for (const member of deferred) {
|
|
135
|
-
// Only the group's top-left cell states the formula; the rest carry the value it produced, which
|
|
136
|
-
// is exactly what the XML form writes for them.
|
|
137
94
|
const group = groups.get(groupKey(member.anchorRow, member.anchorColumn));
|
|
138
95
|
const own = member.row === member.anchorRow && member.column === member.anchorColumn;
|
|
139
96
|
member.cell.value =
|
|
@@ -145,23 +102,15 @@ export function parseWorksheet(part, sheet, sharedStrings, xfStyles, scope) {
|
|
|
145
102
|
function groupKey(row, column) {
|
|
146
103
|
return `${row}:${column}`;
|
|
147
104
|
}
|
|
148
|
-
// Pair a decoded formula with its cached result, in the shape the XML reader produces for the same
|
|
149
|
-
// cell. A formula the decoder could not read leaves the value alone: the cached result is still true,
|
|
150
|
-
// and is what this reader surfaced before formulas were decoded at all.
|
|
151
105
|
function formulaValue(formula, result) {
|
|
152
106
|
if (formula === undefined)
|
|
153
107
|
return result ?? null;
|
|
154
|
-
// Strip the `_xlfn.`/`_xlpm.` on-disk mangling, as the XML reader does, so the model never holds it.
|
|
155
108
|
const stored = unmangleFunctions(formula);
|
|
156
109
|
return result === undefined ? { formula: stored } : { formula: stored, result };
|
|
157
110
|
}
|
|
158
|
-
// The result a formula record cached, decoded by the record's own kind — the binary counterpart of
|
|
159
|
-
// reading `<v>` under the `t` attribute.
|
|
160
111
|
function cachedResult(type, reader, numFmt) {
|
|
161
112
|
switch (type) {
|
|
162
113
|
case BRT.FmlaNum:
|
|
163
|
-
// A formula's cached numeric result honours the cell's date format exactly as a bare number
|
|
164
|
-
// does, so a date-valued formula reads back as a Date rather than a serial.
|
|
165
114
|
return asNumberOrDate(reader.f64(), numFmt);
|
|
166
115
|
case BRT.FmlaBool:
|
|
167
116
|
return reader.u8() !== 0;
|
|
@@ -173,19 +122,11 @@ function cachedResult(type, reader, numFmt) {
|
|
|
173
122
|
return reader.wideString();
|
|
174
123
|
}
|
|
175
124
|
}
|
|
176
|
-
// Excel's grid bounds, zero-based as the binary format counts. [MS-XLSB] states them as MUST
|
|
177
|
-
// constraints, which is exactly why a reader has to check them: a damaged or hostile file states
|
|
178
|
-
// whatever it likes, and an address beyond the grid has nowhere to go. Everything positional funnels
|
|
179
|
-
// through here before it reaches the model, so an out-of-grid record is dropped rather than turned
|
|
180
|
-
// into an unrepresentable address (which the address encoder would reject) or, worse, a column loop
|
|
181
|
-
// four billion iterations long.
|
|
182
125
|
const MAX_ROW_INDEX = 1048575;
|
|
183
126
|
const MAX_COLUMN_INDEX = MAX_COLUMN - 1;
|
|
184
127
|
function inGrid(column, row) {
|
|
185
128
|
return column >= 0 && column <= MAX_COLUMN_INDEX && row >= 0 && row <= MAX_ROW_INDEX;
|
|
186
129
|
}
|
|
187
|
-
// Decode a cell record's payload — the reader is positioned just past the shared `Cell` header, so
|
|
188
|
-
// what remains is exactly the value this record type carries.
|
|
189
130
|
function decodeCell(type, reader, sharedStrings, numFmt) {
|
|
190
131
|
switch (type) {
|
|
191
132
|
case BRT.CellRk:
|
|
@@ -195,45 +136,32 @@ function decodeCell(type, reader, sharedStrings, numFmt) {
|
|
|
195
136
|
case BRT.CellBool:
|
|
196
137
|
return reader.u8() !== 0;
|
|
197
138
|
case BRT.CellError: {
|
|
198
|
-
// An unrecognised error byte keeps the cell non-empty without inventing an error the model
|
|
199
|
-
// does not define; there is no text form to fall back to as there is in XML.
|
|
200
139
|
const error = errorCodeFor(reader.u8());
|
|
201
140
|
return error === undefined ? null : { error };
|
|
202
141
|
}
|
|
203
142
|
case BRT.CellSt:
|
|
204
143
|
return reader.wideString();
|
|
205
144
|
case BRT.CellRString:
|
|
206
|
-
// Rich runs are not modelled in this cut; the flattened text is what a consumer sees.
|
|
207
145
|
return reader.richString();
|
|
208
146
|
case BRT.CellIsst:
|
|
209
147
|
return sharedStrings[reader.u32()] ?? '';
|
|
210
148
|
default:
|
|
211
|
-
// BrtCellBlank: formatted but empty. The style is already applied; the value is genuinely none.
|
|
212
149
|
return null;
|
|
213
150
|
}
|
|
214
151
|
}
|
|
215
|
-
// A number stored under a date format is a date serial — surface it as a Date so a date read from an
|
|
216
|
-
// `.xlsb` is the same value the `.xlsx` twin yields, not a bare number.
|
|
217
152
|
function asNumberOrDate(value, numFmt) {
|
|
218
153
|
return numFmt !== undefined && isDateFormat(numFmt) ? serialToDate(value) : value;
|
|
219
154
|
}
|
|
220
|
-
// `BrtRowHdr` ([MS-XLSB] 2.4.770): the row index, its default format, its height, and a byte of
|
|
221
|
-
// layout flags. Returns the open row (one-based) and the style index its cells inherit.
|
|
222
155
|
function applyRow(reader, sheet, defaultRowHeight) {
|
|
223
156
|
const index = reader.u32();
|
|
224
157
|
const styleIndex = reader.u32();
|
|
225
158
|
const height = reader.u16();
|
|
226
|
-
reader.skip(1);
|
|
159
|
+
reader.skip(1);
|
|
227
160
|
const flags = reader.u8();
|
|
228
|
-
// A row beyond the grid closes the open row without opening another, so its cells are dropped too.
|
|
229
161
|
if (index > MAX_ROW_INDEX)
|
|
230
162
|
return { row: -1, styleIndex: -1 };
|
|
231
163
|
const row = index + 1;
|
|
232
164
|
const handle = sheet.getRow(row);
|
|
233
|
-
// Every row header restates a height; only a row whose height is its *own* has one to record. That
|
|
234
|
-
// is a row the user sized by hand, or one Excel auto-fitted to a taller font or wrapped text — both
|
|
235
|
-
// differ from the sheet default, which is exactly when XML emits `ht`. A row merely restating the
|
|
236
|
-
// default carries no height, so it must not read back with one.
|
|
237
165
|
if ((flags & ROW_CUSTOM_HEIGHT) !== 0 || height !== defaultRowHeight) {
|
|
238
166
|
handle.height = height / TWIPS_PER_POINT;
|
|
239
167
|
}
|
|
@@ -244,7 +172,6 @@ function applyRow(reader, sheet, defaultRowHeight) {
|
|
|
244
172
|
handle.outlineLevel = outlineLevel;
|
|
245
173
|
if ((flags & ROW_COLLAPSED) !== 0)
|
|
246
174
|
handle.collapsed = true;
|
|
247
|
-
// The row's format applies only when it says so, mirroring XML's `customFormat="1"` gate.
|
|
248
175
|
return { row, styleIndex: (flags & ROW_CUSTOM_FORMAT) !== 0 ? styleIndex : -1 };
|
|
249
176
|
}
|
|
250
177
|
const TWIPS_PER_POINT = 20;
|
|
@@ -253,7 +180,6 @@ const ROW_COLLAPSED = 0b0000_1000;
|
|
|
253
180
|
const ROW_HIDDEN = 0b0001_0000;
|
|
254
181
|
const ROW_CUSTOM_HEIGHT = 0b0010_0000;
|
|
255
182
|
const ROW_CUSTOM_FORMAT = 0b0100_0000;
|
|
256
|
-
// `BrtColInfo` ([MS-XLSB] 2.4.319): one record per run of identically-sized columns.
|
|
257
183
|
function applyColumn(reader, sheet, xfStyles, columnStyle) {
|
|
258
184
|
const first = reader.u32();
|
|
259
185
|
const last = reader.u32();
|
|
@@ -262,24 +188,16 @@ function applyColumn(reader, sheet, xfStyles, columnStyle) {
|
|
|
262
188
|
const flags = reader.u16();
|
|
263
189
|
const style = styleIndex > 0 ? xfStyles[styleIndex] : undefined;
|
|
264
190
|
const outlineLevel = (flags >> COLUMN_OUTLINE_SHIFT) & 0b111;
|
|
265
|
-
// A run can name every column to the right of the data; materialising all 16 384 of them would
|
|
266
|
-
// turn a two-column sheet into a 16 384-entry model, so a run is only applied where it carries
|
|
267
|
-
// something a default column does not.
|
|
268
191
|
if (style === undefined &&
|
|
269
192
|
outlineLevel === 0 &&
|
|
270
193
|
(flags & (COLUMN_HIDDEN | COLUMN_CUSTOM_WIDTH | COLUMN_COLLAPSED)) === 0) {
|
|
271
194
|
return;
|
|
272
195
|
}
|
|
273
|
-
// The loop bound comes from the file, so it is clamped to the grid before it is one: an unclamped
|
|
274
|
-
// run declaring four billion columns is a denial of service, not a wide sheet.
|
|
275
196
|
const lastInGrid = Math.min(last, MAX_COLUMN_INDEX);
|
|
276
197
|
if (first > lastInGrid)
|
|
277
198
|
return;
|
|
278
199
|
for (let index = first; index <= lastInGrid; index++) {
|
|
279
200
|
const column = sheet.getColumn(index + 1);
|
|
280
|
-
// The stored width is taken whether or not the file marks it user-set, matching the XML reader:
|
|
281
|
-
// a `<col>`/`BrtColInfo` exists only for a column that differs from the sheet default in *some*
|
|
282
|
-
// way, and it always states the width that column actually has.
|
|
283
201
|
column.width = width / COLUMN_WIDTH_UNITS;
|
|
284
202
|
if ((flags & COLUMN_HIDDEN) !== 0)
|
|
285
203
|
column.hidden = true;
|
|
@@ -293,7 +211,6 @@ function applyColumn(reader, sheet, xfStyles, columnStyle) {
|
|
|
293
211
|
columnStyle.set(index, styleIndex);
|
|
294
212
|
}
|
|
295
213
|
}
|
|
296
|
-
// Column width is stored in 1/256ths of a character, where XML states the character count directly.
|
|
297
214
|
const COLUMN_WIDTH_UNITS = 256;
|
|
298
215
|
const COLUMN_HIDDEN = 0b0000_0001;
|
|
299
216
|
const COLUMN_CUSTOM_WIDTH = 0b0000_0010;
|