@rowsncolumns/toolkit 24.5.17 → 24.5.19
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/dist/cjs/excel-parser/defined-names.d.ts +18 -0
- package/dist/cjs/excel-parser/defined-names.d.ts.map +1 -0
- package/dist/cjs/excel-parser/defined-names.js +59 -0
- package/dist/cjs/excel-parser/interface.d.ts +7 -1
- package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsb/function-table.d.ts +17 -0
- package/dist/cjs/excel-parser/plugins/xlsb/function-table.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsb/function-table.js +988 -0
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts +45 -3
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsb/index.js +406 -374
- package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.d.ts +43 -9
- package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.js +422 -478
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.js +147 -191
- package/dist/cjs/excel-parser/types.d.ts +17 -0
- package/dist/cjs/excel-parser/types.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.d.ts +1 -0
- package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.js +4 -0
- package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -1
- package/dist/cjs/exporter/ods/ods-export.js +6 -3
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/xlsx-export.js +47 -23
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/excel-parser/defined-names.d.ts +18 -0
- package/dist/esm/excel-parser/defined-names.d.ts.map +1 -0
- package/dist/esm/excel-parser/defined-names.js +67 -0
- package/dist/esm/excel-parser/interface.d.ts +7 -1
- package/dist/esm/excel-parser/interface.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsb/function-table.d.ts +17 -0
- package/dist/esm/excel-parser/plugins/xlsb/function-table.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsb/function-table.js +988 -0
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts +45 -3
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsb/index.js +266 -205
- package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.d.ts +43 -9
- package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.js +405 -472
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.js +9 -58
- package/dist/esm/excel-parser/types.d.ts +17 -0
- package/dist/esm/excel-parser/types.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.d.ts +1 -0
- package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.js +3 -0
- package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -1
- package/dist/esm/exporter/ods/ods-export.js +6 -3
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/xlsx-export.js +28 -6
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,522 +1,455 @@
|
|
|
1
1
|
import { number2Alpha } from "@rowsncolumns/utils";
|
|
2
|
+
import { FTAB, FTAB_FIXED_ARGC } from "./function-table";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Decoder for a BIFF12 formula's token stream (`rgce`, [MS-XLSB] 2.2.2 / 2.5.97.88):
|
|
5
|
+
* reverse-Polish tokens rebuilt into the formula's text.
|
|
6
|
+
*
|
|
7
|
+
* Strict by design: a formula decodes completely or not at all. A token it
|
|
8
|
+
* does not know, a function outside the table, a read past the end of the
|
|
9
|
+
* stream or a stack that does not end with exactly one expression throws
|
|
10
|
+
* {@link RgceUnsupportedError}, and the caller keeps the cell's cached value
|
|
11
|
+
* instead of writing a formula that computes something else.
|
|
4
12
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
13
|
+
/** The formula cannot be decoded exactly; keep the cell's cached value. */
|
|
14
|
+
export class RgceUnsupportedError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "RgceUnsupportedError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const MAX_ROW_INDEX = 0xfffff; // 1,048,575: row 1,048,576
|
|
21
|
+
const MAX_COL_INDEX = 0x3fff; // 16,383: column XFD
|
|
22
|
+
const ERROR_CODES = {
|
|
23
|
+
0x00: "#NULL!",
|
|
24
|
+
0x07: "#DIV/0!",
|
|
25
|
+
0x0f: "#VALUE!",
|
|
26
|
+
0x17: "#REF!",
|
|
27
|
+
0x1d: "#NAME?",
|
|
28
|
+
0x24: "#NUM!",
|
|
29
|
+
0x2a: "#N/A",
|
|
30
|
+
0x2b: "#GETTING_DATA",
|
|
31
|
+
};
|
|
32
|
+
const BINARY_OPERATORS = {
|
|
33
|
+
0x03: "+",
|
|
34
|
+
0x04: "-",
|
|
35
|
+
0x05: "*",
|
|
36
|
+
0x06: "/",
|
|
37
|
+
0x07: "^",
|
|
38
|
+
0x08: "&",
|
|
39
|
+
0x09: "<",
|
|
40
|
+
0x0a: "<=",
|
|
41
|
+
0x0b: "=",
|
|
42
|
+
0x0c: ">=",
|
|
43
|
+
0x0d: ">",
|
|
44
|
+
0x0e: "<>",
|
|
45
|
+
0x0f: " ",
|
|
46
|
+
0x10: ",",
|
|
47
|
+
0x11: ":",
|
|
48
|
+
};
|
|
49
|
+
/** `PtgAttr` kinds that only steer evaluation or carry whitespace: no stack effect. */
|
|
50
|
+
const ATTR_NO_EFFECT = new Set([0x01, 0x02, 0x08, 0x20, 0x21, 0x40, 0x41]);
|
|
51
|
+
const ATTR_CHOOSE = 0x04;
|
|
52
|
+
const PTG_UNION = 0x10;
|
|
53
|
+
const ATTR_SUM = 0x10;
|
|
54
|
+
class Reader {
|
|
8
55
|
bytes;
|
|
56
|
+
offset = 0;
|
|
57
|
+
view;
|
|
9
58
|
constructor(bytes) {
|
|
10
59
|
this.bytes = bytes;
|
|
11
60
|
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
12
61
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.offset += 1;
|
|
16
|
-
return value;
|
|
62
|
+
get done() {
|
|
63
|
+
return this.offset >= this.bytes.length;
|
|
17
64
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
65
|
+
take(count) {
|
|
66
|
+
if (this.offset + count > this.bytes.length) {
|
|
67
|
+
throw new RgceUnsupportedError("token runs past the end of the formula");
|
|
68
|
+
}
|
|
69
|
+
const at = this.offset;
|
|
70
|
+
this.offset += count;
|
|
71
|
+
return at;
|
|
22
72
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.offset += 4;
|
|
26
|
-
return value;
|
|
73
|
+
u8() {
|
|
74
|
+
return this.view.getUint8(this.take(1));
|
|
27
75
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.offset += 4;
|
|
31
|
-
return value;
|
|
76
|
+
u16() {
|
|
77
|
+
return this.view.getUint16(this.take(2), true);
|
|
32
78
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.offset += 2;
|
|
36
|
-
return value;
|
|
79
|
+
i16() {
|
|
80
|
+
return this.view.getInt16(this.take(2), true);
|
|
37
81
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.offset += 8;
|
|
41
|
-
return value;
|
|
82
|
+
u32() {
|
|
83
|
+
return this.view.getUint32(this.take(4), true);
|
|
42
84
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return "";
|
|
46
|
-
const byteCount = charCount * 2;
|
|
47
|
-
const slice = this.bytes.subarray(this.offset, this.offset + byteCount);
|
|
48
|
-
this.offset += byteCount;
|
|
49
|
-
return new TextDecoder("utf-16le").decode(slice);
|
|
85
|
+
i32() {
|
|
86
|
+
return this.view.getInt32(this.take(4), true);
|
|
50
87
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
f64() {
|
|
89
|
+
return this.view.getFloat64(this.take(8), true);
|
|
90
|
+
}
|
|
91
|
+
skip(count) {
|
|
92
|
+
this.take(count);
|
|
93
|
+
}
|
|
94
|
+
utf16(chars) {
|
|
95
|
+
const at = this.take(chars * 2);
|
|
96
|
+
return new TextDecoder("utf-16le").decode(this.bytes.subarray(at, at + chars * 2));
|
|
54
97
|
}
|
|
55
98
|
}
|
|
56
|
-
// Common Excel function names by ID (ftab index)
|
|
57
|
-
const FUNCTION_NAMES = {
|
|
58
|
-
0: "COUNT", 1: "IF", 2: "ISNA", 3: "ISERROR", 4: "SUM", 5: "AVERAGE",
|
|
59
|
-
6: "MIN", 7: "MAX", 8: "ROW", 9: "COLUMN", 10: "NA", 11: "NPV",
|
|
60
|
-
12: "STDEV", 13: "DOLLAR", 14: "FIXED", 15: "SIN", 16: "COS",
|
|
61
|
-
17: "TAN", 18: "ATAN", 19: "PI", 20: "SQRT", 21: "EXP", 22: "LN",
|
|
62
|
-
23: "LOG10", 24: "ABS", 25: "INT", 26: "SIGN", 27: "ROUND",
|
|
63
|
-
28: "LOOKUP", 29: "INDEX", 30: "REPT", 31: "MID", 32: "LEN",
|
|
64
|
-
33: "VALUE", 34: "TRUE", 35: "FALSE", 36: "AND", 37: "OR",
|
|
65
|
-
38: "NOT", 39: "MOD", 40: "DCOUNT", 41: "DSUM", 42: "DAVERAGE",
|
|
66
|
-
43: "DMIN", 44: "DMAX", 45: "DSTDEV", 46: "VAR", 47: "DVAR",
|
|
67
|
-
48: "TEXT", 49: "LINEST", 50: "TREND", 51: "LOGEST", 52: "GROWTH",
|
|
68
|
-
61: "MIRR", 62: "IRR", 63: "RAND", 64: "MATCH", 65: "DATE",
|
|
69
|
-
66: "TIME", 67: "DAY", 68: "MONTH", 69: "YEAR", 70: "WEEKDAY",
|
|
70
|
-
71: "HOUR", 72: "MINUTE", 73: "SECOND", 74: "NOW", 75: "AREAS",
|
|
71
|
-
76: "ROWS", 77: "COLUMNS", 78: "OFFSET", 82: "SEARCH", 83: "TRANSPOSE",
|
|
72
|
-
86: "TYPE", 97: "ATAN2", 98: "ASIN", 99: "ACOS", 100: "CHOOSE",
|
|
73
|
-
101: "HLOOKUP", 102: "VLOOKUP", 103: "ISREF", 104: "LOG",
|
|
74
|
-
105: "CHAR", 106: "LOWER", 107: "UPPER", 108: "PROPER", 109: "LEFT",
|
|
75
|
-
110: "RIGHT", 111: "EXACT", 112: "TRIM", 113: "REPLACE", 114: "SUBSTITUTE",
|
|
76
|
-
115: "CODE", 116: "NAMES", 117: "DIRECTORY", 118: "FIND", 119: "CELL",
|
|
77
|
-
120: "ISERR", 121: "ISTEXT", 122: "ISNUMBER", 123: "ISBLANK",
|
|
78
|
-
124: "T", 125: "N", 127: "ISNONTEXT", 128: "ISODD", 129: "ISEVEN",
|
|
79
|
-
162: "CLEAN", 163: "MDETERM", 164: "MINVERSE", 165: "MMULT",
|
|
80
|
-
167: "IPMT", 168: "PPMT", 169: "COUNTA", 183: "PRODUCT",
|
|
81
|
-
184: "FACT", 189: "DPRODUCT", 190: "ISNONTEXT",
|
|
82
|
-
193: "STDEVP", 194: "VARP", 195: "DSTDEVP", 196: "DVARP",
|
|
83
|
-
197: "TRUNC", 198: "ISLOGICAL", 199: "DCOUNTA", 200: "DELETEBAR",
|
|
84
|
-
216: "RANK", 219: "ADDRESS", 220: "DAYS360", 221: "TODAY",
|
|
85
|
-
222: "VDB", 227: "MEDIAN", 228: "SUMPRODUCT", 229: "SINH",
|
|
86
|
-
230: "COSH", 231: "TANH", 232: "ASINH", 233: "ACOSH", 234: "ATANH",
|
|
87
|
-
235: "DGET", 244: "INFO", 247: "DB", 252: "FREQUENCY",
|
|
88
|
-
261: "ERROR.TYPE", 269: "AVEDEV", 270: "BETADIST", 271: "GAMMALN",
|
|
89
|
-
272: "BETAINV", 273: "BINOMDIST", 274: "CHIDIST", 275: "CHIINV",
|
|
90
|
-
276: "COMBIN", 277: "CONFIDENCE", 278: "CRITBINOM", 279: "EVEN",
|
|
91
|
-
280: "EXPONDIST", 281: "FDIST", 282: "FINV", 283: "FISHER",
|
|
92
|
-
284: "FISHERINV", 285: "FLOOR", 286: "GAMMADIST", 287: "GAMMAINV",
|
|
93
|
-
288: "CEILING", 289: "HYPGEOMDIST", 290: "LOGNORMDIST", 291: "LOGINV",
|
|
94
|
-
292: "NEGBINOMDIST", 293: "NORMDIST", 294: "NORMSDIST", 295: "NORMINV",
|
|
95
|
-
296: "NORMSINV", 297: "STANDARDIZE", 298: "ODD", 299: "PERMUT",
|
|
96
|
-
300: "POISSON", 301: "TDIST", 302: "WEIBULL", 303: "SUMXMY2",
|
|
97
|
-
304: "SUMX2MY2", 305: "SUMX2PY2", 306: "CHITEST", 307: "CORREL",
|
|
98
|
-
308: "COVAR", 309: "FORECAST", 310: "FTEST", 311: "INTERCEPT",
|
|
99
|
-
312: "PEARSON", 313: "RSQ", 314: "STEYX", 315: "SLOPE", 316: "TTEST",
|
|
100
|
-
317: "PROB", 318: "DEVSQ", 319: "GEOMEAN", 320: "HARMEAN",
|
|
101
|
-
321: "SUMSQ", 322: "KURT", 323: "SKEW", 324: "ZTEST", 325: "LARGE",
|
|
102
|
-
326: "SMALL", 327: "QUARTILE", 328: "PERCENTILE", 329: "PERCENTRANK",
|
|
103
|
-
330: "MODE", 331: "TRIMMEAN", 332: "TINV", 334: "CONCATENATE",
|
|
104
|
-
335: "POWER", 336: "RADIANS", 337: "DEGREES", 338: "SUBTOTAL",
|
|
105
|
-
339: "SUMIF", 340: "COUNTIF", 341: "COUNTBLANK", 342: "HYPERLINK",
|
|
106
|
-
343: "PHONETIC", 344: "AVERAGEA", 345: "MAXA", 346: "MINA",
|
|
107
|
-
347: "STDEVPA", 348: "VARPA", 349: "STDEVA", 350: "VARA",
|
|
108
|
-
};
|
|
109
99
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* @param rgceBytes - The binary formula data
|
|
114
|
-
* @returns The formula string (without leading "=")
|
|
100
|
+
* A sheet name as a reference prefix: quoted unless it is a plain ASCII
|
|
101
|
+
* identifier no reader could take for a cell (`A1`, `R1C1`, `R`) or a
|
|
102
|
+
* boolean. Quoting is always valid, so anything else is quoted.
|
|
115
103
|
*/
|
|
104
|
+
export const quoteSheetName = (name) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) &&
|
|
105
|
+
!/^[A-Za-z]{1,3}\d+$/.test(name) &&
|
|
106
|
+
!/^(?:[Rr]\d*)?(?:[Cc]\d*)?$/.test(name) &&
|
|
107
|
+
!/^(?:TRUE|FALSE)$/i.test(name)
|
|
108
|
+
? name
|
|
109
|
+
: `'${name.replace(/'/g, "''")}'`;
|
|
110
|
+
const formatNumber = (value) => {
|
|
111
|
+
if (!Number.isFinite(value)) {
|
|
112
|
+
throw new RgceUnsupportedError("non-finite number constant");
|
|
113
|
+
}
|
|
114
|
+
return String(value).replace("e", "E");
|
|
115
|
+
};
|
|
116
|
+
const cellRef = (row, col, rowRelative, colRelative) => `${colRelative ? "" : "$"}${number2Alpha(col)}${rowRelative ? "" : "$"}${row + 1}`;
|
|
117
|
+
/** An area, written the way Excel writes whole columns (`A:B`) and whole rows (`1:2`). */
|
|
118
|
+
const areaRef = (first, last) => {
|
|
119
|
+
if (first.row === 0 && last.row === MAX_ROW_INDEX) {
|
|
120
|
+
return `${first.colRelative ? "" : "$"}${number2Alpha(first.col)}:${last.colRelative ? "" : "$"}${number2Alpha(last.col)}`;
|
|
121
|
+
}
|
|
122
|
+
if (first.col === 0 && last.col === MAX_COL_INDEX) {
|
|
123
|
+
return `${first.rowRelative ? "" : "$"}${first.row + 1}:${last.rowRelative ? "" : "$"}${last.row + 1}`;
|
|
124
|
+
}
|
|
125
|
+
return `${cellRef(first.row, first.col, first.rowRelative, first.colRelative)}:${cellRef(last.row, last.col, last.rowRelative, last.colRelative)}`;
|
|
126
|
+
};
|
|
127
|
+
/** `RgceLoc` / `RgceArea` corner: a 20-bit row (u32) and a 14-bit column with its relative flags (u16). */
|
|
128
|
+
const corner = (row, colField) => ({
|
|
129
|
+
row: row & MAX_ROW_INDEX,
|
|
130
|
+
col: colField & MAX_COL_INDEX,
|
|
131
|
+
rowRelative: (colField & 0x8000) !== 0,
|
|
132
|
+
colRelative: (colField & 0x4000) !== 0,
|
|
133
|
+
});
|
|
134
|
+
/** A `RgceLocRel` corner of a shared formula or name: relative parts are offsets from the base cell. */
|
|
135
|
+
const relativeCorner = (rowField, colField, base) => {
|
|
136
|
+
const rowRelative = (colField & 0x8000) !== 0;
|
|
137
|
+
const colRelative = (colField & 0x4000) !== 0;
|
|
138
|
+
if ((rowRelative || colRelative) && !base) {
|
|
139
|
+
throw new RgceUnsupportedError("relative reference without a base cell");
|
|
140
|
+
}
|
|
141
|
+
const baseRow = (base?.row ?? 1) - 1;
|
|
142
|
+
const baseCol = (base?.col ?? 1) - 1;
|
|
143
|
+
// A relative column is a signed 14-bit offset; a relative row a signed 32-bit one.
|
|
144
|
+
const colOffset = ((colField & MAX_COL_INDEX) << 18) >> 18;
|
|
145
|
+
const row = rowRelative ? baseRow + rowField : rowField;
|
|
146
|
+
const col = colRelative ? baseCol + colOffset : colField & MAX_COL_INDEX;
|
|
147
|
+
// Offsets wrap around the sheet, as Excel's do.
|
|
148
|
+
return {
|
|
149
|
+
row: ((row % (MAX_ROW_INDEX + 1)) + MAX_ROW_INDEX + 1) % (MAX_ROW_INDEX + 1),
|
|
150
|
+
col: ((col % (MAX_COL_INDEX + 1)) + MAX_COL_INDEX + 1) % (MAX_COL_INDEX + 1),
|
|
151
|
+
rowRelative,
|
|
152
|
+
colRelative,
|
|
153
|
+
};
|
|
154
|
+
};
|
|
116
155
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* @
|
|
120
|
-
*
|
|
156
|
+
* Decode a formula's tokens into its text, without the leading `=`.
|
|
157
|
+
*
|
|
158
|
+
* Throws {@link RgceUnsupportedError} when any part of the formula cannot be
|
|
159
|
+
* decoded exactly.
|
|
121
160
|
*/
|
|
122
|
-
export function
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
const reader = new RgceReader(rgceBytes);
|
|
126
|
-
const stack = [];
|
|
127
|
-
if (debug) {
|
|
128
|
-
console.log("[Rgce] Parsing", rgceBytes.length, "bytes:", Array.from(rgceBytes.slice(0, Math.min(30, rgceBytes.length)))
|
|
129
|
-
.map(b => `0x${b.toString(16).padStart(2, '0')}`)
|
|
130
|
-
.join(' '));
|
|
161
|
+
export function decodeRgce(rgce, context = {}) {
|
|
162
|
+
if (rgce.length === 0) {
|
|
163
|
+
throw new RgceUnsupportedError("empty formula");
|
|
131
164
|
}
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
25: 1, // INT
|
|
142
|
-
27: 2, // ROUND
|
|
143
|
-
31: 3, // MID
|
|
144
|
-
32: 1, // LEN
|
|
145
|
-
34: 0, // TRUE
|
|
146
|
-
35: 0, // FALSE
|
|
147
|
-
39: 2, // MOD
|
|
148
|
-
65: 3, // DATE
|
|
149
|
-
66: 3, // TIME
|
|
150
|
-
67: 1, // DAY
|
|
151
|
-
68: 1, // MONTH
|
|
152
|
-
69: 1, // YEAR
|
|
153
|
-
71: 1, // HOUR
|
|
154
|
-
72: 1, // MINUTE
|
|
155
|
-
73: 1, // SECOND
|
|
156
|
-
74: 0, // NOW
|
|
157
|
-
105: 1, // CHAR
|
|
158
|
-
106: 1, // LOWER
|
|
159
|
-
107: 1, // UPPER
|
|
160
|
-
109: 2, // LEFT
|
|
161
|
-
110: 2, // RIGHT
|
|
162
|
-
111: 2, // EXACT
|
|
163
|
-
112: 1, // TRIM
|
|
164
|
-
115: 1, // CODE
|
|
165
|
-
183: 1, // PRODUCT
|
|
166
|
-
197: 2, // TRUNC
|
|
167
|
-
221: 0, // TODAY
|
|
168
|
-
335: 2, // POWER
|
|
169
|
-
336: 1, // RADIANS
|
|
170
|
-
337: 1, // DEGREES
|
|
171
|
-
};
|
|
172
|
-
const canRead = (n) => reader.offset + n <= rgceBytes.length;
|
|
173
|
-
const quoteSheetName = (name) => /[\s!'[\]]/.test(name) ? `'${name.replace(/'/g, "''")}'` : name;
|
|
174
|
-
const resolveSheetName = (ixti) => {
|
|
175
|
-
if (typeof sheetRefResolver === "function") {
|
|
176
|
-
return sheetRefResolver(ixti);
|
|
165
|
+
const reader = new Reader(rgce);
|
|
166
|
+
const stack = [];
|
|
167
|
+
// Stack slots holding a union without its parentheses: fine as a whole
|
|
168
|
+
// formula (`A1,C3`), but an argument must say it is one (`SUM((A1,C3))`).
|
|
169
|
+
const bareUnions = new Set();
|
|
170
|
+
const pop = () => {
|
|
171
|
+
const value = stack.pop();
|
|
172
|
+
if (value === undefined) {
|
|
173
|
+
throw new RgceUnsupportedError("operator without an operand");
|
|
177
174
|
}
|
|
178
|
-
|
|
175
|
+
bareUnions.delete(stack.length);
|
|
176
|
+
return value;
|
|
179
177
|
};
|
|
180
178
|
const popArgs = (count) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
stack.push(`${funcName}(${popArgs(cargs).join(",")})`);
|
|
190
|
-
};
|
|
191
|
-
const pushVariableFunction = (cargs, ftabIndex) => {
|
|
192
|
-
const funcName = FUNCTION_NAMES[ftabIndex] || `FUNC${ftabIndex}`;
|
|
193
|
-
stack.push(`${funcName}(${popArgs(cargs).join(",")})`);
|
|
194
|
-
};
|
|
195
|
-
const formatRef = (rowZero, colZero, rowRel, colRel) => {
|
|
196
|
-
const row = Math.max(0, rowZero);
|
|
197
|
-
const col = Math.max(0, colZero);
|
|
198
|
-
return `${colRel ? "" : "$"}${number2Alpha(col)}${rowRel ? "" : "$"}${row + 1}`;
|
|
199
|
-
};
|
|
200
|
-
const readShortRef = () => {
|
|
201
|
-
const row = reader.readUInt32LE();
|
|
202
|
-
const col = reader.readUInt16LE();
|
|
203
|
-
const rowRel = (col & 0x8000) !== 0;
|
|
204
|
-
const colRel = (col & 0x4000) !== 0;
|
|
205
|
-
return formatRef(row & 0x000fffff, col & 0x3fff, rowRel, colRel);
|
|
206
|
-
};
|
|
207
|
-
const readShortArea = () => {
|
|
208
|
-
const rowFirst = reader.readUInt32LE();
|
|
209
|
-
const rowLast = reader.readUInt32LE();
|
|
210
|
-
const colFirst = reader.readUInt16LE();
|
|
211
|
-
const colLast = reader.readUInt16LE();
|
|
212
|
-
const refFirst = formatRef(rowFirst & 0x000fffff, colFirst & 0x3fff, (colFirst & 0x8000) !== 0, (colFirst & 0x4000) !== 0);
|
|
213
|
-
const refLast = formatRef(rowLast & 0x000fffff, colLast & 0x3fff, (colLast & 0x8000) !== 0, (colLast & 0x4000) !== 0);
|
|
214
|
-
return `${refFirst}:${refLast}`;
|
|
179
|
+
if (count > stack.length) {
|
|
180
|
+
throw new RgceUnsupportedError("function without all its arguments");
|
|
181
|
+
}
|
|
182
|
+
const first = stack.length - count;
|
|
183
|
+
return stack.splice(first, count).map((arg, at) => {
|
|
184
|
+
const isBareUnion = bareUnions.delete(first + at);
|
|
185
|
+
return isBareUnion ? `(${arg})` : arg;
|
|
186
|
+
});
|
|
215
187
|
};
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const baseColZero = Math.max(0, (baseCell?.col ?? 1) - 1);
|
|
223
|
-
return formatRef(baseRowZero + rowOffset, baseColZero + colOffset, true, true);
|
|
188
|
+
const sheet = (ixti) => {
|
|
189
|
+
const name = context.sheetName?.(ixti);
|
|
190
|
+
if (name === undefined) {
|
|
191
|
+
throw new RgceUnsupportedError(`unknown sheet reference ${ixti}`);
|
|
192
|
+
}
|
|
193
|
+
return quoteSheetName(name);
|
|
224
194
|
};
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const colLastOffset = ((colLastRaw & 0x3fff) << 18) >> 18;
|
|
232
|
-
const baseRowZero = Math.max(0, (baseCell?.row ?? 1) - 1);
|
|
233
|
-
const baseColZero = Math.max(0, (baseCell?.col ?? 1) - 1);
|
|
234
|
-
const refFirst = formatRef(baseRowZero + rowFirstOffset, baseColZero + colFirstOffset, true, true);
|
|
235
|
-
const refLast = formatRef(baseRowZero + rowLastOffset, baseColZero + colLastOffset, true, true);
|
|
236
|
-
return `${refFirst}:${refLast}`;
|
|
195
|
+
const callFunction = (iftab, args) => {
|
|
196
|
+
const name = FTAB[iftab];
|
|
197
|
+
if (!name) {
|
|
198
|
+
throw new RgceUnsupportedError(`unknown function ${iftab}`);
|
|
199
|
+
}
|
|
200
|
+
return `${name}(${args.join(",")})`;
|
|
237
201
|
};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const left = stack.pop() || "0";
|
|
264
|
-
stack.push(`${left}*${right}`);
|
|
265
|
-
}
|
|
266
|
-
else if (ptg === 0x06) {
|
|
267
|
-
// PtgDiv
|
|
268
|
-
const right = stack.pop() || "0";
|
|
269
|
-
const left = stack.pop() || "0";
|
|
270
|
-
stack.push(`${left}/${right}`);
|
|
271
|
-
}
|
|
272
|
-
else if (ptg === 0x07) {
|
|
273
|
-
// PtgPower
|
|
274
|
-
const right = stack.pop() || "0";
|
|
275
|
-
const left = stack.pop() || "0";
|
|
276
|
-
stack.push(`${left}^${right}`);
|
|
277
|
-
}
|
|
278
|
-
else if (ptg === 0x08) {
|
|
279
|
-
// PtgConcat
|
|
280
|
-
const right = stack.pop() || '""';
|
|
281
|
-
const left = stack.pop() || '""';
|
|
282
|
-
stack.push(`${left}&${right}`);
|
|
283
|
-
}
|
|
284
|
-
else if (ptg === 0x09) {
|
|
285
|
-
// PtgLT
|
|
286
|
-
const right = stack.pop() || "0";
|
|
287
|
-
const left = stack.pop() || "0";
|
|
288
|
-
stack.push(`${left}<${right}`);
|
|
289
|
-
}
|
|
290
|
-
else if (ptg === 0x0a) {
|
|
291
|
-
// PtgLE
|
|
292
|
-
const right = stack.pop() || "0";
|
|
293
|
-
const left = stack.pop() || "0";
|
|
294
|
-
stack.push(`${left}<=${right}`);
|
|
295
|
-
}
|
|
296
|
-
else if (ptg === 0x0b) {
|
|
297
|
-
// PtgEQ
|
|
298
|
-
const right = stack.pop() || "0";
|
|
299
|
-
const left = stack.pop() || "0";
|
|
300
|
-
stack.push(`${left}=${right}`);
|
|
301
|
-
}
|
|
302
|
-
else if (ptg === 0x0c) {
|
|
303
|
-
// PtgGE
|
|
304
|
-
const right = stack.pop() || "0";
|
|
305
|
-
const left = stack.pop() || "0";
|
|
306
|
-
stack.push(`${left}>=${right}`);
|
|
307
|
-
}
|
|
308
|
-
else if (ptg === 0x0d) {
|
|
309
|
-
// PtgGT
|
|
310
|
-
const right = stack.pop() || "0";
|
|
311
|
-
const left = stack.pop() || "0";
|
|
312
|
-
stack.push(`${left}>${right}`);
|
|
313
|
-
}
|
|
314
|
-
else if (ptg === 0x0e) {
|
|
315
|
-
// PtgNE
|
|
316
|
-
const right = stack.pop() || "0";
|
|
317
|
-
const left = stack.pop() || "0";
|
|
318
|
-
stack.push(`${left}<>${right}`);
|
|
319
|
-
}
|
|
320
|
-
else if (ptg === 0x12) {
|
|
321
|
-
// PtgUPlus
|
|
322
|
-
const operand = stack.pop() || "0";
|
|
323
|
-
stack.push(`+${operand}`);
|
|
324
|
-
}
|
|
325
|
-
else if (ptg === 0x13) {
|
|
326
|
-
// PtgUMinus
|
|
327
|
-
const operand = stack.pop() || "0";
|
|
328
|
-
stack.push(`-${operand}`);
|
|
329
|
-
}
|
|
330
|
-
else if (ptg === 0x14) {
|
|
331
|
-
// PtgPercent
|
|
332
|
-
const operand = stack.pop() || "0";
|
|
333
|
-
stack.push(`${operand}%`);
|
|
334
|
-
}
|
|
335
|
-
else if (ptg === 0x15) {
|
|
336
|
-
// PtgParen
|
|
337
|
-
const operand = stack.pop() || "";
|
|
338
|
-
stack.push(`(${operand})`);
|
|
339
|
-
}
|
|
340
|
-
else if (ptg === 0x16) {
|
|
341
|
-
// PtgMissArg - missing argument (empty between commas)
|
|
202
|
+
while (!reader.done) {
|
|
203
|
+
const ptg = reader.u8();
|
|
204
|
+
const binary = BINARY_OPERATORS[ptg];
|
|
205
|
+
if (binary !== undefined) {
|
|
206
|
+
const right = pop();
|
|
207
|
+
const left = pop();
|
|
208
|
+
if (ptg === PTG_UNION)
|
|
209
|
+
bareUnions.add(stack.length);
|
|
210
|
+
stack.push(`${left}${binary}${right}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
switch (ptg) {
|
|
214
|
+
case 0x12: // PtgUplus
|
|
215
|
+
stack.push(`+${pop()}`);
|
|
216
|
+
continue;
|
|
217
|
+
case 0x13: // PtgUminus
|
|
218
|
+
stack.push(`-${pop()}`);
|
|
219
|
+
continue;
|
|
220
|
+
case 0x14: // PtgPercent
|
|
221
|
+
stack.push(`${pop()}%`);
|
|
222
|
+
continue;
|
|
223
|
+
case 0x15: // PtgParen
|
|
224
|
+
stack.push(`(${pop()})`);
|
|
225
|
+
continue;
|
|
226
|
+
case 0x16: // PtgMissArg
|
|
342
227
|
stack.push("");
|
|
228
|
+
continue;
|
|
229
|
+
case 0x17: {
|
|
230
|
+
// PtgStr: a 16-bit character count, then UTF-16LE.
|
|
231
|
+
const text = reader.utf16(reader.u16());
|
|
232
|
+
stack.push(`"${text.replace(/"/g, '""')}"`);
|
|
233
|
+
continue;
|
|
343
234
|
}
|
|
344
|
-
|
|
345
|
-
//
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// PtgBool - boolean constant
|
|
351
|
-
const value = reader.readUInt8();
|
|
352
|
-
stack.push(value ? "TRUE" : "FALSE");
|
|
353
|
-
}
|
|
354
|
-
else if (ptg === 0x1e) {
|
|
355
|
-
// PtgInt - integer constant
|
|
356
|
-
const value = reader.readUInt16LE();
|
|
357
|
-
stack.push(String(value));
|
|
358
|
-
}
|
|
359
|
-
else if (ptg === 0x1f) {
|
|
360
|
-
// PtgNum - float constant
|
|
361
|
-
const value = reader.readFloat64LE();
|
|
362
|
-
stack.push(String(value));
|
|
363
|
-
}
|
|
364
|
-
else if (ptg === 0x18) {
|
|
365
|
-
// PtgErr - error constant
|
|
366
|
-
if (!canRead(1))
|
|
367
|
-
break;
|
|
368
|
-
const code = reader.readUInt8();
|
|
369
|
-
const errorValues = {
|
|
370
|
-
0x00: "#NULL!",
|
|
371
|
-
0x07: "#DIV/0!",
|
|
372
|
-
0x0f: "#VALUE!",
|
|
373
|
-
0x17: "#REF!",
|
|
374
|
-
0x1d: "#NAME?",
|
|
375
|
-
0x24: "#NUM!",
|
|
376
|
-
0x2a: "#N/A",
|
|
377
|
-
0x2b: "#GETTING_DATA",
|
|
378
|
-
};
|
|
379
|
-
stack.push(errorValues[code] || "#VALUE!");
|
|
380
|
-
}
|
|
381
|
-
else if (ptg === 0x19) {
|
|
382
|
-
// PtgAttr: 1 byte flags + 2 bytes data
|
|
383
|
-
if (!canRead(3))
|
|
384
|
-
break;
|
|
385
|
-
const flags = reader.readUInt8();
|
|
386
|
-
const data = reader.readUInt16LE();
|
|
387
|
-
// Handle various PtgAttr flags
|
|
388
|
-
if ((flags & 0x10) !== 0) {
|
|
389
|
-
// fSum: treat previous operand as SUM(operand)
|
|
390
|
-
const operand = stack.pop() || "";
|
|
391
|
-
stack.push(`SUM(${operand})`);
|
|
392
|
-
}
|
|
393
|
-
// Note: Other flags (fIf=0x02, fChoose=0x04, fSkip=0x08, fGoto=0x01)
|
|
394
|
-
// are for control flow and typically don't affect the formula string
|
|
395
|
-
// They just affect how bytes are skipped during parsing
|
|
396
|
-
}
|
|
397
|
-
else if (ptg === 0x01) {
|
|
398
|
-
// Ambiguous in some XLSB streams:
|
|
399
|
-
// - PtgExp (4-byte payload)
|
|
400
|
-
// - PtgFunc ref-class variant (2-byte payload)
|
|
401
|
-
if (canRead(4)) {
|
|
402
|
-
const srcRow = reader.readUInt16LE() + 1;
|
|
403
|
-
const srcCol = reader.readUInt16LE() + 1;
|
|
404
|
-
stack.push(`__PTGEXP__:${srcRow}:${srcCol}`);
|
|
235
|
+
case 0x19: {
|
|
236
|
+
// PtgAttr: a kind byte and 16 bits of data.
|
|
237
|
+
const kind = reader.u8();
|
|
238
|
+
const data = reader.u16();
|
|
239
|
+
if (kind === ATTR_SUM) {
|
|
240
|
+
stack.push(`SUM(${popArgs(1)[0]})`);
|
|
405
241
|
}
|
|
406
|
-
else if (
|
|
407
|
-
|
|
242
|
+
else if (kind === ATTR_CHOOSE) {
|
|
243
|
+
reader.skip((data + 1) * 2); // CHOOSE's jump table
|
|
408
244
|
}
|
|
409
|
-
else {
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
else if (ptg === 0x02) {
|
|
414
|
-
// PtgFuncVar reference-class variant (0x02)
|
|
415
|
-
// In practice, 0x02 is PtgFuncVar, not PtgTbl
|
|
416
|
-
if (canRead(3)) {
|
|
417
|
-
const cargs = reader.readUInt8();
|
|
418
|
-
pushVariableFunction(cargs, reader.readUInt16LE());
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
break;
|
|
245
|
+
else if (!ATTR_NO_EFFECT.has(kind)) {
|
|
246
|
+
throw new RgceUnsupportedError(`attribute 0x${kind.toString(16)}`);
|
|
422
247
|
}
|
|
248
|
+
continue;
|
|
423
249
|
}
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const cellRef = readShortRef();
|
|
430
|
-
// Get sheet name if available
|
|
431
|
-
const sheetName = resolveSheetName(ixti);
|
|
432
|
-
if (sheetName) {
|
|
433
|
-
const quotedName = quoteSheetName(sheetName);
|
|
434
|
-
stack.push(`${quotedName}!${cellRef}`);
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
stack.push(cellRef);
|
|
250
|
+
case 0x1c: {
|
|
251
|
+
// PtgErr
|
|
252
|
+
const text = ERROR_CODES[reader.u8()];
|
|
253
|
+
if (text === undefined) {
|
|
254
|
+
throw new RgceUnsupportedError("unknown error constant");
|
|
438
255
|
}
|
|
256
|
+
stack.push(text);
|
|
257
|
+
continue;
|
|
439
258
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
259
|
+
case 0x1d: // PtgBool
|
|
260
|
+
stack.push(reader.u8() !== 0 ? "TRUE" : "FALSE");
|
|
261
|
+
continue;
|
|
262
|
+
case 0x1e: // PtgInt
|
|
263
|
+
stack.push(String(reader.u16()));
|
|
264
|
+
continue;
|
|
265
|
+
case 0x1f: // PtgNum
|
|
266
|
+
stack.push(formatNumber(reader.f64()));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
// Operand and function tokens carry a value class in bits 5-6.
|
|
270
|
+
switch (ptg) {
|
|
271
|
+
case 0x21:
|
|
272
|
+
case 0x41:
|
|
273
|
+
case 0x61: {
|
|
274
|
+
// PtgFunc: a fixed-arity function.
|
|
275
|
+
const iftab = reader.u16();
|
|
276
|
+
const argc = FTAB_FIXED_ARGC[iftab];
|
|
277
|
+
if (argc === undefined || argc < 0) {
|
|
278
|
+
throw new RgceUnsupportedError(`function ${iftab} is not fixed-arity`);
|
|
454
279
|
}
|
|
280
|
+
stack.push(callFunction(iftab, popArgs(argc)));
|
|
281
|
+
continue;
|
|
455
282
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
else if (basePtg === 0x02) {
|
|
466
|
-
// PtgFuncVar - function with variable args
|
|
467
|
-
if (!canRead(3))
|
|
468
|
-
break;
|
|
469
|
-
const cargs = reader.readUInt8();
|
|
470
|
-
pushVariableFunction(cargs, reader.readUInt16LE());
|
|
471
|
-
}
|
|
472
|
-
else if (basePtg === 0x04) {
|
|
473
|
-
// PtgRef - cell reference
|
|
474
|
-
if (!canRead(6))
|
|
475
|
-
break;
|
|
476
|
-
stack.push(readShortRef());
|
|
477
|
-
}
|
|
478
|
-
else if (basePtg === 0x05) {
|
|
479
|
-
// PtgArea - range reference
|
|
480
|
-
if (!canRead(12))
|
|
481
|
-
break;
|
|
482
|
-
stack.push(readShortArea());
|
|
283
|
+
case 0x22:
|
|
284
|
+
case 0x42:
|
|
285
|
+
case 0x62: {
|
|
286
|
+
// PtgFuncVar: the argument count (up to 255), then the function (bit 15: a command).
|
|
287
|
+
const argc = reader.u8();
|
|
288
|
+
const field = reader.u16();
|
|
289
|
+
if (field & 0x8000) {
|
|
290
|
+
throw new RgceUnsupportedError("command-equivalent function");
|
|
483
291
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
break;
|
|
494
|
-
stack.push(readRelShortArea());
|
|
292
|
+
const iftab = field & 0x7fff;
|
|
293
|
+
const args = popArgs(argc);
|
|
294
|
+
if (iftab === 0xff) {
|
|
295
|
+
// A user-defined or add-in function: its name is the first argument.
|
|
296
|
+
const [name, ...rest] = args;
|
|
297
|
+
if (!name) {
|
|
298
|
+
throw new RgceUnsupportedError("user-defined function without a name");
|
|
299
|
+
}
|
|
300
|
+
stack.push(`${name}(${rest.join(",")})`);
|
|
495
301
|
}
|
|
496
302
|
else {
|
|
497
|
-
|
|
498
|
-
break;
|
|
303
|
+
stack.push(callFunction(iftab, args));
|
|
499
304
|
}
|
|
305
|
+
continue;
|
|
500
306
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
307
|
+
case 0x23:
|
|
308
|
+
case 0x43:
|
|
309
|
+
case 0x63: {
|
|
310
|
+
// PtgName: a 1-based index into the workbook's names.
|
|
311
|
+
const index = reader.u32();
|
|
312
|
+
const name = context.definedName?.(index);
|
|
313
|
+
if (name === undefined) {
|
|
314
|
+
throw new RgceUnsupportedError(`unknown defined name ${index}`);
|
|
505
315
|
}
|
|
506
|
-
|
|
316
|
+
stack.push(name);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
case 0x24:
|
|
320
|
+
case 0x44:
|
|
321
|
+
case 0x64: {
|
|
322
|
+
// PtgRef
|
|
323
|
+
const row = reader.u32();
|
|
324
|
+
const c = corner(row, reader.u16());
|
|
325
|
+
stack.push(cellRef(c.row, c.col, c.rowRelative, c.colRelative));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
case 0x25:
|
|
329
|
+
case 0x45:
|
|
330
|
+
case 0x65: {
|
|
331
|
+
// PtgArea
|
|
332
|
+
const rowFirst = reader.u32();
|
|
333
|
+
const rowLast = reader.u32();
|
|
334
|
+
const colFirst = reader.u16();
|
|
335
|
+
const colLast = reader.u16();
|
|
336
|
+
stack.push(areaRef(corner(rowFirst, colFirst), corner(rowLast, colLast)));
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
case 0x26:
|
|
340
|
+
case 0x46:
|
|
341
|
+
case 0x66: // PtgMemArea
|
|
342
|
+
case 0x27:
|
|
343
|
+
case 0x47:
|
|
344
|
+
case 0x67: // PtgMemErr
|
|
345
|
+
case 0x28:
|
|
346
|
+
case 0x48:
|
|
347
|
+
case 0x68: // PtgMemNoMem
|
|
348
|
+
// A precomputed reference: the tokens that follow compute it.
|
|
349
|
+
reader.skip(4);
|
|
350
|
+
reader.u16(); // cce
|
|
351
|
+
continue;
|
|
352
|
+
case 0x29:
|
|
353
|
+
case 0x49:
|
|
354
|
+
case 0x69: // PtgMemFunc
|
|
355
|
+
reader.u16(); // cce
|
|
356
|
+
continue;
|
|
357
|
+
case 0x2a:
|
|
358
|
+
case 0x4a:
|
|
359
|
+
case 0x6a: // PtgRefErr
|
|
360
|
+
reader.skip(6);
|
|
361
|
+
stack.push("#REF!");
|
|
362
|
+
continue;
|
|
363
|
+
case 0x2b:
|
|
364
|
+
case 0x4b:
|
|
365
|
+
case 0x6b: // PtgAreaErr
|
|
366
|
+
reader.skip(12);
|
|
367
|
+
stack.push("#REF!");
|
|
368
|
+
continue;
|
|
369
|
+
case 0x2c:
|
|
370
|
+
case 0x4c:
|
|
371
|
+
case 0x6c: {
|
|
372
|
+
// PtgRefN: relative to the base cell.
|
|
373
|
+
const row = reader.i32();
|
|
374
|
+
const c = relativeCorner(row, reader.u16(), context.baseCell);
|
|
375
|
+
stack.push(cellRef(c.row, c.col, c.rowRelative, c.colRelative));
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
case 0x2d:
|
|
379
|
+
case 0x4d:
|
|
380
|
+
case 0x6d: {
|
|
381
|
+
// PtgAreaN
|
|
382
|
+
const rowFirst = reader.i32();
|
|
383
|
+
const rowLast = reader.i32();
|
|
384
|
+
const colFirst = reader.u16();
|
|
385
|
+
const colLast = reader.u16();
|
|
386
|
+
stack.push(areaRef(relativeCorner(rowFirst, colFirst, context.baseCell), relativeCorner(rowLast, colLast, context.baseCell)));
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
case 0x3a:
|
|
390
|
+
case 0x5a:
|
|
391
|
+
case 0x7a: {
|
|
392
|
+
// PtgRef3d
|
|
393
|
+
const prefix = sheet(reader.u16());
|
|
394
|
+
const row = reader.u32();
|
|
395
|
+
const c = corner(row, reader.u16());
|
|
396
|
+
stack.push(`${prefix}!${cellRef(c.row, c.col, c.rowRelative, c.colRelative)}`);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
case 0x3b:
|
|
400
|
+
case 0x5b:
|
|
401
|
+
case 0x7b: {
|
|
402
|
+
// PtgArea3d
|
|
403
|
+
const prefix = sheet(reader.u16());
|
|
404
|
+
const rowFirst = reader.u32();
|
|
405
|
+
const rowLast = reader.u32();
|
|
406
|
+
const colFirst = reader.u16();
|
|
407
|
+
const colLast = reader.u16();
|
|
408
|
+
stack.push(`${prefix}!${areaRef(corner(rowFirst, colFirst), corner(rowLast, colLast))}`);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
case 0x3c:
|
|
412
|
+
case 0x5c:
|
|
413
|
+
case 0x7c: // PtgRefErr3d
|
|
414
|
+
case 0x3d:
|
|
415
|
+
case 0x5d:
|
|
416
|
+
case 0x7d: {
|
|
417
|
+
// PtgAreaErr3d. Excel keeps the sheet a deleted reference was on
|
|
418
|
+
// (`Sheet1!#REF!`); on a sheet it cannot name, the error alone.
|
|
419
|
+
const ixti = reader.u16();
|
|
420
|
+
const name = context.sheetName?.(ixti);
|
|
421
|
+
reader.skip((ptg & 0x1f) === 0x1c ? 6 : 12);
|
|
422
|
+
stack.push(name === undefined ? "#REF!" : `${quoteSheetName(name)}!#REF!`);
|
|
423
|
+
continue;
|
|
507
424
|
}
|
|
508
425
|
}
|
|
426
|
+
// PtgExp / PtgTbl (shared, array or data-table formulas), PtgArray,
|
|
427
|
+
// PtgNameX, the extended tokens (structured references) and anything else.
|
|
428
|
+
throw new RgceUnsupportedError(`token 0x${ptg.toString(16)}`);
|
|
509
429
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
430
|
+
if (stack.length !== 1) {
|
|
431
|
+
throw new RgceUnsupportedError(`formula leaves ${stack.length} expressions`);
|
|
432
|
+
}
|
|
433
|
+
return stack[0];
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* A formula's text, or `""` when it cannot be decoded exactly — never a part of
|
|
437
|
+
* one. Prefer {@link decodeRgce}, which says why.
|
|
438
|
+
*
|
|
439
|
+
* @deprecated the lenient form of {@link decodeRgce}.
|
|
440
|
+
*/
|
|
441
|
+
export function parseRgce(rgceBytes, sheetRefResolver, baseCell) {
|
|
442
|
+
if (!rgceBytes || rgceBytes.length === 0)
|
|
443
|
+
return "";
|
|
444
|
+
const sheetName = typeof sheetRefResolver === "function"
|
|
445
|
+
? sheetRefResolver
|
|
446
|
+
: (ixti) => sheetRefResolver?.[ixti];
|
|
447
|
+
try {
|
|
448
|
+
return decodeRgce(rgceBytes, { sheetName, baseCell });
|
|
516
449
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (error instanceof RgceUnsupportedError)
|
|
452
|
+
return "";
|
|
453
|
+
throw error;
|
|
520
454
|
}
|
|
521
|
-
return result;
|
|
522
455
|
}
|