@borgar/fx 3.1.0 → 4.0.0-rc.2
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/.eslintrc +25 -12
- package/.jsdoc/config.json +17 -0
- package/.jsdoc/publish.js +195 -0
- package/README.md +8 -311
- package/dist/fx.js +1 -1
- package/docs/API.md +708 -0
- package/docs/AST format.md +144 -0
- package/docs/References.md +60 -0
- package/lib/a1.js +156 -30
- package/lib/a1.spec.js +9 -2
- package/lib/{addMeta.js → addTokenMeta.js} +50 -5
- package/lib/{addMeta.spec.js → addTokenMeta.spec.js} +16 -16
- package/lib/constants.js +14 -4
- package/lib/fixRanges.js +64 -10
- package/lib/fixRanges.spec.js +35 -6
- package/lib/index.js +105 -17
- package/lib/isType.js +119 -8
- package/lib/lexer-srefs.spec.js +311 -0
- package/lib/lexer.js +55 -15
- package/lib/lexer.spec.js +223 -214
- package/lib/lexerParts.js +38 -14
- package/lib/mergeRefTokens.js +38 -25
- package/lib/mergeRefTokens.spec.js +39 -39
- package/lib/parseRef.js +17 -12
- package/lib/parser.js +498 -0
- package/lib/parser.spec.js +777 -0
- package/lib/rc.js +95 -22
- package/lib/rc.spec.js +16 -5
- package/lib/sr.js +277 -0
- package/lib/sr.spec.js +179 -0
- package/lib/translate-toA1.spec.js +38 -20
- package/lib/translate-toRC.spec.js +23 -23
- package/lib/translate.js +111 -30
- package/package.json +3 -1
- package/References.md +0 -39
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { test, Test } from 'tape';
|
|
2
2
|
import { translateToA1 } from './translate.js';
|
|
3
3
|
import { tokenize } from './lexer.js';
|
|
4
|
-
import {
|
|
5
|
-
import { FUNCTION, FX_PREFIX, OPERATOR,
|
|
4
|
+
import { addTokenMeta } from './addTokenMeta.js';
|
|
5
|
+
import { ERROR, FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_BEAM } from './constants.js';
|
|
6
6
|
|
|
7
7
|
Test.prototype.isR2A = function isTokens (expr, anchor, result) {
|
|
8
8
|
this.is(translateToA1(expr, anchor), result, expr);
|
|
@@ -109,6 +109,24 @@ test('translate out of bounds coords from RC to A1', t => {
|
|
|
109
109
|
t.isR2A('=R[-2]', 'A1', '=1048575:1048575');
|
|
110
110
|
t.isR2A('=R[1048575]C', 'A2', '=A1');
|
|
111
111
|
t.isR2A('=R[1048575]C', 'A3', '=A2');
|
|
112
|
+
|
|
113
|
+
const f1 = '=R[-1]C[-1]';
|
|
114
|
+
t.is(translateToA1(f1, 'A1', { wrapEdges: false }), '=#REF!', f1);
|
|
115
|
+
|
|
116
|
+
const tokens = addTokenMeta(tokenize('SUM(Sheet1!R[-1]C[-1])', { r1c1: true, withLocation: true }));
|
|
117
|
+
t.deepEqual(translateToA1(tokens, 'A1', { wrapEdges: false }), [
|
|
118
|
+
{ type: FUNCTION, value: 'SUM', loc: [ 0, 3 ], index: 0, depth: 0 },
|
|
119
|
+
{ type: OPERATOR, value: '(', loc: [ 3, 4 ], index: 1, depth: 1, groupId: 'fxg1' },
|
|
120
|
+
{ type: ERROR, value: '#REF!', loc: [ 4, 9 ], index: 2, depth: 1 },
|
|
121
|
+
{ type: OPERATOR, value: ')', loc: [ 9, 10 ], index: 3, depth: 1, groupId: 'fxg1' }
|
|
122
|
+
], 'tokens with meta');
|
|
123
|
+
|
|
124
|
+
const f2 = '=Sheet4!R[-2]C[-2]:R[-1]C[-1]';
|
|
125
|
+
t.is(translateToA1(f2, 'B2', { wrapEdges: false }), '=#REF!', f2);
|
|
126
|
+
|
|
127
|
+
const f3 = '=Sheet4!R[-2]C[-2]:R[-1]C[-1]';
|
|
128
|
+
t.is(translateToA1(f3, 'B2', { wrapEdges: false, mergeRefs: false }), '=Sheet4!#REF!:A1', f3);
|
|
129
|
+
|
|
112
130
|
t.end();
|
|
113
131
|
});
|
|
114
132
|
|
|
@@ -133,26 +151,26 @@ test('translate works with merged ranges', t => {
|
|
|
133
151
|
// - Translate works with ranges that have context attached
|
|
134
152
|
// - If input is a tokenlist, output is also a tokenlist
|
|
135
153
|
// - If tokens have ranges, those ranges are adjusted to new token lengths
|
|
136
|
-
// - Properties added by
|
|
154
|
+
// - Properties added by addTokenMeta are preserved
|
|
137
155
|
const expr = '=SUM(IF(RC[1],R2C5,R3C5),Sheet1!R2*Sheet2!C[-2])';
|
|
138
|
-
const tokens =
|
|
156
|
+
const tokens = addTokenMeta(tokenize(expr, { withLocation: true, r1c1: true }));
|
|
139
157
|
const expected = [
|
|
140
|
-
{ type: FX_PREFIX, value: '=',
|
|
141
|
-
{ type: FUNCTION, value: 'SUM',
|
|
142
|
-
{ type: OPERATOR, value: '(',
|
|
143
|
-
{ type: FUNCTION, value: 'IF',
|
|
144
|
-
{ type: OPERATOR, value: '(',
|
|
145
|
-
{ type:
|
|
146
|
-
{ type: OPERATOR, value: ',',
|
|
147
|
-
{ type:
|
|
148
|
-
{ type: OPERATOR, value: ',',
|
|
149
|
-
{ type:
|
|
150
|
-
{ type: OPERATOR, value: ')',
|
|
151
|
-
{ type: OPERATOR, value: ',',
|
|
152
|
-
{ type:
|
|
153
|
-
{ type: OPERATOR, value: '*',
|
|
154
|
-
{ type:
|
|
155
|
-
{ type: OPERATOR, value: ')',
|
|
158
|
+
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ], index: 0, depth: 0 },
|
|
159
|
+
{ type: FUNCTION, value: 'SUM', loc: [ 1, 4 ], index: 1, depth: 0 },
|
|
160
|
+
{ type: OPERATOR, value: '(', loc: [ 4, 5 ], index: 2, depth: 1, groupId: 'fxg3' },
|
|
161
|
+
{ type: FUNCTION, value: 'IF', loc: [ 5, 7 ], index: 3, depth: 1 },
|
|
162
|
+
{ type: OPERATOR, value: '(', loc: [ 7, 8 ], index: 4, depth: 2, groupId: 'fxg1' },
|
|
163
|
+
{ type: REF_RANGE, value: 'E10', loc: [ 8, 11 ], index: 5, depth: 2 },
|
|
164
|
+
{ type: OPERATOR, value: ',', loc: [ 11, 12 ], index: 6, depth: 2 },
|
|
165
|
+
{ type: REF_RANGE, value: '$E$2', loc: [ 12, 16 ], index: 7, depth: 2 },
|
|
166
|
+
{ type: OPERATOR, value: ',', loc: [ 16, 17 ], index: 8, depth: 2 },
|
|
167
|
+
{ type: REF_RANGE, value: '$E$3', loc: [ 17, 21 ], index: 9, depth: 2 },
|
|
168
|
+
{ type: OPERATOR, value: ')', loc: [ 21, 22 ], index: 10, depth: 2, groupId: 'fxg1' },
|
|
169
|
+
{ type: OPERATOR, value: ',', loc: [ 22, 23 ], index: 11, depth: 1 },
|
|
170
|
+
{ type: REF_BEAM, value: 'Sheet1!$2:$2', loc: [ 23, 35 ], index: 12, depth: 1, groupId: 'fxg2' },
|
|
171
|
+
{ type: OPERATOR, value: '*', loc: [ 35, 36 ], index: 13, depth: 1 },
|
|
172
|
+
{ type: REF_BEAM, value: 'Sheet2!B:B', loc: [ 36, 46 ], index: 14, depth: 1 },
|
|
173
|
+
{ type: OPERATOR, value: ')', loc: [ 46, 47 ], index: 15, depth: 1, groupId: 'fxg3' }
|
|
156
174
|
];
|
|
157
175
|
t.deepEqual(translateToA1(tokens, 'D10'), expected, expr);
|
|
158
176
|
t.end();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { test, Test } from 'tape';
|
|
2
|
-
import {
|
|
2
|
+
import { translateToR1C1 } from './translate.js';
|
|
3
3
|
import { tokenize } from './lexer.js';
|
|
4
|
-
import {
|
|
5
|
-
import { FUNCTION, FX_PREFIX, OPERATOR,
|
|
4
|
+
import { addTokenMeta } from './addTokenMeta.js';
|
|
5
|
+
import { FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_BEAM } from './constants.js';
|
|
6
6
|
|
|
7
7
|
Test.prototype.isA2R = function isTokens (expr, anchor, result) {
|
|
8
|
-
this.is(
|
|
8
|
+
this.is(translateToR1C1(expr, anchor), result, expr);
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
test('translate absolute cells from A1 to RC', t => {
|
|
@@ -126,27 +126,27 @@ test('translate works with merged ranges', t => {
|
|
|
126
126
|
// - Translate works with ranges that have context attached
|
|
127
127
|
// - If input is a tokenlist, output is also a tokenlist
|
|
128
128
|
// - If tokens have ranges, those ranges are adjusted to new token lengths
|
|
129
|
-
// - Properties added by
|
|
129
|
+
// - Properties added by addTokenMeta are preserved
|
|
130
130
|
const expr = '=SUM(IF(E10,$E$2,$E$3),Sheet1!$2:$2*Sheet2!B:B)';
|
|
131
|
-
const tokens =
|
|
131
|
+
const tokens = addTokenMeta(tokenize(expr, { withLocation: true }));
|
|
132
132
|
const expected = [
|
|
133
|
-
{ type: FX_PREFIX, value: '=',
|
|
134
|
-
{ type: FUNCTION, value: 'SUM',
|
|
135
|
-
{ type: OPERATOR, value: '(',
|
|
136
|
-
{ type: FUNCTION, value: 'IF',
|
|
137
|
-
{ type: OPERATOR, value: '(',
|
|
138
|
-
{ type:
|
|
139
|
-
{ type: OPERATOR, value: ',',
|
|
140
|
-
{ type:
|
|
141
|
-
{ type: OPERATOR, value: ',',
|
|
142
|
-
{ type:
|
|
143
|
-
{ type: OPERATOR, value: ')',
|
|
144
|
-
{ type: OPERATOR, value: ',',
|
|
145
|
-
{ type:
|
|
146
|
-
{ type: OPERATOR, value: '*',
|
|
147
|
-
{ type:
|
|
148
|
-
{ type: OPERATOR, value: ')',
|
|
133
|
+
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ], index: 0, depth: 0 },
|
|
134
|
+
{ type: FUNCTION, value: 'SUM', loc: [ 1, 4 ], index: 1, depth: 0 },
|
|
135
|
+
{ type: OPERATOR, value: '(', loc: [ 4, 5 ], index: 2, depth: 1, groupId: 'fxg7' },
|
|
136
|
+
{ type: FUNCTION, value: 'IF', loc: [ 5, 7 ], index: 3, depth: 1 },
|
|
137
|
+
{ type: OPERATOR, value: '(', loc: [ 7, 8 ], index: 4, depth: 2, groupId: 'fxg4' },
|
|
138
|
+
{ type: REF_RANGE, value: 'RC[1]', loc: [ 8, 13 ], index: 5, depth: 2, groupId: 'fxg1' },
|
|
139
|
+
{ type: OPERATOR, value: ',', loc: [ 13, 14 ], index: 6, depth: 2 },
|
|
140
|
+
{ type: REF_RANGE, value: 'R2C5', loc: [ 14, 18 ], index: 7, depth: 2, groupId: 'fxg2' },
|
|
141
|
+
{ type: OPERATOR, value: ',', loc: [ 18, 19 ], index: 8, depth: 2 },
|
|
142
|
+
{ type: REF_RANGE, value: 'R3C5', loc: [ 19, 23 ], index: 9, depth: 2, groupId: 'fxg3' },
|
|
143
|
+
{ type: OPERATOR, value: ')', loc: [ 23, 24 ], index: 10, depth: 2, groupId: 'fxg4' },
|
|
144
|
+
{ type: OPERATOR, value: ',', loc: [ 24, 25 ], index: 11, depth: 1 },
|
|
145
|
+
{ type: REF_BEAM, value: 'Sheet1!R2', loc: [ 25, 34 ], index: 12, depth: 1, groupId: 'fxg5' },
|
|
146
|
+
{ type: OPERATOR, value: '*', loc: [ 34, 35 ], index: 13, depth: 1 },
|
|
147
|
+
{ type: REF_BEAM, value: 'Sheet2!C[-2]', loc: [ 35, 47 ], index: 14, depth: 1, groupId: 'fxg6' },
|
|
148
|
+
{ type: OPERATOR, value: ')', loc: [ 47, 48 ], index: 15, depth: 1, groupId: 'fxg7' }
|
|
149
149
|
];
|
|
150
|
-
t.deepEqual(
|
|
150
|
+
t.deepEqual(translateToR1C1(tokens, 'D10'), expected, expr);
|
|
151
151
|
t.end();
|
|
152
152
|
});
|
package/lib/translate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MAX_ROWS, MAX_COLS } from './constants.js';
|
|
1
|
+
import { MAX_ROWS, MAX_COLS, ERROR } from './constants.js';
|
|
2
2
|
import { fromA1, parseA1Ref, stringifyA1Ref } from './a1.js';
|
|
3
|
-
import {
|
|
3
|
+
import { parseR1C1Ref, stringifyR1C1Ref } from './rc.js';
|
|
4
4
|
import { tokenize } from './lexer.js';
|
|
5
5
|
import { isRange } from './isType.js';
|
|
6
6
|
|
|
@@ -12,13 +12,30 @@ const calc = (abs, vX, aX) => {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
const settings = {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
withLocation: false,
|
|
16
|
+
mergeRefs: false,
|
|
17
17
|
allowTernary: true,
|
|
18
18
|
r1c1: false
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
|
|
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 (be careful that `mergeRefs`
|
|
27
|
+
* *must* be `false`).
|
|
28
|
+
*
|
|
29
|
+
* ```js
|
|
30
|
+
* translateToR1C1("=SUM(E10,$E$2,Sheet!$E$3)", "D10");
|
|
31
|
+
* // => "=SUM(RC[1],R2C5,Sheet!R3C5)");
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @param {(string | Array<Object>)} formula A string (an Excel formula) or a token list that should be adjusted.
|
|
35
|
+
* @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
|
|
36
|
+
* @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
|
|
37
|
+
*/
|
|
38
|
+
export function translateToR1C1 (fx, anchorCell) {
|
|
22
39
|
const { top, left } = fromA1(anchorCell);
|
|
23
40
|
const isString = typeof fx === 'string';
|
|
24
41
|
|
|
@@ -42,17 +59,17 @@ export function translateToRC (fx, anchorCell) {
|
|
|
42
59
|
range.$c0 = d.$left;
|
|
43
60
|
range.$c1 = d.$right;
|
|
44
61
|
ref.range = range;
|
|
45
|
-
token.value =
|
|
62
|
+
token.value = stringifyR1C1Ref(ref);
|
|
46
63
|
// if token includes offsets, those offsets are now likely wrong!
|
|
47
|
-
if (token.
|
|
48
|
-
token.
|
|
64
|
+
if (token.loc) {
|
|
65
|
+
token.loc[0] += offsetSkew;
|
|
49
66
|
offsetSkew += token.value.length - tokenValue.length;
|
|
50
|
-
token.
|
|
67
|
+
token.loc[1] += offsetSkew;
|
|
51
68
|
}
|
|
52
69
|
}
|
|
53
|
-
else if (offsetSkew && token.
|
|
54
|
-
token.
|
|
55
|
-
token.
|
|
70
|
+
else if (offsetSkew && token.loc) {
|
|
71
|
+
token.loc[0] += offsetSkew;
|
|
72
|
+
token.loc[1] += offsetSkew;
|
|
56
73
|
}
|
|
57
74
|
});
|
|
58
75
|
|
|
@@ -61,7 +78,7 @@ export function translateToRC (fx, anchorCell) {
|
|
|
61
78
|
: tokens;
|
|
62
79
|
}
|
|
63
80
|
|
|
64
|
-
function toFixed (val, abs, base, max) {
|
|
81
|
+
function toFixed (val, abs, base, max, wrapEdges = true) {
|
|
65
82
|
let v = val;
|
|
66
83
|
if (v != null && !abs) {
|
|
67
84
|
v = base + val;
|
|
@@ -70,33 +87,89 @@ function toFixed (val, abs, base, max) {
|
|
|
70
87
|
// references but the behaviour is consistent with INDIRECT:
|
|
71
88
|
// ... In A1: RC[-1] => R1C[16383].
|
|
72
89
|
if (v < 0) {
|
|
90
|
+
if (!wrapEdges) {
|
|
91
|
+
return NaN;
|
|
92
|
+
}
|
|
73
93
|
v = max + v + 1;
|
|
74
94
|
}
|
|
75
95
|
// ... In B1: =RC[16383] => =RC[-1]
|
|
76
96
|
if (v > max) {
|
|
97
|
+
if (!wrapEdges) {
|
|
98
|
+
return NaN;
|
|
99
|
+
}
|
|
77
100
|
v -= max + 1;
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
return v;
|
|
81
104
|
}
|
|
82
105
|
|
|
83
|
-
|
|
106
|
+
const defaultOptions = {
|
|
107
|
+
wrapEdges: true,
|
|
108
|
+
mergeRefs: true
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Translates ranges in a formula or list of tokens from relative R1C1 syntax to
|
|
113
|
+
* absolute A1 syntax.
|
|
114
|
+
*
|
|
115
|
+
* Returns the same formula with the ranges translated. If an array of tokens
|
|
116
|
+
* was supplied, then the same array is returned (be careful that `mergeRefs`
|
|
117
|
+
* *must* be `false`).
|
|
118
|
+
*
|
|
119
|
+
* ```js
|
|
120
|
+
* translateToA1("=SUM(RC[1],R2C5,Sheet!R3C5)", "D10");
|
|
121
|
+
* // => "=SUM(E10,$E$2,Sheet!$E$3)");
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* If an input range is -1,-1 relative rows/columns and the anchor is A1, the
|
|
125
|
+
* resulting range will (by default) wrap around to the bottom of the sheet
|
|
126
|
+
* resulting in the range XFD1048576. This may not be what you want so may set
|
|
127
|
+
* `wrapEdges` to false which will instead turn the range into a `#REF!` error.
|
|
128
|
+
*
|
|
129
|
+
* ```js
|
|
130
|
+
* translateToA1("=R[-1]C[-1]", "A1");
|
|
131
|
+
* // => "=XFD1048576");
|
|
132
|
+
*
|
|
133
|
+
* translateToA1("=R[-1]C[-1]", "A1", { wrapEdges: false });
|
|
134
|
+
* // => "=#REF!");
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* Note that if you are passing in a list of tokens that was not created using
|
|
138
|
+
* `mergeRefs` and you disable edge wrapping (or you simply set both options
|
|
139
|
+
* to false), you can end up with a formula such as `=#REF!:B2` or
|
|
140
|
+
* `=Sheet3!#REF!:F3`. These are valid formulas in the Excel formula language
|
|
141
|
+
* and Excel will accept them, but they are not supported in Google Sheets.
|
|
142
|
+
*
|
|
143
|
+
* @param {(string | Array<Object>)} formula A string (an Excel formula) or a token list that should be adjusted.
|
|
144
|
+
* @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
|
|
145
|
+
* @param {Object} [options={}] The options
|
|
146
|
+
* @param {boolean} [options.wrapEdges=true] Wrap out-of-bounds ranges around sheet edges rather than turning them to #REF! errors
|
|
147
|
+
* @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`).
|
|
148
|
+
* @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
|
|
149
|
+
*/
|
|
150
|
+
export function translateToA1 (formula, anchorCell, options = defaultOptions) {
|
|
84
151
|
const anchor = fromA1(anchorCell);
|
|
85
|
-
const isString = typeof
|
|
152
|
+
const isString = typeof formula === 'string';
|
|
153
|
+
const opts = { ...defaultOptions, ...options };
|
|
86
154
|
|
|
87
155
|
const tokens = isString
|
|
88
|
-
? tokenize(
|
|
89
|
-
|
|
156
|
+
? tokenize(formula, {
|
|
157
|
+
withLocation: false,
|
|
158
|
+
mergeRefs: opts.mergeRefs,
|
|
159
|
+
allowTernary: true,
|
|
160
|
+
r1c1: true
|
|
161
|
+
})
|
|
162
|
+
: formula;
|
|
90
163
|
|
|
91
164
|
let offsetSkew = 0;
|
|
92
165
|
tokens.forEach(token => {
|
|
93
166
|
if (isRange(token)) {
|
|
94
167
|
const tokenValue = token.value;
|
|
95
|
-
const ref =
|
|
168
|
+
const ref = parseR1C1Ref(tokenValue, { allowTernary: true });
|
|
96
169
|
const d = ref.range;
|
|
97
170
|
const range = {};
|
|
98
|
-
const r0 = toFixed(d.r0, d.$r0, anchor.top, MAX_ROWS);
|
|
99
|
-
const r1 = toFixed(d.r1, d.$r1, anchor.top, MAX_ROWS);
|
|
171
|
+
const r0 = toFixed(d.r0, d.$r0, anchor.top, MAX_ROWS, opts.wrapEdges);
|
|
172
|
+
const r1 = toFixed(d.r1, d.$r1, anchor.top, MAX_ROWS, opts.wrapEdges);
|
|
100
173
|
if (r0 > r1) {
|
|
101
174
|
range.top = r1;
|
|
102
175
|
range.$top = d.$r1;
|
|
@@ -109,8 +182,8 @@ export function translateToA1 (fx, anchorCell) {
|
|
|
109
182
|
range.bottom = r1;
|
|
110
183
|
range.$bottom = d.$r1;
|
|
111
184
|
}
|
|
112
|
-
const c0 = toFixed(d.c0, d.$c0, anchor.left, MAX_COLS);
|
|
113
|
-
const c1 = toFixed(d.c1, d.$c1, anchor.left, MAX_COLS);
|
|
185
|
+
const c0 = toFixed(d.c0, d.$c0, anchor.left, MAX_COLS, opts.wrapEdges);
|
|
186
|
+
const c1 = toFixed(d.c1, d.$c1, anchor.left, MAX_COLS, opts.wrapEdges);
|
|
114
187
|
if (c0 > c1) {
|
|
115
188
|
range.left = c1;
|
|
116
189
|
range.$left = d.$c1;
|
|
@@ -123,18 +196,26 @@ export function translateToA1 (fx, anchorCell) {
|
|
|
123
196
|
range.right = c1;
|
|
124
197
|
range.$right = d.$c1;
|
|
125
198
|
}
|
|
126
|
-
|
|
127
|
-
|
|
199
|
+
if (isNaN(r0) || isNaN(r1) || isNaN(c0) || isNaN(c1)) {
|
|
200
|
+
// convert to ref error
|
|
201
|
+
token.type = ERROR;
|
|
202
|
+
token.value = '#REF!';
|
|
203
|
+
delete token.groupId;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
ref.range = range;
|
|
207
|
+
token.value = stringifyA1Ref(ref);
|
|
208
|
+
}
|
|
128
209
|
// if token includes offsets, those offsets are now likely wrong!
|
|
129
|
-
if (token.
|
|
130
|
-
token.
|
|
210
|
+
if (token.loc) {
|
|
211
|
+
token.loc[0] += offsetSkew;
|
|
131
212
|
offsetSkew += token.value.length - tokenValue.length;
|
|
132
|
-
token.
|
|
213
|
+
token.loc[1] += offsetSkew;
|
|
133
214
|
}
|
|
134
215
|
}
|
|
135
|
-
else if (offsetSkew && token.
|
|
136
|
-
token.
|
|
137
|
-
token.
|
|
216
|
+
else if (offsetSkew && token.loc) {
|
|
217
|
+
token.loc[0] += offsetSkew;
|
|
218
|
+
token.loc[1] += offsetSkew;
|
|
138
219
|
}
|
|
139
220
|
});
|
|
140
221
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgar/fx",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.2",
|
|
4
4
|
"description": "Utilities for working with Excel formulas",
|
|
5
5
|
"main": "dist/fx.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"version": "npm run build",
|
|
16
16
|
"lint": "eslint lib/*.js",
|
|
17
17
|
"test": "tape lib/*.spec.js | tap-min",
|
|
18
|
+
"build:docs": "echo '# @borgar/fx API\n'>docs/API.md; jsdoc -c .jsdoc/config.json -d console lib>>docs/API.md",
|
|
18
19
|
"build": "NODE_ENV=production rollup -c"
|
|
19
20
|
},
|
|
20
21
|
"repository": {
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"@rollup/plugin-babel": "~6.0.3",
|
|
46
47
|
"babel-eslint": "~10.1.0",
|
|
47
48
|
"eslint": "~8.34.0",
|
|
49
|
+
"jsdoc": "~4.0.2",
|
|
48
50
|
"rollup": "~3.17.2",
|
|
49
51
|
"rollup-plugin-minification": "~0.2.0",
|
|
50
52
|
"tap-min": "~2.0.0",
|
package/References.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# References and Ranges
|
|
2
|
-
|
|
3
|
-
In Excels spreadsheet formula language terminology, a reference is similar to what is in most programming is called a variable. Spreadsheets do not have variables though, they have cells. The cells can be referenced in formulas, either directly (such as `=SUM(A1)`), or through aliases (such as `=SUM(someName)`).
|
|
4
|
-
|
|
5
|
-
A range is when a cell, or a set of cells, is referenced directly. Ranges in formulas can come in one of two syntax styles: The commonly known A1 style, as well as R1C1 style where both axes are numerical. Only one style can be used at a time in a formula.
|
|
6
|
-
|
|
7
|
-
This tokenizer considers there to be three "types" of ranges:
|
|
8
|
-
|
|
9
|
-
## Ranges (`RANGE`)
|
|
10
|
-
|
|
11
|
-
The basic type of range will be referencing either:
|
|
12
|
-
|
|
13
|
-
* A single cell, like `A1` or `AF31`.
|
|
14
|
-
* A bounded rectangle of cells, like `A1:B2` or `AF17:AF31`.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Range ternary (`RANGE_TERNARY`)
|
|
18
|
-
|
|
19
|
-
Ternary ranges are rectangles of cells defined by only three of the four possible sides. They are are unbounded in either bottom or right dimension:
|
|
20
|
-
|
|
21
|
-
* A rectangle of cells that is unbounded to the bottom, like `A1:A` or `C3:D`.
|
|
22
|
-
* A rectangle of cells that is unbounded to the right, like `A1:1` or `F2:5`.
|
|
23
|
-
|
|
24
|
-
This type of range is not supported in Excel, so it is an opt-in for the tokenizer (see README.md).
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## Range beams (`RANGE_BEAM`)
|
|
28
|
-
|
|
29
|
-
Range beams are rectangles of cells that are unbounded in either left and right, or top and bottom dimensions.
|
|
30
|
-
|
|
31
|
-
* A rectangle of cells that is unbounded to the top and bottom, like `A:A` or `C:D`.
|
|
32
|
-
* A rectangle of cells that is unbounded to the left and right, like `1:1` or `2:5`.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
Spreadsheet applications will normalize all of these types when you enter a formula, flipping the left/right and top/bottom coordinates as needed to keep the range top to bottom and left to right.
|
|
37
|
-
|
|
38
|
-
The library has tools to both normalize the ranges, as well as filling in the missing boundaries (see README.md).
|
|
39
|
-
|