@borgar/fx 4.12.0 → 5.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/dist/index-BMr6cTgc.d.cts +1444 -0
- package/dist/index-BMr6cTgc.d.ts +1444 -0
- package/dist/index.cjs +3054 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2984 -0
- package/dist/index.js.map +1 -0
- package/dist/xlsx/index.cjs +3120 -0
- package/dist/xlsx/index.cjs.map +1 -0
- package/dist/xlsx/index.d.cts +55 -0
- package/dist/xlsx/index.d.ts +55 -0
- package/dist/xlsx/index.js +3049 -0
- package/dist/xlsx/index.js.map +1 -0
- package/docs/API.md +2959 -718
- package/docs/AST_format.md +2 -2
- package/eslint.config.mjs +40 -0
- package/lib/a1.spec.ts +32 -0
- package/lib/a1.ts +26 -0
- package/lib/addA1RangeBounds.ts +50 -0
- package/lib/addTokenMeta.spec.ts +166 -0
- package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
- package/lib/astTypes.ts +211 -0
- package/lib/cloneToken.ts +29 -0
- package/lib/{constants.js → constants.ts} +6 -3
- package/lib/fixRanges.spec.ts +220 -0
- package/lib/fixRanges.ts +260 -0
- package/lib/fromCol.spec.ts +15 -0
- package/lib/{fromCol.js → fromCol.ts} +1 -1
- package/lib/index.spec.ts +119 -0
- package/lib/index.ts +76 -0
- package/lib/isNodeType.ts +151 -0
- package/lib/isType.spec.ts +208 -0
- package/lib/{isType.js → isType.ts} +26 -25
- package/lib/lexers/advRangeOp.ts +18 -0
- package/lib/lexers/canEndRange.ts +25 -0
- package/lib/lexers/lexBoolean.ts +55 -0
- package/lib/lexers/lexContext.ts +104 -0
- package/lib/lexers/lexError.ts +15 -0
- package/lib/lexers/lexFunction.ts +37 -0
- package/lib/lexers/lexNameFuncCntx.ts +112 -0
- package/lib/lexers/lexNamed.ts +60 -0
- package/lib/lexers/lexNewLine.ts +12 -0
- package/lib/lexers/lexNumber.ts +48 -0
- package/lib/lexers/lexOperator.ts +26 -0
- package/lib/lexers/lexRange.ts +15 -0
- package/lib/lexers/lexRangeA1.ts +134 -0
- package/lib/lexers/lexRangeR1C1.ts +146 -0
- package/lib/lexers/lexRangeTrim.ts +26 -0
- package/lib/lexers/lexRefOp.ts +19 -0
- package/lib/lexers/lexString.ts +22 -0
- package/lib/lexers/lexStructured.ts +25 -0
- package/lib/lexers/lexWhitespace.ts +31 -0
- package/lib/lexers/sets.ts +51 -0
- package/lib/mergeRefTokens.spec.ts +141 -0
- package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +47 -32
- package/lib/nodeTypes.ts +54 -0
- package/lib/parse.spec.ts +1410 -0
- package/lib/{parser.js → parse.ts} +81 -63
- package/lib/parseA1Range.spec.ts +233 -0
- package/lib/parseA1Range.ts +206 -0
- package/lib/parseA1Ref.spec.ts +337 -0
- package/lib/parseA1Ref.ts +115 -0
- package/lib/parseR1C1Range.ts +191 -0
- package/lib/parseR1C1Ref.spec.ts +323 -0
- package/lib/parseR1C1Ref.ts +127 -0
- package/lib/parseRef.spec.ts +90 -0
- package/lib/parseRef.ts +240 -0
- package/lib/parseSRange.ts +240 -0
- package/lib/parseStructRef.spec.ts +168 -0
- package/lib/parseStructRef.ts +76 -0
- package/lib/stringifyA1Range.spec.ts +72 -0
- package/lib/stringifyA1Range.ts +72 -0
- package/lib/stringifyA1Ref.spec.ts +64 -0
- package/lib/stringifyA1Ref.ts +59 -0
- package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
- package/lib/stringifyR1C1Range.spec.ts +92 -0
- package/lib/stringifyR1C1Range.ts +73 -0
- package/lib/stringifyR1C1Ref.spec.ts +63 -0
- package/lib/stringifyR1C1Ref.ts +67 -0
- package/lib/stringifyStructRef.spec.ts +124 -0
- package/lib/stringifyStructRef.ts +113 -0
- package/lib/stringifyTokens.ts +15 -0
- package/lib/toCol.spec.ts +11 -0
- package/lib/{toCol.js → toCol.ts} +4 -4
- package/lib/tokenTypes.ts +76 -0
- package/lib/tokenize-srefs.spec.ts +429 -0
- package/lib/tokenize.spec.ts +2103 -0
- package/lib/tokenize.ts +346 -0
- package/lib/translate.spec.ts +35 -0
- package/lib/translateToA1.spec.ts +247 -0
- package/lib/translateToA1.ts +231 -0
- package/lib/translateToR1C1.spec.ts +227 -0
- package/lib/translateToR1C1.ts +145 -0
- package/lib/types.ts +179 -0
- package/lib/xlsx/index.spec.ts +27 -0
- package/lib/xlsx/index.ts +32 -0
- package/package.json +46 -30
- package/tsconfig.json +28 -0
- package/typedoc-ignore-links.ts +17 -0
- package/typedoc.json +41 -0
- package/.eslintrc +0 -22
- package/dist/fx.d.ts +0 -823
- package/dist/fx.js +0 -2
- package/dist/package.json +0 -1
- package/lib/a1.js +0 -348
- package/lib/a1.spec.js +0 -458
- package/lib/addTokenMeta.spec.js +0 -153
- package/lib/astTypes.js +0 -96
- package/lib/extraTypes.js +0 -74
- package/lib/fixRanges.js +0 -104
- package/lib/fixRanges.spec.js +0 -170
- package/lib/fromCol.spec.js +0 -11
- package/lib/index.js +0 -134
- package/lib/index.spec.js +0 -67
- package/lib/isType.spec.js +0 -168
- package/lib/lexer-srefs.spec.js +0 -324
- package/lib/lexer.js +0 -283
- package/lib/lexer.spec.js +0 -1953
- package/lib/lexerParts.js +0 -228
- package/lib/mergeRefTokens.spec.js +0 -121
- package/lib/package.json +0 -1
- package/lib/parseRef.js +0 -157
- package/lib/parseRef.spec.js +0 -71
- package/lib/parseSRange.js +0 -167
- package/lib/parseStructRef.js +0 -48
- package/lib/parseStructRef.spec.js +0 -164
- package/lib/parser.spec.js +0 -1208
- package/lib/rc.js +0 -341
- package/lib/rc.spec.js +0 -403
- package/lib/stringifyStructRef.js +0 -80
- package/lib/stringifyStructRef.spec.js +0 -182
- package/lib/toCol.spec.js +0 -11
- package/lib/translate-toA1.spec.js +0 -214
- package/lib/translate-toRC.spec.js +0 -197
- package/lib/translate.js +0 -239
- package/lib/translate.spec.js +0 -21
- package/rollup.config.mjs +0 -22
- package/tsd.json +0 -12
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import { test, Test } from 'tape';
|
|
2
|
-
import { translateToA1 } from './translate.js';
|
|
3
|
-
import { tokenize } from './lexer.js';
|
|
4
|
-
import { addTokenMeta } from './addTokenMeta.js';
|
|
5
|
-
import { ERROR, FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_BEAM, REF_STRUCT } from './constants.js';
|
|
6
|
-
|
|
7
|
-
Test.prototype.isR2A = function isTokens (expr, anchor, result, opts) {
|
|
8
|
-
this.is(translateToA1(expr, anchor, opts), result, expr);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
test('translate absolute cells from RC to A1', t => {
|
|
12
|
-
t.isR2A('=R1C1', 'B2', '=$A$1');
|
|
13
|
-
t.isR2A('=R2C1', 'B2', '=$A$2');
|
|
14
|
-
t.isR2A('=R3C1', 'B2', '=$A$3');
|
|
15
|
-
t.isR2A('=R1C2', 'B2', '=$B$1');
|
|
16
|
-
t.isR2A('=R2C2', 'B2', '=$B$2');
|
|
17
|
-
t.isR2A('=R3C2', 'B2', '=$B$3');
|
|
18
|
-
t.isR2A('=R1C3', 'B2', '=$C$1');
|
|
19
|
-
t.isR2A('=R2C3', 'B2', '=$C$2');
|
|
20
|
-
t.isR2A('=R3C3', 'B2', '=$C$3');
|
|
21
|
-
// absolute cells, anchor has no real effect
|
|
22
|
-
t.isR2A('=R1C1', 'Z19', '=$A$1');
|
|
23
|
-
t.isR2A('=R2C1', 'Z19', '=$A$2');
|
|
24
|
-
t.isR2A('=R3C1', 'Z19', '=$A$3');
|
|
25
|
-
t.isR2A('=R1C2', 'Z19', '=$B$1');
|
|
26
|
-
t.isR2A('=R2C2', 'Z19', '=$B$2');
|
|
27
|
-
t.isR2A('=R3C2', 'Z19', '=$B$3');
|
|
28
|
-
t.isR2A('=R1C3', 'Z19', '=$C$1');
|
|
29
|
-
t.isR2A('=R2C3', 'Z19', '=$C$2');
|
|
30
|
-
t.isR2A('=R3C3', 'Z19', '=$C$3');
|
|
31
|
-
t.end();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('translate relative cells from RC to A1', t => {
|
|
35
|
-
t.isR2A('=R[-1]C[-1]', 'B2', '=A1');
|
|
36
|
-
t.isR2A('=RC[-1]', 'B2', '=A2');
|
|
37
|
-
t.isR2A('=R[1]C[-1]', 'B2', '=A3');
|
|
38
|
-
t.isR2A('=R[-1]C', 'B2', '=B1');
|
|
39
|
-
t.isR2A('=RC', 'B2', '=B2');
|
|
40
|
-
t.isR2A('=R[1]C', 'B2', '=B3');
|
|
41
|
-
t.isR2A('=R[-1]C[1]', 'B2', '=C1');
|
|
42
|
-
t.isR2A('=RC[1]', 'B2', '=C2');
|
|
43
|
-
t.isR2A('=R[1]C[1]', 'B2', '=C3');
|
|
44
|
-
// relative cells move with anchor
|
|
45
|
-
t.isR2A('=R[-1]C[-1]', 'I12', '=H11');
|
|
46
|
-
t.isR2A('=RC[-1]', 'I12', '=H12');
|
|
47
|
-
t.isR2A('=R[1]C[-1]', 'I12', '=H13');
|
|
48
|
-
t.isR2A('=R[-1]C', 'I12', '=I11');
|
|
49
|
-
t.isR2A('=RC', 'I12', '=I12');
|
|
50
|
-
t.isR2A('=R[1]C', 'I12', '=I13');
|
|
51
|
-
t.isR2A('=R[-1]C[1]', 'I12', '=J11');
|
|
52
|
-
t.isR2A('=RC[1]', 'I12', '=J12');
|
|
53
|
-
t.isR2A('=R[1]C[1]', 'I12', '=J13');
|
|
54
|
-
// relative cells, but with [0] notation
|
|
55
|
-
t.isR2A('=R[0]C[-1]', 'B2', '=A2');
|
|
56
|
-
t.isR2A('=R[-1]C[0]', 'B2', '=B1');
|
|
57
|
-
t.isR2A('=R[0]C[0]', 'B2', '=B2');
|
|
58
|
-
t.isR2A('=R[1]C[0]', 'B2', '=B3');
|
|
59
|
-
t.isR2A('=R[0]C[1]', 'B2', '=C2');
|
|
60
|
-
t.end();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('translate rows from RC to A1', t => {
|
|
64
|
-
t.isR2A('=R', 'B2', '=2:2');
|
|
65
|
-
t.isR2A('=R[0]', 'B2', '=2:2');
|
|
66
|
-
t.isR2A('=R', 'B13', '=13:13');
|
|
67
|
-
t.isR2A('=R:R', 'B2', '=2:2');
|
|
68
|
-
t.isR2A('=R2:R2', 'B2', '=$2:$2');
|
|
69
|
-
t.isR2A('=R:R2', 'B2', '=2:$2');
|
|
70
|
-
t.isR2A('=R[1]:R[-1]', 'Z10', '=9:11');
|
|
71
|
-
t.end();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('translate cols from RC to A1', t => {
|
|
75
|
-
t.isR2A('=C', 'B2', '=B:B');
|
|
76
|
-
t.isR2A('=C[0]', 'B2', '=B:B');
|
|
77
|
-
t.isR2A('=C', 'Z2', '=Z:Z');
|
|
78
|
-
t.isR2A('=C:C', 'B2', '=B:B');
|
|
79
|
-
t.isR2A('=C2:C2', 'B2', '=$B:$B');
|
|
80
|
-
t.isR2A('=C:C2', 'B2', '=B:$B');
|
|
81
|
-
t.isR2A('=C[1]:C[-1]', 'M10', '=L:N');
|
|
82
|
-
t.end();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('translate partials from RC to A1', t => {
|
|
86
|
-
t.isR2A('=R[-5]C[-2]:C[-2]', 'C6', '=A1:A');
|
|
87
|
-
t.isR2A('=R[-5]C[-3]:R[-5]', 'D6', '=A1:1');
|
|
88
|
-
t.isR2A('=R[-6]C1:C1', 'C7', '=$A1:$A');
|
|
89
|
-
t.isR2A('=C1:R[-6]C1', 'D7', '=$A1:$A');
|
|
90
|
-
t.isR2A('=R[-6]C1:R[-6]', 'C7', '=$A1:1');
|
|
91
|
-
t.isR2A('=R[-6]:R[-6]C1', 'C7', '=$A1:1');
|
|
92
|
-
t.isR2A('=R1C[-2]:C[-2]', 'C6', '=A$1:A');
|
|
93
|
-
t.isR2A('=C[-2]:R1C[-2]', 'C6', '=A$1:A');
|
|
94
|
-
t.isR2A('=R1C[-3]:R1', 'D6', '=A$1:$1');
|
|
95
|
-
t.isR2A('=R1C[-3]:R1', 'D6', '=A$1:$1');
|
|
96
|
-
t.isR2A('=R1C1:C1', 'D6', '=$A$1:$A');
|
|
97
|
-
t.isR2A('=C1:R1C1', 'D6', '=$A$1:$A');
|
|
98
|
-
t.isR2A('=R1C1:R1', 'D6', '=$A$1:$1');
|
|
99
|
-
t.isR2A('=R1:R1C1', 'D6', '=$A$1:$1');
|
|
100
|
-
t.end();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('translate bounds coords from RC to A1', t => {
|
|
104
|
-
t.isR2A('=C[-1]', 'A1', '=XFD:XFD');
|
|
105
|
-
t.isR2A('=C[-2]', 'A1', '=XFC:XFC');
|
|
106
|
-
t.isR2A('=RC[16383]', 'B1', '=A1');
|
|
107
|
-
t.isR2A('=RC[16383]', 'C1', '=B1');
|
|
108
|
-
t.isR2A('=R[-1]', 'A1', '=1048576:1048576');
|
|
109
|
-
t.isR2A('=R[-2]', 'A1', '=1048575:1048575');
|
|
110
|
-
t.isR2A('=R[1048575]C', 'A2', '=A1');
|
|
111
|
-
t.isR2A('=R[1048575]C', 'A3', '=A2');
|
|
112
|
-
|
|
113
|
-
t.isR2A('=R1:R1048576', 'A1', '=$1:$1048576');
|
|
114
|
-
t.isR2A('=C1:C16384', 'A1', '=$A:$XFD');
|
|
115
|
-
t.isR2A('=R1C1:R1048576C16384', 'A1', '=$A$1:$XFD$1048576');
|
|
116
|
-
|
|
117
|
-
const f1 = '=R[-1]C[-1]';
|
|
118
|
-
t.is(translateToA1(f1, 'A1', { wrapEdges: false }), '=#REF!', f1);
|
|
119
|
-
|
|
120
|
-
const tokens = addTokenMeta(tokenize('SUM(Sheet1!R[-1]C[-1])', { r1c1: true, withLocation: true }));
|
|
121
|
-
t.deepEqual(translateToA1(tokens, 'A1', { wrapEdges: false }), [
|
|
122
|
-
{ type: FUNCTION, value: 'SUM', loc: [ 0, 3 ], index: 0, depth: 0 },
|
|
123
|
-
{ type: OPERATOR, value: '(', loc: [ 3, 4 ], index: 1, depth: 1, groupId: 'fxg1' },
|
|
124
|
-
{ type: ERROR, value: '#REF!', loc: [ 4, 9 ], index: 2, depth: 1 },
|
|
125
|
-
{ type: OPERATOR, value: ')', loc: [ 9, 10 ], index: 3, depth: 1, groupId: 'fxg1' }
|
|
126
|
-
], 'tokens with meta');
|
|
127
|
-
|
|
128
|
-
const f2 = '=Sheet4!R[-2]C[-2]:R[-1]C[-1]';
|
|
129
|
-
t.is(translateToA1(f2, 'B2', { wrapEdges: false }), '=#REF!', f2);
|
|
130
|
-
|
|
131
|
-
const f3 = '=Sheet4!R[-2]C[-2]:R[-1]C[-1]';
|
|
132
|
-
t.is(translateToA1(f3, 'B2', { wrapEdges: false, mergeRefs: false }), '=Sheet4!#REF!:A1', f3);
|
|
133
|
-
|
|
134
|
-
t.end();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('translate mixed rel/abs coords from RC to A1', t => {
|
|
138
|
-
t.isR2A('=R1C[0]', 'B2', '=B$1');
|
|
139
|
-
t.isR2A('=R[4]C4', 'B4', '=$D8');
|
|
140
|
-
t.isR2A('=R[4]:R10', 'B4', '=8:$10');
|
|
141
|
-
t.isR2A('=C10:C[10]', 'B4', '=$J:L');
|
|
142
|
-
t.isR2A('=R1C1:R2C2', 'D4', '=$A$1:$B$2');
|
|
143
|
-
t.isR2A('=R[-1]C[-1]:R[2]C[2]', 'D4', '=C3:F6');
|
|
144
|
-
t.end();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('translate involved formula from RC to A1', t => {
|
|
148
|
-
t.isR2A('=SUM(IF(RC[1],R2C5,R3C5),Sheet1!R2*Sheet2!C[-2])', 'D10',
|
|
149
|
-
'=SUM(IF(E10,$E$2,$E$3),Sheet1!$2:$2*Sheet2!B:B)');
|
|
150
|
-
t.end();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('translate works with merged ranges', t => {
|
|
154
|
-
// This tests that:
|
|
155
|
-
// - Translate works with ranges that have context attached
|
|
156
|
-
// - If input is a tokenlist, output is also a tokenlist
|
|
157
|
-
// - If tokens have ranges, those ranges are adjusted to new token lengths
|
|
158
|
-
// - Properties added by addTokenMeta are preserved
|
|
159
|
-
const expr = '=SUM(IF(RC[1],R2C5,R3C5),Sheet1!R2*Sheet2!C[-2])';
|
|
160
|
-
const tokens = addTokenMeta(tokenize(expr, { withLocation: true, r1c1: true }));
|
|
161
|
-
const expected = [
|
|
162
|
-
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ], index: 0, depth: 0 },
|
|
163
|
-
{ type: FUNCTION, value: 'SUM', loc: [ 1, 4 ], index: 1, depth: 0 },
|
|
164
|
-
{ type: OPERATOR, value: '(', loc: [ 4, 5 ], index: 2, depth: 1, groupId: 'fxg3' },
|
|
165
|
-
{ type: FUNCTION, value: 'IF', loc: [ 5, 7 ], index: 3, depth: 1 },
|
|
166
|
-
{ type: OPERATOR, value: '(', loc: [ 7, 8 ], index: 4, depth: 2, groupId: 'fxg1' },
|
|
167
|
-
{ type: REF_RANGE, value: 'E10', loc: [ 8, 11 ], index: 5, depth: 2 },
|
|
168
|
-
{ type: OPERATOR, value: ',', loc: [ 11, 12 ], index: 6, depth: 2 },
|
|
169
|
-
{ type: REF_RANGE, value: '$E$2', loc: [ 12, 16 ], index: 7, depth: 2 },
|
|
170
|
-
{ type: OPERATOR, value: ',', loc: [ 16, 17 ], index: 8, depth: 2 },
|
|
171
|
-
{ type: REF_RANGE, value: '$E$3', loc: [ 17, 21 ], index: 9, depth: 2 },
|
|
172
|
-
{ type: OPERATOR, value: ')', loc: [ 21, 22 ], index: 10, depth: 2, groupId: 'fxg1' },
|
|
173
|
-
{ type: OPERATOR, value: ',', loc: [ 22, 23 ], index: 11, depth: 1 },
|
|
174
|
-
{ type: REF_BEAM, value: 'Sheet1!$2:$2', loc: [ 23, 35 ], index: 12, depth: 1, groupId: 'fxg2' },
|
|
175
|
-
{ type: OPERATOR, value: '*', loc: [ 35, 36 ], index: 13, depth: 1 },
|
|
176
|
-
{ type: REF_BEAM, value: 'Sheet2!B:B', loc: [ 36, 46 ], index: 14, depth: 1 },
|
|
177
|
-
{ type: OPERATOR, value: ')', loc: [ 46, 47 ], index: 15, depth: 1, groupId: 'fxg3' }
|
|
178
|
-
];
|
|
179
|
-
t.deepEqual(translateToA1(tokens, 'D10'), expected, expr);
|
|
180
|
-
t.end();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('translate works with xlsx mode references', t => {
|
|
184
|
-
const testExpr = (expr, anchor, expected) => {
|
|
185
|
-
const opts = { mergeRefs: true, xlsx: true, r1c1: true };
|
|
186
|
-
t.deepEqual(translateToA1(tokenize(expr, opts), anchor, opts), expected, expr);
|
|
187
|
-
};
|
|
188
|
-
testExpr("'[My Fancy Workbook.xlsx]'!R1C", 'B2', [
|
|
189
|
-
{ type: REF_RANGE, value: "'[My Fancy Workbook.xlsx]'!B$1" }
|
|
190
|
-
]);
|
|
191
|
-
testExpr('[Workbook.xlsx]!R1C', 'B2', [
|
|
192
|
-
{ type: REF_RANGE, value: '[Workbook.xlsx]!B$1' }
|
|
193
|
-
]);
|
|
194
|
-
testExpr('[Workbook.xlsx]Sheet1!R1C', 'B2', [
|
|
195
|
-
{ type: REF_RANGE, value: '[Workbook.xlsx]Sheet1!B$1' }
|
|
196
|
-
]);
|
|
197
|
-
testExpr('[Workbook.xlsx]!table[#data]', 'B2', [
|
|
198
|
-
{ type: REF_STRUCT, value: '[Workbook.xlsx]!table[#data]' }
|
|
199
|
-
]);
|
|
200
|
-
t.end();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('translate works with trimmed ranges', t => {
|
|
204
|
-
const testExpr = (expr, anchor, expected) => {
|
|
205
|
-
const opts = { mergeRefs: true, xlsx: true, r1c1: true };
|
|
206
|
-
t.deepEqual(translateToA1(tokenize(expr, opts), anchor, opts), expected, expr);
|
|
207
|
-
};
|
|
208
|
-
testExpr('Sheet!R[-1]C[-1].:.RC*Sheet2!C[50].:.C[700]', 'B2', [
|
|
209
|
-
{ type: 'range', value: 'Sheet!A1.:.B2' },
|
|
210
|
-
{ type: 'operator', value: '*' },
|
|
211
|
-
{ type: 'range_beam', value: 'Sheet2!AZ.:.ZZ' }
|
|
212
|
-
]);
|
|
213
|
-
t.end();
|
|
214
|
-
});
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { test, Test } from 'tape';
|
|
2
|
-
import { translateToR1C1 } from './translate.js';
|
|
3
|
-
import { tokenize } from './lexer.js';
|
|
4
|
-
import { addTokenMeta } from './addTokenMeta.js';
|
|
5
|
-
import { FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_BEAM, REF_STRUCT } from './constants.js';
|
|
6
|
-
|
|
7
|
-
Test.prototype.isA2R = function isTokens (expr, anchor, result) {
|
|
8
|
-
this.is(translateToR1C1(expr, anchor), result, expr);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
test('translate absolute cells from A1 to RC', t => {
|
|
12
|
-
t.isA2R('=$A$1', 'B2', '=R1C1');
|
|
13
|
-
t.isA2R('=$A$2', 'B2', '=R2C1');
|
|
14
|
-
t.isA2R('=$A$3', 'B2', '=R3C1');
|
|
15
|
-
t.isA2R('=$B$1', 'B2', '=R1C2');
|
|
16
|
-
t.isA2R('=$B$2', 'B2', '=R2C2');
|
|
17
|
-
t.isA2R('=$B$3', 'B2', '=R3C2');
|
|
18
|
-
t.isA2R('=$C$1', 'B2', '=R1C3');
|
|
19
|
-
t.isA2R('=$C$2', 'B2', '=R2C3');
|
|
20
|
-
t.isA2R('=$C$3', 'B2', '=R3C3');
|
|
21
|
-
// absolute cells, anchor has no real effect
|
|
22
|
-
t.isA2R('=$A$1', 'Z19', '=R1C1');
|
|
23
|
-
t.isA2R('=$A$2', 'Z19', '=R2C1');
|
|
24
|
-
t.isA2R('=$A$3', 'Z19', '=R3C1');
|
|
25
|
-
t.isA2R('=$B$1', 'Z19', '=R1C2');
|
|
26
|
-
t.isA2R('=$B$2', 'Z19', '=R2C2');
|
|
27
|
-
t.isA2R('=$B$3', 'Z19', '=R3C2');
|
|
28
|
-
t.isA2R('=$C$1', 'Z19', '=R1C3');
|
|
29
|
-
t.isA2R('=$C$2', 'Z19', '=R2C3');
|
|
30
|
-
t.isA2R('=$C$3', 'Z19', '=R3C3');
|
|
31
|
-
t.end();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('translate relative cells from A1 to RC', t => {
|
|
35
|
-
t.isA2R('=A1', 'B2', '=R[-1]C[-1]');
|
|
36
|
-
t.isA2R('=A2', 'B2', '=RC[-1]');
|
|
37
|
-
t.isA2R('=A3', 'B2', '=R[1]C[-1]');
|
|
38
|
-
t.isA2R('=B1', 'B2', '=R[-1]C');
|
|
39
|
-
t.isA2R('=B2', 'B2', '=RC');
|
|
40
|
-
t.isA2R('=B3', 'B2', '=R[1]C');
|
|
41
|
-
t.isA2R('=C1', 'B2', '=R[-1]C[1]');
|
|
42
|
-
t.isA2R('=C2', 'B2', '=RC[1]');
|
|
43
|
-
t.isA2R('=C3', 'B2', '=R[1]C[1]');
|
|
44
|
-
// relative cells, but with [0] notation
|
|
45
|
-
t.isA2R('=H11', 'I12', '=R[-1]C[-1]');
|
|
46
|
-
t.isA2R('=H12', 'I12', '=RC[-1]');
|
|
47
|
-
t.isA2R('=H13', 'I12', '=R[1]C[-1]');
|
|
48
|
-
t.isA2R('=I11', 'I12', '=R[-1]C');
|
|
49
|
-
t.isA2R('=I12', 'I12', '=RC');
|
|
50
|
-
t.isA2R('=I13', 'I12', '=R[1]C');
|
|
51
|
-
t.isA2R('=J11', 'I12', '=R[-1]C[1]');
|
|
52
|
-
t.isA2R('=J12', 'I12', '=RC[1]');
|
|
53
|
-
t.isA2R('=J13', 'I12', '=R[1]C[1]');
|
|
54
|
-
t.end();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('translate rows from A1 to RC', t => {
|
|
58
|
-
t.isA2R('=2:2', 'B1', '=R[1]');
|
|
59
|
-
t.isA2R('=2:2', 'B2', '=R');
|
|
60
|
-
t.isA2R('=2:2', 'B3', '=R[-1]');
|
|
61
|
-
t.isA2R('=13:13', 'B13', '=R');
|
|
62
|
-
t.isA2R('=$2:$2', 'B2', '=R2');
|
|
63
|
-
t.isA2R('=2:$2', 'B2', '=R:R2');
|
|
64
|
-
t.isA2R('=11:9', 'Z10', '=R[-1]:R[1]');
|
|
65
|
-
t.end();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('translate cols from A1 to RC', t => {
|
|
69
|
-
t.isA2R('=B:B', 'A2', '=C[1]');
|
|
70
|
-
t.isA2R('=B:B', 'B2', '=C');
|
|
71
|
-
t.isA2R('=B:B', 'C2', '=C[-1]');
|
|
72
|
-
t.isA2R('=Z:Z', 'Z2', '=C');
|
|
73
|
-
t.isA2R('=B:B', 'B2', '=C');
|
|
74
|
-
t.isA2R('=$B:$B', 'B2', '=C2');
|
|
75
|
-
t.isA2R('=B:$B', 'B2', '=C:C2');
|
|
76
|
-
t.isA2R('=N:L', 'M10', '=C[-1]:C[1]');
|
|
77
|
-
t.end();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('translate partials from A1 to RC', t => {
|
|
81
|
-
t.isA2R('=A1:A', 'C6', '=R[-5]C[-2]:C[-2]');
|
|
82
|
-
t.isA2R('=A1:1', 'D6', '=R[-5]C[-3]:R[-5]');
|
|
83
|
-
t.isA2R('=$A1:$A', 'C7', '=R[-6]C1:C1');
|
|
84
|
-
t.isA2R('=$A:$A1', 'D7', '=R[-6]C1:C1');
|
|
85
|
-
t.isA2R('=$A1:1', 'C7', '=R[-6]C1:R[-6]');
|
|
86
|
-
t.isA2R('=1:$A1', 'C7', '=R[-6]C1:R[-6]');
|
|
87
|
-
t.isA2R('=A$1:A', 'C6', '=R1C[-2]:C[-2]');
|
|
88
|
-
t.isA2R('=A:A$1', 'C6', '=R1C[-2]:C[-2]');
|
|
89
|
-
t.isA2R('=A$1:$1', 'D6', '=R1C[-3]:R1');
|
|
90
|
-
t.isA2R('=$1:A$1', 'D6', '=R1C[-3]:R1');
|
|
91
|
-
t.isA2R('=$A$1:$A', 'D6', '=R1C1:C1');
|
|
92
|
-
t.isA2R('=$A:$A$1', 'D6', '=R1C1:C1');
|
|
93
|
-
t.isA2R('=$A$1:$1', 'D6', '=R1C1:R1');
|
|
94
|
-
t.isA2R('=$1:$A$1', 'D6', '=R1C1:R1');
|
|
95
|
-
t.end();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('translate boundary coords from A1 to RC', t => {
|
|
99
|
-
t.isA2R('=XFD:XFD', 'A1', '=C[16383]');
|
|
100
|
-
t.isA2R('=A1', 'B1', '=RC[-1]');
|
|
101
|
-
t.isA2R('=B1', 'C1', '=RC[-1]');
|
|
102
|
-
t.isA2R('=1048576:1048576', 'A1', '=R[1048575]');
|
|
103
|
-
t.isA2R('=$1:$1048576', 'A1', '=R1:R1048576');
|
|
104
|
-
t.isA2R('=$A:$XFD', 'A1', '=C1:C16384');
|
|
105
|
-
t.isA2R('=$A$1:$XFD$1048576', 'A1', '=R1C1:R1048576C16384');
|
|
106
|
-
t.isA2R('=A1', 'A2', '=R[-1]C');
|
|
107
|
-
t.isA2R('=A2', 'A3', '=R[-1]C');
|
|
108
|
-
t.end();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('translate mixed rel/abs coords from A1 to RC', t => {
|
|
112
|
-
t.isA2R('=B$1', 'B2', '=R1C');
|
|
113
|
-
t.isA2R('=$D8', 'B4', '=R[4]C4');
|
|
114
|
-
t.isA2R('=8:$10', 'B4', '=R[4]:R10');
|
|
115
|
-
t.isA2R('=$J:L', 'B4', '=C10:C[10]');
|
|
116
|
-
t.isA2R('=$A$1:$B$2', 'D4', '=R1C1:R2C2');
|
|
117
|
-
t.isA2R('=C3:F6', 'D4', '=R[-1]C[-1]:R[2]C[2]');
|
|
118
|
-
t.end();
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('translate involved cases from A1 to RC', t => {
|
|
122
|
-
t.isA2R('=SUM(IF(E10,$E$2,$E$3),Sheet1!$2:$2*Sheet2!B:B)', 'D10',
|
|
123
|
-
'=SUM(IF(RC[1],R2C5,R3C5),Sheet1!R2*Sheet2!C[-2])');
|
|
124
|
-
|
|
125
|
-
// make sure we don't get confused by structured, or named refs
|
|
126
|
-
t.isA2R('=A1+Table1[#Data]', 'D10', '=R[-9]C[-3]+Table1[#Data]');
|
|
127
|
-
t.isA2R('=A1+foobar', 'D10', '=R[-9]C[-3]+foobar');
|
|
128
|
-
|
|
129
|
-
// This [123]Sheet!A1 variant of the syntax is used internally in xlsx files
|
|
130
|
-
t.isA2R('=[2]Sheet1!A1', 'D10', '=[2]Sheet1!R[-9]C[-3]');
|
|
131
|
-
|
|
132
|
-
t.end();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('translate works with merged ranges', t => {
|
|
136
|
-
// This tests that:
|
|
137
|
-
// - Translate works with ranges that have context attached
|
|
138
|
-
// - If input is a tokenlist, output is also a tokenlist
|
|
139
|
-
// - If tokens have ranges, those ranges are adjusted to new token lengths
|
|
140
|
-
// - Properties added by addTokenMeta are preserved
|
|
141
|
-
const expr = '=SUM(IF(E10,$E$2,$E$3),Sheet1!$2:$2*Sheet2!B:B)';
|
|
142
|
-
const tokens = addTokenMeta(tokenize(expr, { withLocation: true }));
|
|
143
|
-
const expected = [
|
|
144
|
-
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ], index: 0, depth: 0 },
|
|
145
|
-
{ type: FUNCTION, value: 'SUM', loc: [ 1, 4 ], index: 1, depth: 0 },
|
|
146
|
-
{ type: OPERATOR, value: '(', loc: [ 4, 5 ], index: 2, depth: 1, groupId: 'fxg7' },
|
|
147
|
-
{ type: FUNCTION, value: 'IF', loc: [ 5, 7 ], index: 3, depth: 1 },
|
|
148
|
-
{ type: OPERATOR, value: '(', loc: [ 7, 8 ], index: 4, depth: 2, groupId: 'fxg4' },
|
|
149
|
-
{ type: REF_RANGE, value: 'RC[1]', loc: [ 8, 13 ], index: 5, depth: 2, groupId: 'fxg1' },
|
|
150
|
-
{ type: OPERATOR, value: ',', loc: [ 13, 14 ], index: 6, depth: 2 },
|
|
151
|
-
{ type: REF_RANGE, value: 'R2C5', loc: [ 14, 18 ], index: 7, depth: 2, groupId: 'fxg2' },
|
|
152
|
-
{ type: OPERATOR, value: ',', loc: [ 18, 19 ], index: 8, depth: 2 },
|
|
153
|
-
{ type: REF_RANGE, value: 'R3C5', loc: [ 19, 23 ], index: 9, depth: 2, groupId: 'fxg3' },
|
|
154
|
-
{ type: OPERATOR, value: ')', loc: [ 23, 24 ], index: 10, depth: 2, groupId: 'fxg4' },
|
|
155
|
-
{ type: OPERATOR, value: ',', loc: [ 24, 25 ], index: 11, depth: 1 },
|
|
156
|
-
{ type: REF_BEAM, value: 'Sheet1!R2', loc: [ 25, 34 ], index: 12, depth: 1, groupId: 'fxg5' },
|
|
157
|
-
{ type: OPERATOR, value: '*', loc: [ 34, 35 ], index: 13, depth: 1 },
|
|
158
|
-
{ type: REF_BEAM, value: 'Sheet2!C[-2]', loc: [ 35, 47 ], index: 14, depth: 1, groupId: 'fxg6' },
|
|
159
|
-
{ type: OPERATOR, value: ')', loc: [ 47, 48 ], index: 15, depth: 1, groupId: 'fxg7' }
|
|
160
|
-
];
|
|
161
|
-
t.deepEqual(translateToR1C1(tokens, 'D10'), expected, expr);
|
|
162
|
-
t.end();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('translate works with xlsx mode', t => {
|
|
166
|
-
const testExpr = (expr, anchor, expected) => {
|
|
167
|
-
const opts = { mergeRefs: true, xlsx: true, r1c1: false };
|
|
168
|
-
const tokens = tokenize(expr, opts);
|
|
169
|
-
t.deepEqual(translateToR1C1(tokens, anchor, opts), expected, expr);
|
|
170
|
-
};
|
|
171
|
-
testExpr("'[My Fancy Workbook.xlsx]'!B$1", 'B2', [
|
|
172
|
-
{ type: REF_RANGE, value: "'[My Fancy Workbook.xlsx]'!R1C" }
|
|
173
|
-
]);
|
|
174
|
-
testExpr('[Workbook.xlsx]!B$1', 'B2', [
|
|
175
|
-
{ type: REF_RANGE, value: '[Workbook.xlsx]!R1C' }
|
|
176
|
-
]);
|
|
177
|
-
testExpr('[Workbook.xlsx]Sheet1!B$1', 'B2', [
|
|
178
|
-
{ type: REF_RANGE, value: '[Workbook.xlsx]Sheet1!R1C' }
|
|
179
|
-
]);
|
|
180
|
-
testExpr('[Workbook.xlsx]!table[#data]', 'B2', [
|
|
181
|
-
{ type: REF_STRUCT, value: '[Workbook.xlsx]!table[#data]' }
|
|
182
|
-
]);
|
|
183
|
-
t.end();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('translate works with trimmed ranges', t => {
|
|
187
|
-
const testExpr = (expr, anchor, expected) => {
|
|
188
|
-
const opts = { mergeRefs: true, xlsx: true, r1c1: false };
|
|
189
|
-
t.deepEqual(translateToR1C1(tokenize(expr, opts), anchor, opts), expected, expr);
|
|
190
|
-
};
|
|
191
|
-
testExpr('Sheet!A1.:.B2*Sheet2!AZ.:.ZZ', 'B2', [
|
|
192
|
-
{ type: 'range', value: 'Sheet!R[-1]C[-1].:.RC' },
|
|
193
|
-
{ type: 'operator', value: '*' },
|
|
194
|
-
{ type: 'range_beam', value: 'Sheet2!C[50].:.C[700]' }
|
|
195
|
-
]);
|
|
196
|
-
t.end();
|
|
197
|
-
});
|
package/lib/translate.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { MAX_ROWS, MAX_COLS, ERROR } from './constants.js';
|
|
2
|
-
import { fromA1, parseA1Ref, stringifyA1Ref } from './a1.js';
|
|
3
|
-
import { parseR1C1Ref, stringifyR1C1Ref } from './rc.js';
|
|
4
|
-
import { tokenize } from './lexer.js';
|
|
5
|
-
import { isRange } from './isType.js';
|
|
6
|
-
|
|
7
|
-
const calc = (abs, vX, aX) => {
|
|
8
|
-
if (vX == null) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
return abs ? vX : vX - aX;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const settings = {
|
|
15
|
-
withLocation: false,
|
|
16
|
-
mergeRefs: false,
|
|
17
|
-
allowTernary: true,
|
|
18
|
-
r1c1: false
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Translates ranges in a formula or list of tokens from absolute A1 syntax to
|
|
23
|
-
* relative R1C1 syntax.
|
|
24
|
-
*
|
|
25
|
-
* Returns the same formula with the ranges translated. If an array of tokens
|
|
26
|
-
* was supplied, then the same array is returned.
|
|
27
|
-
*
|
|
28
|
-
* ```js
|
|
29
|
-
* translateToR1C1("=SUM(E10,$E$2,Sheet!$E$3)", "D10");
|
|
30
|
-
* // => "=SUM(RC[1],R2C5,Sheet!R3C5)");
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* @param {(string | Array<Token>)} formula A string (an Excel formula) or a token list that should be adjusted.
|
|
34
|
-
* @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
|
|
35
|
-
* @param {object} [options={}] The options
|
|
36
|
-
* @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
|
|
37
|
-
* @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
|
|
38
|
-
* @returns {(string | Array<Token>)} A formula string or token list (depending on which was input)
|
|
39
|
-
*/
|
|
40
|
-
export function translateToR1C1 (formula, anchorCell, { xlsx = false, allowTernary = true } = {}) {
|
|
41
|
-
const { top, left } = fromA1(anchorCell);
|
|
42
|
-
const isString = typeof formula === 'string';
|
|
43
|
-
|
|
44
|
-
const tokens = isString
|
|
45
|
-
? tokenize(formula, { ...settings, xlsx, allowTernary })
|
|
46
|
-
: formula;
|
|
47
|
-
|
|
48
|
-
let offsetSkew = 0;
|
|
49
|
-
const refOpts = { xlsx, allowTernary };
|
|
50
|
-
tokens.forEach(token => {
|
|
51
|
-
if (isRange(token)) {
|
|
52
|
-
const tokenValue = token.value;
|
|
53
|
-
const ref = parseA1Ref(tokenValue, refOpts);
|
|
54
|
-
const d = ref.range;
|
|
55
|
-
const range = {};
|
|
56
|
-
range.r0 = calc(d.$top, d.top, top);
|
|
57
|
-
range.r1 = calc(d.$bottom, d.bottom, top);
|
|
58
|
-
range.c0 = calc(d.$left, d.left, left);
|
|
59
|
-
range.c1 = calc(d.$right, d.right, left);
|
|
60
|
-
range.$r0 = d.$top;
|
|
61
|
-
range.$r1 = d.$bottom;
|
|
62
|
-
range.$c0 = d.$left;
|
|
63
|
-
range.$c1 = d.$right;
|
|
64
|
-
if (d.trim) {
|
|
65
|
-
range.trim = d.trim;
|
|
66
|
-
}
|
|
67
|
-
ref.range = range;
|
|
68
|
-
token.value = stringifyR1C1Ref(ref, refOpts);
|
|
69
|
-
// if token includes offsets, those offsets are now likely wrong!
|
|
70
|
-
if (token.loc) {
|
|
71
|
-
token.loc[0] += offsetSkew;
|
|
72
|
-
offsetSkew += token.value.length - tokenValue.length;
|
|
73
|
-
token.loc[1] += offsetSkew;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else if (offsetSkew && token.loc) {
|
|
77
|
-
token.loc[0] += offsetSkew;
|
|
78
|
-
token.loc[1] += offsetSkew;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return isString
|
|
83
|
-
? tokens.map(d => d.value).join('')
|
|
84
|
-
: tokens;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function toFixed (val, abs, base, max, wrapEdges = true) {
|
|
88
|
-
let v = val;
|
|
89
|
-
if (v != null && !abs) {
|
|
90
|
-
v = base + val;
|
|
91
|
-
// Excel "wraps around" when value goes out of lower bounds.
|
|
92
|
-
// It's a bit quirky on entry as Excel _really wants_ to re-rewite the
|
|
93
|
-
// references but the behaviour is consistent with INDIRECT:
|
|
94
|
-
// ... In A1: RC[-1] => R1C[16383].
|
|
95
|
-
if (v < 0) {
|
|
96
|
-
if (!wrapEdges) {
|
|
97
|
-
return NaN;
|
|
98
|
-
}
|
|
99
|
-
v = max + v + 1;
|
|
100
|
-
}
|
|
101
|
-
// ... In B1: =RC[16383] => =RC[-1]
|
|
102
|
-
if (v > max) {
|
|
103
|
-
if (!wrapEdges) {
|
|
104
|
-
return NaN;
|
|
105
|
-
}
|
|
106
|
-
v -= max + 1;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return v;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const defaultOptions = {
|
|
113
|
-
wrapEdges: true,
|
|
114
|
-
mergeRefs: true,
|
|
115
|
-
allowTernary: true,
|
|
116
|
-
xlsx: false
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Translates ranges in a formula or list of tokens from relative R1C1 syntax to
|
|
121
|
-
* absolute A1 syntax.
|
|
122
|
-
*
|
|
123
|
-
* Returns the same formula with the ranges translated. If an array of tokens
|
|
124
|
-
* was supplied, then the same array is returned.
|
|
125
|
-
*
|
|
126
|
-
* ```js
|
|
127
|
-
* translateToA1("=SUM(RC[1],R2C5,Sheet!R3C5)", "D10");
|
|
128
|
-
* // => "=SUM(E10,$E$2,Sheet!$E$3)");
|
|
129
|
-
* ```
|
|
130
|
-
*
|
|
131
|
-
* If an input range is -1,-1 relative rows/columns and the anchor is A1, the
|
|
132
|
-
* resulting range will (by default) wrap around to the bottom of the sheet
|
|
133
|
-
* resulting in the range XFD1048576. This may not be what you want so may set
|
|
134
|
-
* `wrapEdges` to false which will instead turn the range into a `#REF!` error.
|
|
135
|
-
*
|
|
136
|
-
* ```js
|
|
137
|
-
* translateToA1("=R[-1]C[-1]", "A1");
|
|
138
|
-
* // => "=XFD1048576");
|
|
139
|
-
*
|
|
140
|
-
* translateToA1("=R[-1]C[-1]", "A1", { wrapEdges: false });
|
|
141
|
-
* // => "=#REF!");
|
|
142
|
-
* ```
|
|
143
|
-
*
|
|
144
|
-
* Note that if you are passing in a list of tokens that was not created using
|
|
145
|
-
* `mergeRefs` and you disable edge wrapping (or you simply set both options
|
|
146
|
-
* to false), you can end up with a formula such as `=#REF!:B2` or
|
|
147
|
-
* `=Sheet3!#REF!:F3`. These are valid formulas in the Excel formula language
|
|
148
|
-
* and Excel will accept them, but they are not supported in Google Sheets.
|
|
149
|
-
*
|
|
150
|
-
* @param {(string | Array<Token>)} formula A string (an Excel formula) or a token list that should be adjusted.
|
|
151
|
-
* @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
|
|
152
|
-
* @param {object} [options={}] The options
|
|
153
|
-
* @param {boolean} [options.wrapEdges=true] Wrap out-of-bounds ranges around sheet edges rather than turning them to #REF! errors
|
|
154
|
-
* @param {boolean} [options.mergeRefs=true] Should ranges be treated as whole references (`Sheet1!A1:B2`) or as separate tokens for each part: (`Sheet1`,`!`,`A1`,`:`,`B2`).
|
|
155
|
-
* @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
|
|
156
|
-
* @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
|
|
157
|
-
* @returns {(string | Array<Token>)} A formula string or token list (depending on which was input)
|
|
158
|
-
*/
|
|
159
|
-
export function translateToA1 (formula, anchorCell, options = defaultOptions) {
|
|
160
|
-
const anchor = fromA1(anchorCell);
|
|
161
|
-
const isString = typeof formula === 'string';
|
|
162
|
-
const opts = { ...defaultOptions, ...options };
|
|
163
|
-
|
|
164
|
-
const tokens = isString
|
|
165
|
-
? tokenize(formula, {
|
|
166
|
-
withLocation: false,
|
|
167
|
-
mergeRefs: opts.mergeRefs,
|
|
168
|
-
xlsx: opts.xlsx,
|
|
169
|
-
allowTernary: opts.allowTernary,
|
|
170
|
-
r1c1: true
|
|
171
|
-
})
|
|
172
|
-
: formula;
|
|
173
|
-
|
|
174
|
-
let offsetSkew = 0;
|
|
175
|
-
const refOpts = { xlsx: opts.xlsx, allowTernary: opts.allowTernary };
|
|
176
|
-
tokens.forEach(token => {
|
|
177
|
-
if (isRange(token)) {
|
|
178
|
-
const tokenValue = token.value;
|
|
179
|
-
const ref = parseR1C1Ref(tokenValue, refOpts);
|
|
180
|
-
const d = ref.range;
|
|
181
|
-
const range = {};
|
|
182
|
-
const r0 = toFixed(d.r0, d.$r0, anchor.top, MAX_ROWS, opts.wrapEdges);
|
|
183
|
-
const r1 = toFixed(d.r1, d.$r1, anchor.top, MAX_ROWS, opts.wrapEdges);
|
|
184
|
-
if (r0 > r1) {
|
|
185
|
-
range.top = r1;
|
|
186
|
-
range.$top = d.$r1;
|
|
187
|
-
range.bottom = r0;
|
|
188
|
-
range.$bottom = d.$r0;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
range.top = r0;
|
|
192
|
-
range.$top = d.$r0;
|
|
193
|
-
range.bottom = r1;
|
|
194
|
-
range.$bottom = d.$r1;
|
|
195
|
-
}
|
|
196
|
-
const c0 = toFixed(d.c0, d.$c0, anchor.left, MAX_COLS, opts.wrapEdges);
|
|
197
|
-
const c1 = toFixed(d.c1, d.$c1, anchor.left, MAX_COLS, opts.wrapEdges);
|
|
198
|
-
if (c0 > c1) {
|
|
199
|
-
range.left = c1;
|
|
200
|
-
range.$left = d.$c1;
|
|
201
|
-
range.right = c0;
|
|
202
|
-
range.$right = d.$c0;
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
range.left = c0;
|
|
206
|
-
range.$left = d.$c0;
|
|
207
|
-
range.right = c1;
|
|
208
|
-
range.$right = d.$c1;
|
|
209
|
-
}
|
|
210
|
-
if (d.trim) {
|
|
211
|
-
range.trim = d.trim;
|
|
212
|
-
}
|
|
213
|
-
if (isNaN(r0) || isNaN(r1) || isNaN(c0) || isNaN(c1)) {
|
|
214
|
-
// convert to ref error
|
|
215
|
-
token.type = ERROR;
|
|
216
|
-
token.value = '#REF!';
|
|
217
|
-
delete token.groupId;
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
ref.range = range;
|
|
221
|
-
token.value = stringifyA1Ref(ref, refOpts);
|
|
222
|
-
}
|
|
223
|
-
// if token includes offsets, those offsets are now likely wrong!
|
|
224
|
-
if (token.loc) {
|
|
225
|
-
token.loc[0] += offsetSkew;
|
|
226
|
-
offsetSkew += token.value.length - tokenValue.length;
|
|
227
|
-
token.loc[1] += offsetSkew;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
else if (offsetSkew && token.loc) {
|
|
231
|
-
token.loc[0] += offsetSkew;
|
|
232
|
-
token.loc[1] += offsetSkew;
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
return isString
|
|
237
|
-
? tokens.map(d => d.value).join('')
|
|
238
|
-
: tokens;
|
|
239
|
-
}
|
package/lib/translate.spec.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { test, Test } from 'tape';
|
|
2
|
-
import { translateToR1C1, translateToA1 } from './translate.js';
|
|
3
|
-
|
|
4
|
-
Test.prototype.okayRoundTrip = function roundTrip (expr, anchor, options) {
|
|
5
|
-
const rc = translateToR1C1(expr, anchor, options);
|
|
6
|
-
const a1 = translateToA1(rc, anchor, options);
|
|
7
|
-
this.is(a1, expr, 'Round trip: ' + expr);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
test('translate absolute cells from A1 to RC', t => {
|
|
11
|
-
t.okayRoundTrip('=Sheet1!$1:$1048576', 'A1');
|
|
12
|
-
t.okayRoundTrip('=D$1:$BJ$1048576', 'A1');
|
|
13
|
-
t.okayRoundTrip('=VLOOKUP(C7,Röðun,4,0)', 'A1');
|
|
14
|
-
t.okayRoundTrip('=COUNTIF(B$1442:B$1048576,$G1442)', 'A1');
|
|
15
|
-
t.okayRoundTrip('=IF(p2m<=D5,10,0)*scene_spend', 'A1');
|
|
16
|
-
t.okayRoundTrip('=(kwh_used_daily*kwhbtu*co2btu)/1000000', 'A1');
|
|
17
|
-
t.okayRoundTrip('=NOPLATT1+g1_+ROIC1+WACC+G1+g1_+G130+ROIC2+WACC+g2_+WACC+N', 'A1');
|
|
18
|
-
// FIXME: translate needs to be be able to specify allowTernary=false
|
|
19
|
-
t.okayRoundTrip('=foo:C3:D4', 'A1', { allowTernary: false });
|
|
20
|
-
t.end();
|
|
21
|
-
});
|