@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,398 @@
1
+ // Decoding a BIFF12 `Ptg` token stream back into formula text — the one place where the binary and
2
+ // XML serialisations of a workbook are genuinely different *languages* rather than different spellings.
3
+ //
4
+ // An `.xlsx` stores `SUM(A1:A5)/COUNT(A1:A5)` as those nineteen characters. An `.xlsb` stores the same
5
+ // formula as a postfix (reverse-Polish) token stream: two range operands, two calls, a divide. So the
6
+ // decoder is a stack machine — each operand pushes its own text, each operator pops what it needs and
7
+ // pushes the joined result, and a well-formed stream leaves exactly one string behind.
8
+ //
9
+ // Two things make the reconstruction exact rather than approximate:
10
+ //
11
+ // - **Parentheses are stored, not inferred.** Excel emits an explicit `PtgParen` wherever the author
12
+ // typed one, so there is no precedence arithmetic here and no risk of `(1+2)*3` reading back as
13
+ // `1+2*3`. The token stream already says which is which.
14
+ // - **A reference names no sheet.** A 3-D reference carries an *index* into the workbook's
15
+ // `BrtExternSheet` table, which in turn names a span of sheets in a supporting book. Resolving
16
+ // that indirection — and re-quoting the sheet name the way Excel would — is what turns token
17
+ // `ixti=2` back into `'Odd Name'!A1`.
18
+ //
19
+ // **A token this decoder does not know makes the whole formula undecodable, by design.** The stream is
20
+ // self-describing only if every token's length is known, so guessing past an unrecognised token would
21
+ // desynchronise the parse and produce confident nonsense. Instead the decoder returns `undefined` and
22
+ // its caller keeps what it can still trust — the cached result Excel stored beside the formula. The
23
+ // gaps that reach that path are listed in `docs/knowledge/specs/xlsb-binary-format-output.md`.
24
+ import { MAX_COLUMN, numberToColumn } from "../../core/address.js";
25
+ import { quoteSheetName } from "../../core/formula.js";
26
+ import { errorCodeFor, RecordReader } from "./primitives.js";
27
+ import { FTAB_USER_DEFINED, fixedArityFor, functionNameFor } from "./ptg-functions.js";
28
+ /**
29
+ * Decode a `CellParsedFormula`'s token stream into formula text, in the same on-disk spelling the XML
30
+ * form writes into `<f>` — `_xlfn.`-prefixed function names included, so the caller applies the same
31
+ * `unmangleFunctions` normalisation to either serialisation.
32
+ *
33
+ * @param rgce the token stream.
34
+ * @param rgcb the trailing extra-data block: the array constants, and the cell ranges a precomputed
35
+ * range token refers to. Its entries are consumed in token order.
36
+ * @returns the formula text, or `undefined` if the stream uses a token this reader does not decode.
37
+ * @throws {XlsbParseError} if a token runs past the end of the stream (a malformed formula).
38
+ */
39
+ export function decodeFormula(rgce, rgcb, scope) {
40
+ const tokens = new RecordReader(rgce);
41
+ const extra = new RecordReader(rgcb);
42
+ const stack = [];
43
+ // Pushing `undefined` is how an undecodable token is reported without unwinding: the loop stops and
44
+ // the arity check below rejects the stream. It keeps every token case a plain expression.
45
+ const push = (text) => {
46
+ if (text === undefined)
47
+ return false;
48
+ stack.push(text);
49
+ return true;
50
+ };
51
+ while (!tokens.done) {
52
+ if (!step(tokens.u8(), tokens, extra, stack, scope, push))
53
+ return undefined;
54
+ }
55
+ return stack.length === 1 ? stack[0] : undefined;
56
+ }
57
+ /**
58
+ * The master cell a token stream defers to, when the stream is nothing but a `PtgExp` (a shared or
59
+ * array formula's member) or a `PtgTbl` (a data-table cell) rather than a formula of its own.
60
+ *
61
+ * The column lives in the extra-data block rather than the token, which is the one place BIFF12
62
+ * splits a single reference across the two halves of a `CellParsedFormula`.
63
+ */
64
+ export function formulaAnchor(rgce, rgcb) {
65
+ if (rgce.length !== 5 || (rgce[0] !== PTG.Exp && rgce[0] !== PTG.Tbl))
66
+ return undefined;
67
+ const tokens = new RecordReader(rgce);
68
+ tokens.skip(1);
69
+ const row = tokens.u32();
70
+ const extra = new RecordReader(rgcb);
71
+ if (extra.remaining < 4)
72
+ return undefined;
73
+ return { row, column: extra.u32() };
74
+ }
75
+ // One token: decode it, mutate the stack, and report whether the stream is still decodable.
76
+ function step(ptg, tokens, extra, stack, scope, push) {
77
+ const binary = BINARY_OPERATORS.get(ptg);
78
+ if (binary !== undefined) {
79
+ const right = stack.pop();
80
+ const left = stack.pop();
81
+ return left !== undefined && right !== undefined && push(`${left}${binary}${right}`);
82
+ }
83
+ switch (ptg) {
84
+ case PTG.Uplus:
85
+ case PTG.Uminus:
86
+ case PTG.Percent: {
87
+ const operand = stack.pop();
88
+ if (operand === undefined)
89
+ return false;
90
+ return push(ptg === PTG.Percent ? `${operand}%` : `${ptg === PTG.Uplus ? '+' : '-'}${operand}`);
91
+ }
92
+ case PTG.Paren: {
93
+ const operand = stack.pop();
94
+ return operand !== undefined && push(`(${operand})`);
95
+ }
96
+ case PTG.MissArg:
97
+ // An omitted argument — `IF(A1>0,,1)` — is a real operand whose text is nothing at all.
98
+ return push('');
99
+ case PTG.Str:
100
+ return push(quoteString(tokens.shortString()));
101
+ case PTG.Attr:
102
+ return attribute(tokens, stack, push);
103
+ case PTG.Err:
104
+ return push(errorCodeFor(tokens.u8()));
105
+ case PTG.Bool:
106
+ return push(tokens.u8() !== 0 ? 'TRUE' : 'FALSE');
107
+ case PTG.Int:
108
+ return push(String(tokens.u16()));
109
+ case PTG.Num:
110
+ return push(numberText(tokens.f64()));
111
+ default:
112
+ // Every remaining token is an operand or call whose meaning is independent of its result class
113
+ // (reference, value, or array) — the class only tells the calculation engine how to coerce it.
114
+ return ptg >= CLASSED_TOKEN_FLOOR
115
+ ? operand((ptg & CLASSED_TOKEN_MASK) | CLASSED_TOKEN_FLOOR, tokens, extra, stack, scope, push)
116
+ : false;
117
+ }
118
+ }
119
+ // A class-carrying operand or call token, reduced to its base ptg.
120
+ function operand(base, tokens, extra, stack, scope, push) {
121
+ switch (base) {
122
+ case PTG.Array:
123
+ tokens.skip(14); // A size hint the extra-data block restates; the block is the authority.
124
+ return push(arrayConstant(extra));
125
+ case PTG.Func: {
126
+ const name = functionNameFor(tokens.u16());
127
+ const arity = name === undefined ? undefined : fixedArityFor(name);
128
+ return name !== undefined && arity !== undefined && push(call(name, arity, stack));
129
+ }
130
+ case PTG.FuncVar:
131
+ return variadicCall(tokens, stack, push);
132
+ case PTG.Name: {
133
+ // Cited 1-based, and into the *unfiltered* name list — the placeholder names Excel registers for
134
+ // post-2007 functions occupy indices too, even though they are not the workbook's defined names.
135
+ return push(scope.names[tokens.u32() - 1]);
136
+ }
137
+ case PTG.Ref:
138
+ return push(cellText(tokens.u32(), tokens.u16()));
139
+ case PTG.Area:
140
+ return push(rangeText(tokens.u32(), tokens.u32(), tokens.u16(), tokens.u16()));
141
+ case PTG.Ref3d: {
142
+ const prefix = sheetPrefix(tokens.u16(), scope);
143
+ const text = cellText(tokens.u32(), tokens.u16());
144
+ return prefix !== undefined && text !== undefined && push(prefix + text);
145
+ }
146
+ case PTG.Area3d: {
147
+ const prefix = sheetPrefix(tokens.u16(), scope);
148
+ const text = rangeText(tokens.u32(), tokens.u32(), tokens.u16(), tokens.u16());
149
+ return prefix !== undefined && text !== undefined && push(prefix + text);
150
+ }
151
+ case PTG.RefErr:
152
+ tokens.skip(6);
153
+ return push(REFERENCE_ERROR);
154
+ case PTG.AreaErr:
155
+ tokens.skip(12);
156
+ return push(REFERENCE_ERROR);
157
+ case PTG.RefErr3d:
158
+ tokens.skip(8);
159
+ return push(REFERENCE_ERROR);
160
+ case PTG.AreaErr3d:
161
+ tokens.skip(14);
162
+ return push(REFERENCE_ERROR);
163
+ case PTG.MemArea:
164
+ // A precomputed range: the tokens it was computed from follow inline, so the header is skipped
165
+ // and the walk simply continues into them. Its extra-data entry — the resulting rectangles — is
166
+ // a calculation shortcut with nothing to say about the text, but must still be consumed in order.
167
+ tokens.skip(6);
168
+ return skipExtraRanges(extra);
169
+ default:
170
+ return false;
171
+ }
172
+ }
173
+ // `PtgAttr` ([MS-XLSB] 2.5.97.1): a family of hints the calculation engine leaves in the stream.
174
+ // Almost all are invisible in the formula text — the jump offsets an `IF` uses to skip the branch it
175
+ // did not take, the marker on a volatile function. The one that carries meaning is `bitSum`, Excel's
176
+ // encoding of a single-argument `SUM`, which is a call by any other name.
177
+ function attribute(tokens, stack, push) {
178
+ const flags = tokens.u8();
179
+ const data = tokens.u16();
180
+ if ((flags & ATTR_CHOOSE) !== 0) {
181
+ // The one variable-length attribute: `data` counts CHOOSE's branches, each with a jump offset,
182
+ // plus one for the end of the call.
183
+ tokens.skip((data + 1) * 2);
184
+ return true;
185
+ }
186
+ if ((flags & ATTR_SUM) !== 0)
187
+ return push(call('SUM', 1, stack));
188
+ // `bitSpace` records whitespace the author typed around a token. It is cosmetic — Excel redisplays
189
+ // the formula identically without it — and reattaching it to the right operand is not something a
190
+ // postfix walk can do, so it is dropped rather than misplaced.
191
+ return true;
192
+ }
193
+ // `PtgFuncVar` ([MS-XLSB] 2.5.97.4): a call whose argument count is in the token. Index 255 is not a
194
+ // function at all but the indirection every post-2007 function is called through: the name comes from
195
+ // the stream's first operand, which is a `PtgName` pointing at Excel's `_xlfn.`-prefixed placeholder.
196
+ function variadicCall(tokens, stack, push) {
197
+ const count = tokens.u8() & FUNCVAR_PARAM_MASK;
198
+ const index = tokens.u16() & FUNCVAR_INDEX_MASK;
199
+ if (index !== FTAB_USER_DEFINED) {
200
+ const name = functionNameFor(index);
201
+ return name !== undefined && push(call(name, count, stack));
202
+ }
203
+ if (count < 1 || stack.length < count)
204
+ return false;
205
+ const parts = stack.splice(stack.length - count, count);
206
+ const [name, ...args] = parts;
207
+ return push(`${name}(${args.join(',')})`);
208
+ }
209
+ // Pop `arity` arguments and push the call. Arguments were pushed left to right, so they come off the
210
+ // stack as one contiguous run in source order.
211
+ function call(name, arity, stack) {
212
+ if (stack.length < arity)
213
+ return undefined;
214
+ return `${name}(${stack.splice(stack.length - arity, arity).join(',')})`;
215
+ }
216
+ // `PtgExtraArray` ([MS-XLSB] 2.5.97.2): the elements of an array constant, row-major, behind a
217
+ // row/column count. The element encodings are fixed-width apart from the string, which carries its
218
+ // own length — so the block is walked, never indexed.
219
+ function arrayConstant(extra) {
220
+ const rows = extra.u32();
221
+ const columns = extra.u32();
222
+ if (rows === 0 || columns === 0 || rows * columns > MAX_ARRAY_ELEMENTS)
223
+ return undefined;
224
+ const lines = [];
225
+ for (let row = 0; row < rows; row++) {
226
+ const cells = [];
227
+ for (let column = 0; column < columns; column++) {
228
+ const element = arrayElement(extra);
229
+ if (element === undefined)
230
+ return undefined;
231
+ cells.push(element);
232
+ }
233
+ lines.push(cells.join(','));
234
+ }
235
+ return `{${lines.join(';')}}`;
236
+ }
237
+ // One `SerAr` ([MS-XLSB] 2.5.129) element of an array constant.
238
+ function arrayElement(extra) {
239
+ switch (extra.u8()) {
240
+ case SER_NUM:
241
+ return numberText(extra.f64());
242
+ case SER_STR:
243
+ return quoteString(extra.shortString());
244
+ case SER_BOOL:
245
+ return extra.u8() !== 0 ? 'TRUE' : 'FALSE';
246
+ case SER_ERR: {
247
+ const text = errorCodeFor(extra.u8());
248
+ extra.skip(3);
249
+ return text;
250
+ }
251
+ default:
252
+ return undefined;
253
+ }
254
+ }
255
+ // Consume the `PtgExtraMem` a precomputed-range token owns: a count, then that many cell ranges.
256
+ function skipExtraRanges(extra) {
257
+ const count = extra.u32();
258
+ if (count * RANGE_BYTES > extra.remaining)
259
+ return false;
260
+ extra.skip(count * RANGE_BYTES);
261
+ return true;
262
+ }
263
+ // The sheet part of a 3-D reference, `Data!` or `'Odd Name'!` or `Data:More!`, from the index the
264
+ // token carries into the workbook's extern-sheet table.
265
+ function sheetPrefix(ixti, scope) {
266
+ const xti = scope.externSheets[ixti];
267
+ if (xti === undefined || xti.supBook !== scope.selfSupBook)
268
+ return undefined;
269
+ const first = scope.sheetNames[xti.firstSheet];
270
+ const last = scope.sheetNames[xti.lastSheet];
271
+ if (first === undefined || last === undefined)
272
+ return undefined;
273
+ return `${first === last ? quoteSheetName(first) : quoteSheetName(first, last)}!`;
274
+ }
275
+ // A single cell, from the row and the packed column word every reference token shares. The two high
276
+ // bits of that word say whether each axis is relative, which is exactly where the `$` signs go.
277
+ function cellText(row, packedColumn) {
278
+ const column = packedColumn & COLUMN_MASK;
279
+ if (row > MAX_ROW_INDEX || column >= MAX_COLUMN)
280
+ return undefined;
281
+ const columnAbs = (packedColumn & COLUMN_RELATIVE) === 0 ? '$' : '';
282
+ const rowAbs = (packedColumn & ROW_RELATIVE) === 0 ? '$' : '';
283
+ return `${columnAbs}${numberToColumn(column + 1)}${rowAbs}${row + 1}`;
284
+ }
285
+ // A range. A range that spans every row of its columns, or every column of its rows, is written in
286
+ // Excel's abbreviated form (`A:A`, `2:2`) — which is not cosmetic: it is the only spelling Excel
287
+ // writes for a whole-column reference, so anything else would fail to match the XML twin.
288
+ function rangeText(rowFirst, rowLast, packedFirst, packedLast) {
289
+ if (rowFirst === 0 && rowLast === MAX_ROW_INDEX) {
290
+ const first = columnOnly(packedFirst);
291
+ const last = columnOnly(packedLast);
292
+ return first === undefined || last === undefined ? undefined : `${first}:${last}`;
293
+ }
294
+ if ((packedFirst & COLUMN_MASK) === 0 && (packedLast & COLUMN_MASK) === MAX_COLUMN - 1) {
295
+ return rowFirst > MAX_ROW_INDEX || rowLast > MAX_ROW_INDEX
296
+ ? undefined
297
+ : `${rowOnly(rowFirst, packedFirst)}:${rowOnly(rowLast, packedLast)}`;
298
+ }
299
+ const first = cellText(rowFirst, packedFirst);
300
+ const last = cellText(rowLast, packedLast);
301
+ return first === undefined || last === undefined ? undefined : `${first}:${last}`;
302
+ }
303
+ function columnOnly(packedColumn) {
304
+ const column = packedColumn & COLUMN_MASK;
305
+ if (column >= MAX_COLUMN)
306
+ return undefined;
307
+ return `${(packedColumn & COLUMN_RELATIVE) === 0 ? '$' : ''}${numberToColumn(column + 1)}`;
308
+ }
309
+ function rowOnly(row, packedColumn) {
310
+ return `${(packedColumn & ROW_RELATIVE) === 0 ? '$' : ''}${row + 1}`;
311
+ }
312
+ // A string literal, in the formula's own escaping: the delimiter is a double quote, and a double quote
313
+ // inside the text is doubled.
314
+ function quoteString(text) {
315
+ return `"${text.replace(/"/g, '""')}"`;
316
+ }
317
+ // A numeric literal. JavaScript and Excel agree on every ordinary number; they part company only at
318
+ // the exponent's case, which is normalised here so `1E+21` does not read back as `1e+21`.
319
+ function numberText(value) {
320
+ return String(value).toUpperCase();
321
+ }
322
+ const REFERENCE_ERROR = '#REF!';
323
+ // The infix operators, by their ptg. `PtgIsect` is Excel's space operator (`A1:A3 A2:A5`) and
324
+ // `PtgUnion` its comma — both are operators despite looking like punctuation.
325
+ const BINARY_OPERATORS = new Map([
326
+ [0x03, '+'],
327
+ [0x04, '-'],
328
+ [0x05, '*'],
329
+ [0x06, '/'],
330
+ [0x07, '^'],
331
+ [0x08, '&'],
332
+ [0x09, '<'],
333
+ [0x0a, '<='],
334
+ [0x0b, '='],
335
+ [0x0c, '>='],
336
+ [0x0d, '>'],
337
+ [0x0e, '<>'],
338
+ [0x0f, ' '],
339
+ [0x10, ','],
340
+ [0x11, ':'],
341
+ ]);
342
+ // The token numbers this decoder names. Operand tokens are listed at their *base* value: the stream
343
+ // carries them offset by a result class (+0x20 value, +0x40 array), which changes how the calculation
344
+ // engine coerces the operand but never what it says.
345
+ const PTG = {
346
+ Exp: 0x01,
347
+ Tbl: 0x02,
348
+ Uplus: 0x12,
349
+ Uminus: 0x13,
350
+ Percent: 0x14,
351
+ Paren: 0x15,
352
+ MissArg: 0x16,
353
+ Str: 0x17,
354
+ Attr: 0x19,
355
+ Err: 0x1c,
356
+ Bool: 0x1d,
357
+ Int: 0x1e,
358
+ Num: 0x1f,
359
+ Array: 0x20,
360
+ Func: 0x21,
361
+ FuncVar: 0x22,
362
+ Name: 0x23,
363
+ Ref: 0x24,
364
+ Area: 0x25,
365
+ MemArea: 0x26,
366
+ RefErr: 0x2a,
367
+ AreaErr: 0x2b,
368
+ Ref3d: 0x3a,
369
+ Area3d: 0x3b,
370
+ RefErr3d: 0x3c,
371
+ AreaErr3d: 0x3d,
372
+ };
373
+ const CLASSED_TOKEN_FLOOR = 0x20;
374
+ const CLASSED_TOKEN_MASK = 0x1f;
375
+ // `PtgAttr` flag bits.
376
+ const ATTR_CHOOSE = 0x04;
377
+ const ATTR_SUM = 0x10;
378
+ // `PtgFuncVar`'s two packed fields each reserve their top bit for a flag the text does not carry.
379
+ const FUNCVAR_PARAM_MASK = 0x7f;
380
+ const FUNCVAR_INDEX_MASK = 0x7fff;
381
+ // The packed column word shared by every reference token: 14 bits of column, then the two relative-
382
+ // axis flags.
383
+ const COLUMN_MASK = 0x3fff;
384
+ const COLUMN_RELATIVE = 0x4000;
385
+ const ROW_RELATIVE = 0x8000;
386
+ const MAX_ROW_INDEX = 1048575;
387
+ // `SerAr` element tags.
388
+ const SER_NUM = 0x00;
389
+ const SER_STR = 0x01;
390
+ const SER_BOOL = 0x02;
391
+ const SER_ERR = 0x04;
392
+ // An `UncheckedRfX` is four 32-bit bounds.
393
+ const RANGE_BYTES = 16;
394
+ // A bound on an array constant's declared size. The elements themselves are read from the extra-data
395
+ // block, which cannot outrun its own record — but the row × column product is multiplied *before* any
396
+ // of it is read, and a forged pair would otherwise buy a loop of its own choosing. Excel's own limit
397
+ // on an array constant is far below this.
398
+ const MAX_ARRAY_ELEMENTS = 1 << 20;
@@ -0,0 +1,86 @@
1
+ import type { Color } from '../../core/style.ts';
2
+ import { type ErrorCode } from '../../core/value.ts';
3
+ /** The `Cell` structure ([MS-XLSB] 2.5.10) every cell record opens with. */
4
+ export interface CellHeader {
5
+ /** Zero-based column index. */
6
+ readonly column: number;
7
+ /** Zero-based index into the style sheet's cell-XF collection. */
8
+ readonly styleIndex: number;
9
+ }
10
+ /** An `UncheckedRfX` ([MS-XLSB] 2.5.155) cell range — all four bounds zero-based and inclusive. */
11
+ export interface RangeBounds {
12
+ readonly rowFirst: number;
13
+ readonly rowLast: number;
14
+ readonly colFirst: number;
15
+ readonly colLast: number;
16
+ }
17
+ /**
18
+ * A bounds-checked cursor over one record's payload.
19
+ *
20
+ * Each accessor advances the cursor by exactly the bytes it consumed, so a record is decoded by
21
+ * naming its fields in order. Reading past the payload throws {@link XlsbParseError} — a record that
22
+ * is shorter than its own definition is a malformed file, not a case to guess through.
23
+ */
24
+ export declare class RecordReader {
25
+ #private;
26
+ constructor(data: Uint8Array);
27
+ /** Bytes left in the record. */
28
+ get remaining(): number;
29
+ /** Whether the whole payload has been consumed. */
30
+ get done(): boolean;
31
+ /** Advance past `count` bytes without decoding them — a reserved or unmodelled field. */
32
+ skip(count: number): void;
33
+ /**
34
+ * The next `count` bytes as a **view**, for a field whose own decoding happens elsewhere — a formula
35
+ * token stream, whose meaning depends on workbook tables this record knows nothing about. A view
36
+ * rather than a copy for the same reason a record's payload is one: the declared length comes from
37
+ * the file, so it must bound a read, never an allocation.
38
+ */
39
+ bytes(count: number): Uint8Array;
40
+ u8(): number;
41
+ u16(): number;
42
+ i16(): number;
43
+ u32(): number;
44
+ i32(): number;
45
+ /** An `Xnum` ([MS-XLSB] 2.5.172): a little-endian IEEE-754 double. */
46
+ f64(): number;
47
+ /**
48
+ * An `RkNumber` ([MS-XLSB] 2.5.122): a number packed into 32 bits. Two flag bits steal the low end
49
+ * of the word — `fInt` says the remaining 30 bits are a signed integer rather than the *high* 30
50
+ * bits of a double whose low 34 bits are zero, and `fX100` says the result was scaled up by 100 to
51
+ * keep two decimal places in the integer form. It exists because most real spreadsheet numbers are
52
+ * small integers or two-decimal currency, and this stores them in half the bytes of a double.
53
+ */
54
+ rk(): number;
55
+ /** An `XLWideString` ([MS-XLSB] 2.5.169): a 4-byte character count then that many UTF-16LE units. */
56
+ wideString(): string;
57
+ /**
58
+ * A UTF-16 string whose character count is 16-bit rather than 32-bit — the form used *inside* a
59
+ * formula token stream (`PtgStr`, and the string elements of an array constant), where a 4-byte
60
+ * count on every literal would be pure overhead.
61
+ */
62
+ shortString(): string;
63
+ /** An `XLNullableWideString` ([MS-XLSB] 2.5.166): an {@link wideString} that can also be absent. */
64
+ nullableWideString(): string | undefined;
65
+ /**
66
+ * A `RichStr` ([MS-XLSB] 2.5.124): a string that may carry per-run formatting and phonetic guides.
67
+ * Only the text is returned — the run and phonetic tails are left unread, which is safe because the
68
+ * record's framing (not this cursor) bounds where the payload ends.
69
+ */
70
+ richString(): string;
71
+ /** The `Cell` structure ([MS-XLSB] 2.5.10) that opens every cell record. */
72
+ cell(): CellHeader;
73
+ /** An `UncheckedRfX` ([MS-XLSB] 2.5.155): four zero-based, inclusive range bounds. */
74
+ range(): RangeBounds;
75
+ /**
76
+ * A `BrtColor` ([MS-XLSB] 2.4.337), mapped onto the model's {@link Color}.
77
+ *
78
+ * The four encodings are mutually exclusive and the type tag picks which of the payload's fields
79
+ * carry meaning; the rest are explicitly undefined. An *automatic* colour (type 0) names nothing at
80
+ * all, and reads back as no colour — the same absence the XML reader produces for `<color auto="1"/>`,
81
+ * so a cell whose font colour was never set does not gain one on read.
82
+ */
83
+ color(): Color | undefined;
84
+ }
85
+ /** A `BErr` ([MS-XLSB] 2.5.98.2) error code, as the model's error string. */
86
+ export declare function errorCodeFor(code: number): ErrorCode | undefined;