@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.
- package/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- 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;
|