@shbernal/ts-xlsx 1.0.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 (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,301 @@
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
+ import { encodeAddress, MAX_COLUMN } from "../../core/address.js";
20
+ import { isDateFormat, serialToDate } from "../../core/date.js";
21
+ import { unmangleFunctions } from "../../core/formula.js";
22
+ import { assignStyleFacets } from "../../core/style.js";
23
+ import { applyXfToCell } from "../style/xf-style.js";
24
+ import { decodeFormula, formulaAnchor } from "./formula.js";
25
+ import { errorCodeFor, RecordReader } from "./primitives.js";
26
+ import { readRecords } from "./record-stream.js";
27
+ 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
+ const CELL_RECORDS = new Set([
32
+ BRT.CellBlank,
33
+ BRT.CellRk,
34
+ BRT.CellError,
35
+ BRT.CellBool,
36
+ BRT.CellReal,
37
+ BRT.CellSt,
38
+ BRT.CellIsst,
39
+ BRT.CellRString,
40
+ ]);
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
+ const FORMULA_RECORDS = new Set([
44
+ BRT.FmlaString,
45
+ BRT.FmlaNum,
46
+ BRT.FmlaBool,
47
+ BRT.FmlaError,
48
+ ]);
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
+ 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
+ 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
+ let rowStyle = -1;
61
+ // A column's format is the last fallback. Column records always precede the cell table.
62
+ 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
+ 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
+ const groups = new Map();
70
+ const deferred = [];
71
+ for (const record of readRecords(part)) {
72
+ const reader = new RecordReader(record.data);
73
+ if (record.type === BRT.WsFmtInfo) {
74
+ reader.skip(6); // dxGCol, cchDefColWidth: the default *column* width, which the model does not read.
75
+ defaultRowHeight = reader.u16();
76
+ }
77
+ else if (record.type === BRT.ColInfo) {
78
+ applyColumn(reader, sheet, xfStyles, columnStyle);
79
+ }
80
+ else if (record.type === BRT.RowHdr) {
81
+ const header = applyRow(reader, sheet, defaultRowHeight);
82
+ row = header.row;
83
+ rowStyle = header.styleIndex;
84
+ }
85
+ else if (record.type === BRT.MergeCell) {
86
+ const { rowFirst, rowLast, colFirst, colLast } = reader.range();
87
+ if (inGrid(colFirst, rowFirst) && inGrid(colLast, rowLast)) {
88
+ sheet.mergeCells(`${encodeAddress(colFirst + 1, rowFirst + 1)}:${encodeAddress(colLast + 1, rowLast + 1)}`);
89
+ }
90
+ }
91
+ else if (record.type === BRT.ArrFmla) {
92
+ const { rowFirst, colFirst } = reader.range();
93
+ reader.skip(1); // fAlwaysCalc: a recalculation hint, not part of the formula.
94
+ groups.set(groupKey(rowFirst, colFirst), {
95
+ rgce: reader.bytes(reader.u32()),
96
+ rgcb: reader.bytes(reader.u32()),
97
+ });
98
+ }
99
+ else if ((CELL_RECORDS.has(record.type) || FORMULA_RECORDS.has(record.type)) && row > 0) {
100
+ const { column, styleIndex } = reader.cell();
101
+ if (!inGrid(column, row - 1))
102
+ 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
+ const resolved = styleIndex > 0 ? styleIndex : rowStyle >= 0 ? rowStyle : (columnStyle.get(column) ?? -1);
107
+ const style = resolved >= 0 ? xfStyles[resolved] : xfStyles[0];
108
+ const cell = sheet.getCell(encodeAddress(column + 1, row));
109
+ applyXfToCell(cell, style);
110
+ if (!FORMULA_RECORDS.has(record.type)) {
111
+ cell.value = decodeCell(record.type, reader, sharedStrings, style?.numFmt);
112
+ continue;
113
+ }
114
+ const result = cachedResult(record.type, reader, style?.numFmt);
115
+ reader.skip(2); // grbitFlags: per-cell recalculation hints the model does not carry.
116
+ const rgce = reader.bytes(reader.u32());
117
+ const rgcb = reader.bytes(reader.u32());
118
+ const anchor = formulaAnchor(rgce, rgcb);
119
+ if (anchor === undefined) {
120
+ cell.value = formulaValue(decodeFormula(rgce, rgcb, scope), result);
121
+ }
122
+ else {
123
+ deferred.push({
124
+ cell,
125
+ row: row - 1,
126
+ column,
127
+ anchorRow: anchor.row,
128
+ anchorColumn: anchor.column,
129
+ result,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ 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
+ const group = groups.get(groupKey(member.anchorRow, member.anchorColumn));
138
+ const own = member.row === member.anchorRow && member.column === member.anchorColumn;
139
+ member.cell.value =
140
+ group === undefined || !own
141
+ ? (member.result ?? null)
142
+ : formulaValue(decodeFormula(group.rgce, group.rgcb, scope), member.result);
143
+ }
144
+ }
145
+ function groupKey(row, column) {
146
+ return `${row}:${column}`;
147
+ }
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
+ function formulaValue(formula, result) {
152
+ if (formula === undefined)
153
+ return result ?? null;
154
+ // Strip the `_xlfn.`/`_xlpm.` on-disk mangling, as the XML reader does, so the model never holds it.
155
+ const stored = unmangleFunctions(formula);
156
+ return result === undefined ? { formula: stored } : { formula: stored, result };
157
+ }
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
+ function cachedResult(type, reader, numFmt) {
161
+ switch (type) {
162
+ 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
+ return asNumberOrDate(reader.f64(), numFmt);
166
+ case BRT.FmlaBool:
167
+ return reader.u8() !== 0;
168
+ case BRT.FmlaError: {
169
+ const error = errorCodeFor(reader.u8());
170
+ return error === undefined ? undefined : { error };
171
+ }
172
+ default:
173
+ return reader.wideString();
174
+ }
175
+ }
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
+ const MAX_ROW_INDEX = 1048575;
183
+ const MAX_COLUMN_INDEX = MAX_COLUMN - 1;
184
+ function inGrid(column, row) {
185
+ return column >= 0 && column <= MAX_COLUMN_INDEX && row >= 0 && row <= MAX_ROW_INDEX;
186
+ }
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
+ function decodeCell(type, reader, sharedStrings, numFmt) {
190
+ switch (type) {
191
+ case BRT.CellRk:
192
+ return asNumberOrDate(reader.rk(), numFmt);
193
+ case BRT.CellReal:
194
+ return asNumberOrDate(reader.f64(), numFmt);
195
+ case BRT.CellBool:
196
+ return reader.u8() !== 0;
197
+ 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
+ const error = errorCodeFor(reader.u8());
201
+ return error === undefined ? null : { error };
202
+ }
203
+ case BRT.CellSt:
204
+ return reader.wideString();
205
+ case BRT.CellRString:
206
+ // Rich runs are not modelled in this cut; the flattened text is what a consumer sees.
207
+ return reader.richString();
208
+ case BRT.CellIsst:
209
+ return sharedStrings[reader.u32()] ?? '';
210
+ default:
211
+ // BrtCellBlank: formatted but empty. The style is already applied; the value is genuinely none.
212
+ return null;
213
+ }
214
+ }
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
+ function asNumberOrDate(value, numFmt) {
218
+ return numFmt !== undefined && isDateFormat(numFmt) ? serialToDate(value) : value;
219
+ }
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
+ function applyRow(reader, sheet, defaultRowHeight) {
223
+ const index = reader.u32();
224
+ const styleIndex = reader.u32();
225
+ const height = reader.u16();
226
+ reader.skip(1); // fExtraAsc/fExtraDsc: border padding, a rendering hint the model does not carry.
227
+ const flags = reader.u8();
228
+ // A row beyond the grid closes the open row without opening another, so its cells are dropped too.
229
+ if (index > MAX_ROW_INDEX)
230
+ return { row: -1, styleIndex: -1 };
231
+ const row = index + 1;
232
+ 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
+ if ((flags & ROW_CUSTOM_HEIGHT) !== 0 || height !== defaultRowHeight) {
238
+ handle.height = height / TWIPS_PER_POINT;
239
+ }
240
+ if ((flags & ROW_HIDDEN) !== 0)
241
+ handle.hidden = true;
242
+ const outlineLevel = flags & ROW_OUTLINE_LEVEL;
243
+ if (outlineLevel > 0)
244
+ handle.outlineLevel = outlineLevel;
245
+ if ((flags & ROW_COLLAPSED) !== 0)
246
+ handle.collapsed = true;
247
+ // The row's format applies only when it says so, mirroring XML's `customFormat="1"` gate.
248
+ return { row, styleIndex: (flags & ROW_CUSTOM_FORMAT) !== 0 ? styleIndex : -1 };
249
+ }
250
+ const TWIPS_PER_POINT = 20;
251
+ const ROW_OUTLINE_LEVEL = 0b0000_0111;
252
+ const ROW_COLLAPSED = 0b0000_1000;
253
+ const ROW_HIDDEN = 0b0001_0000;
254
+ const ROW_CUSTOM_HEIGHT = 0b0010_0000;
255
+ const ROW_CUSTOM_FORMAT = 0b0100_0000;
256
+ // `BrtColInfo` ([MS-XLSB] 2.4.319): one record per run of identically-sized columns.
257
+ function applyColumn(reader, sheet, xfStyles, columnStyle) {
258
+ const first = reader.u32();
259
+ const last = reader.u32();
260
+ const width = reader.u32();
261
+ const styleIndex = reader.u32();
262
+ const flags = reader.u16();
263
+ const style = styleIndex > 0 ? xfStyles[styleIndex] : undefined;
264
+ 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
+ if (style === undefined &&
269
+ outlineLevel === 0 &&
270
+ (flags & (COLUMN_HIDDEN | COLUMN_CUSTOM_WIDTH | COLUMN_COLLAPSED)) === 0) {
271
+ return;
272
+ }
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
+ const lastInGrid = Math.min(last, MAX_COLUMN_INDEX);
276
+ if (first > lastInGrid)
277
+ return;
278
+ for (let index = first; index <= lastInGrid; index++) {
279
+ 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
+ column.width = width / COLUMN_WIDTH_UNITS;
284
+ if ((flags & COLUMN_HIDDEN) !== 0)
285
+ column.hidden = true;
286
+ if (outlineLevel > 0)
287
+ column.outlineLevel = outlineLevel;
288
+ if ((flags & COLUMN_COLLAPSED) !== 0)
289
+ column.collapsed = true;
290
+ if (style !== undefined)
291
+ assignStyleFacets(column, style);
292
+ if (styleIndex > 0)
293
+ columnStyle.set(index, styleIndex);
294
+ }
295
+ }
296
+ // Column width is stored in 1/256ths of a character, where XML states the character count directly.
297
+ const COLUMN_WIDTH_UNITS = 256;
298
+ const COLUMN_HIDDEN = 0b0000_0001;
299
+ const COLUMN_CUSTOM_WIDTH = 0b0000_0010;
300
+ const COLUMN_COLLAPSED = 0b1000_0000_0000;
301
+ const COLUMN_OUTLINE_SHIFT = 8;
@@ -0,0 +1,20 @@
1
+ import { Workbook } from '../../core/workbook.ts';
2
+ import { type ReadXlsxOptions } from '../opc/read-options.ts';
3
+ /** The office-document part every `.xlsb` package is entered through. */
4
+ export declare const XLSB_WORKBOOK_PART = "xl/workbook.bin";
5
+ /**
6
+ * Read an `.xlsb` (binary BIFF12) package into a {@link Workbook}.
7
+ *
8
+ * @throws {UnsupportedFormatError} if the input is not an `.xlsb` package — a legacy `.xls`
9
+ * (`.format === 'xls'`), an XML `.xlsx` or unrecognised blob (`'unknown'`).
10
+ * @throws {XlsbParseError} if a binary part is malformed.
11
+ * @throws {@link PackageReadError} if the input is a ZIP that cannot be unpacked — a corrupt or
12
+ * truncated archive, or one exceeding the inflate bound (a probable zip bomb).
13
+ */
14
+ export declare function readXlsb(data: Uint8Array, options?: ReadXlsxOptions): Workbook;
15
+ /**
16
+ * Build the model from an already-inflated `.xlsb` package. Separate from {@link readXlsb} so the
17
+ * `.xlsx` reader can hand over a package it has already inflated and classified, rather than
18
+ * inflating the same bytes twice.
19
+ */
20
+ export declare function readXlsbPackage(files: Record<string, Uint8Array>): Workbook;
@@ -0,0 +1,199 @@
1
+ // The buffered `.xlsb` reader: a binary OPC package in, a {@link Workbook} model out.
2
+ //
3
+ // An `.xlsb` is the *same* OPC/ZIP container and the *same* relationship graph as an `.xlsx` — the
4
+ // workbook part points at its sheets, its shared strings, and its style sheet through ordinary
5
+ // `.rels` XML. Only the office-document parts differ: `xl/workbook.bin`, `xl/worksheets/sheetN.bin`
6
+ // and friends are BIFF12 record streams instead of XML. So this module reuses the container layer
7
+ // wholesale (`../opc/`, the bounded inflater) and swaps only the part parsers, which is
8
+ // exactly the "two codecs over one model" shape the format note argues for.
9
+ //
10
+ // The model produced is the one `readXlsx` produces, not a parallel one: the same `Workbook`, the
11
+ // same `XfStyle` table, the same cells. That is what lets a caller convert between the two forms —
12
+ // and what the corpus asserts, by reading a workbook Excel saved in both forms and comparing.
13
+ //
14
+ // Not yet decoded (each its own slice of work, none silently wrong): rich-text runs, tables, pivots,
15
+ // and conditional formatting.
16
+ import { unmangleFunctions } from "../../core/formula.js";
17
+ import { INTERNAL } from "../../core/internal.js";
18
+ import { Workbook } from "../../core/workbook.js";
19
+ import { UnsupportedFormatError } from "../opc/errors.js";
20
+ import { packageAccessors, parseRelationships, resolveWorkbookPart } from "../opc/read-opc.js";
21
+ import { DEFAULT_MAX_UNCOMPRESSED } from "../opc/read-options.js";
22
+ import { inflateSpreadsheetPackage } from "../opc/sniff-format.js";
23
+ import { decodeFormula } from "./formula.js";
24
+ import { RecordReader } from "./primitives.js";
25
+ import { parseSharedStrings } from "./read-shared-strings.js";
26
+ import { parseStyleTable } from "./read-styles.js";
27
+ import { parseWorksheet } from "./read-worksheet.js";
28
+ import { readRecords } from "./record-stream.js";
29
+ import { BRT } from "./record-types.js";
30
+ /** The office-document part every `.xlsb` package is entered through. */
31
+ export const XLSB_WORKBOOK_PART = 'xl/workbook.bin';
32
+ /**
33
+ * Read an `.xlsb` (binary BIFF12) package into a {@link Workbook}.
34
+ *
35
+ * @throws {UnsupportedFormatError} if the input is not an `.xlsb` package — a legacy `.xls`
36
+ * (`.format === 'xls'`), an XML `.xlsx` or unrecognised blob (`'unknown'`).
37
+ * @throws {XlsbParseError} if a binary part is malformed.
38
+ * @throws {@link PackageReadError} if the input is a ZIP that cannot be unpacked — a corrupt or
39
+ * truncated archive, or one exceeding the inflate bound (a probable zip bomb).
40
+ */
41
+ export function readXlsb(data, options = {}) {
42
+ const cap = options.maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
43
+ return readXlsbPackage(inflateSpreadsheetPackage(data, cap));
44
+ }
45
+ /**
46
+ * Build the model from an already-inflated `.xlsb` package. Separate from {@link readXlsb} so the
47
+ * `.xlsx` reader can hand over a package it has already inflated and classified, rather than
48
+ * inflating the same bytes twice.
49
+ */
50
+ export function readXlsbPackage(files) {
51
+ const { partText, partBytes } = packageAccessors(files);
52
+ const workbookPart = partBytes(XLSB_WORKBOOK_PART);
53
+ if (workbookPart === undefined) {
54
+ throw new UnsupportedFormatError('unknown', `not a valid .xlsb package: ${XLSB_WORKBOOK_PART} is missing`);
55
+ }
56
+ const rels = parseRelationships(partText('xl/_rels/workbook.bin.rels') ?? '');
57
+ const sharedStrings = parseSharedStrings(partBytes('xl/sharedStrings.bin'));
58
+ const { cellXfs, namedStyles, defaultFont } = parseStyleTable(partBytes('xl/styles.bin'));
59
+ const workbook = new Workbook();
60
+ // As in the XML reader, the named-style layer is restored only when a file declares more than the
61
+ // Normal default, so an ordinary workbook keeps an empty table and writes just that default back.
62
+ if (namedStyles.length > 1)
63
+ workbook[INTERNAL].restoreNamedStyles(namedStyles);
64
+ // As in the XML reader, font 0 is the workbook's declared default and must survive a re-write; an
65
+ // assumed Calibri in its place changes every empty cell and every character-unit column width.
66
+ workbook[INTERNAL].restoreDefaultFont(defaultFont);
67
+ const declaration = readWorkbookPart(workbookPart);
68
+ const scope = {
69
+ sheetNames: declaration.sheets.map((sheet) => sheet.name),
70
+ externSheets: declaration.externSheets,
71
+ selfSupBook: declaration.selfSupBook,
72
+ names: declaration.names.map((name) => name.name),
73
+ };
74
+ for (const declared of declaration.sheets) {
75
+ const sheet = workbook.addWorksheet(declared.name, { state: declared.state });
76
+ const target = declared.relId === undefined ? undefined : rels.get(declared.relId);
77
+ const part = target === undefined ? undefined : partBytes(resolveWorkbookPart(target));
78
+ if (part !== undefined)
79
+ parseWorksheet(part, sheet, sharedStrings, cellXfs, scope);
80
+ }
81
+ for (const defined of definedNames(declaration, scope))
82
+ workbook.defineName(defined);
83
+ return workbook;
84
+ }
85
+ // One pass over `xl/workbook.bin`, gathering everything the rest of the read depends on: the sheet
86
+ // bundle, the externals block a 3-D reference resolves through, and the defined names.
87
+ function readWorkbookPart(part) {
88
+ const sheets = [];
89
+ const names = [];
90
+ let externSheets = [];
91
+ let inBundle = false;
92
+ let inExternals = false;
93
+ // A workbook with no external links declares exactly one supporting book: itself. Rather than
94
+ // enumerate every record type that could open another — and risk miscounting into a *wrong* sheet
95
+ // name — anything else inside the externals block disqualifies the whole table.
96
+ let supportingBooks = 0;
97
+ let selfSupBook;
98
+ for (const record of readRecords(part)) {
99
+ if (record.type === BRT.BeginBundleShs)
100
+ inBundle = true;
101
+ else if (record.type === BRT.EndBundleShs)
102
+ inBundle = false;
103
+ else if (record.type === BRT.BundleSh && inBundle)
104
+ sheets.push(readSheet(record.data));
105
+ else if (record.type === BRT.BeginExternals)
106
+ inExternals = true;
107
+ else if (record.type === BRT.EndExternals)
108
+ inExternals = false;
109
+ else if (record.type === BRT.ExternSheet)
110
+ externSheets = readExternSheets(record.data);
111
+ else if (record.type === BRT.SupSelf)
112
+ selfSupBook = supportingBooks++;
113
+ else if (inExternals)
114
+ supportingBooks++;
115
+ else if (record.type === BRT.Name)
116
+ names.push(readName(record.data));
117
+ }
118
+ return {
119
+ sheets,
120
+ names,
121
+ externSheets,
122
+ selfSupBook: supportingBooks === 1 ? selfSupBook : undefined,
123
+ };
124
+ }
125
+ // `BrtBundleSh` ([MS-XLSB] 2.4.303) — the binary spelling of `<sheet name state r:id/>`.
126
+ function readSheet(data) {
127
+ const reader = new RecordReader(data);
128
+ const state = SHEET_STATES[reader.u32()] ?? 'visible';
129
+ reader.skip(4); // iTabId: the sheet's stable id, which the model assigns itself.
130
+ const relId = reader.nullableWideString();
131
+ return { name: reader.wideString(), relId, state };
132
+ }
133
+ // `BrtExternSheet` ([MS-XLSB] 2.4.677): the `Xti` table every 3-D reference indexes into.
134
+ function readExternSheets(data) {
135
+ const reader = new RecordReader(data);
136
+ const count = reader.u32();
137
+ const entries = [];
138
+ // Each entry is three 4-byte fields; checking the count against what the record holds keeps a forged
139
+ // one from driving the loop rather than the record's own length.
140
+ if (count * XTI_BYTES > reader.remaining)
141
+ return entries;
142
+ for (let index = 0; index < count; index++) {
143
+ entries.push({ supBook: reader.u32(), firstSheet: reader.i32(), lastSheet: reader.i32() });
144
+ }
145
+ return entries;
146
+ }
147
+ const XTI_BYTES = 12;
148
+ // `BrtName` ([MS-XLSB] 2.4.673). The target is left undecoded here: it is a token stream that may cite
149
+ // a sheet by index, and the sheet bundle is not necessarily complete at this point in the stream.
150
+ function readName(data) {
151
+ const reader = new RecordReader(data);
152
+ const flags = reader.u32();
153
+ reader.skip(1); // chKey: the Alt-key shortcut a macro name can carry.
154
+ const itab = reader.u32();
155
+ const name = reader.wideString();
156
+ const rgce = reader.bytes(reader.u32());
157
+ const rgcb = reader.bytes(reader.u32());
158
+ return {
159
+ name,
160
+ scopeSheet: itab === GLOBAL_NAME_SCOPE ? undefined : itab,
161
+ isFunction: (flags & NAME_IS_FUNCTION) !== 0,
162
+ rgce,
163
+ rgcb,
164
+ };
165
+ }
166
+ // `itab` for a workbook-global name; any other value is a zero-based sheet index.
167
+ const GLOBAL_NAME_SCOPE = 0xffffffff;
168
+ // `fFunc`: the name registers something callable rather than a range.
169
+ const NAME_IS_FUNCTION = 0x00000002;
170
+ // The workbook's defined names, as the model holds them.
171
+ //
172
+ // Two kinds of `BrtName` are dropped, both because the XML form does not persist them either — so
173
+ // carrying them through would make the two readings of one workbook disagree. A *function* name is
174
+ // Excel's registration of a callable (every post-2007 function gets one, `_xlfn.TEXTJOIN` and
175
+ // friends); its target is the placeholder `#NAME?`, not a range. And `_xlnm._FilterDatabase` is the
176
+ // built-in Excel derives from a sheet's autofilter, which the model reconstructs from the autofilter
177
+ // itself. A name whose target uses a token this reader cannot decode is dropped too, rather than
178
+ // surfaced with a target that is a guess.
179
+ function definedNames(declaration, scope) {
180
+ const names = [];
181
+ for (const declared of declaration.names) {
182
+ if (declared.isFunction || declared.name === FILTER_DATABASE_NAME)
183
+ continue;
184
+ const refersTo = decodeFormula(declared.rgce, declared.rgcb, scope);
185
+ if (refersTo === undefined)
186
+ continue;
187
+ const sheet = declared.scopeSheet === undefined ? undefined : scope.sheetNames[declared.scopeSheet];
188
+ names.push({
189
+ name: declared.name,
190
+ ...(sheet === undefined ? {} : { scope: sheet }),
191
+ // Stripped back to the readable form, the same normalisation the XML reader applies.
192
+ refersTo: unmangleFunctions(refersTo),
193
+ });
194
+ }
195
+ return names;
196
+ }
197
+ const FILTER_DATABASE_NAME = '_xlnm._FilterDatabase';
198
+ // `hsState` ([MS-XLSB] 2.4.303), indexed by its stored value.
199
+ const SHEET_STATES = ['visible', 'hidden', 'veryHidden'];
@@ -0,0 +1,14 @@
1
+ /** One framed BIFF12 record: its type, and a **view** onto its payload (never a copy). */
2
+ export interface BiffRecord {
3
+ readonly type: number;
4
+ readonly data: Uint8Array;
5
+ }
6
+ /**
7
+ * Decode a `.bin` part into its record sequence, in order.
8
+ *
9
+ * Lazy: a caller that stops early (having found what it needs) never frames the rest of the part.
10
+ *
11
+ * @throws {XlsbParseError} if a record header is truncated or a record's declared size runs past the
12
+ * end of the part.
13
+ */
14
+ export declare function readRecords(part: Uint8Array): Generator<BiffRecord>;
@@ -0,0 +1,56 @@
1
+ // The BIFF12 record framing every `.bin` part of an `.xlsb` package is built from ([MS-XLSB] 2.1.4).
2
+ //
3
+ // A part is a bare concatenation of records — no header, no index, no terminator — each framed as a
4
+ // variable-length type, a variable-length size, and that many payload bytes. Both prefixes are
5
+ // 7-bits-per-byte with the high bit meaning "one more byte follows": the type is 1–2 bytes (so the
6
+ // single-byte space is reserved for the hot records — a cell is one byte of framing), the size is 1–4.
7
+ //
8
+ // **This is the reader's hostile-input frontier.** The declared size is attacker-controlled, so it is
9
+ // never allowed to drive an allocation: a record's payload is handed out as a `subarray` *view* onto
10
+ // the part the inflate bound already materialised and capped, and a size that would run past the end
11
+ // of the part is rejected outright rather than clamped. A lying length therefore costs nothing and
12
+ // fails closed, and the whole stream's memory is exactly the part's own bytes.
13
+ import { XlsbParseError } from "./errors.js";
14
+ /**
15
+ * Decode a `.bin` part into its record sequence, in order.
16
+ *
17
+ * Lazy: a caller that stops early (having found what it needs) never frames the rest of the part.
18
+ *
19
+ * @throws {XlsbParseError} if a record header is truncated or a record's declared size runs past the
20
+ * end of the part.
21
+ */
22
+ export function* readRecords(part) {
23
+ let offset = 0;
24
+ while (offset < part.length) {
25
+ const lowType = byteAt(part, offset++);
26
+ let type = lowType & 0x7f;
27
+ if ((lowType & 0x80) !== 0)
28
+ type |= (byteAt(part, offset++) & 0x7f) << 7;
29
+ // Up to four size bytes, least-significant seven bits first. The fourth byte's continuation bit
30
+ // is specified as ignored, which the fixed bound expresses: the loop simply stops there.
31
+ let size = 0;
32
+ for (let index = 0; index < 4; index++) {
33
+ const piece = byteAt(part, offset++);
34
+ size |= (piece & 0x7f) << (7 * index);
35
+ if ((piece & 0x80) === 0)
36
+ break;
37
+ }
38
+ // The one check that makes a forged size harmless: compare it against what the part *actually*
39
+ // holds, before anything is handed out. `part.length - offset` cannot be negative here — byteAt
40
+ // has already proven every header byte was in range.
41
+ if (size > part.length - offset) {
42
+ throw new XlsbParseError(`BIFF12 record ${type} declares ${size} bytes but only ${part.length - offset} remain in the part`);
43
+ }
44
+ yield { type, data: part.subarray(offset, offset + size) };
45
+ offset += size;
46
+ }
47
+ }
48
+ // Every header byte goes through here, so a part that ends mid-header fails closed rather than
49
+ // folding `undefined` into the arithmetic (which `noUncheckedIndexedAccess` would otherwise let
50
+ // through as a silent NaN-shaped type or size).
51
+ function byteAt(part, index) {
52
+ const value = part[index];
53
+ if (value === undefined)
54
+ throw new XlsbParseError('truncated BIFF12 record header');
55
+ return value;
56
+ }
@@ -0,0 +1,55 @@
1
+ /** BIFF12 record numbers, grouped by the part whose stream they appear in. */
2
+ export declare const BRT: {
3
+ readonly RowHdr: 0;
4
+ readonly CellBlank: 1;
5
+ readonly CellRk: 2;
6
+ readonly CellError: 3;
7
+ readonly CellBool: 4;
8
+ readonly CellReal: 5;
9
+ readonly CellSt: 6;
10
+ readonly CellIsst: 7;
11
+ readonly FmlaString: 8;
12
+ readonly FmlaNum: 9;
13
+ readonly FmlaBool: 10;
14
+ readonly FmlaError: 11;
15
+ readonly CellRString: 62;
16
+ readonly ColInfo: 60;
17
+ readonly MergeCell: 176;
18
+ readonly SSTItem: 19;
19
+ readonly BeginSst: 159;
20
+ readonly EndSst: 160;
21
+ readonly Fmt: 44;
22
+ readonly Font: 43;
23
+ readonly Fill: 45;
24
+ readonly Border: 46;
25
+ readonly XF: 47;
26
+ readonly Style: 48;
27
+ readonly BeginFmts: 615;
28
+ readonly EndFmts: 616;
29
+ readonly BeginFonts: 611;
30
+ readonly EndFonts: 612;
31
+ readonly BeginFills: 603;
32
+ readonly EndFills: 604;
33
+ readonly BeginBorders: 613;
34
+ readonly EndBorders: 614;
35
+ readonly BeginCellStyleXFs: 626;
36
+ readonly EndCellStyleXFs: 627;
37
+ readonly BeginCellXFs: 617;
38
+ readonly EndCellXFs: 618;
39
+ readonly BeginStyles: 619;
40
+ readonly EndStyles: 620;
41
+ readonly BundleSh: 156;
42
+ readonly BeginBundleShs: 143;
43
+ readonly EndBundleShs: 144;
44
+ readonly Name: 39;
45
+ readonly BeginExternals: 353;
46
+ readonly EndExternals: 354;
47
+ readonly SupSelf: 357;
48
+ readonly ExternSheet: 362;
49
+ readonly WsProp: 147;
50
+ readonly WsDim: 148;
51
+ readonly WsFmtInfo: 485;
52
+ readonly BeginSheetData: 145;
53
+ readonly EndSheetData: 146;
54
+ readonly ArrFmla: 426;
55
+ };