xls-codec 4.9.0 → 4.11.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/README.md +43 -19
- package/dist/biff/ptg-functions.cjs +3 -0
- package/dist/biff/ptg-functions.d.cts +3 -1
- package/dist/biff/ptg-functions.d.ts +3 -1
- package/dist/biff/ptg-functions.js +3 -1
- package/dist/biff/ptg-writer.cjs +546 -0
- package/dist/biff/ptg-writer.d.cts +7 -0
- package/dist/biff/ptg-writer.d.ts +7 -0
- package/dist/biff/ptg-writer.js +545 -0
- package/dist/biff/string-writer.cjs +6 -0
- package/dist/biff/string-writer.d.cts +3 -1
- package/dist/biff/string-writer.d.ts +3 -1
- package/dist/biff/string-writer.js +6 -1
- package/dist/content.cjs +1 -1
- package/dist/content.d.cts +1 -1
- package/dist/content.d.ts +1 -1
- package/dist/content.js +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/workbook/comment-writer.cjs +75 -0
- package/dist/workbook/comment-writer.d.cts +10 -0
- package/dist/workbook/comment-writer.d.ts +10 -0
- package/dist/workbook/comment-writer.js +74 -0
- package/dist/workbook/encryption.cjs +67 -20
- package/dist/workbook/encryption.d.cts +2 -2
- package/dist/workbook/encryption.d.ts +2 -2
- package/dist/workbook/encryption.js +68 -21
- package/dist/workbook/sheet-writer.cjs +50 -2
- package/dist/workbook/sheet-writer.d.cts +1 -1
- package/dist/workbook/sheet-writer.d.ts +1 -1
- package/dist/workbook/sheet-writer.js +50 -2
- package/dist/written-cells.cjs +3 -3
- package/dist/written-cells.d.cts +2 -2
- package/dist/written-cells.d.ts +2 -2
- package/dist/written-cells.js +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_biff_errors = require("./errors.cjs");
|
|
3
|
+
const require_biff_write_errors = require("./write-errors.cjs");
|
|
4
|
+
const require_biff_ptg_functions = require("./ptg-functions.cjs");
|
|
5
|
+
const require_biff_string_writer = require("./string-writer.cjs");
|
|
6
|
+
let document_schema_js = require("document-schema.js");
|
|
7
|
+
//#region src/biff/ptg-writer.ts
|
|
8
|
+
const PTG_ADD = 3;
|
|
9
|
+
const PTG_SUB = 4;
|
|
10
|
+
const PTG_MUL = 5;
|
|
11
|
+
const PTG_DIV = 6;
|
|
12
|
+
const PTG_POWER = 7;
|
|
13
|
+
const PTG_CONCAT = 8;
|
|
14
|
+
const PTG_LT = 9;
|
|
15
|
+
const PTG_LE = 10;
|
|
16
|
+
const PTG_EQ = 11;
|
|
17
|
+
const PTG_GE = 12;
|
|
18
|
+
const PTG_GT = 13;
|
|
19
|
+
const PTG_NE = 14;
|
|
20
|
+
const PTG_UPLUS = 18;
|
|
21
|
+
const PTG_UMINUS = 19;
|
|
22
|
+
const PTG_PERCENT = 20;
|
|
23
|
+
const PTG_PAREN = 21;
|
|
24
|
+
const PTG_MISSARG = 22;
|
|
25
|
+
const PTG_STR = 23;
|
|
26
|
+
const PTG_ERR = 28;
|
|
27
|
+
const PTG_BOOL = 29;
|
|
28
|
+
const PTG_INT = 30;
|
|
29
|
+
const PTG_NUM = 31;
|
|
30
|
+
/** The "value" class of the reference/function token family -- see biff/ptg.ts's own top comment for why REF/VALUE/ARRAY share one on-disk field layout and this writer, like every other real minimal BIFF8 writer, does not need to distinguish them for an ordinary (non-array) formula. */
|
|
31
|
+
const PTG_REF_VALUE = 68;
|
|
32
|
+
const PTG_AREA_VALUE = 69;
|
|
33
|
+
const PTG_FUNC_VALUE = 65;
|
|
34
|
+
const PTG_FUNCVAR_VALUE = 66;
|
|
35
|
+
const COLUMN_RELATIVE_BIT = 16384;
|
|
36
|
+
const ROW_RELATIVE_BIT = 32768;
|
|
37
|
+
/** BIFF8's own 16-bit row index and 8-bit column index ceilings ([MS-XLS] 2.4.221's Rw structure and 2.4.53's Col256U structure) -- the identical grid workbook/sheet-writer.ts's own checkedCellPosition enforces for a cell record's own row/column. */
|
|
38
|
+
const MAX_ROW_INDEX = 65535;
|
|
39
|
+
const MAX_COLUMN_INDEX = 255;
|
|
40
|
+
/** PtgInt's own ceiling ([MS-XLS] 2.5.198.28): an unsigned 16-bit integer. A plain non-negative integer literal above this, or one carrying a decimal point or exponent in its own source text, is written as PtgNum instead, so the reader's own String(cursor.u16())/String(cursor.f64()) reconstructs the identical text. */
|
|
41
|
+
const PTG_INT_MAX = 65535;
|
|
42
|
+
var RgceBuilder = class {
|
|
43
|
+
parts = [];
|
|
44
|
+
push(...bytes) {
|
|
45
|
+
this.parts.push(bytes);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
u16(value) {
|
|
49
|
+
const bits = value & 65535;
|
|
50
|
+
return this.push(bits & 255, bits >>> 8 & 255);
|
|
51
|
+
}
|
|
52
|
+
f64(value) {
|
|
53
|
+
const buffer = /* @__PURE__ */ new ArrayBuffer(8);
|
|
54
|
+
new DataView(buffer).setFloat64(0, value, true);
|
|
55
|
+
return this.push(...new Uint8Array(buffer));
|
|
56
|
+
}
|
|
57
|
+
concat(bytes) {
|
|
58
|
+
this.parts.push(Array.from(bytes));
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
build() {
|
|
62
|
+
const flat = this.parts.flat();
|
|
63
|
+
return new Uint8Array(flat);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const NUMBER_RE = /^[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?/;
|
|
67
|
+
const WORD_RE = /^[A-Za-z][A-Za-z0-9_.]*/;
|
|
68
|
+
const ERROR_RE = /^#[A-Za-z0-9/?!_]+/;
|
|
69
|
+
const OPERATORS = [
|
|
70
|
+
"<=",
|
|
71
|
+
">=",
|
|
72
|
+
"<>",
|
|
73
|
+
"+",
|
|
74
|
+
"-",
|
|
75
|
+
"*",
|
|
76
|
+
"/",
|
|
77
|
+
"^",
|
|
78
|
+
"&",
|
|
79
|
+
"<",
|
|
80
|
+
">",
|
|
81
|
+
"=",
|
|
82
|
+
"%"
|
|
83
|
+
];
|
|
84
|
+
function tokenize(text) {
|
|
85
|
+
const tokens = [];
|
|
86
|
+
let index = 0;
|
|
87
|
+
while (index < text.length) {
|
|
88
|
+
const char = text[index];
|
|
89
|
+
if (char === " " || char === " " || char === "\n" || char === "\r") {
|
|
90
|
+
index += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (char === "$") {
|
|
94
|
+
tokens.push({
|
|
95
|
+
type: "dollar",
|
|
96
|
+
text: "$"
|
|
97
|
+
});
|
|
98
|
+
index += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (char === ":") {
|
|
102
|
+
tokens.push({
|
|
103
|
+
type: "colon",
|
|
104
|
+
text: ":"
|
|
105
|
+
});
|
|
106
|
+
index += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (char === ",") {
|
|
110
|
+
tokens.push({
|
|
111
|
+
type: "comma",
|
|
112
|
+
text: ","
|
|
113
|
+
});
|
|
114
|
+
index += 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (char === "(") {
|
|
118
|
+
tokens.push({
|
|
119
|
+
type: "lparen",
|
|
120
|
+
text: "("
|
|
121
|
+
});
|
|
122
|
+
index += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (char === ")") {
|
|
126
|
+
tokens.push({
|
|
127
|
+
type: "rparen",
|
|
128
|
+
text: ")"
|
|
129
|
+
});
|
|
130
|
+
index += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (char === "\"") {
|
|
134
|
+
let value = "";
|
|
135
|
+
let cursor = index + 1;
|
|
136
|
+
for (;;) {
|
|
137
|
+
if (cursor >= text.length) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(text)} carries an unterminated string literal starting at offset ${index}`);
|
|
138
|
+
const current = text.charAt(cursor);
|
|
139
|
+
if (current === "\"") {
|
|
140
|
+
if (text.charAt(cursor + 1) === "\"") {
|
|
141
|
+
value += "\"";
|
|
142
|
+
cursor += 2;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
cursor += 1;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
value += current;
|
|
149
|
+
cursor += 1;
|
|
150
|
+
}
|
|
151
|
+
tokens.push({
|
|
152
|
+
type: "string",
|
|
153
|
+
text: value
|
|
154
|
+
});
|
|
155
|
+
index = cursor;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (char === "#") {
|
|
159
|
+
const match = ERROR_RE.exec(text.slice(index));
|
|
160
|
+
if (match?.[0] === void 0) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(text)} carries an unrecognised error literal starting at offset ${index}`);
|
|
161
|
+
tokens.push({
|
|
162
|
+
type: "error",
|
|
163
|
+
text: match[0]
|
|
164
|
+
});
|
|
165
|
+
index += match[0].length;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const numberMatch = NUMBER_RE.exec(text.slice(index));
|
|
169
|
+
if (numberMatch?.[0] !== void 0) {
|
|
170
|
+
tokens.push({
|
|
171
|
+
type: "number",
|
|
172
|
+
text: numberMatch[0]
|
|
173
|
+
});
|
|
174
|
+
index += numberMatch[0].length;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const wordMatch = WORD_RE.exec(text.slice(index));
|
|
178
|
+
if (wordMatch?.[0] !== void 0) {
|
|
179
|
+
tokens.push({
|
|
180
|
+
type: "word",
|
|
181
|
+
text: wordMatch[0]
|
|
182
|
+
});
|
|
183
|
+
index += wordMatch[0].length;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const op = OPERATORS.find((candidate) => text.startsWith(candidate, index));
|
|
187
|
+
if (op !== void 0) {
|
|
188
|
+
tokens.push({
|
|
189
|
+
type: "op",
|
|
190
|
+
text: op
|
|
191
|
+
});
|
|
192
|
+
index += op.length;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(text)} carries an unrecognised character ${JSON.stringify(char)} at offset ${index}`);
|
|
196
|
+
}
|
|
197
|
+
tokens.push({
|
|
198
|
+
type: "eof",
|
|
199
|
+
text: ""
|
|
200
|
+
});
|
|
201
|
+
return tokens;
|
|
202
|
+
}
|
|
203
|
+
var FormulaParser = class {
|
|
204
|
+
tokens;
|
|
205
|
+
position = 0;
|
|
206
|
+
sourceText;
|
|
207
|
+
constructor(tokens, sourceText) {
|
|
208
|
+
this.tokens = tokens;
|
|
209
|
+
this.sourceText = sourceText;
|
|
210
|
+
}
|
|
211
|
+
peek(offset = 0) {
|
|
212
|
+
const token = this.tokens[this.position + offset];
|
|
213
|
+
if (token === void 0) throw new require_biff_write_errors.BiffWriteError(`internal error: formula token stream for ${JSON.stringify(this.sourceText)} ran past its own end`);
|
|
214
|
+
return token;
|
|
215
|
+
}
|
|
216
|
+
advance() {
|
|
217
|
+
const token = this.peek();
|
|
218
|
+
this.position += 1;
|
|
219
|
+
return token;
|
|
220
|
+
}
|
|
221
|
+
expect(type) {
|
|
222
|
+
const token = this.peek();
|
|
223
|
+
if (token.type !== type) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} expected a ${type} but found ${JSON.stringify(token.text)}`);
|
|
224
|
+
return this.advance();
|
|
225
|
+
}
|
|
226
|
+
parseFormula() {
|
|
227
|
+
const node = this.parseComparison();
|
|
228
|
+
this.expect("eof");
|
|
229
|
+
return node;
|
|
230
|
+
}
|
|
231
|
+
parseComparison() {
|
|
232
|
+
let node = this.parseConcat();
|
|
233
|
+
for (;;) {
|
|
234
|
+
const token = this.peek();
|
|
235
|
+
const opcode = this.comparisonOpcode(token);
|
|
236
|
+
if (opcode === void 0) return node;
|
|
237
|
+
this.advance();
|
|
238
|
+
const right = this.parseConcat();
|
|
239
|
+
node = {
|
|
240
|
+
kind: "binary",
|
|
241
|
+
opcode,
|
|
242
|
+
left: node,
|
|
243
|
+
right
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
comparisonOpcode(token) {
|
|
248
|
+
if (token.type !== "op") return void 0;
|
|
249
|
+
switch (token.text) {
|
|
250
|
+
case "<": return PTG_LT;
|
|
251
|
+
case "<=": return PTG_LE;
|
|
252
|
+
case "=": return PTG_EQ;
|
|
253
|
+
case ">=": return PTG_GE;
|
|
254
|
+
case ">": return PTG_GT;
|
|
255
|
+
case "<>": return PTG_NE;
|
|
256
|
+
default: return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
parseConcat() {
|
|
260
|
+
let node = this.parseAdditive();
|
|
261
|
+
while (this.peek().type === "op" && this.peek().text === "&") {
|
|
262
|
+
this.advance();
|
|
263
|
+
const right = this.parseAdditive();
|
|
264
|
+
node = {
|
|
265
|
+
kind: "binary",
|
|
266
|
+
opcode: PTG_CONCAT,
|
|
267
|
+
left: node,
|
|
268
|
+
right
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return node;
|
|
272
|
+
}
|
|
273
|
+
parseAdditive() {
|
|
274
|
+
let node = this.parseMultiplicative();
|
|
275
|
+
for (;;) {
|
|
276
|
+
const token = this.peek();
|
|
277
|
+
if (token.type !== "op" || token.text !== "+" && token.text !== "-") return node;
|
|
278
|
+
this.advance();
|
|
279
|
+
const right = this.parseMultiplicative();
|
|
280
|
+
node = {
|
|
281
|
+
kind: "binary",
|
|
282
|
+
opcode: token.text === "+" ? PTG_ADD : PTG_SUB,
|
|
283
|
+
left: node,
|
|
284
|
+
right
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
parseMultiplicative() {
|
|
289
|
+
let node = this.parsePower();
|
|
290
|
+
for (;;) {
|
|
291
|
+
const token = this.peek();
|
|
292
|
+
if (token.type !== "op" || token.text !== "*" && token.text !== "/") return node;
|
|
293
|
+
this.advance();
|
|
294
|
+
const right = this.parsePower();
|
|
295
|
+
node = {
|
|
296
|
+
kind: "binary",
|
|
297
|
+
opcode: token.text === "*" ? PTG_MUL : PTG_DIV,
|
|
298
|
+
left: node,
|
|
299
|
+
right
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
parsePower() {
|
|
304
|
+
let node = this.parsePercent();
|
|
305
|
+
while (this.peek().type === "op" && this.peek().text === "^") {
|
|
306
|
+
this.advance();
|
|
307
|
+
const right = this.parsePercent();
|
|
308
|
+
node = {
|
|
309
|
+
kind: "binary",
|
|
310
|
+
opcode: PTG_POWER,
|
|
311
|
+
left: node,
|
|
312
|
+
right
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return node;
|
|
316
|
+
}
|
|
317
|
+
parsePercent() {
|
|
318
|
+
let node = this.parseUnary();
|
|
319
|
+
while (this.peek().type === "op" && this.peek().text === "%") {
|
|
320
|
+
this.advance();
|
|
321
|
+
node = {
|
|
322
|
+
kind: "percent",
|
|
323
|
+
operand: node
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return node;
|
|
327
|
+
}
|
|
328
|
+
parseUnary() {
|
|
329
|
+
const token = this.peek();
|
|
330
|
+
if (token.type === "op" && (token.text === "+" || token.text === "-")) {
|
|
331
|
+
this.advance();
|
|
332
|
+
const operand = this.parseUnary();
|
|
333
|
+
return {
|
|
334
|
+
kind: "unary",
|
|
335
|
+
opcode: token.text === "+" ? PTG_UPLUS : PTG_UMINUS,
|
|
336
|
+
operand
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return this.parsePrimary();
|
|
340
|
+
}
|
|
341
|
+
parsePrimary() {
|
|
342
|
+
const token = this.peek();
|
|
343
|
+
if (token.type === "number") {
|
|
344
|
+
this.advance();
|
|
345
|
+
return this.numberNode(token.text);
|
|
346
|
+
}
|
|
347
|
+
if (token.type === "string") {
|
|
348
|
+
this.advance();
|
|
349
|
+
return {
|
|
350
|
+
kind: "str",
|
|
351
|
+
value: token.text
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (token.type === "error") {
|
|
355
|
+
this.advance();
|
|
356
|
+
const code = require_biff_errors.errorCodeOf(token.text);
|
|
357
|
+
if (code === void 0) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} carries error literal ${token.text}, which is not one of the eight error values [MS-XLS] 2.5.10 defines`);
|
|
358
|
+
return {
|
|
359
|
+
kind: "err",
|
|
360
|
+
code
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (token.type === "lparen") {
|
|
364
|
+
this.advance();
|
|
365
|
+
const inner = this.parseComparison();
|
|
366
|
+
this.expect("rparen");
|
|
367
|
+
return {
|
|
368
|
+
kind: "paren",
|
|
369
|
+
inner
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (token.type === "dollar") return this.parseRefOrArea();
|
|
373
|
+
if (token.type === "word") {
|
|
374
|
+
if (this.peek(1).type === "lparen") return this.parseCall();
|
|
375
|
+
if (token.text === "TRUE" || token.text === "FALSE") {
|
|
376
|
+
this.advance();
|
|
377
|
+
return {
|
|
378
|
+
kind: "bool",
|
|
379
|
+
value: token.text === "TRUE"
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return this.parseRefOrArea();
|
|
383
|
+
}
|
|
384
|
+
throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} carries an unexpected token ${JSON.stringify(token.text)}`);
|
|
385
|
+
}
|
|
386
|
+
numberNode(text) {
|
|
387
|
+
const value = Number.parseFloat(text);
|
|
388
|
+
if (/^[0-9]+$/.test(text) && Number.isInteger(value) && value >= 0 && value <= PTG_INT_MAX) return {
|
|
389
|
+
kind: "int",
|
|
390
|
+
value
|
|
391
|
+
};
|
|
392
|
+
return {
|
|
393
|
+
kind: "num",
|
|
394
|
+
value
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
parseCall() {
|
|
398
|
+
const nameToken = this.expect("word");
|
|
399
|
+
this.expect("lparen");
|
|
400
|
+
const args = [];
|
|
401
|
+
if (this.peek().type !== "rparen") {
|
|
402
|
+
args.push(this.parseArgument());
|
|
403
|
+
while (this.peek().type === "comma") {
|
|
404
|
+
this.advance();
|
|
405
|
+
args.push(this.parseArgument());
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
this.expect("rparen");
|
|
409
|
+
const iftab = require_biff_ptg_functions.FTAB_IFTAB_BY_NAME.get(nameToken.text);
|
|
410
|
+
if (iftab === void 0) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} calls ${nameToken.text}(), which is not one of the built-in functions [MS-XLS] 2.5.198.17's own Ftab enumerates`);
|
|
411
|
+
const fixedArity = require_biff_ptg_functions.FTAB_FIXED_ARITY.get(iftab);
|
|
412
|
+
if (fixedArity !== void 0 && fixedArity !== args.length) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} calls ${nameToken.text}() with ${args.length} argument(s), but [MS-XLS]'s own Ftab grammar fixes its arity at ${fixedArity}`);
|
|
413
|
+
return {
|
|
414
|
+
kind: "call",
|
|
415
|
+
iftab,
|
|
416
|
+
variable: fixedArity === void 0,
|
|
417
|
+
args
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
parseArgument() {
|
|
421
|
+
const token = this.peek();
|
|
422
|
+
if (token.type === "comma" || token.type === "rparen") return { kind: "missarg" };
|
|
423
|
+
return this.parseComparison();
|
|
424
|
+
}
|
|
425
|
+
parseRefOrArea() {
|
|
426
|
+
const start = this.parseCellPoint();
|
|
427
|
+
if (this.peek().type === "colon") {
|
|
428
|
+
this.advance();
|
|
429
|
+
return {
|
|
430
|
+
kind: "area",
|
|
431
|
+
start,
|
|
432
|
+
end: this.parseCellPoint()
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
kind: "ref",
|
|
437
|
+
point: start
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/** One cell reference's own point, per its own leading `$` (column-absolute) and, for a column-only word, a trailing `$` (row-absolute) -- see this module's own top comment for why a plain (non-`$`-prefixed) reference always lexes as one combined letters-then-digits word token, while a `$`-separated one splits across a dollar/word/dollar/number sequence instead. */
|
|
441
|
+
parseCellPoint() {
|
|
442
|
+
let columnAbsolute = false;
|
|
443
|
+
if (this.peek().type === "dollar") {
|
|
444
|
+
this.advance();
|
|
445
|
+
columnAbsolute = true;
|
|
446
|
+
}
|
|
447
|
+
const word = this.expect("word");
|
|
448
|
+
const combined = /^([A-Za-z]{1,3})([0-9]+)$/.exec(word.text);
|
|
449
|
+
let columnLetters;
|
|
450
|
+
let rowDigits;
|
|
451
|
+
let rowAbsolute = false;
|
|
452
|
+
if (combined?.[1] !== void 0 && combined[2] !== void 0) {
|
|
453
|
+
columnLetters = combined[1];
|
|
454
|
+
rowDigits = combined[2];
|
|
455
|
+
} else {
|
|
456
|
+
if (!/^[A-Za-z]{1,3}$/.test(word.text)) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} carries ${JSON.stringify(word.text)}, which is not a valid cell reference`);
|
|
457
|
+
columnLetters = word.text;
|
|
458
|
+
if (this.peek().type === "dollar") {
|
|
459
|
+
this.advance();
|
|
460
|
+
rowAbsolute = true;
|
|
461
|
+
}
|
|
462
|
+
const rowToken = this.expect("number");
|
|
463
|
+
if (!/^[0-9]+$/.test(rowToken.text)) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} carries ${JSON.stringify(word.text + rowToken.text)}, which is not a valid cell reference`);
|
|
464
|
+
rowDigits = rowToken.text;
|
|
465
|
+
}
|
|
466
|
+
const column = (0, document_schema_js.columnLettersToIndex)(columnLetters);
|
|
467
|
+
const row = Number.parseInt(rowDigits, 10) - 1;
|
|
468
|
+
if (column === void 0 || column > MAX_COLUMN_INDEX || row < 0 || row > MAX_ROW_INDEX) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(this.sourceText)} references ${columnLetters}${rowDigits}, which is outside BIFF8's own grid (rows 1-65536, columns A-IV)`);
|
|
469
|
+
return {
|
|
470
|
+
row,
|
|
471
|
+
column,
|
|
472
|
+
columnAbsolute,
|
|
473
|
+
rowAbsolute
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
function columnField(point) {
|
|
478
|
+
return point.column | (point.columnAbsolute ? 0 : COLUMN_RELATIVE_BIT) | (point.rowAbsolute ? 0 : ROW_RELATIVE_BIT);
|
|
479
|
+
}
|
|
480
|
+
function compileNode(builder, node) {
|
|
481
|
+
switch (node.kind) {
|
|
482
|
+
case "int":
|
|
483
|
+
builder.push(PTG_INT).u16(node.value);
|
|
484
|
+
return;
|
|
485
|
+
case "num":
|
|
486
|
+
builder.push(PTG_NUM).f64(node.value);
|
|
487
|
+
return;
|
|
488
|
+
case "str":
|
|
489
|
+
builder.push(PTG_STR).concat(require_biff_string_writer.writeShortXLUnicodeString(node.value));
|
|
490
|
+
return;
|
|
491
|
+
case "bool":
|
|
492
|
+
builder.push(PTG_BOOL, node.value ? 1 : 0);
|
|
493
|
+
return;
|
|
494
|
+
case "err":
|
|
495
|
+
builder.push(PTG_ERR, node.code);
|
|
496
|
+
return;
|
|
497
|
+
case "missarg":
|
|
498
|
+
builder.push(PTG_MISSARG);
|
|
499
|
+
return;
|
|
500
|
+
case "ref":
|
|
501
|
+
builder.push(PTG_REF_VALUE).u16(node.point.row).u16(columnField(node.point));
|
|
502
|
+
return;
|
|
503
|
+
case "area":
|
|
504
|
+
builder.push(PTG_AREA_VALUE).u16(node.start.row).u16(node.end.row).u16(columnField(node.start)).u16(columnField(node.end));
|
|
505
|
+
return;
|
|
506
|
+
case "binary":
|
|
507
|
+
compileNode(builder, node.left);
|
|
508
|
+
compileNode(builder, node.right);
|
|
509
|
+
builder.push(node.opcode);
|
|
510
|
+
return;
|
|
511
|
+
case "unary":
|
|
512
|
+
compileNode(builder, node.operand);
|
|
513
|
+
builder.push(node.opcode);
|
|
514
|
+
return;
|
|
515
|
+
case "percent":
|
|
516
|
+
compileNode(builder, node.operand);
|
|
517
|
+
builder.push(PTG_PERCENT);
|
|
518
|
+
return;
|
|
519
|
+
case "paren":
|
|
520
|
+
compileNode(builder, node.inner);
|
|
521
|
+
builder.push(PTG_PAREN);
|
|
522
|
+
return;
|
|
523
|
+
case "call":
|
|
524
|
+
for (const arg of node.args) compileNode(builder, arg);
|
|
525
|
+
if (node.variable) {
|
|
526
|
+
if (node.args.length > 255) throw new require_biff_write_errors.BiffWriteError(`a function call with ${node.args.length} arguments cannot be written: PtgFuncVar's own cparams field ([MS-XLS] 2.5.198.25) is a single byte, so it cannot exceed 255`);
|
|
527
|
+
builder.push(PTG_FUNCVAR_VALUE, node.args.length).u16(node.iftab);
|
|
528
|
+
} else builder.push(PTG_FUNC_VALUE).u16(node.iftab);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/** [MS-XLS] 2.5.198.3's own cce ceiling: a two-byte length field, so rgce itself can never exceed this regardless of the 8224-byte whole-record limit biff/record-writer.ts already enforces. */
|
|
533
|
+
const MAX_RGCE_LENGTH = 65535;
|
|
534
|
+
/**
|
|
535
|
+
* Compiles same-sheet formula text into a Formula record's own rgce token stream -- the write-side counterpart of biff/ptg.ts's parseFormulaText, scoped to the subset this module's own top comment describes. Throws BiffWriteError, naming the construct, for anything outside that scope rather than emitting a token stream this package's own reader could not read back.
|
|
536
|
+
*/
|
|
537
|
+
function compileFormulaText(text) {
|
|
538
|
+
const node = new FormulaParser(tokenize(text), text).parseFormula();
|
|
539
|
+
const builder = new RgceBuilder();
|
|
540
|
+
compileNode(builder, node);
|
|
541
|
+
const rgce = builder.build();
|
|
542
|
+
if (rgce.length > MAX_RGCE_LENGTH) throw new require_biff_write_errors.BiffWriteError(`formula text ${JSON.stringify(text)} compiles to ${rgce.length} bytes of rgce, above the ${MAX_RGCE_LENGTH}-byte ceiling its own cce field can hold`);
|
|
543
|
+
return rgce;
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
546
|
+
exports.compileFormulaText = compileFormulaText;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/biff/ptg-writer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Compiles same-sheet formula text into a Formula record's own rgce token stream -- the write-side counterpart of biff/ptg.ts's parseFormulaText, scoped to the subset this module's own top comment describes. Throws BiffWriteError, naming the construct, for anything outside that scope rather than emitting a token stream this package's own reader could not read back.
|
|
4
|
+
*/
|
|
5
|
+
declare function compileFormulaText(text: string): Uint8Array<ArrayBuffer>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { compileFormulaText };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/biff/ptg-writer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Compiles same-sheet formula text into a Formula record's own rgce token stream -- the write-side counterpart of biff/ptg.ts's parseFormulaText, scoped to the subset this module's own top comment describes. Throws BiffWriteError, naming the construct, for anything outside that scope rather than emitting a token stream this package's own reader could not read back.
|
|
4
|
+
*/
|
|
5
|
+
declare function compileFormulaText(text: string): Uint8Array<ArrayBuffer>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { compileFormulaText };
|