@shbernal/ts-xlsx 1.0.3 → 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.d.ts +10 -0
- package/dist/core/cell.js +4 -123
- package/dist/core/color-resolution.js +0 -60
- package/dist/core/column.d.ts +9 -1
- package/dist/core/column.js +0 -52
- 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.d.ts +30 -0
- package/dist/core/limits.js +2 -0
- 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 -44
- 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.d.ts +9 -1
- package/dist/core/row.js +0 -58
- 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.d.ts +20 -0
- package/dist/core/text-metrics.js +10 -0
- package/dist/core/theme.js +0 -85
- package/dist/core/value.d.ts +40 -0
- package/dist/core/value.js +30 -48
- package/dist/core/workbook-protection.js +0 -13
- package/dist/core/workbook.d.ts +13 -0
- package/dist/core/workbook.js +11 -368
- package/dist/core/worksheet-model.js +2 -23
- package/dist/core/worksheet.d.ts +22 -0
- package/dist/core/worksheet.js +14 -485
- 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.d.ts +3 -1
- package/dist/entries/core.js +3 -7
- 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 +5 -35
- 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 +9 -10
- 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,35 +1,8 @@
|
|
|
1
|
-
// The [MS-XLSB] primitive types a record payload is built from, behind one bounds-checked cursor.
|
|
2
|
-
//
|
|
3
|
-
// Binary parsing is where a reader most easily goes wrong in two directions at once: silently reading
|
|
4
|
-
// past the end of a buffer, and allocating on a length the file (not the reader) chose. `RecordReader`
|
|
5
|
-
// closes both. Every read goes through a single `#take`, so overrunning the record is impossible
|
|
6
|
-
// rather than merely unlikely; and a length-prefixed string checks its byte count against what the
|
|
7
|
-
// record actually holds *before* a single character is materialised, so a forged `cch` costs one
|
|
8
|
-
// comparison instead of gigabytes.
|
|
9
|
-
//
|
|
10
|
-
// Structures decoded here — RkNumber, XLWideString, BrtColor, Cell, UncheckedRfX, BErr — are shared
|
|
11
|
-
// across the workbook, worksheet, styles, and shared-string parsers; nothing part-specific lives here.
|
|
12
1
|
import { isErrorCode } from '../../core/value.js';
|
|
13
2
|
import { XlsbParseError } from './errors.js';
|
|
14
|
-
// A length-prefixed string is decoded in code-unit batches rather than one `String.fromCharCode` call
|
|
15
|
-
// per character (quadratic concatenation) or one spread of every unit (which blows the argument limit
|
|
16
|
-
// on a long string). 4096 is comfortably under every engine's limit and makes the batching invisible.
|
|
17
3
|
const CHARS_PER_BATCH = 4096;
|
|
18
|
-
// `XLNullableWideString` marks "no string" with a character count of 0xFFFFFFFF rather than 0 — an
|
|
19
|
-
// empty string and an absent one are different values (a sheet's relationship id is nullable; its
|
|
20
|
-
// name is not).
|
|
21
4
|
const NULL_STRING_LENGTH = 0xffffffff;
|
|
22
|
-
// One reusable 8-byte window for reassembling an RkNumber's truncated double. The alternative — a
|
|
23
|
-
// fresh ArrayBuffer per RK cell — would allocate once per numeric cell in the workbook, on the single
|
|
24
|
-
// hottest path in the reader. Safe to share: the write and the read below are one synchronous pair.
|
|
25
5
|
const rkScratch = new DataView(new ArrayBuffer(8));
|
|
26
|
-
/**
|
|
27
|
-
* A bounds-checked cursor over one record's payload.
|
|
28
|
-
*
|
|
29
|
-
* Each accessor advances the cursor by exactly the bytes it consumed, so a record is decoded by
|
|
30
|
-
* naming its fields in order. Reading past the payload throws {@link XlsbParseError} — a record that
|
|
31
|
-
* is shorter than its own definition is a malformed file, not a case to guess through.
|
|
32
|
-
*/
|
|
33
6
|
export class RecordReader {
|
|
34
7
|
#data;
|
|
35
8
|
#view;
|
|
@@ -38,24 +11,15 @@ export class RecordReader {
|
|
|
38
11
|
this.#data = data;
|
|
39
12
|
this.#view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
40
13
|
}
|
|
41
|
-
/** Bytes left in the record. */
|
|
42
14
|
get remaining() {
|
|
43
15
|
return this.#data.length - this.#offset;
|
|
44
16
|
}
|
|
45
|
-
/** Whether the whole payload has been consumed. */
|
|
46
17
|
get done() {
|
|
47
18
|
return this.remaining <= 0;
|
|
48
19
|
}
|
|
49
|
-
/** Advance past `count` bytes without decoding them — a reserved or unmodelled field. */
|
|
50
20
|
skip(count) {
|
|
51
21
|
this.#take(count);
|
|
52
22
|
}
|
|
53
|
-
/**
|
|
54
|
-
* The next `count` bytes as a **view**, for a field whose own decoding happens elsewhere — a formula
|
|
55
|
-
* token stream, whose meaning depends on workbook tables this record knows nothing about. A view
|
|
56
|
-
* rather than a copy for the same reason a record's payload is one: the declared length comes from
|
|
57
|
-
* the file, so it must bound a read, never an allocation.
|
|
58
|
-
*/
|
|
59
23
|
bytes(count) {
|
|
60
24
|
return this.#data.subarray(this.#take(count), this.#offset);
|
|
61
25
|
}
|
|
@@ -74,57 +38,32 @@ export class RecordReader {
|
|
|
74
38
|
i32() {
|
|
75
39
|
return this.#view.getInt32(this.#take(4), true);
|
|
76
40
|
}
|
|
77
|
-
/** An `Xnum` ([MS-XLSB] 2.5.172): a little-endian IEEE-754 double. */
|
|
78
41
|
f64() {
|
|
79
42
|
return this.#view.getFloat64(this.#take(8), true);
|
|
80
43
|
}
|
|
81
|
-
/**
|
|
82
|
-
* An `RkNumber` ([MS-XLSB] 2.5.122): a number packed into 32 bits. Two flag bits steal the low end
|
|
83
|
-
* of the word — `fInt` says the remaining 30 bits are a signed integer rather than the *high* 30
|
|
84
|
-
* bits of a double whose low 34 bits are zero, and `fX100` says the result was scaled up by 100 to
|
|
85
|
-
* keep two decimal places in the integer form. It exists because most real spreadsheet numbers are
|
|
86
|
-
* small integers or two-decimal currency, and this stores them in half the bytes of a double.
|
|
87
|
-
*/
|
|
88
44
|
rk() {
|
|
89
|
-
// Read signed: the `fInt` branch needs an arithmetic shift to sign-extend a negative 30-bit
|
|
90
|
-
// integer, which only works on a value JavaScript already considers negative.
|
|
91
45
|
const raw = this.i32();
|
|
92
46
|
const value = (raw & 0b10) !== 0 ? raw >> 2 : truncatedDouble(raw & ~0b11);
|
|
93
47
|
return (raw & 0b01) !== 0 ? value / 100 : value;
|
|
94
48
|
}
|
|
95
|
-
/** An `XLWideString` ([MS-XLSB] 2.5.169): a 4-byte character count then that many UTF-16LE units. */
|
|
96
49
|
wideString() {
|
|
97
50
|
return this.#characters(this.u32());
|
|
98
51
|
}
|
|
99
|
-
/**
|
|
100
|
-
* A UTF-16 string whose character count is 16-bit rather than 32-bit — the form used *inside* a
|
|
101
|
-
* formula token stream (`PtgStr`, and the string elements of an array constant), where a 4-byte
|
|
102
|
-
* count on every literal would be pure overhead.
|
|
103
|
-
*/
|
|
104
52
|
shortString() {
|
|
105
53
|
return this.#characters(this.u16());
|
|
106
54
|
}
|
|
107
|
-
/** An `XLNullableWideString` ([MS-XLSB] 2.5.166): an {@link wideString} that can also be absent. */
|
|
108
55
|
nullableWideString() {
|
|
109
56
|
const length = this.u32();
|
|
110
57
|
return length === NULL_STRING_LENGTH ? undefined : this.#characters(length);
|
|
111
58
|
}
|
|
112
|
-
/**
|
|
113
|
-
* A `RichStr` ([MS-XLSB] 2.5.124): a string that may carry per-run formatting and phonetic guides.
|
|
114
|
-
* Only the text is returned — the run and phonetic tails are left unread, which is safe because the
|
|
115
|
-
* record's framing (not this cursor) bounds where the payload ends.
|
|
116
|
-
*/
|
|
117
59
|
richString() {
|
|
118
|
-
this.skip(1);
|
|
60
|
+
this.skip(1);
|
|
119
61
|
return this.wideString();
|
|
120
62
|
}
|
|
121
|
-
/** The `Cell` structure ([MS-XLSB] 2.5.10) that opens every cell record. */
|
|
122
63
|
cell() {
|
|
123
64
|
const column = this.u32();
|
|
124
|
-
// The style index shares its word with a phonetic-display flag in the high byte.
|
|
125
65
|
return { column, styleIndex: this.u32() & 0x00ffffff };
|
|
126
66
|
}
|
|
127
|
-
/** An `UncheckedRfX` ([MS-XLSB] 2.5.155): four zero-based, inclusive range bounds. */
|
|
128
67
|
range() {
|
|
129
68
|
return {
|
|
130
69
|
rowFirst: this.u32(),
|
|
@@ -133,14 +72,6 @@ export class RecordReader {
|
|
|
133
72
|
colLast: this.u32(),
|
|
134
73
|
};
|
|
135
74
|
}
|
|
136
|
-
/**
|
|
137
|
-
* A `BrtColor` ([MS-XLSB] 2.4.337), mapped onto the model's {@link Color}.
|
|
138
|
-
*
|
|
139
|
-
* The four encodings are mutually exclusive and the type tag picks which of the payload's fields
|
|
140
|
-
* carry meaning; the rest are explicitly undefined. An *automatic* colour (type 0) names nothing at
|
|
141
|
-
* all, and reads back as no colour — the same absence the XML reader produces for `<color auto="1"/>`,
|
|
142
|
-
* so a cell whose font colour was never set does not gain one on read.
|
|
143
|
-
*/
|
|
144
75
|
color() {
|
|
145
76
|
const flags = this.u8();
|
|
146
77
|
const index = this.u8();
|
|
@@ -152,15 +83,11 @@ export class RecordReader {
|
|
|
152
83
|
const base = colorByType(flags >> 1, index, alpha, red, green, blue);
|
|
153
84
|
if (base === undefined)
|
|
154
85
|
return undefined;
|
|
155
|
-
// Tint is stored as a fraction of the signed 16-bit range, where the extreme values mean 100%
|
|
156
|
-
// lightening/darkening; the model carries it as OOXML does, in [-1, 1].
|
|
157
86
|
if (tintAndShade === 0)
|
|
158
87
|
return base;
|
|
159
88
|
return { ...base, tint: Math.max(-1, Math.min(1, tintAndShade / 0x7fff)) };
|
|
160
89
|
}
|
|
161
90
|
#characters(count) {
|
|
162
|
-
// Check the byte count against the record *before* building anything: this is the guard that
|
|
163
|
-
// makes a forged character count a cheap failure rather than an allocation the file chose.
|
|
164
91
|
const start = this.#take(count * 2);
|
|
165
92
|
let text = '';
|
|
166
93
|
let batch = [];
|
|
@@ -173,8 +100,6 @@ export class RecordReader {
|
|
|
173
100
|
}
|
|
174
101
|
return batch.length > 0 ? text + String.fromCharCode(...batch) : text;
|
|
175
102
|
}
|
|
176
|
-
// The single choke point every read passes through. Returns the offset the caller may read from,
|
|
177
|
-
// having proven that `count` bytes are there.
|
|
178
103
|
#take(count) {
|
|
179
104
|
if (count > this.remaining) {
|
|
180
105
|
throw new XlsbParseError(`BIFF12 record field needs ${count} bytes but only ${this.remaining} remain in the record`);
|
|
@@ -184,7 +109,6 @@ export class RecordReader {
|
|
|
184
109
|
return start;
|
|
185
110
|
}
|
|
186
111
|
}
|
|
187
|
-
/** A `BErr` ([MS-XLSB] 2.5.98.2) error code, as the model's error string. */
|
|
188
112
|
export function errorCodeFor(code) {
|
|
189
113
|
const text = BERR_CODES.get(code);
|
|
190
114
|
return text !== undefined && isErrorCode(text) ? text : undefined;
|
|
@@ -199,14 +123,11 @@ const BERR_CODES = new Map([
|
|
|
199
123
|
[0x2a, '#N/A'],
|
|
200
124
|
[0x2b, '#GETTING_DATA'],
|
|
201
125
|
]);
|
|
202
|
-
// Rebuild the double whose *high* 32 bits are `high` and whose low 34 bits [MS-XLSB] guarantees to be
|
|
203
|
-
// zero. Written big-endian so the given word lands in the high half regardless of host endianness.
|
|
204
126
|
function truncatedDouble(high) {
|
|
205
127
|
rkScratch.setInt32(0, high, false);
|
|
206
128
|
rkScratch.setUint32(4, 0, false);
|
|
207
129
|
return rkScratch.getFloat64(0, false);
|
|
208
130
|
}
|
|
209
|
-
// The `xColorType` tag ([MS-XLSB] 2.4.337) selects which of a BrtColor's fields carry the colour.
|
|
210
131
|
const COLOR_TYPE_INDEXED = 1;
|
|
211
132
|
const COLOR_TYPE_RGB = 2;
|
|
212
133
|
const COLOR_TYPE_THEME = 3;
|
|
@@ -217,9 +138,6 @@ function colorByType(type, index, alpha, red, green, blue) {
|
|
|
217
138
|
case COLOR_TYPE_RGB:
|
|
218
139
|
return { argb: hexByte(alpha) + hexByte(red) + hexByte(green) + hexByte(blue) };
|
|
219
140
|
case COLOR_TYPE_THEME:
|
|
220
|
-
// The binary theme index and OOXML's `theme="…"` attribute both count the `clrScheme`
|
|
221
|
-
// subelements in declaration order (dk1, lt1, dk2, lt2, accent1–6, hlink, folHlink), so the
|
|
222
|
-
// index carries across unchanged.
|
|
223
141
|
return { theme: index };
|
|
224
142
|
default:
|
|
225
143
|
return undefined;
|
|
@@ -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,
|