@borgar/fx 4.13.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.js → advRangeOp.ts} +1 -1
- package/lib/lexers/{canEndRange.js → canEndRange.ts} +2 -2
- package/lib/lexers/{lexBoolean.js → lexBoolean.ts} +25 -6
- package/lib/lexers/{lexContext.js → lexContext.ts} +14 -6
- package/lib/lexers/{lexError.js → lexError.ts} +3 -3
- package/lib/lexers/{lexFunction.js → lexFunction.ts} +3 -2
- package/lib/lexers/lexNameFuncCntx.ts +112 -0
- package/lib/lexers/{lexNamed.js → lexNamed.ts} +4 -4
- package/lib/lexers/{lexNewLine.js → lexNewLine.ts} +3 -2
- package/lib/lexers/{lexNumber.js → lexNumber.ts} +4 -3
- package/lib/lexers/{lexOperator.js → lexOperator.ts} +5 -4
- package/lib/lexers/lexRange.ts +15 -0
- package/lib/lexers/{lexRangeA1.js → lexRangeA1.ts} +11 -7
- package/lib/lexers/{lexRangeR1C1.js → lexRangeR1C1.ts} +10 -6
- package/lib/lexers/{lexRangeTrim.js → lexRangeTrim.ts} +3 -2
- package/lib/lexers/{lexRefOp.js → lexRefOp.ts} +4 -3
- package/lib/lexers/{lexString.js → lexString.ts} +3 -3
- package/lib/lexers/{lexStructured.js → lexStructured.ts} +5 -5
- package/lib/lexers/{lexWhitespace.js → lexWhitespace.ts} +3 -2
- package/lib/lexers/sets.ts +51 -0
- package/lib/mergeRefTokens.spec.ts +141 -0
- package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +14 -9
- 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.js → parseSRange.ts} +15 -10
- 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 +45 -31
- package/tsconfig.json +28 -0
- package/typedoc-ignore-links.ts +17 -0
- package/typedoc.json +41 -0
- package/.eslintrc +0 -22
- package/benchmark/benchmark.js +0 -48
- package/benchmark/formulas.json +0 -15677
- 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 -171
- 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 -264
- package/lib/lexer.spec.js +0 -1953
- package/lib/lexers/lexRange.js +0 -8
- package/lib/lexers/sets.js +0 -38
- 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/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
package/docs/AST_format.md
CHANGED
|
@@ -45,7 +45,7 @@ interface ReferenceIdentifier extends Node {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
An identifier for a range or a
|
|
48
|
+
An identifier for a range or a name.
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
## Literal
|
|
@@ -140,7 +140,7 @@ interface ArrayExpression extends Node {
|
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
An array expression. Excel does not have empty or sparse arrays and restricts array elements to literals. Google Sheets allows `ReferenceIdentifier`s as elements of arrays, the fx parser
|
|
143
|
+
An array expression. Excel does not have empty or sparse arrays and restricts array elements to literals. Google Sheets allows `ReferenceIdentifier`s as elements of arrays, the fx parser has an option for this but it is off by default.
|
|
144
144
|
|
|
145
145
|
## LambdaExpression
|
|
146
146
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import borgarLint from '@borgar/eslint-config';
|
|
2
|
+
import eslint from '@eslint/js';
|
|
3
|
+
import globals from 'globals';
|
|
4
|
+
import tseslint from 'typescript-eslint';
|
|
5
|
+
import { defineConfig } from 'eslint/config';
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
{
|
|
9
|
+
files: [
|
|
10
|
+
'**/*.js',
|
|
11
|
+
'**/*.ts',
|
|
12
|
+
'**/*.mjs'
|
|
13
|
+
],
|
|
14
|
+
ignores: [
|
|
15
|
+
'dist/*'
|
|
16
|
+
],
|
|
17
|
+
languageOptions: {
|
|
18
|
+
globals: { ...globals.browser },
|
|
19
|
+
parserOptions: { projectService: { allowDefaultProject: [] } }
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
eslint.configs.recommended,
|
|
23
|
+
tseslint.configs.recommendedTypeChecked,
|
|
24
|
+
tseslint.configs.stylisticTypeChecked,
|
|
25
|
+
borgarLint.config.recommended,
|
|
26
|
+
borgarLint.config.stylistic({
|
|
27
|
+
commaDangle: false,
|
|
28
|
+
singleBlocks: true,
|
|
29
|
+
lineLength: 120
|
|
30
|
+
}),
|
|
31
|
+
{
|
|
32
|
+
rules: {
|
|
33
|
+
'no-shadow': 'off',
|
|
34
|
+
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
35
|
+
'@typescript-eslint/no-unsafe-call': 'off',
|
|
36
|
+
'@typescript-eslint/prefer-optional-chain': 'off',
|
|
37
|
+
'@typescript-eslint/no-unused-expressions': 'off',
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]);
|
package/lib/a1.spec.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/object-property-newline */
|
|
2
|
+
import { describe, test, expect } from 'vitest';
|
|
3
|
+
import { toRelative, toAbsolute } from './a1.ts';
|
|
4
|
+
import type { RangeA1 } from './types.ts';
|
|
5
|
+
|
|
6
|
+
describe('A1 utilities', () => {
|
|
7
|
+
test('toAbsolute and toRelative', () => {
|
|
8
|
+
const relA1Range = {
|
|
9
|
+
top: 0, left: 0, bottom: 0, right: 0,
|
|
10
|
+
$top: false, $left: false, $bottom: false, $right: false
|
|
11
|
+
};
|
|
12
|
+
const absA1Range = {
|
|
13
|
+
top: 0, left: 0, bottom: 0, right: 0,
|
|
14
|
+
$top: true, $left: true, $bottom: true, $right: true
|
|
15
|
+
};
|
|
16
|
+
expect(toAbsolute(relA1Range)).toEqual(absA1Range);
|
|
17
|
+
expect(toRelative(absA1Range)).toEqual(relA1Range);
|
|
18
|
+
|
|
19
|
+
const relA1RangeT: RangeA1 = {
|
|
20
|
+
top: 0, left: 0, bottom: 0, right: 0,
|
|
21
|
+
$top: false, $left: false, $bottom: false, $right: false,
|
|
22
|
+
trim: 'both'
|
|
23
|
+
};
|
|
24
|
+
const absA1RangeT: RangeA1 = {
|
|
25
|
+
top: 0, left: 0, bottom: 0, right: 0,
|
|
26
|
+
$top: true, $left: true, $bottom: true, $right: true,
|
|
27
|
+
trim: 'both'
|
|
28
|
+
};
|
|
29
|
+
expect(toAbsolute(relA1RangeT)).toEqual(absA1RangeT);
|
|
30
|
+
expect(toRelative(absA1RangeT)).toEqual(relA1RangeT);
|
|
31
|
+
});
|
|
32
|
+
});
|
package/lib/a1.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RangeA1 } from './types.ts';
|
|
2
|
+
|
|
3
|
+
export function toRelative (range: RangeA1): RangeA1 {
|
|
4
|
+
return Object.assign({}, range, { $left: false, $right: false, $top: false, $bottom: false });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function toAbsolute (range: RangeA1): RangeA1 {
|
|
8
|
+
return Object.assign({}, range, { $left: true, $right: true, $top: true, $bottom: true });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function toRow (top: number): string {
|
|
12
|
+
return String(top + 1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function rangeOperator (trim: 'head' | 'tail' | 'both' | null | undefined): string {
|
|
16
|
+
if (trim === 'both') {
|
|
17
|
+
return '.:.';
|
|
18
|
+
}
|
|
19
|
+
else if (trim === 'head') {
|
|
20
|
+
return '.:';
|
|
21
|
+
}
|
|
22
|
+
else if (trim === 'tail') {
|
|
23
|
+
return ':.';
|
|
24
|
+
}
|
|
25
|
+
return ':';
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { MAX_ROWS, MAX_COLS } from './constants.ts';
|
|
2
|
+
import type { RangeA1 } from './types.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fill the any missing bounds in range objects. Top will be set to 0, bottom to
|
|
6
|
+
* 1048575, left to 0, and right to 16383, if they are `null` or `undefined`.
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* addA1RangeBounds({
|
|
10
|
+
* top: 0,
|
|
11
|
+
* left: 0,
|
|
12
|
+
* bottom: 1,
|
|
13
|
+
* $top: true,
|
|
14
|
+
* $left: false,
|
|
15
|
+
* $bottom: false,
|
|
16
|
+
* });
|
|
17
|
+
* // => {
|
|
18
|
+
* // top: 0,
|
|
19
|
+
* // left: 0,
|
|
20
|
+
* // bottom: 1,
|
|
21
|
+
* // right: 16383, // ← Added
|
|
22
|
+
* // $top: true,
|
|
23
|
+
* // $left: false,
|
|
24
|
+
* // $bottom: false,
|
|
25
|
+
* // $right: false // ← Added
|
|
26
|
+
* // }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param range The range part of a reference object.
|
|
30
|
+
* @returns The same range with missing bounds filled in.
|
|
31
|
+
*/
|
|
32
|
+
export function addA1RangeBounds (range: RangeA1): RangeA1 {
|
|
33
|
+
if (range.top == null) {
|
|
34
|
+
range.top = 0;
|
|
35
|
+
range.$top = false;
|
|
36
|
+
}
|
|
37
|
+
if (range.bottom == null) {
|
|
38
|
+
range.bottom = MAX_ROWS;
|
|
39
|
+
range.$bottom = false;
|
|
40
|
+
}
|
|
41
|
+
if (range.left == null) {
|
|
42
|
+
range.left = 0;
|
|
43
|
+
range.$left = false;
|
|
44
|
+
}
|
|
45
|
+
if (range.right == null) {
|
|
46
|
+
range.right = MAX_COLS;
|
|
47
|
+
range.$right = false;
|
|
48
|
+
}
|
|
49
|
+
return range;
|
|
50
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { FX_PREFIX, OPERATOR, NUMBER, REF_RANGE, REF_BEAM, FUNCTION, WHITESPACE, REF_STRUCT } from './constants.ts';
|
|
3
|
+
import { addTokenMeta } from './addTokenMeta.ts';
|
|
4
|
+
import { tokenize } from './tokenize.ts';
|
|
5
|
+
|
|
6
|
+
function isMetaTokens (expr: string, expected: any[], context?: any, opts?: any) {
|
|
7
|
+
const actual = addTokenMeta(tokenize(expr, opts), context);
|
|
8
|
+
if (actual.length === expected.length) {
|
|
9
|
+
actual.forEach((d, i) => {
|
|
10
|
+
const keys = Object.keys(d).concat(Object.keys(expected[i]));
|
|
11
|
+
keys.forEach(key => {
|
|
12
|
+
if (actual[i][key] === expected[i][key]) {
|
|
13
|
+
delete actual[i][key];
|
|
14
|
+
delete expected[i][key];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
expect(actual).toEqual(expected);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('add extra meta to operators', () => {
|
|
23
|
+
test('parens should be grouped and tagged with depth', () => {
|
|
24
|
+
isMetaTokens('=((1)+(1))', [
|
|
25
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
26
|
+
{ index: 1, depth: 1, type: OPERATOR, value: '(', groupId: 'fxg3' },
|
|
27
|
+
{ index: 2, depth: 2, type: OPERATOR, value: '(', groupId: 'fxg1' },
|
|
28
|
+
{ index: 3, depth: 2, type: NUMBER, value: '1' },
|
|
29
|
+
{ index: 4, depth: 2, type: OPERATOR, value: ')', groupId: 'fxg1' },
|
|
30
|
+
{ index: 5, depth: 1, type: OPERATOR, value: '+' },
|
|
31
|
+
{ index: 6, depth: 2, type: OPERATOR, value: '(', groupId: 'fxg2' },
|
|
32
|
+
{ index: 7, depth: 2, type: NUMBER, value: '1' },
|
|
33
|
+
{ index: 8, depth: 2, type: OPERATOR, value: ')', groupId: 'fxg2' },
|
|
34
|
+
{ index: 9, depth: 1, type: OPERATOR, value: ')', groupId: 'fxg3' }
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('don\'t be fooled by imbalanced parens', () => {
|
|
39
|
+
isMetaTokens('=)())', [
|
|
40
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
41
|
+
{ index: 1, depth: 0, type: OPERATOR, value: ')', error: true },
|
|
42
|
+
{ index: 2, depth: 1, type: OPERATOR, value: '(', groupId: 'fxg1' },
|
|
43
|
+
{ index: 3, depth: 1, type: OPERATOR, value: ')', groupId: 'fxg1' },
|
|
44
|
+
{ index: 4, depth: 0, type: OPERATOR, value: ')', error: true }
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('don\'t be fooled by nested curlys', () => {
|
|
49
|
+
isMetaTokens('={{}}', [
|
|
50
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
51
|
+
{ index: 1, depth: 1, type: OPERATOR, value: '{', groupId: 'fxg1' },
|
|
52
|
+
{ index: 2, depth: 1, type: OPERATOR, value: '{', error: true },
|
|
53
|
+
{ index: 3, depth: 1, type: OPERATOR, value: '}', groupId: 'fxg1' },
|
|
54
|
+
{ index: 4, depth: 0, type: OPERATOR, value: '}', error: true }
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('group ranges if they are equivalent', () => {
|
|
59
|
+
isMetaTokens("=B11,B11:B12,'Sheet11'!B11,SHEET1!$B11,sheet1!$b$11,A1:B11,[foo]Sheet1!B11,'[foo]Sheet1'!B11", [
|
|
60
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
61
|
+
{ index: 1, depth: 0, type: REF_RANGE, value: 'B11', groupId: 'fxg1' },
|
|
62
|
+
{ index: 2, depth: 0, type: OPERATOR, value: ',' },
|
|
63
|
+
{ index: 3, depth: 0, type: REF_RANGE, value: 'B11:B12', groupId: 'fxg2' },
|
|
64
|
+
{ index: 4, depth: 0, type: OPERATOR, value: ',' },
|
|
65
|
+
{ index: 5, depth: 0, type: REF_RANGE, value: "'Sheet11'!B11", groupId: 'fxg3' },
|
|
66
|
+
{ index: 6, depth: 0, type: OPERATOR, value: ',' },
|
|
67
|
+
{ index: 7, depth: 0, type: REF_RANGE, value: 'SHEET1!$B11', groupId: 'fxg1' },
|
|
68
|
+
{ index: 8, depth: 0, type: OPERATOR, value: ',' },
|
|
69
|
+
{ index: 9, depth: 0, type: REF_RANGE, value: 'sheet1!$b$11', groupId: 'fxg1' },
|
|
70
|
+
{ index: 10, depth: 0, type: OPERATOR, value: ',' },
|
|
71
|
+
{ index: 11, depth: 0, type: REF_RANGE, value: 'A1:B11', groupId: 'fxg4' },
|
|
72
|
+
{ index: 12, depth: 0, type: OPERATOR, value: ',' },
|
|
73
|
+
{ index: 13, depth: 0, type: REF_RANGE, value: '[foo]Sheet1!B11', groupId: 'fxg1' },
|
|
74
|
+
{ index: 14, depth: 0, type: OPERATOR, value: ',' },
|
|
75
|
+
{ index: 15, depth: 0, type: REF_RANGE, value: "'[foo]Sheet1'!B11", groupId: 'fxg1' }
|
|
76
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('group beam references', () => {
|
|
80
|
+
isMetaTokens('=A:A,1:1,Sheet1!A:A:1:1,[foo]Sheet1!1:1', [
|
|
81
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
82
|
+
{ index: 1, depth: 0, type: REF_BEAM, value: 'A:A', groupId: 'fxg1' },
|
|
83
|
+
{ index: 2, depth: 0, type: OPERATOR, value: ',' },
|
|
84
|
+
{ index: 3, depth: 0, type: REF_BEAM, value: '1:1', groupId: 'fxg2' },
|
|
85
|
+
{ index: 4, depth: 0, type: OPERATOR, value: ',' },
|
|
86
|
+
{ index: 5, depth: 0, type: REF_BEAM, value: 'Sheet1!A:A', groupId: 'fxg1' },
|
|
87
|
+
{ index: 6, depth: 0, type: OPERATOR, value: ':' },
|
|
88
|
+
{ index: 7, depth: 0, type: REF_BEAM, value: '1:1', groupId: 'fxg2' },
|
|
89
|
+
{ index: 8, depth: 0, type: OPERATOR, value: ',' },
|
|
90
|
+
{ index: 9, depth: 0, type: REF_BEAM, value: '[foo]Sheet1!1:1', groupId: 'fxg2' }
|
|
91
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('complex function with nested grouping', () => {
|
|
95
|
+
isMetaTokens('=SUM((1, 2), {3, 4})', [
|
|
96
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
97
|
+
{ index: 1, depth: 0, type: FUNCTION, value: 'SUM' },
|
|
98
|
+
{ index: 2, depth: 1, type: OPERATOR, value: '(', groupId: 'fxg3' },
|
|
99
|
+
{ index: 3, depth: 2, type: OPERATOR, value: '(', groupId: 'fxg1' },
|
|
100
|
+
{ index: 4, depth: 2, type: NUMBER, value: '1' },
|
|
101
|
+
{ index: 5, depth: 2, type: OPERATOR, value: ',' },
|
|
102
|
+
{ index: 6, depth: 2, type: WHITESPACE, value: ' ' },
|
|
103
|
+
{ index: 7, depth: 2, type: NUMBER, value: '2' },
|
|
104
|
+
{ index: 8, depth: 2, type: OPERATOR, value: ')', groupId: 'fxg1' },
|
|
105
|
+
{ index: 9, depth: 1, type: OPERATOR, value: ',' },
|
|
106
|
+
{ index: 10, depth: 1, type: WHITESPACE, value: ' ' },
|
|
107
|
+
{ index: 11, depth: 2, type: OPERATOR, value: '{', groupId: 'fxg2' },
|
|
108
|
+
{ index: 12, depth: 2, type: NUMBER, value: '3' },
|
|
109
|
+
{ index: 13, depth: 2, type: OPERATOR, value: ',' },
|
|
110
|
+
{ index: 14, depth: 2, type: WHITESPACE, value: ' ' },
|
|
111
|
+
{ index: 15, depth: 2, type: NUMBER, value: '4' },
|
|
112
|
+
{ index: 16, depth: 2, type: OPERATOR, value: '}', groupId: 'fxg2' },
|
|
113
|
+
{ index: 17, depth: 1, type: OPERATOR, value: ')', groupId: 'fxg3' }
|
|
114
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('group structured references', () => {
|
|
118
|
+
isMetaTokens('=table[#all]+table[foobar]+table[[#All]]', [
|
|
119
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
120
|
+
{ index: 1, depth: 0, type: REF_STRUCT, value: 'table[#all]', groupId: 'fxg1' },
|
|
121
|
+
{ index: 2, depth: 0, type: OPERATOR, value: '+' },
|
|
122
|
+
{ index: 3, depth: 0, type: REF_STRUCT, value: 'table[foobar]', groupId: 'fxg2' },
|
|
123
|
+
{ index: 4, depth: 0, type: OPERATOR, value: '+' },
|
|
124
|
+
{ index: 5, depth: 0, type: REF_STRUCT, value: 'table[[#All]]', groupId: 'fxg1' }
|
|
125
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('group workbook references in xlsx mode', () => {
|
|
129
|
+
isMetaTokens('=[foo]!A1+[foo]Sheet1!A1+Sheet1!A1+A1', [
|
|
130
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
131
|
+
{ index: 1, depth: 0, type: REF_RANGE, value: '[foo]!A1', groupId: 'fxg1' },
|
|
132
|
+
{ index: 2, depth: 0, type: OPERATOR, value: '+' },
|
|
133
|
+
{ index: 3, depth: 0, type: REF_RANGE, value: '[foo]Sheet1!A1', groupId: 'fxg1' },
|
|
134
|
+
{ index: 4, depth: 0, type: OPERATOR, value: '+' },
|
|
135
|
+
{ index: 5, depth: 0, type: REF_RANGE, value: 'Sheet1!A1', groupId: 'fxg1' },
|
|
136
|
+
{ index: 6, depth: 0, type: OPERATOR, value: '+' },
|
|
137
|
+
{ index: 7, depth: 0, type: REF_RANGE, value: 'A1', groupId: 'fxg1' }
|
|
138
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' }, { xlsx: true });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('group structured references in xlsx mode', () => {
|
|
142
|
+
isMetaTokens('=[foo]!table[#data]+[foo]Sheet1!table[#data]+Sheet1!table[#data]+table[#data]', [
|
|
143
|
+
{ index: 0, depth: 0, type: FX_PREFIX, value: '=' },
|
|
144
|
+
{ index: 1, depth: 0, type: REF_STRUCT, value: '[foo]!table[#data]', groupId: 'fxg1' },
|
|
145
|
+
{ index: 2, depth: 0, type: OPERATOR, value: '+' },
|
|
146
|
+
{ index: 3, depth: 0, type: REF_STRUCT, value: '[foo]Sheet1!table[#data]', groupId: 'fxg1' },
|
|
147
|
+
{ index: 4, depth: 0, type: OPERATOR, value: '+' },
|
|
148
|
+
{ index: 5, depth: 0, type: REF_STRUCT, value: 'Sheet1!table[#data]', groupId: 'fxg1' },
|
|
149
|
+
{ index: 6, depth: 0, type: OPERATOR, value: '+' },
|
|
150
|
+
{ index: 7, depth: 0, type: REF_STRUCT, value: 'table[#data]', groupId: 'fxg1' }
|
|
151
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' }, { xlsx: true });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('trimming should not affect range equivalency', () => {
|
|
155
|
+
isMetaTokens('=A1:B2*A1.:B2*A1:.B2*A1.:.B2', [
|
|
156
|
+
{ type: FX_PREFIX, value: '=', index: 0, depth: 0 },
|
|
157
|
+
{ type: REF_RANGE, value: 'A1:B2', index: 1, depth: 0, groupId: 'fxg1' },
|
|
158
|
+
{ type: OPERATOR, value: '*', index: 2, depth: 0 },
|
|
159
|
+
{ type: REF_RANGE, value: 'A1.:B2', index: 3, depth: 0, groupId: 'fxg1' },
|
|
160
|
+
{ type: OPERATOR, value: '*', index: 4, depth: 0 },
|
|
161
|
+
{ type: REF_RANGE, value: 'A1:.B2', index: 5, depth: 0, groupId: 'fxg1' },
|
|
162
|
+
{ type: OPERATOR, value: '*', index: 6, depth: 0 },
|
|
163
|
+
{ type: REF_RANGE, value: 'A1.:.B2', index: 7, depth: 0, groupId: 'fxg1' }
|
|
164
|
+
], { sheetName: 'Sheet1', workbookName: 'foo' }, { xlsx: true });
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { REF_RANGE, REF_BEAM, REF_TERNARY, UNKNOWN, REF_STRUCT } from './constants.
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { REF_RANGE, REF_BEAM, REF_TERNARY, UNKNOWN, REF_STRUCT } from './constants.ts';
|
|
2
|
+
import { parseA1RefXlsx } from './parseA1Ref.ts';
|
|
3
|
+
import { parseStructRefXlsx } from './parseStructRef.ts';
|
|
4
|
+
import type { ReferenceA1Xlsx, ReferenceStructXlsx, Token, TokenEnhanced } from './types.ts';
|
|
4
5
|
|
|
5
|
-
function getIDer () {
|
|
6
|
+
function getIDer (): () => string {
|
|
6
7
|
let i = 1;
|
|
7
8
|
return () => 'fxg' + (i++);
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
function sameValue (a, b) {
|
|
11
|
+
function sameValue (a: unknown, b: unknown): boolean {
|
|
11
12
|
if (a == null && b == null) {
|
|
12
13
|
return true;
|
|
13
14
|
}
|
|
14
15
|
return a === b;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
function sameArray (a, b) {
|
|
18
|
+
function sameArray (a: unknown[], b: unknown[]): boolean {
|
|
18
19
|
if ((Array.isArray(a) !== Array.isArray(b)) || a.length !== b.length) {
|
|
19
20
|
return false;
|
|
20
21
|
}
|
|
@@ -26,14 +27,14 @@ function sameArray (a, b) {
|
|
|
26
27
|
return true;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function sameStr (a, b) {
|
|
30
|
+
function sameStr (a: string, b: string): boolean {
|
|
30
31
|
if (!a && !b) {
|
|
31
32
|
return true;
|
|
32
33
|
}
|
|
33
34
|
return String(a).toLowerCase() === String(b).toLowerCase();
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
function isEquivalent (refA, refB) {
|
|
37
|
+
function isEquivalent (refA, refB): boolean {
|
|
37
38
|
// if named, name must match
|
|
38
39
|
if ((refA.name || refB.name) && refA.name !== refB.name) {
|
|
39
40
|
return false;
|
|
@@ -71,7 +72,7 @@ function isEquivalent (refA, refB) {
|
|
|
71
72
|
return true;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
function addContext (ref, sheetName, workbookName) {
|
|
75
|
+
function addContext (ref, sheetName: string, workbookName: string) {
|
|
75
76
|
if (!ref.sheetName) {
|
|
76
77
|
ref.sheetName = sheetName;
|
|
77
78
|
}
|
|
@@ -81,6 +82,29 @@ function addContext (ref, sheetName, workbookName) {
|
|
|
81
82
|
return ref;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
type RefWithGroupId = (ReferenceA1Xlsx | ReferenceStructXlsx) & { groupId: string };
|
|
86
|
+
class Reflist {
|
|
87
|
+
refs: RefWithGroupId[];
|
|
88
|
+
uid: () => string;
|
|
89
|
+
|
|
90
|
+
constructor (uid: () => string) {
|
|
91
|
+
this.refs = [];
|
|
92
|
+
this.uid = uid;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getGroupId (ref: ReferenceA1Xlsx | ReferenceStructXlsx): string {
|
|
96
|
+
const known = this.refs.find(d => isEquivalent(d, ref));
|
|
97
|
+
if (known) {
|
|
98
|
+
return known.groupId;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const r: RefWithGroupId = Object.assign(ref, { groupId: this.uid() });
|
|
102
|
+
this.refs.push(r);
|
|
103
|
+
return r.groupId;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
/**
|
|
85
109
|
* Runs through a list of tokens and adds extra attributes such as matching
|
|
86
110
|
* parens and ranges.
|
|
@@ -121,17 +145,17 @@ function addContext (ref, sheetName, workbookName) {
|
|
|
121
145
|
*
|
|
122
146
|
* All will be tagged with `.error` (boolean `true`).
|
|
123
147
|
*
|
|
124
|
-
* @param
|
|
125
|
-
* @param
|
|
126
|
-
* @param
|
|
127
|
-
* @param
|
|
128
|
-
* @returns
|
|
148
|
+
* @param tokenlist An array of tokens (from `tokenize()`)
|
|
149
|
+
* @param [context={}] A contest used to match `A1` to `Sheet1!A1`.
|
|
150
|
+
* @param [context.sheetName=''] An implied sheet name ('Sheet1')
|
|
151
|
+
* @param [context.workbookName=''] An implied workbook name ('report.xlsx')
|
|
152
|
+
* @returns The input array with the enchanced tokens
|
|
129
153
|
*/
|
|
130
|
-
export function addTokenMeta (tokenlist, { sheetName = '', workbookName = '' } = {}) {
|
|
154
|
+
export function addTokenMeta (tokenlist: Token[], { sheetName = '', workbookName = '' } = {}): TokenEnhanced[] {
|
|
131
155
|
const parenStack = [];
|
|
132
156
|
let arrayStart = null;
|
|
133
157
|
const uid = getIDer();
|
|
134
|
-
const knownRefs =
|
|
158
|
+
const knownRefs = new Reflist(uid);
|
|
135
159
|
|
|
136
160
|
const getCurrDepth = () => parenStack.length + (arrayStart ? 1 : 0);
|
|
137
161
|
|
|
@@ -178,29 +202,25 @@ export function addTokenMeta (tokenlist, { sheetName = '', workbookName = '' } =
|
|
|
178
202
|
else if (
|
|
179
203
|
token.type === REF_RANGE ||
|
|
180
204
|
token.type === REF_BEAM ||
|
|
181
|
-
token.type === REF_TERNARY
|
|
182
|
-
token.type === REF_STRUCT
|
|
205
|
+
token.type === REF_TERNARY
|
|
183
206
|
) {
|
|
184
|
-
const ref = (token.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
const ref = parseA1RefXlsx(token.value, { allowTernary: true });
|
|
208
|
+
if (ref && 'range' in ref) {
|
|
209
|
+
addContext(ref, sheetName, workbookName);
|
|
210
|
+
token.groupId = knownRefs.getGroupId(ref);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (token.type === REF_STRUCT) {
|
|
214
|
+
const ref = parseStructRefXlsx(token.value);
|
|
215
|
+
if (ref) {
|
|
189
216
|
addContext(ref, sheetName, workbookName);
|
|
190
|
-
|
|
191
|
-
if (known) {
|
|
192
|
-
token.groupId = known.groupId;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
ref.groupId = uid();
|
|
196
|
-
token.groupId = ref.groupId;
|
|
197
|
-
knownRefs.push(ref);
|
|
198
|
-
}
|
|
217
|
+
token.groupId = knownRefs.getGroupId(ref);
|
|
199
218
|
}
|
|
200
219
|
}
|
|
201
220
|
else if (token.type === UNKNOWN) {
|
|
202
221
|
token.error = true;
|
|
203
222
|
}
|
|
204
223
|
});
|
|
205
|
-
|
|
224
|
+
|
|
225
|
+
return tokenlist as TokenEnhanced[];
|
|
206
226
|
}
|