@borgar/fx 4.10.2 → 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/dist/fx.js +2 -2
- package/lib/a1.js +2 -56
- package/lib/a1.spec.js +0 -11
- package/lib/addTokenMeta.js +1 -1
- package/lib/fixRanges.js +2 -1
- package/lib/fromCol.js +32 -0
- package/lib/fromCol.spec.js +11 -0
- package/lib/index.js +5 -6
- package/lib/isType.spec.js +168 -0
- package/lib/lexerParts.js +2 -2
- package/lib/parseSRange.js +165 -0
- package/lib/parseStructRef.js +48 -0
- package/lib/parseStructRef.spec.js +160 -0
- package/lib/stringifyStructRef.js +80 -0
- package/lib/{sr.spec.js → stringifyStructRef.spec.js} +3 -151
- package/lib/toCol.js +23 -0
- package/lib/toCol.spec.js +11 -0
- package/package.json +2 -2
- package/rollup.config.mjs +5 -5
- package/lib/sr.js +0 -293
package/lib/a1.js
CHANGED
|
@@ -1,66 +1,12 @@
|
|
|
1
1
|
import { MAX_ROWS, MAX_COLS } from './constants.js';
|
|
2
2
|
import { parseRef } from './parseRef.js';
|
|
3
|
+
import { toCol } from './toCol.js';
|
|
4
|
+
import { fromCol } from './fromCol.js';
|
|
3
5
|
import { stringifyPrefix, stringifyPrefixAlt } from './stringifyPrefix.js';
|
|
4
6
|
|
|
5
7
|
const clamp = (min, val, max) => Math.min(Math.max(val, min), max);
|
|
6
8
|
const toColStr = (c, a) => (a ? '$' : '') + toCol(c);
|
|
7
9
|
const toRowStr = (r, a) => (a ? '$' : '') + toRow(r);
|
|
8
|
-
const charFrom = String.fromCharCode;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Convert a column string representation to a 0 based
|
|
12
|
-
* offset number (`"C"` = `2`).
|
|
13
|
-
*
|
|
14
|
-
* The method expects a valid column identifier made up of _only_
|
|
15
|
-
* A-Z letters, which may be either upper or lower case. Other input will
|
|
16
|
-
* return garbage.
|
|
17
|
-
*
|
|
18
|
-
* @param {string} columnString The column string identifier
|
|
19
|
-
* @returns {number} Zero based column index number
|
|
20
|
-
*/
|
|
21
|
-
export function fromCol (columnString) {
|
|
22
|
-
const x = (columnString || '');
|
|
23
|
-
const l = x.length;
|
|
24
|
-
let n = 0;
|
|
25
|
-
if (l > 2) {
|
|
26
|
-
const c = x.charCodeAt(l - 3);
|
|
27
|
-
const a = c > 95 ? 32 : 0;
|
|
28
|
-
n += (1 + c - a - 65) * 676;
|
|
29
|
-
}
|
|
30
|
-
if (l > 1) {
|
|
31
|
-
const c = x.charCodeAt(l - 2);
|
|
32
|
-
const a = c > 95 ? 32 : 0;
|
|
33
|
-
n += (1 + c - a - 65) * 26;
|
|
34
|
-
}
|
|
35
|
-
if (l) {
|
|
36
|
-
const c = x.charCodeAt(l - 1);
|
|
37
|
-
const a = c > 95 ? 32 : 0;
|
|
38
|
-
n += (c - a) - 65;
|
|
39
|
-
}
|
|
40
|
-
return n;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Convert a 0 based offset number to a column string
|
|
45
|
-
* representation (`2` = `"C"`).
|
|
46
|
-
*
|
|
47
|
-
* The method expects a number between 0 and 16383. Other input will
|
|
48
|
-
* return garbage.
|
|
49
|
-
*
|
|
50
|
-
* @param {number} columnIndex Zero based column index number
|
|
51
|
-
* @returns {string} The column string identifier
|
|
52
|
-
*/
|
|
53
|
-
export function toCol (columnIndex) {
|
|
54
|
-
return (
|
|
55
|
-
(columnIndex >= 702
|
|
56
|
-
? charFrom(((((columnIndex - 702) / 676) - 0) % 26) + 65)
|
|
57
|
-
: '') +
|
|
58
|
-
(columnIndex >= 26
|
|
59
|
-
? charFrom(((((columnIndex / 26) - 1) % 26) + 65))
|
|
60
|
-
: '') +
|
|
61
|
-
charFrom((columnIndex % 26) + 65)
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
10
|
|
|
65
11
|
export function fromRow (rowStr) {
|
|
66
12
|
return +rowStr - 1;
|
package/lib/a1.spec.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/* eslint-disable object-property-newline, object-curly-newline */
|
|
2
2
|
import { test, Test } from 'tape';
|
|
3
3
|
import {
|
|
4
|
-
fromCol,
|
|
5
|
-
toCol,
|
|
6
4
|
fromRow,
|
|
7
5
|
toRow,
|
|
8
6
|
toRelative,
|
|
@@ -33,15 +31,6 @@ Test.prototype.isA1Equal = function isA1Equal (expr, expect, opts) {
|
|
|
33
31
|
|
|
34
32
|
// What happens when B2:A1 -> should work!
|
|
35
33
|
test('convert to and from column and row ids', t => {
|
|
36
|
-
t.is(fromCol('a'), 0);
|
|
37
|
-
t.is(fromCol('A'), 0);
|
|
38
|
-
t.is(fromCol('AA'), 26);
|
|
39
|
-
t.is(fromCol('zz'), 701);
|
|
40
|
-
t.is(fromCol('ZZZ'), 18277);
|
|
41
|
-
t.is(toCol(0), 'A');
|
|
42
|
-
t.is(toCol(26), 'AA');
|
|
43
|
-
t.is(toCol(701), 'ZZ');
|
|
44
|
-
t.is(toCol(18277), 'ZZZ');
|
|
45
34
|
t.is(fromRow('11'), 10);
|
|
46
35
|
t.is(fromRow('1'), 0);
|
|
47
36
|
t.is(toRow(12), '13');
|
package/lib/addTokenMeta.js
CHANGED
package/lib/fixRanges.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRange } from './isType.js';
|
|
2
2
|
import { parseA1Ref, stringifyA1Ref, addA1RangeBounds } from './a1.js';
|
|
3
|
-
import { parseStructRef
|
|
3
|
+
import { parseStructRef } from './parseStructRef.js';
|
|
4
|
+
import { stringifyStructRef } from './stringifyStructRef.js';
|
|
4
5
|
import { tokenize } from './lexer.js';
|
|
5
6
|
import { REF_STRUCT } from './constants.js';
|
|
6
7
|
|
package/lib/fromCol.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a column string representation to a 0 based
|
|
3
|
+
* offset number (`"C"` = `2`).
|
|
4
|
+
*
|
|
5
|
+
* The method expects a valid column identifier made up of _only_
|
|
6
|
+
* A-Z letters, which may be either upper or lower case. Other input will
|
|
7
|
+
* return garbage.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} columnString The column string identifier
|
|
10
|
+
* @returns {number} Zero based column index number
|
|
11
|
+
*/
|
|
12
|
+
export function fromCol (columnString) {
|
|
13
|
+
const x = (columnString || '');
|
|
14
|
+
const l = x.length;
|
|
15
|
+
let n = 0;
|
|
16
|
+
if (l > 2) {
|
|
17
|
+
const c = x.charCodeAt(l - 3);
|
|
18
|
+
const a = c > 95 ? 32 : 0;
|
|
19
|
+
n += (1 + c - a - 65) * 676;
|
|
20
|
+
}
|
|
21
|
+
if (l > 1) {
|
|
22
|
+
const c = x.charCodeAt(l - 2);
|
|
23
|
+
const a = c > 95 ? 32 : 0;
|
|
24
|
+
n += (1 + c - a - 65) * 26;
|
|
25
|
+
}
|
|
26
|
+
if (l) {
|
|
27
|
+
const c = x.charCodeAt(l - 1);
|
|
28
|
+
const a = c > 95 ? 32 : 0;
|
|
29
|
+
n += (c - a) - 65;
|
|
30
|
+
}
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { test } from 'tape';
|
|
2
|
+
import { fromCol } from './fromCol.js';
|
|
3
|
+
|
|
4
|
+
test('fromCol parses column id strings to numbers', t => {
|
|
5
|
+
t.is(fromCol('a'), 0);
|
|
6
|
+
t.is(fromCol('A'), 0);
|
|
7
|
+
t.is(fromCol('AA'), 26);
|
|
8
|
+
t.is(fromCol('zz'), 701);
|
|
9
|
+
t.is(fromCol('ZZZ'), 18277);
|
|
10
|
+
t.end();
|
|
11
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -16,9 +16,10 @@ export {
|
|
|
16
16
|
isWhitespace
|
|
17
17
|
} from './isType.js';
|
|
18
18
|
|
|
19
|
+
export { fromCol } from './fromCol.js';
|
|
20
|
+
export { toCol } from './toCol.js';
|
|
21
|
+
|
|
19
22
|
export {
|
|
20
|
-
fromCol,
|
|
21
|
-
toCol,
|
|
22
23
|
parseA1Ref,
|
|
23
24
|
stringifyA1Ref,
|
|
24
25
|
addA1RangeBounds
|
|
@@ -29,10 +30,8 @@ export {
|
|
|
29
30
|
stringifyR1C1Ref
|
|
30
31
|
} from './rc.js';
|
|
31
32
|
|
|
32
|
-
export {
|
|
33
|
-
|
|
34
|
-
stringifyStructRef
|
|
35
|
-
} from './sr.js';
|
|
33
|
+
export { stringifyStructRef } from './stringifyStructRef.js';
|
|
34
|
+
export { parseStructRef } from './parseStructRef.js';
|
|
36
35
|
|
|
37
36
|
import {
|
|
38
37
|
// token types
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { test } from 'tape';
|
|
2
|
+
import {
|
|
3
|
+
REF_RANGE, REF_BEAM, REF_NAMED, REF_TERNARY, REF_STRUCT,
|
|
4
|
+
FX_PREFIX, WHITESPACE, NEWLINE,
|
|
5
|
+
FUNCTION, OPERATOR,
|
|
6
|
+
ERROR, STRING, NUMBER, BOOLEAN
|
|
7
|
+
} from './constants.js';
|
|
8
|
+
import { isRange, isReference, isLiteral, isError, isWhitespace, isFunction, isFxPrefix, isOperator } from './isType.js';
|
|
9
|
+
|
|
10
|
+
test('isRange', t => {
|
|
11
|
+
t.is(isRange(null), false, 'null is not a range');
|
|
12
|
+
t.is(isRange({}), false, 'plain object is not a range');
|
|
13
|
+
t.is(isRange({ type: BOOLEAN }), false, 'BOOLEAN is not a range');
|
|
14
|
+
t.is(isRange({ type: ERROR }), false, 'ERROR is not a range');
|
|
15
|
+
t.is(isRange({ type: FUNCTION }), false, 'FUNCTION is not a range');
|
|
16
|
+
t.is(isRange({ type: FX_PREFIX }), false, 'FX_PREFIX is not a range');
|
|
17
|
+
t.is(isRange({ type: NEWLINE }), false, 'NEWLINE is not a range');
|
|
18
|
+
t.is(isRange({ type: NUMBER }), false, 'NUMBER is not a range');
|
|
19
|
+
t.is(isRange({ type: OPERATOR }), false, 'OPERATOR is not a range');
|
|
20
|
+
t.is(isRange({ type: REF_BEAM }), true, 'REF_BEAM is a range');
|
|
21
|
+
t.is(isRange({ type: REF_NAMED }), false, 'REF_NAMED is not a range');
|
|
22
|
+
t.is(isRange({ type: REF_RANGE }), true, 'REF_RANGE is a range');
|
|
23
|
+
t.is(isRange({ type: REF_STRUCT }), false, 'REF_STRUCT is not a range');
|
|
24
|
+
t.is(isRange({ type: REF_TERNARY }), true, 'REF_TERNARY is a range');
|
|
25
|
+
t.is(isRange({ type: STRING }), false, 'STRING is not a range');
|
|
26
|
+
t.is(isRange({ type: WHITESPACE }), false, 'WHITESPACE is not a range');
|
|
27
|
+
t.end();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('isReference', t => {
|
|
31
|
+
t.is(isReference(null), false, 'null is not a reference');
|
|
32
|
+
t.is(isReference({}), false, 'plain object is not a reference');
|
|
33
|
+
t.is(isReference({ type: BOOLEAN }), false, 'BOOLEAN is not a reference');
|
|
34
|
+
t.is(isReference({ type: ERROR }), false, 'ERROR is not a reference');
|
|
35
|
+
t.is(isReference({ type: FUNCTION }), false, 'FUNCTION is not a reference');
|
|
36
|
+
t.is(isReference({ type: FX_PREFIX }), false, 'FX_PREFIX is not a reference');
|
|
37
|
+
t.is(isReference({ type: NEWLINE }), false, 'NEWLINE is not a reference');
|
|
38
|
+
t.is(isReference({ type: NUMBER }), false, 'NUMBER is not a reference');
|
|
39
|
+
t.is(isReference({ type: OPERATOR }), false, 'OPERATOR is not a reference');
|
|
40
|
+
t.is(isReference({ type: REF_BEAM }), true, 'REF_BEAM is a reference');
|
|
41
|
+
t.is(isReference({ type: REF_NAMED }), true, 'REF_NAMED is not a reference');
|
|
42
|
+
t.is(isReference({ type: REF_RANGE }), true, 'REF_RANGE is a reference');
|
|
43
|
+
t.is(isReference({ type: REF_STRUCT }), true, 'REF_STRUCT is not a reference');
|
|
44
|
+
t.is(isReference({ type: REF_TERNARY }), true, 'REF_TERNARY is a reference');
|
|
45
|
+
t.is(isReference({ type: STRING }), false, 'STRING is not a reference');
|
|
46
|
+
t.is(isReference({ type: WHITESPACE }), false, 'WHITESPACE is not a reference');
|
|
47
|
+
t.end();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('isLiteral', t => {
|
|
51
|
+
t.is(isLiteral(null), false, 'null is not a literal');
|
|
52
|
+
t.is(isLiteral({}), false, 'plain object is not a literal');
|
|
53
|
+
t.is(isLiteral({ type: BOOLEAN }), true, 'BOOLEAN is not a literal');
|
|
54
|
+
t.is(isLiteral({ type: ERROR }), true, 'ERROR is not a literal');
|
|
55
|
+
t.is(isLiteral({ type: FUNCTION }), false, 'FUNCTION is not a literal');
|
|
56
|
+
t.is(isLiteral({ type: FX_PREFIX }), false, 'FX_PREFIX is not a literal');
|
|
57
|
+
t.is(isLiteral({ type: NEWLINE }), false, 'NEWLINE is not a literal');
|
|
58
|
+
t.is(isLiteral({ type: NUMBER }), true, 'NUMBER is not a literal');
|
|
59
|
+
t.is(isLiteral({ type: OPERATOR }), false, 'OPERATOR is not a literal');
|
|
60
|
+
t.is(isLiteral({ type: REF_BEAM }), false, 'REF_BEAM is a literal');
|
|
61
|
+
t.is(isLiteral({ type: REF_NAMED }), false, 'REF_NAMED is not a literal');
|
|
62
|
+
t.is(isLiteral({ type: REF_RANGE }), false, 'REF_RANGE is a literal');
|
|
63
|
+
t.is(isLiteral({ type: REF_STRUCT }), false, 'REF_STRUCT is not a literal');
|
|
64
|
+
t.is(isLiteral({ type: REF_TERNARY }), false, 'REF_TERNARY is a literal');
|
|
65
|
+
t.is(isLiteral({ type: STRING }), true, 'STRING is not a literal');
|
|
66
|
+
t.is(isLiteral({ type: WHITESPACE }), false, 'WHITESPACE is not a literal');
|
|
67
|
+
t.end();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('isError', t => {
|
|
71
|
+
t.is(isError(null), false, 'null is not an error');
|
|
72
|
+
t.is(isError({}), false, 'plain object is not an error');
|
|
73
|
+
t.is(isError({ type: BOOLEAN }), false, 'BOOLEAN is not an error');
|
|
74
|
+
t.is(isError({ type: ERROR }), true, 'ERROR is not an error');
|
|
75
|
+
t.is(isError({ type: FUNCTION }), false, 'FUNCTION is not an error');
|
|
76
|
+
t.is(isError({ type: FX_PREFIX }), false, 'FX_PREFIX is not an error');
|
|
77
|
+
t.is(isError({ type: NEWLINE }), false, 'NEWLINE is not an error');
|
|
78
|
+
t.is(isError({ type: NUMBER }), false, 'NUMBER is not an error');
|
|
79
|
+
t.is(isError({ type: OPERATOR }), false, 'OPERATOR is not an error');
|
|
80
|
+
t.is(isError({ type: REF_BEAM }), false, 'REF_BEAM is an error');
|
|
81
|
+
t.is(isError({ type: REF_NAMED }), false, 'REF_NAMED is not an error');
|
|
82
|
+
t.is(isError({ type: REF_RANGE }), false, 'REF_RANGE is an error');
|
|
83
|
+
t.is(isError({ type: REF_STRUCT }), false, 'REF_STRUCT is not an error');
|
|
84
|
+
t.is(isError({ type: REF_TERNARY }), false, 'REF_TERNARY is an error');
|
|
85
|
+
t.is(isError({ type: STRING }), false, 'STRING is not an error');
|
|
86
|
+
t.is(isError({ type: WHITESPACE }), false, 'WHITESPACE is not an error');
|
|
87
|
+
t.end();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('isWhitespace', t => {
|
|
91
|
+
t.is(isWhitespace(null), false, 'null is not a whitespace');
|
|
92
|
+
t.is(isWhitespace({}), false, 'plain object is not a whitespace');
|
|
93
|
+
t.is(isWhitespace({ type: BOOLEAN }), false, 'BOOLEAN is not a whitespace');
|
|
94
|
+
t.is(isWhitespace({ type: ERROR }), false, 'ERROR is not a whitespace');
|
|
95
|
+
t.is(isWhitespace({ type: FUNCTION }), false, 'FUNCTION is not a whitespace');
|
|
96
|
+
t.is(isWhitespace({ type: FX_PREFIX }), false, 'FX_PREFIX is not a whitespace');
|
|
97
|
+
t.is(isWhitespace({ type: NEWLINE }), true, 'NEWLINE is not a whitespace');
|
|
98
|
+
t.is(isWhitespace({ type: NUMBER }), false, 'NUMBER is not a whitespace');
|
|
99
|
+
t.is(isWhitespace({ type: OPERATOR }), false, 'OPERATOR is not a whitespace');
|
|
100
|
+
t.is(isWhitespace({ type: REF_BEAM }), false, 'REF_BEAM is a whitespace');
|
|
101
|
+
t.is(isWhitespace({ type: REF_NAMED }), false, 'REF_NAMED is not a whitespace');
|
|
102
|
+
t.is(isWhitespace({ type: REF_RANGE }), false, 'REF_RANGE is a whitespace');
|
|
103
|
+
t.is(isWhitespace({ type: REF_STRUCT }), false, 'REF_STRUCT is not a whitespace');
|
|
104
|
+
t.is(isWhitespace({ type: REF_TERNARY }), false, 'REF_TERNARY is a whitespace');
|
|
105
|
+
t.is(isWhitespace({ type: STRING }), false, 'STRING is not a whitespace');
|
|
106
|
+
t.is(isWhitespace({ type: WHITESPACE }), true, 'WHITESPACE is not a whitespace');
|
|
107
|
+
t.end();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('isFunction', t => {
|
|
111
|
+
t.is(isFunction(null), false, 'null is not a function');
|
|
112
|
+
t.is(isFunction({}), false, 'plain object is not a function');
|
|
113
|
+
t.is(isFunction({ type: BOOLEAN }), false, 'BOOLEAN is not a function');
|
|
114
|
+
t.is(isFunction({ type: ERROR }), false, 'ERROR is not a function');
|
|
115
|
+
t.is(isFunction({ type: FUNCTION }), true, 'FUNCTION is not a function');
|
|
116
|
+
t.is(isFunction({ type: FX_PREFIX }), false, 'FX_PREFIX is not a function');
|
|
117
|
+
t.is(isFunction({ type: NEWLINE }), false, 'NEWLINE is not a function');
|
|
118
|
+
t.is(isFunction({ type: NUMBER }), false, 'NUMBER is not a function');
|
|
119
|
+
t.is(isFunction({ type: OPERATOR }), false, 'OPERATOR is not a function');
|
|
120
|
+
t.is(isFunction({ type: REF_BEAM }), false, 'REF_BEAM is a function');
|
|
121
|
+
t.is(isFunction({ type: REF_NAMED }), false, 'REF_NAMED is not a function');
|
|
122
|
+
t.is(isFunction({ type: REF_RANGE }), false, 'REF_RANGE is a function');
|
|
123
|
+
t.is(isFunction({ type: REF_STRUCT }), false, 'REF_STRUCT is not a function');
|
|
124
|
+
t.is(isFunction({ type: REF_TERNARY }), false, 'REF_TERNARY is a function');
|
|
125
|
+
t.is(isFunction({ type: STRING }), false, 'STRING is not a function');
|
|
126
|
+
t.is(isFunction({ type: WHITESPACE }), false, 'WHITESPACE is not a function');
|
|
127
|
+
t.end();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('isFxPrefix', t => {
|
|
131
|
+
t.is(isFxPrefix(null), false, 'null is not a prefix');
|
|
132
|
+
t.is(isFxPrefix({}), false, 'plain object is not a prefix');
|
|
133
|
+
t.is(isFxPrefix({ type: BOOLEAN }), false, 'BOOLEAN is not a prefix');
|
|
134
|
+
t.is(isFxPrefix({ type: ERROR }), false, 'ERROR is not a prefix');
|
|
135
|
+
t.is(isFxPrefix({ type: FUNCTION }), false, 'FUNCTION is not a prefix');
|
|
136
|
+
t.is(isFxPrefix({ type: FX_PREFIX }), true, 'FX_PREFIX is not a prefix');
|
|
137
|
+
t.is(isFxPrefix({ type: NEWLINE }), false, 'NEWLINE is not a prefix');
|
|
138
|
+
t.is(isFxPrefix({ type: NUMBER }), false, 'NUMBER is not a prefix');
|
|
139
|
+
t.is(isFxPrefix({ type: OPERATOR }), false, 'OPERATOR is not a prefix');
|
|
140
|
+
t.is(isFxPrefix({ type: REF_BEAM }), false, 'REF_BEAM is a prefix');
|
|
141
|
+
t.is(isFxPrefix({ type: REF_NAMED }), false, 'REF_NAMED is not a prefix');
|
|
142
|
+
t.is(isFxPrefix({ type: REF_RANGE }), false, 'REF_RANGE is a prefix');
|
|
143
|
+
t.is(isFxPrefix({ type: REF_STRUCT }), false, 'REF_STRUCT is not a prefix');
|
|
144
|
+
t.is(isFxPrefix({ type: REF_TERNARY }), false, 'REF_TERNARY is a prefix');
|
|
145
|
+
t.is(isFxPrefix({ type: STRING }), false, 'STRING is not a prefix');
|
|
146
|
+
t.is(isFxPrefix({ type: WHITESPACE }), false, 'WHITESPACE is not a prefix');
|
|
147
|
+
t.end();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('isOperator', t => {
|
|
151
|
+
t.is(isOperator(null), false, 'null is not a operator');
|
|
152
|
+
t.is(isOperator({}), false, 'plain object is not a operator');
|
|
153
|
+
t.is(isOperator({ type: BOOLEAN }), false, 'BOOLEAN is not a operator');
|
|
154
|
+
t.is(isOperator({ type: ERROR }), false, 'ERROR is not a operator');
|
|
155
|
+
t.is(isOperator({ type: FUNCTION }), false, 'FUNCTION is not a operator');
|
|
156
|
+
t.is(isOperator({ type: FX_PREFIX }), false, 'FX_PREFIX is not a operator');
|
|
157
|
+
t.is(isOperator({ type: NEWLINE }), false, 'NEWLINE is not a operator');
|
|
158
|
+
t.is(isOperator({ type: NUMBER }), false, 'NUMBER is not a operator');
|
|
159
|
+
t.is(isOperator({ type: OPERATOR }), true, 'OPERATOR is not a operator');
|
|
160
|
+
t.is(isOperator({ type: REF_BEAM }), false, 'REF_BEAM is a operator');
|
|
161
|
+
t.is(isOperator({ type: REF_NAMED }), false, 'REF_NAMED is not a operator');
|
|
162
|
+
t.is(isOperator({ type: REF_RANGE }), false, 'REF_RANGE is a operator');
|
|
163
|
+
t.is(isOperator({ type: REF_STRUCT }), false, 'REF_STRUCT is not a operator');
|
|
164
|
+
t.is(isOperator({ type: REF_TERNARY }), false, 'REF_TERNARY is a operator');
|
|
165
|
+
t.is(isOperator({ type: STRING }), false, 'STRING is not a operator');
|
|
166
|
+
t.is(isOperator({ type: WHITESPACE }), false, 'WHITESPACE is not a operator');
|
|
167
|
+
t.end();
|
|
168
|
+
});
|
package/lib/lexerParts.js
CHANGED
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
MAX_ROWS,
|
|
19
19
|
OPERATOR_TRIM
|
|
20
20
|
} from './constants.js';
|
|
21
|
-
import { fromCol } from './
|
|
22
|
-
import { parseSRange } from './
|
|
21
|
+
import { fromCol } from './fromCol.js';
|
|
22
|
+
import { parseSRange } from './parseSRange.js';
|
|
23
23
|
|
|
24
24
|
const re_ERROR = /^#(NAME\?|FIELD!|CALC!|VALUE!|REF!|DIV\/0!|NULL!|NUM!|N\/A|GETTING_DATA\b|SPILL!|UNKNOWN!|FIELD\b|CALC\b|SYNTAX\?|ERROR!|CONNECT!|BLOCKED!|EXTERNAL!)/i;
|
|
25
25
|
const re_OPERATOR = /^(<=|>=|<>|[-+/*^%&<>=]|[{},;]|[()]|@|:|!|#)/;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const re_SRcolumnB = /^\[('['#@[\]]|[^'#@[\]])+\]/i;
|
|
2
|
+
const re_SRcolumnN = /^([^#@[\]:]+)/i;
|
|
3
|
+
|
|
4
|
+
const keyTerms = {
|
|
5
|
+
'headers': 1,
|
|
6
|
+
'data': 2,
|
|
7
|
+
'totals': 4,
|
|
8
|
+
'all': 8,
|
|
9
|
+
'this row': 16,
|
|
10
|
+
'@': 16
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const fz = (...a) => Object.freeze(a);
|
|
14
|
+
|
|
15
|
+
// only combinations allowed are: #data + (#headers | #totals | #data)
|
|
16
|
+
const sectionMap = {
|
|
17
|
+
// no terms
|
|
18
|
+
0: fz(),
|
|
19
|
+
// single term
|
|
20
|
+
1: fz('headers'),
|
|
21
|
+
2: fz('data'),
|
|
22
|
+
4: fz('totals'),
|
|
23
|
+
8: fz('all'),
|
|
24
|
+
16: fz('this row'),
|
|
25
|
+
// headers+data
|
|
26
|
+
3: fz('headers', 'data'),
|
|
27
|
+
// totals+data
|
|
28
|
+
6: fz('data', 'totals')
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const matchColumn = (s, allowUnbraced = true) => {
|
|
32
|
+
let m = re_SRcolumnB.exec(s);
|
|
33
|
+
if (m) {
|
|
34
|
+
const value = m[0].slice(1, -1).replace(/'(['#@[\]])/g, '$1');
|
|
35
|
+
return [ m[0], value ];
|
|
36
|
+
}
|
|
37
|
+
if (allowUnbraced) {
|
|
38
|
+
m = re_SRcolumnN.exec(s);
|
|
39
|
+
if (m) {
|
|
40
|
+
return [ m[0], m[0] ];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function parseSRange (raw) {
|
|
47
|
+
const columns = [];
|
|
48
|
+
let pos = 0;
|
|
49
|
+
let s = raw;
|
|
50
|
+
let m;
|
|
51
|
+
let m1;
|
|
52
|
+
let terms = 0;
|
|
53
|
+
|
|
54
|
+
// start of structured ref?
|
|
55
|
+
if ((m = /^(\[\s*)/.exec(s))) {
|
|
56
|
+
// quickly determine if this is a simple keyword or column
|
|
57
|
+
// [#keyword]
|
|
58
|
+
if ((m1 = /^\[#([a-z ]+)\]/i.exec(s))) {
|
|
59
|
+
const k = m1[1].toLowerCase();
|
|
60
|
+
pos += m1[0].length;
|
|
61
|
+
if (keyTerms[k]) {
|
|
62
|
+
terms |= keyTerms[k];
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// [column]
|
|
69
|
+
else if ((m1 = matchColumn(s, false))) {
|
|
70
|
+
pos += m1[0].length;
|
|
71
|
+
columns.push(m1[1]);
|
|
72
|
+
}
|
|
73
|
+
// use the "normal" method
|
|
74
|
+
// [[#keyword]]
|
|
75
|
+
// [[column]]
|
|
76
|
+
// [@]
|
|
77
|
+
// [@column]
|
|
78
|
+
// [@[column]]
|
|
79
|
+
// [@column:column]
|
|
80
|
+
// [@column:[column]]
|
|
81
|
+
// [@[column]:column]
|
|
82
|
+
// [@[column]:[column]]
|
|
83
|
+
// [column:column]
|
|
84
|
+
// [column:[column]]
|
|
85
|
+
// [[column]:column]
|
|
86
|
+
// [[column]:[column]]
|
|
87
|
+
// [[#keyword],column]
|
|
88
|
+
// [[#keyword],column:column]
|
|
89
|
+
// [[#keyword],[#keyword],column:column]
|
|
90
|
+
// ...
|
|
91
|
+
else {
|
|
92
|
+
let expect_more = true;
|
|
93
|
+
s = s.slice(m[1].length);
|
|
94
|
+
pos += m[1].length;
|
|
95
|
+
// match keywords as we find them
|
|
96
|
+
while (
|
|
97
|
+
expect_more &&
|
|
98
|
+
(m = /^\[#([a-z ]+)\](\s*,\s*)?/i.exec(s))
|
|
99
|
+
) {
|
|
100
|
+
const k = m[1].toLowerCase();
|
|
101
|
+
if (keyTerms[k]) {
|
|
102
|
+
terms |= keyTerms[k];
|
|
103
|
+
s = s.slice(m[0].length);
|
|
104
|
+
pos += m[0].length;
|
|
105
|
+
expect_more = !!m[2];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// is there an @ specifier?
|
|
112
|
+
if (expect_more && (m = /^@/.exec(s))) {
|
|
113
|
+
terms |= keyTerms['@'];
|
|
114
|
+
s = s.slice(1);
|
|
115
|
+
pos += 1;
|
|
116
|
+
expect_more = s[0] !== ']';
|
|
117
|
+
}
|
|
118
|
+
// not all keyword terms may be combined
|
|
119
|
+
if (!(terms in sectionMap)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
// column definitions
|
|
123
|
+
const leftCol = expect_more ? matchColumn(raw.slice(pos)) : null;
|
|
124
|
+
if (leftCol) {
|
|
125
|
+
pos += leftCol[0].length;
|
|
126
|
+
columns.push(leftCol[1]);
|
|
127
|
+
s = raw.slice(pos);
|
|
128
|
+
if (s[0] === ':') {
|
|
129
|
+
s = s.slice(1);
|
|
130
|
+
pos++;
|
|
131
|
+
const rightCol = matchColumn(s);
|
|
132
|
+
if (rightCol) {
|
|
133
|
+
pos += rightCol[0].length;
|
|
134
|
+
columns.push(rightCol[1]);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
expect_more = false;
|
|
141
|
+
}
|
|
142
|
+
// advance ws
|
|
143
|
+
while (raw[pos] === ' ') {
|
|
144
|
+
pos++;
|
|
145
|
+
}
|
|
146
|
+
// close the ref
|
|
147
|
+
if (expect_more || raw[pos] !== ']') {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
// step over the closing ]
|
|
151
|
+
pos++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const sections = sectionMap[terms];
|
|
159
|
+
return {
|
|
160
|
+
columns,
|
|
161
|
+
sections: sections ? sections.concat() : sections,
|
|
162
|
+
length: pos,
|
|
163
|
+
token: raw.slice(0, pos)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseRef } from './parseRef.js';
|
|
2
|
+
import { parseSRange } from './parseSRange.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse a structured reference string into an object representing it.
|
|
6
|
+
*
|
|
7
|
+
* ```js
|
|
8
|
+
* parseStructRef('workbook.xlsx!tableName[[#Data],[Column1]:[Column2]]');
|
|
9
|
+
* // => {
|
|
10
|
+
* // context: [ 'workbook.xlsx' ],
|
|
11
|
+
* // sections: [ 'data' ],
|
|
12
|
+
* // columns: [ 'my column', '@foo' ],
|
|
13
|
+
* // table: 'tableName',
|
|
14
|
+
* // }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* For A:A or A1:A style ranges, `null` will be used for any dimensions that the
|
|
18
|
+
* syntax does not specify:
|
|
19
|
+
*
|
|
20
|
+
* @tutorial References.md
|
|
21
|
+
* @param {string} ref A structured reference string
|
|
22
|
+
* @param {object} [options={}] Options
|
|
23
|
+
* @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)
|
|
24
|
+
* @returns {(ReferenceStruct|null)} An object representing a valid reference or null if it is invalid.
|
|
25
|
+
*/
|
|
26
|
+
export function parseStructRef (ref, options = { xlsx: false }) {
|
|
27
|
+
const r = parseRef(ref, options);
|
|
28
|
+
if (r && r.struct) {
|
|
29
|
+
const structData = parseSRange(r.struct);
|
|
30
|
+
if (structData && structData.length === r.struct.length) {
|
|
31
|
+
return options.xlsx
|
|
32
|
+
? {
|
|
33
|
+
workbookName: r.workbookName,
|
|
34
|
+
sheetName: r.sheetName,
|
|
35
|
+
table: r.name,
|
|
36
|
+
columns: structData.columns,
|
|
37
|
+
sections: structData.sections
|
|
38
|
+
}
|
|
39
|
+
: {
|
|
40
|
+
context: r.context,
|
|
41
|
+
table: r.name,
|
|
42
|
+
columns: structData.columns,
|
|
43
|
+
sections: structData.sections
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|