@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.
Files changed (136) hide show
  1. package/README.md +21 -0
  2. package/dist/core/address.js +0 -33
  3. package/dist/core/autofilter.js +0 -9
  4. package/dist/core/cell.d.ts +10 -0
  5. package/dist/core/cell.js +4 -123
  6. package/dist/core/color-resolution.js +0 -60
  7. package/dist/core/column.d.ts +9 -1
  8. package/dist/core/column.js +0 -52
  9. package/dist/core/comment-thread.js +0 -45
  10. package/dist/core/conditional-formatting-overlay.js +0 -11
  11. package/dist/core/conditional-formatting.js +0 -10
  12. package/dist/core/containers.js +0 -15
  13. package/dist/core/data-validation-overlay.js +0 -25
  14. package/dist/core/data-validation.js +0 -7
  15. package/dist/core/date.js +0 -39
  16. package/dist/core/formula.js +0 -117
  17. package/dist/core/grid-edits.js +0 -38
  18. package/dist/core/image.js +0 -25
  19. package/dist/core/internal.js +0 -27
  20. package/dist/core/limits.d.ts +30 -0
  21. package/dist/core/limits.js +2 -0
  22. package/dist/core/merge.d.ts +18 -0
  23. package/dist/core/merge.js +22 -7
  24. package/dist/core/modern-functions.js +0 -17
  25. package/dist/core/page-setup.js +0 -5
  26. package/dist/core/pivot-table.d.ts +1 -1
  27. package/dist/core/pivot-table.js +0 -44
  28. package/dist/core/preserved.js +0 -4
  29. package/dist/core/protection.js +0 -26
  30. package/dist/core/range.js +0 -112
  31. package/dist/core/row-input.d.ts +24 -0
  32. package/dist/core/row-input.js +29 -0
  33. package/dist/core/row.d.ts +9 -1
  34. package/dist/core/row.js +0 -58
  35. package/dist/core/style.js +0 -26
  36. package/dist/core/table-style.js +0 -28
  37. package/dist/core/table.js +0 -97
  38. package/dist/core/text-metrics.d.ts +20 -0
  39. package/dist/core/text-metrics.js +10 -0
  40. package/dist/core/theme.js +0 -85
  41. package/dist/core/value.d.ts +40 -0
  42. package/dist/core/value.js +30 -48
  43. package/dist/core/workbook-protection.js +0 -13
  44. package/dist/core/workbook.d.ts +13 -0
  45. package/dist/core/workbook.js +11 -368
  46. package/dist/core/worksheet-model.js +2 -23
  47. package/dist/core/worksheet.d.ts +22 -0
  48. package/dist/core/worksheet.js +14 -485
  49. package/dist/customui/errors.js +0 -10
  50. package/dist/customui/index.js +0 -5
  51. package/dist/customui/ribbon.js +0 -43
  52. package/dist/entries/core.d.ts +3 -1
  53. package/dist/entries/core.js +3 -7
  54. package/dist/entries/csv.js +0 -4
  55. package/dist/entries/customui.js +0 -2
  56. package/dist/entries/errors.js +0 -13
  57. package/dist/entries/vba.js +0 -5
  58. package/dist/entries/xlsb.js +0 -6
  59. package/dist/entries/xlsx.js +0 -13
  60. package/dist/errors.d.ts +14 -0
  61. package/dist/errors.js +5 -56
  62. package/dist/index.js +0 -12
  63. package/dist/io/csv/read.js +0 -18
  64. package/dist/io/csv/write.js +5 -35
  65. package/dist/io/opc/errors.js +0 -30
  66. package/dist/io/opc/inflate.js +0 -35
  67. package/dist/io/opc/namespaces.js +0 -15
  68. package/dist/io/opc/part-paths.js +0 -11
  69. package/dist/io/opc/read-opc.js +0 -31
  70. package/dist/io/opc/read-options.js +0 -6
  71. package/dist/io/opc/rels.js +0 -13
  72. package/dist/io/opc/sniff-format.js +0 -50
  73. package/dist/io/style/xf-style.js +0 -31
  74. package/dist/io/xlsb/errors.js +0 -10
  75. package/dist/io/xlsb/formula.js +1 -99
  76. package/dist/io/xlsb/primitives.js +1 -83
  77. package/dist/io/xlsb/ptg-functions.js +0 -34
  78. package/dist/io/xlsb/read-shared-strings.js +0 -7
  79. package/dist/io/xlsb/read-styles.js +3 -66
  80. package/dist/io/xlsb/read-worksheet.js +4 -87
  81. package/dist/io/xlsb/read.js +2 -60
  82. package/dist/io/xlsb/record-stream.js +0 -28
  83. package/dist/io/xlsb/record-types.js +0 -19
  84. package/dist/io/xlsx/cell-accumulator.js +0 -32
  85. package/dist/io/xlsx/cell-value.js +0 -29
  86. package/dist/io/xlsx/color-xml.d.ts +6 -0
  87. package/dist/io/xlsx/color-xml.js +42 -0
  88. package/dist/io/xlsx/comments.js +0 -84
  89. package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
  90. package/dist/io/xlsx/conditional-formatting.js +1 -97
  91. package/dist/io/xlsx/data-validation.js +0 -56
  92. package/dist/io/xlsx/edit-vba.js +0 -54
  93. package/dist/io/xlsx/errors.js +0 -12
  94. package/dist/io/xlsx/hyperlinks.js +0 -40
  95. package/dist/io/xlsx/images.js +0 -32
  96. package/dist/io/xlsx/namespaces.js +0 -42
  97. package/dist/io/xlsx/package-plan.js +0 -47
  98. package/dist/io/xlsx/pivot.js +0 -22
  99. package/dist/io/xlsx/{pivot-read.js → read-pivot.js} +0 -30
  100. package/dist/io/xlsx/read-rows.js +1 -83
  101. package/dist/io/xlsx/{shared-strings-read.js → read-shared-strings.js} +0 -11
  102. package/dist/io/xlsx/read-styles.d.ts +25 -1
  103. package/dist/io/xlsx/read-styles.js +61 -108
  104. package/dist/io/xlsx/read-worksheet.js +1 -88
  105. package/dist/io/xlsx/read.js +3 -211
  106. package/dist/io/xlsx/relationships.js +0 -11
  107. package/dist/io/xlsx/rich-runs.js +0 -16
  108. package/dist/io/xlsx/rich-text.js +0 -12
  109. package/dist/io/xlsx/shared-formulas.js +0 -7
  110. package/dist/io/xlsx/shared-strings.js +0 -21
  111. package/dist/io/xlsx/sheet-properties.js +1 -64
  112. package/dist/io/xlsx/styles.d.ts +1 -29
  113. package/dist/io/xlsx/styles.js +2 -366
  114. package/dist/io/xlsx/tables.js +5 -52
  115. package/dist/io/xlsx/threaded-comments.js +0 -103
  116. package/dist/io/xlsx/workbook-xml.js +0 -94
  117. package/dist/io/xlsx/worksheet-xml.js +0 -174
  118. package/dist/io/xlsx/write-stream.js +0 -137
  119. package/dist/io/xlsx/write.js +1 -134
  120. package/dist/io/xlsx/x14-ext.js +0 -8
  121. package/dist/vba/cfb-writer.js +12 -59
  122. package/dist/vba/cfb.js +2 -32
  123. package/dist/vba/codepage.js +1 -23
  124. package/dist/vba/errors.js +0 -15
  125. package/dist/vba/index.js +0 -10
  126. package/dist/vba/ms-ovba.js +3 -46
  127. package/dist/vba/project-editor.js +4 -89
  128. package/dist/vba/project.js +2 -34
  129. package/dist/vba/vba-encoding.js +1 -17
  130. package/dist/xml/errors.js +0 -10
  131. package/dist/xml/xml-read.js +3 -82
  132. package/dist/xml/xml.js +0 -39
  133. package/package.json +9 -10
  134. package/skills/ts-xlsx-upstream/SKILL.md +178 -0
  135. /package/dist/io/xlsx/{pivot-read.d.ts → read-pivot.d.ts} +0 -0
  136. /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); // fRichStr / fExtStr flags: which optional tails follow the text.
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); // unused
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); // reserved
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); // iLevel: the outline depth of a built-in RowLevel/ColLevel style.
316
+ reader.skip(1);
380
317
  const name = reader.wideString();
381
318
  return {
382
319
  xfId,