reamkit 1.14.0 → 1.15.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 +34 -8
- package/dist/esm/core/converter/facade.d.ts +8 -1
- package/dist/esm/core/converter/facade.js +34 -8
- package/dist/esm/core/converter/ream.d.ts +1 -0
- package/dist/esm/core/converter/ream.js +13 -8
- package/dist/esm/core/ir/features.d.ts +1 -0
- package/dist/esm/core/ir/features.js +1 -0
- package/dist/esm/core/ir/sheet.d.ts +7 -0
- package/dist/esm/core/ole/cfb.d.ts +19 -0
- package/dist/esm/core/ole/cfb.js +158 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +20 -1
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +188 -0
- package/dist/esm/excel/conditional-format.d.ts +4 -2
- package/dist/esm/excel/conditional-format.js +138 -4
- package/dist/esm/excel/formula/context.d.ts +11 -0
- package/dist/esm/excel/formula/dates.d.ts +11 -0
- package/dist/esm/excel/formula/dates.js +46 -0
- package/dist/esm/excel/formula/eval.d.ts +11 -0
- package/dist/esm/excel/formula/eval.js +196 -0
- package/dist/esm/excel/formula/functions.d.ts +7 -0
- package/dist/esm/excel/formula/functions.js +1359 -0
- package/dist/esm/excel/formula/index.d.ts +14 -0
- package/dist/esm/excel/formula/index.js +17 -0
- package/dist/esm/excel/formula/lexer.d.ts +8 -0
- package/dist/esm/excel/formula/lexer.js +163 -0
- package/dist/esm/excel/formula/parser.d.ts +57 -0
- package/dist/esm/excel/formula/parser.js +274 -0
- package/dist/esm/excel/formula/value.d.ts +52 -0
- package/dist/esm/excel/formula/value.js +102 -0
- package/dist/esm/excel/number-format.d.ts +2 -0
- package/dist/esm/excel/number-format.js +5 -1
- package/dist/esm/excel/print-model.d.ts +3 -0
- package/dist/esm/excel/print-model.js +1 -1
- package/dist/esm/excel/sheet-to-flow.d.ts +4 -1
- package/dist/esm/excel/sheet-to-flow.js +46 -2
- package/dist/esm/excel/worksheet-parser.js +66 -0
- package/dist/esm/excel/xls/biff-chart.d.ts +8 -0
- package/dist/esm/excel/xls/biff-chart.js +121 -0
- package/dist/esm/excel/xls/biff-reader.d.ts +4 -0
- package/dist/esm/excel/xls/biff-reader.js +1418 -0
- package/dist/esm/excel/xls/biff-styles.d.ts +8 -0
- package/dist/esm/excel/xls/biff-styles.js +299 -0
- package/dist/esm/excel/xls/escher.d.ts +24 -0
- package/dist/esm/excel/xls/escher.js +171 -0
- package/dist/esm/excel/xls/xls-reader.d.ts +3 -0
- package/dist/esm/excel/xls/xls-reader.js +30 -0
- package/dist/esm/excel/xlsx-reader.d.ts +2 -1
- package/dist/esm/excel/xlsx-reader.js +28 -3
- package/dist/esm/excel/xlsx-to-pdf.d.ts +1 -0
- package/dist/esm/excel/xlsx-to-pdf.js +2 -2
- package/dist/esm/excel/xlsx-writer.js +2 -0
- package/dist/esm/pptx/ppt/ppt-reader.d.ts +4 -0
- package/dist/esm/pptx/ppt/ppt-reader.js +337 -0
- package/dist/esm/pptx/ppt/ppt-text.d.ts +69 -0
- package/dist/esm/pptx/ppt/ppt-text.js +756 -0
- package/dist/esm/word/doc/doc-reader.d.ts +4 -0
- package/dist/esm/word/doc/doc-reader.js +421 -0
- package/dist/esm/word/doc/doc-text.d.ts +79 -0
- package/dist/esm/word/doc/doc-text.js +706 -0
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Ast } from './parser.js';
|
|
2
|
+
import { EvalContext } from './context.js';
|
|
3
|
+
import { Shift } from './eval.js';
|
|
4
|
+
export type { EvalContext } from './context.js';
|
|
5
|
+
export type { Shift } from './eval.js';
|
|
6
|
+
export type { Scalar, FValue, Rect, FErr } from './value.js';
|
|
7
|
+
export { num, str, bool, err, BLANK } from './value.js';
|
|
8
|
+
export { serialFromDate, serialToParts, timePeriodMatches } from './dates.js';
|
|
9
|
+
export { NO_SHIFT, evaluate } from './eval.js';
|
|
10
|
+
export interface CompiledFormula {
|
|
11
|
+
readonly ast: Ast;
|
|
12
|
+
}
|
|
13
|
+
export declare function compileFormula(src: string): CompiledFormula | undefined;
|
|
14
|
+
export declare function evaluateToBool(compiled: CompiledFormula, ctx: EvalContext, shift?: Shift): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parse } from "./parser.js";
|
|
2
|
+
import "./dates.js";
|
|
3
|
+
import { toBool } from "./value.js";
|
|
4
|
+
import { NO_SHIFT, evaluate } from "./eval.js";
|
|
5
|
+
//#region src/excel/formula/index.ts
|
|
6
|
+
function compileFormula(src) {
|
|
7
|
+
try {
|
|
8
|
+
return { ast: parse(src) };
|
|
9
|
+
} catch {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function evaluateToBool(compiled, ctx, shift = NO_SHIFT) {
|
|
14
|
+
return toBool(evaluate(compiled.ast, ctx, shift), ctx) === true;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { compileFormula, evaluateToBool };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type TokenKind = 'num' | 'str' | 'err' | 'word' | 'sheetq' | 'op' | 'eof';
|
|
2
|
+
export interface Token {
|
|
3
|
+
readonly kind: TokenKind;
|
|
4
|
+
readonly text: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class LexError extends Error {
|
|
7
|
+
}
|
|
8
|
+
export declare function tokenize(src: string): Array<Token>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
//#region src/excel/formula/lexer.ts
|
|
2
|
+
var MAX_SOURCE = 8192;
|
|
3
|
+
var KNOWN_ERRORS = new Set([
|
|
4
|
+
"#NULL!",
|
|
5
|
+
"#DIV/0!",
|
|
6
|
+
"#VALUE!",
|
|
7
|
+
"#REF!",
|
|
8
|
+
"#NAME?",
|
|
9
|
+
"#NUM!",
|
|
10
|
+
"#N/A"
|
|
11
|
+
]);
|
|
12
|
+
var LexError = class extends Error {};
|
|
13
|
+
function isDigit(ch) {
|
|
14
|
+
return ch >= "0" && ch <= "9";
|
|
15
|
+
}
|
|
16
|
+
function isWordStart(ch) {
|
|
17
|
+
return ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_" || ch === "$";
|
|
18
|
+
}
|
|
19
|
+
function isWordPart(ch) {
|
|
20
|
+
return isWordStart(ch) || isDigit(ch) || ch === ".";
|
|
21
|
+
}
|
|
22
|
+
function tokenize(src) {
|
|
23
|
+
if (src.length > MAX_SOURCE) throw new LexError("formula too long");
|
|
24
|
+
const out = [];
|
|
25
|
+
let i = 0;
|
|
26
|
+
const n = src.length;
|
|
27
|
+
while (i < n) {
|
|
28
|
+
const ch = src[i];
|
|
29
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
30
|
+
i++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (ch === "'") {
|
|
34
|
+
let s = "";
|
|
35
|
+
i++;
|
|
36
|
+
while (i < n) {
|
|
37
|
+
if (src[i] === "'") {
|
|
38
|
+
if (src[i + 1] === "'") {
|
|
39
|
+
s += "'";
|
|
40
|
+
i += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
i++;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
s += src[i];
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
out.push({
|
|
50
|
+
kind: "sheetq",
|
|
51
|
+
text: s
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (ch === "\"") {
|
|
56
|
+
let s = "";
|
|
57
|
+
i++;
|
|
58
|
+
while (i < n) {
|
|
59
|
+
if (src[i] === "\"") {
|
|
60
|
+
if (src[i + 1] === "\"") {
|
|
61
|
+
s += "\"";
|
|
62
|
+
i += 2;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
i++;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
s += src[i];
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
out.push({
|
|
72
|
+
kind: "str",
|
|
73
|
+
text: s
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (ch === "#") {
|
|
78
|
+
let j = i + 1;
|
|
79
|
+
while (j < n && /[A-Za-z0-9/!?]/.test(src[j])) j++;
|
|
80
|
+
const lit = src.slice(i, j).toUpperCase();
|
|
81
|
+
if (!KNOWN_ERRORS.has(lit)) throw new LexError(`bad error literal ${lit}`);
|
|
82
|
+
out.push({
|
|
83
|
+
kind: "err",
|
|
84
|
+
text: lit
|
|
85
|
+
});
|
|
86
|
+
i = j;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (isDigit(ch) || ch === "." && i + 1 < n && isDigit(src[i + 1])) {
|
|
90
|
+
let j = i;
|
|
91
|
+
while (j < n && isDigit(src[j])) j++;
|
|
92
|
+
if (src[j] === ".") {
|
|
93
|
+
j++;
|
|
94
|
+
while (j < n && isDigit(src[j])) j++;
|
|
95
|
+
}
|
|
96
|
+
if (src[j] === "e" || src[j] === "E") {
|
|
97
|
+
let k = j + 1;
|
|
98
|
+
if (src[k] === "+" || src[k] === "-") k++;
|
|
99
|
+
if (k < n && isDigit(src[k])) {
|
|
100
|
+
k++;
|
|
101
|
+
while (k < n && isDigit(src[k])) k++;
|
|
102
|
+
j = k;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
out.push({
|
|
106
|
+
kind: "num",
|
|
107
|
+
text: src.slice(i, j)
|
|
108
|
+
});
|
|
109
|
+
i = j;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (isWordStart(ch)) {
|
|
113
|
+
let j = i + 1;
|
|
114
|
+
while (j < n && isWordPart(src[j])) j++;
|
|
115
|
+
out.push({
|
|
116
|
+
kind: "word",
|
|
117
|
+
text: src.slice(i, j)
|
|
118
|
+
});
|
|
119
|
+
i = j;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === "<" && src[i + 1] === "=") {
|
|
123
|
+
out.push({
|
|
124
|
+
kind: "op",
|
|
125
|
+
text: "<="
|
|
126
|
+
});
|
|
127
|
+
i += 2;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (ch === ">" && src[i + 1] === "=") {
|
|
131
|
+
out.push({
|
|
132
|
+
kind: "op",
|
|
133
|
+
text: ">="
|
|
134
|
+
});
|
|
135
|
+
i += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === "<" && src[i + 1] === ">") {
|
|
139
|
+
out.push({
|
|
140
|
+
kind: "op",
|
|
141
|
+
text: "<>"
|
|
142
|
+
});
|
|
143
|
+
i += 2;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if ("+-*/^&=<>%:(),!{};".includes(ch)) {
|
|
147
|
+
out.push({
|
|
148
|
+
kind: "op",
|
|
149
|
+
text: ch
|
|
150
|
+
});
|
|
151
|
+
i++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
throw new LexError(`unexpected character ${JSON.stringify(ch)}`);
|
|
155
|
+
}
|
|
156
|
+
out.push({
|
|
157
|
+
kind: "eof",
|
|
158
|
+
text: ""
|
|
159
|
+
});
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { tokenize };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { FErr } from './value.js';
|
|
2
|
+
export interface Axis {
|
|
3
|
+
readonly index: number;
|
|
4
|
+
readonly abs: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface CellRef {
|
|
7
|
+
readonly col: Axis;
|
|
8
|
+
readonly row: Axis;
|
|
9
|
+
}
|
|
10
|
+
export type Ast = {
|
|
11
|
+
readonly k: 'num';
|
|
12
|
+
readonly v: number;
|
|
13
|
+
} | {
|
|
14
|
+
readonly k: 'str';
|
|
15
|
+
readonly v: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly k: 'bool';
|
|
18
|
+
readonly v: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
readonly k: 'err';
|
|
21
|
+
readonly v: FErr;
|
|
22
|
+
} | {
|
|
23
|
+
readonly k: 'cell';
|
|
24
|
+
readonly ref: CellRef;
|
|
25
|
+
readonly sheet?: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly k: 'range';
|
|
28
|
+
readonly a: CellRef;
|
|
29
|
+
readonly b: CellRef;
|
|
30
|
+
readonly sheet?: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly k: 'name';
|
|
33
|
+
readonly name: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly k: 'array';
|
|
36
|
+
readonly rows: ReadonlyArray<ReadonlyArray<Ast>>;
|
|
37
|
+
} | {
|
|
38
|
+
readonly k: 'unary';
|
|
39
|
+
readonly op: '-' | '+';
|
|
40
|
+
readonly x: Ast;
|
|
41
|
+
} | {
|
|
42
|
+
readonly k: 'pct';
|
|
43
|
+
readonly x: Ast;
|
|
44
|
+
} | {
|
|
45
|
+
readonly k: 'bin';
|
|
46
|
+
readonly op: BinOp;
|
|
47
|
+
readonly a: Ast;
|
|
48
|
+
readonly b: Ast;
|
|
49
|
+
} | {
|
|
50
|
+
readonly k: 'call';
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly args: ReadonlyArray<Ast>;
|
|
53
|
+
};
|
|
54
|
+
export type BinOp = '+' | '-' | '*' | '/' | '^' | '&' | '=' | '<>' | '<' | '>' | '<=' | '>=';
|
|
55
|
+
export declare class ParseError extends Error {
|
|
56
|
+
}
|
|
57
|
+
export declare function parse(src: string): Ast;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { tokenize } from "./lexer.js";
|
|
2
|
+
//#region src/excel/formula/parser.ts
|
|
3
|
+
var ParseError = class extends Error {};
|
|
4
|
+
var PREC = {
|
|
5
|
+
"=": 1,
|
|
6
|
+
"<>": 1,
|
|
7
|
+
"<": 1,
|
|
8
|
+
">": 1,
|
|
9
|
+
"<=": 1,
|
|
10
|
+
">=": 1,
|
|
11
|
+
"&": 2,
|
|
12
|
+
"+": 3,
|
|
13
|
+
"-": 3,
|
|
14
|
+
"*": 4,
|
|
15
|
+
"/": 4,
|
|
16
|
+
"^": 5
|
|
17
|
+
};
|
|
18
|
+
var RIGHT_ASSOC = new Set(["^"]);
|
|
19
|
+
var MAX_DEPTH = 64;
|
|
20
|
+
var MAX_ARGS = 255;
|
|
21
|
+
var CELL_RE = /^(\$?)([A-Za-z]{1,3})(\$?)([0-9]{1,7})$/;
|
|
22
|
+
var MAX_COL = 16383;
|
|
23
|
+
var MAX_ROW = 1048575;
|
|
24
|
+
function parse(src) {
|
|
25
|
+
const parser = new Parser(tokenize(src));
|
|
26
|
+
const ast = parser.parseExpr(0);
|
|
27
|
+
parser.expectEof();
|
|
28
|
+
return ast;
|
|
29
|
+
}
|
|
30
|
+
var Parser = class {
|
|
31
|
+
pos = 0;
|
|
32
|
+
depth = 0;
|
|
33
|
+
constructor(toks) {
|
|
34
|
+
this.toks = toks;
|
|
35
|
+
}
|
|
36
|
+
peek() {
|
|
37
|
+
return this.toks[this.pos];
|
|
38
|
+
}
|
|
39
|
+
next() {
|
|
40
|
+
return this.toks[this.pos++];
|
|
41
|
+
}
|
|
42
|
+
expectEof() {
|
|
43
|
+
if (this.peek().kind !== "eof") throw new ParseError(`trailing tokens at ${this.pos}`);
|
|
44
|
+
}
|
|
45
|
+
parseExpr(minBp) {
|
|
46
|
+
if (++this.depth > MAX_DEPTH) throw new ParseError("expression too deep");
|
|
47
|
+
let left = this.parseUnary();
|
|
48
|
+
for (;;) {
|
|
49
|
+
const t = this.peek();
|
|
50
|
+
if (t.kind !== "op" || !(t.text in PREC)) break;
|
|
51
|
+
const op = t.text;
|
|
52
|
+
const p = PREC[op];
|
|
53
|
+
if (p < minBp) break;
|
|
54
|
+
this.next();
|
|
55
|
+
const right = this.parseExpr(RIGHT_ASSOC.has(op) ? p : p + 1);
|
|
56
|
+
left = {
|
|
57
|
+
k: "bin",
|
|
58
|
+
op,
|
|
59
|
+
a: left,
|
|
60
|
+
b: right
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
this.depth--;
|
|
64
|
+
return left;
|
|
65
|
+
}
|
|
66
|
+
parseUnary() {
|
|
67
|
+
const t = this.peek();
|
|
68
|
+
if (t.kind === "op" && (t.text === "-" || t.text === "+")) {
|
|
69
|
+
this.next();
|
|
70
|
+
return {
|
|
71
|
+
k: "unary",
|
|
72
|
+
op: t.text,
|
|
73
|
+
x: this.parseUnary()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return this.parsePostfix();
|
|
77
|
+
}
|
|
78
|
+
parsePostfix() {
|
|
79
|
+
let x = this.parsePrimary();
|
|
80
|
+
while (this.peek().kind === "op" && this.peek().text === "%") {
|
|
81
|
+
this.next();
|
|
82
|
+
x = {
|
|
83
|
+
k: "pct",
|
|
84
|
+
x
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return x;
|
|
88
|
+
}
|
|
89
|
+
parsePrimary() {
|
|
90
|
+
const t = this.next();
|
|
91
|
+
switch (t.kind) {
|
|
92
|
+
case "num": {
|
|
93
|
+
const v = Number(t.text);
|
|
94
|
+
if (!Number.isFinite(v)) throw new ParseError(`bad number ${t.text}`);
|
|
95
|
+
return {
|
|
96
|
+
k: "num",
|
|
97
|
+
v
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
case "str": return {
|
|
101
|
+
k: "str",
|
|
102
|
+
v: t.text
|
|
103
|
+
};
|
|
104
|
+
case "err": return {
|
|
105
|
+
k: "err",
|
|
106
|
+
v: t.text
|
|
107
|
+
};
|
|
108
|
+
case "word":
|
|
109
|
+
if (this.peek().kind === "op" && this.peek().text === "!") {
|
|
110
|
+
this.next();
|
|
111
|
+
return this.parseSheetCell(t.text);
|
|
112
|
+
}
|
|
113
|
+
return this.parseWord(t.text);
|
|
114
|
+
case "sheetq":
|
|
115
|
+
this.expect("!");
|
|
116
|
+
return this.parseSheetCell(t.text);
|
|
117
|
+
case "op":
|
|
118
|
+
if (t.text === "(") {
|
|
119
|
+
const e = this.parseExpr(0);
|
|
120
|
+
this.expect(")");
|
|
121
|
+
return e;
|
|
122
|
+
}
|
|
123
|
+
if (t.text === "{") return this.parseArray();
|
|
124
|
+
throw new ParseError(`unexpected operator ${t.text}`);
|
|
125
|
+
case "eof": throw new ParseError("unexpected end of formula");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
parseWord(word) {
|
|
129
|
+
if (this.peek().kind === "op" && this.peek().text === "(") {
|
|
130
|
+
this.next();
|
|
131
|
+
const args = this.parseArgs();
|
|
132
|
+
this.expect(")");
|
|
133
|
+
return {
|
|
134
|
+
k: "call",
|
|
135
|
+
name: word.toUpperCase(),
|
|
136
|
+
args
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const upper = word.toUpperCase();
|
|
140
|
+
if (upper === "TRUE") return {
|
|
141
|
+
k: "bool",
|
|
142
|
+
v: true
|
|
143
|
+
};
|
|
144
|
+
if (upper === "FALSE") return {
|
|
145
|
+
k: "bool",
|
|
146
|
+
v: false
|
|
147
|
+
};
|
|
148
|
+
const ref = parseCellRef(word);
|
|
149
|
+
if (ref) {
|
|
150
|
+
if (this.peek().kind === "op" && this.peek().text === ":") {
|
|
151
|
+
const save = this.pos;
|
|
152
|
+
this.next();
|
|
153
|
+
const rhs = this.peek();
|
|
154
|
+
if (rhs.kind === "word") {
|
|
155
|
+
const b = parseCellRef(rhs.text);
|
|
156
|
+
if (b) {
|
|
157
|
+
this.next();
|
|
158
|
+
return {
|
|
159
|
+
k: "range",
|
|
160
|
+
a: ref,
|
|
161
|
+
b
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.pos = save;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
k: "cell",
|
|
169
|
+
ref
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
k: "name",
|
|
174
|
+
name: upper
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
parseSheetCell(sheet) {
|
|
178
|
+
const t = this.next();
|
|
179
|
+
if (t.kind !== "word") throw new ParseError("expected a cell after !");
|
|
180
|
+
const a = parseCellRef(t.text);
|
|
181
|
+
if (!a) throw new ParseError("expected a cell reference after !");
|
|
182
|
+
if (this.peek().kind === "op" && this.peek().text === ":") {
|
|
183
|
+
this.next();
|
|
184
|
+
const rhs = this.next();
|
|
185
|
+
if (rhs.kind !== "word") throw new ParseError("expected a cell after :");
|
|
186
|
+
const b = parseCellRef(rhs.text);
|
|
187
|
+
if (!b) throw new ParseError("bad range end after !");
|
|
188
|
+
return {
|
|
189
|
+
k: "range",
|
|
190
|
+
a,
|
|
191
|
+
b,
|
|
192
|
+
sheet
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
k: "cell",
|
|
197
|
+
ref: a,
|
|
198
|
+
sheet
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
parseArray() {
|
|
202
|
+
const rows = [];
|
|
203
|
+
let row = [];
|
|
204
|
+
let count = 0;
|
|
205
|
+
for (;;) {
|
|
206
|
+
row.push(this.parseExpr(0));
|
|
207
|
+
if (++count > MAX_ARGS) throw new ParseError("array constant too large");
|
|
208
|
+
const t = this.peek();
|
|
209
|
+
if (t.kind === "op" && t.text === ",") {
|
|
210
|
+
this.next();
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (t.kind === "op" && t.text === ";") {
|
|
214
|
+
this.next();
|
|
215
|
+
rows.push(row);
|
|
216
|
+
row = [];
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
rows.push(row);
|
|
222
|
+
this.expect("}");
|
|
223
|
+
return {
|
|
224
|
+
k: "array",
|
|
225
|
+
rows
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
parseArgs() {
|
|
229
|
+
const args = [];
|
|
230
|
+
if (this.peek().kind === "op" && this.peek().text === ")") return args;
|
|
231
|
+
for (;;) {
|
|
232
|
+
args.push(this.parseExpr(0));
|
|
233
|
+
if (args.length > MAX_ARGS) throw new ParseError("too many arguments");
|
|
234
|
+
const t = this.peek();
|
|
235
|
+
if (t.kind === "op" && t.text === ",") {
|
|
236
|
+
this.next();
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
return args;
|
|
242
|
+
}
|
|
243
|
+
expect(op) {
|
|
244
|
+
const t = this.next();
|
|
245
|
+
if (t.kind !== "op" || t.text !== op) throw new ParseError(`expected ${op}`);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
function parseCellRef(word) {
|
|
249
|
+
const m = CELL_RE.exec(word);
|
|
250
|
+
if (!m) return void 0;
|
|
251
|
+
const colAbs = m[1] === "$";
|
|
252
|
+
const col = columnToIndex(m[2]);
|
|
253
|
+
const rowAbs = m[3] === "$";
|
|
254
|
+
const row = Number(m[4]) - 1;
|
|
255
|
+
if (col > MAX_COL || row < 0 || row > MAX_ROW) return void 0;
|
|
256
|
+
return {
|
|
257
|
+
col: {
|
|
258
|
+
index: col,
|
|
259
|
+
abs: colAbs
|
|
260
|
+
},
|
|
261
|
+
row: {
|
|
262
|
+
index: row,
|
|
263
|
+
abs: rowAbs
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function columnToIndex(letters) {
|
|
268
|
+
let idx = 0;
|
|
269
|
+
const up = letters.toUpperCase();
|
|
270
|
+
for (let i = 0; i < up.length; i++) idx = idx * 26 + (up.charCodeAt(i) - 64);
|
|
271
|
+
return idx - 1;
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
export { parse };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EvalContext } from './context.js';
|
|
2
|
+
export type FErr = '#NULL!' | '#DIV/0!' | '#VALUE!' | '#REF!' | '#NAME?' | '#NUM!' | '#N/A';
|
|
3
|
+
export interface Rect {
|
|
4
|
+
readonly r0: number;
|
|
5
|
+
readonly c0: number;
|
|
6
|
+
readonly r1: number;
|
|
7
|
+
readonly c1: number;
|
|
8
|
+
}
|
|
9
|
+
export type Scalar = {
|
|
10
|
+
readonly t: 'num';
|
|
11
|
+
readonly v: number;
|
|
12
|
+
} | {
|
|
13
|
+
readonly t: 'str';
|
|
14
|
+
readonly v: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly t: 'bool';
|
|
17
|
+
readonly v: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
readonly t: 'err';
|
|
20
|
+
readonly v: FErr;
|
|
21
|
+
} | {
|
|
22
|
+
readonly t: 'blank';
|
|
23
|
+
};
|
|
24
|
+
export type FValue = Scalar | {
|
|
25
|
+
readonly t: 'ref';
|
|
26
|
+
readonly rect: Rect;
|
|
27
|
+
readonly sheet?: number;
|
|
28
|
+
} | {
|
|
29
|
+
readonly t: 'arr';
|
|
30
|
+
readonly rows: ReadonlyArray<ReadonlyArray<Scalar>>;
|
|
31
|
+
};
|
|
32
|
+
export declare const BLANK: Scalar;
|
|
33
|
+
export declare const TRUE: Scalar;
|
|
34
|
+
export declare const FALSE: Scalar;
|
|
35
|
+
export declare function num(v: number): Scalar;
|
|
36
|
+
export declare function str(v: string): Scalar;
|
|
37
|
+
export declare function bool(v: boolean): Scalar;
|
|
38
|
+
export declare function err(v: FErr): Scalar;
|
|
39
|
+
export declare function isErr(v: FValue): v is {
|
|
40
|
+
t: 'err';
|
|
41
|
+
v: FErr;
|
|
42
|
+
};
|
|
43
|
+
export declare function deref(v: FValue, ctx: EvalContext): Scalar;
|
|
44
|
+
export declare function arrEach(rows: ReadonlyArray<ReadonlyArray<Scalar>>, visit: (value: Scalar) => void): void;
|
|
45
|
+
export declare function refEach(v: {
|
|
46
|
+
readonly rect: Rect;
|
|
47
|
+
readonly sheet?: number;
|
|
48
|
+
}, ctx: EvalContext, visit: (row: number, col: number, value: Scalar) => void): void;
|
|
49
|
+
export declare function refGet(sheet: number | undefined, ctx: EvalContext, row: number, col: number): Scalar;
|
|
50
|
+
export declare function toNumber(v: FValue, ctx: EvalContext): number | FErr;
|
|
51
|
+
export declare function toText(v: FValue, ctx: EvalContext): string | FErr;
|
|
52
|
+
export declare function toBool(v: FValue, ctx: EvalContext): boolean | FErr;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//#region src/excel/formula/value.ts
|
|
2
|
+
var BLANK = { t: "blank" };
|
|
3
|
+
var TRUE = {
|
|
4
|
+
t: "bool",
|
|
5
|
+
v: true
|
|
6
|
+
};
|
|
7
|
+
var FALSE = {
|
|
8
|
+
t: "bool",
|
|
9
|
+
v: false
|
|
10
|
+
};
|
|
11
|
+
function num(v) {
|
|
12
|
+
return Number.isFinite(v) ? {
|
|
13
|
+
t: "num",
|
|
14
|
+
v
|
|
15
|
+
} : {
|
|
16
|
+
t: "err",
|
|
17
|
+
v: "#NUM!"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function str(v) {
|
|
21
|
+
return {
|
|
22
|
+
t: "str",
|
|
23
|
+
v
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function bool(v) {
|
|
27
|
+
return v ? TRUE : FALSE;
|
|
28
|
+
}
|
|
29
|
+
function err(v) {
|
|
30
|
+
return {
|
|
31
|
+
t: "err",
|
|
32
|
+
v
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function isErr(v) {
|
|
36
|
+
return v.t === "err";
|
|
37
|
+
}
|
|
38
|
+
function deref(v, ctx) {
|
|
39
|
+
if (v.t === "arr") return v.rows[0]?.[0] ?? BLANK;
|
|
40
|
+
if (v.t !== "ref") return v;
|
|
41
|
+
const { r0, c0, r1, c1 } = v.rect;
|
|
42
|
+
if (r0 === r1 && c0 === c1) return refGet(v.sheet, ctx, r0, c0);
|
|
43
|
+
return err("#VALUE!");
|
|
44
|
+
}
|
|
45
|
+
function arrEach(rows, visit) {
|
|
46
|
+
for (const row of rows) for (const s of row) visit(s);
|
|
47
|
+
}
|
|
48
|
+
function refEach(v, ctx, visit) {
|
|
49
|
+
if (v.sheet !== void 0) ctx.eachCellOn?.(v.sheet, v.rect, visit);
|
|
50
|
+
else ctx.eachCell(v.rect, visit);
|
|
51
|
+
}
|
|
52
|
+
function refGet(sheet, ctx, row, col) {
|
|
53
|
+
return sheet !== void 0 ? ctx.getCellOn?.(sheet, row, col) ?? BLANK : ctx.getCell(row, col);
|
|
54
|
+
}
|
|
55
|
+
function toNumber(v, ctx) {
|
|
56
|
+
const s = deref(v, ctx);
|
|
57
|
+
switch (s.t) {
|
|
58
|
+
case "num": return s.v;
|
|
59
|
+
case "bool": return s.v ? 1 : 0;
|
|
60
|
+
case "blank": return 0;
|
|
61
|
+
case "err": return s.v;
|
|
62
|
+
case "str": {
|
|
63
|
+
const trimmed = s.v.trim();
|
|
64
|
+
if (trimmed === "") return "#VALUE!";
|
|
65
|
+
const n = Number(trimmed);
|
|
66
|
+
return Number.isFinite(n) ? n : "#VALUE!";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function toText(v, ctx) {
|
|
71
|
+
const s = deref(v, ctx);
|
|
72
|
+
switch (s.t) {
|
|
73
|
+
case "str": return s.v;
|
|
74
|
+
case "num": return numToText(s.v);
|
|
75
|
+
case "bool": return s.v ? "TRUE" : "FALSE";
|
|
76
|
+
case "blank": return "";
|
|
77
|
+
case "err": return s.v;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function toBool(v, ctx) {
|
|
81
|
+
const s = deref(v, ctx);
|
|
82
|
+
switch (s.t) {
|
|
83
|
+
case "bool": return s.v;
|
|
84
|
+
case "num": return s.v !== 0;
|
|
85
|
+
case "blank": return false;
|
|
86
|
+
case "err": return s.v;
|
|
87
|
+
case "str": {
|
|
88
|
+
const u = s.v.trim().toUpperCase();
|
|
89
|
+
if (u === "TRUE") return true;
|
|
90
|
+
if (u === "FALSE") return false;
|
|
91
|
+
const n = Number(s.v.trim());
|
|
92
|
+
if (s.v.trim() !== "" && Number.isFinite(n)) return n !== 0;
|
|
93
|
+
return "#VALUE!";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function numToText(n) {
|
|
98
|
+
if (Number.isInteger(n)) return String(n);
|
|
99
|
+
return String(n);
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { BLANK, arrEach, bool, deref, err, isErr, num, refEach, refGet, str, toBool, toNumber, toText };
|
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export declare function applyNumberFormat(rawValue: string, numFmtId: number, customFormats: ReadonlyMap<number, string>, date1904?: boolean): string;
|
|
2
|
+
export declare function excelSerialToDate(serial: number, date1904: boolean): Date;
|
|
3
|
+
export declare function excelSerialFromUtcParts(year: number, month0: number, day: number, date1904: boolean): number;
|
|
@@ -46,6 +46,10 @@ function excelSerialToDate(serial, date1904) {
|
|
|
46
46
|
if (date1904) return new Date(Date.UTC(1904, 0, 1) + ms);
|
|
47
47
|
return new Date(Date.UTC(1899, 11, 30) + ms);
|
|
48
48
|
}
|
|
49
|
+
function excelSerialFromUtcParts(year, month0, day, date1904) {
|
|
50
|
+
const epoch = date1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
51
|
+
return Math.round((Date.UTC(year, month0, day) - epoch) / 864e5);
|
|
52
|
+
}
|
|
49
53
|
var MONTH_ABBR = [
|
|
50
54
|
"Jan",
|
|
51
55
|
"Feb",
|
|
@@ -435,4 +439,4 @@ function unquoteLiteral(s) {
|
|
|
435
439
|
return out;
|
|
436
440
|
}
|
|
437
441
|
//#endregion
|
|
438
|
-
export { applyNumberFormat };
|
|
442
|
+
export { applyNumberFormat, excelSerialFromUtcParts, excelSerialToDate };
|