@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.
Files changed (141) hide show
  1. package/dist/index-BMr6cTgc.d.cts +1444 -0
  2. package/dist/index-BMr6cTgc.d.ts +1444 -0
  3. package/dist/index.cjs +3054 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +2984 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/xlsx/index.cjs +3120 -0
  10. package/dist/xlsx/index.cjs.map +1 -0
  11. package/dist/xlsx/index.d.cts +55 -0
  12. package/dist/xlsx/index.d.ts +55 -0
  13. package/dist/xlsx/index.js +3049 -0
  14. package/dist/xlsx/index.js.map +1 -0
  15. package/docs/API.md +2959 -718
  16. package/docs/AST_format.md +2 -2
  17. package/eslint.config.mjs +40 -0
  18. package/lib/a1.spec.ts +32 -0
  19. package/lib/a1.ts +26 -0
  20. package/lib/addA1RangeBounds.ts +50 -0
  21. package/lib/addTokenMeta.spec.ts +166 -0
  22. package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
  23. package/lib/astTypes.ts +211 -0
  24. package/lib/cloneToken.ts +29 -0
  25. package/lib/{constants.js → constants.ts} +6 -3
  26. package/lib/fixRanges.spec.ts +220 -0
  27. package/lib/fixRanges.ts +260 -0
  28. package/lib/fromCol.spec.ts +15 -0
  29. package/lib/{fromCol.js → fromCol.ts} +1 -1
  30. package/lib/index.spec.ts +119 -0
  31. package/lib/index.ts +76 -0
  32. package/lib/isNodeType.ts +151 -0
  33. package/lib/isType.spec.ts +208 -0
  34. package/lib/{isType.js → isType.ts} +26 -25
  35. package/lib/lexers/{advRangeOp.js → advRangeOp.ts} +1 -1
  36. package/lib/lexers/{canEndRange.js → canEndRange.ts} +2 -2
  37. package/lib/lexers/{lexBoolean.js → lexBoolean.ts} +25 -6
  38. package/lib/lexers/{lexContext.js → lexContext.ts} +14 -6
  39. package/lib/lexers/{lexError.js → lexError.ts} +3 -3
  40. package/lib/lexers/{lexFunction.js → lexFunction.ts} +3 -2
  41. package/lib/lexers/lexNameFuncCntx.ts +112 -0
  42. package/lib/lexers/{lexNamed.js → lexNamed.ts} +4 -4
  43. package/lib/lexers/{lexNewLine.js → lexNewLine.ts} +3 -2
  44. package/lib/lexers/{lexNumber.js → lexNumber.ts} +4 -3
  45. package/lib/lexers/{lexOperator.js → lexOperator.ts} +5 -4
  46. package/lib/lexers/lexRange.ts +15 -0
  47. package/lib/lexers/{lexRangeA1.js → lexRangeA1.ts} +11 -7
  48. package/lib/lexers/{lexRangeR1C1.js → lexRangeR1C1.ts} +10 -6
  49. package/lib/lexers/{lexRangeTrim.js → lexRangeTrim.ts} +3 -2
  50. package/lib/lexers/{lexRefOp.js → lexRefOp.ts} +4 -3
  51. package/lib/lexers/{lexString.js → lexString.ts} +3 -3
  52. package/lib/lexers/{lexStructured.js → lexStructured.ts} +5 -5
  53. package/lib/lexers/{lexWhitespace.js → lexWhitespace.ts} +3 -2
  54. package/lib/lexers/sets.ts +51 -0
  55. package/lib/mergeRefTokens.spec.ts +141 -0
  56. package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +14 -9
  57. package/lib/nodeTypes.ts +54 -0
  58. package/lib/parse.spec.ts +1410 -0
  59. package/lib/{parser.js → parse.ts} +81 -63
  60. package/lib/parseA1Range.spec.ts +233 -0
  61. package/lib/parseA1Range.ts +206 -0
  62. package/lib/parseA1Ref.spec.ts +337 -0
  63. package/lib/parseA1Ref.ts +115 -0
  64. package/lib/parseR1C1Range.ts +191 -0
  65. package/lib/parseR1C1Ref.spec.ts +323 -0
  66. package/lib/parseR1C1Ref.ts +127 -0
  67. package/lib/parseRef.spec.ts +90 -0
  68. package/lib/parseRef.ts +240 -0
  69. package/lib/{parseSRange.js → parseSRange.ts} +15 -10
  70. package/lib/parseStructRef.spec.ts +168 -0
  71. package/lib/parseStructRef.ts +76 -0
  72. package/lib/stringifyA1Range.spec.ts +72 -0
  73. package/lib/stringifyA1Range.ts +72 -0
  74. package/lib/stringifyA1Ref.spec.ts +64 -0
  75. package/lib/stringifyA1Ref.ts +59 -0
  76. package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
  77. package/lib/stringifyR1C1Range.spec.ts +92 -0
  78. package/lib/stringifyR1C1Range.ts +73 -0
  79. package/lib/stringifyR1C1Ref.spec.ts +63 -0
  80. package/lib/stringifyR1C1Ref.ts +67 -0
  81. package/lib/stringifyStructRef.spec.ts +124 -0
  82. package/lib/stringifyStructRef.ts +113 -0
  83. package/lib/stringifyTokens.ts +15 -0
  84. package/lib/toCol.spec.ts +11 -0
  85. package/lib/{toCol.js → toCol.ts} +4 -4
  86. package/lib/tokenTypes.ts +76 -0
  87. package/lib/tokenize-srefs.spec.ts +429 -0
  88. package/lib/tokenize.spec.ts +2103 -0
  89. package/lib/tokenize.ts +346 -0
  90. package/lib/translate.spec.ts +35 -0
  91. package/lib/translateToA1.spec.ts +247 -0
  92. package/lib/translateToA1.ts +231 -0
  93. package/lib/translateToR1C1.spec.ts +227 -0
  94. package/lib/translateToR1C1.ts +145 -0
  95. package/lib/types.ts +179 -0
  96. package/lib/xlsx/index.spec.ts +27 -0
  97. package/lib/xlsx/index.ts +32 -0
  98. package/package.json +45 -31
  99. package/tsconfig.json +28 -0
  100. package/typedoc-ignore-links.ts +17 -0
  101. package/typedoc.json +41 -0
  102. package/.eslintrc +0 -22
  103. package/benchmark/benchmark.js +0 -48
  104. package/benchmark/formulas.json +0 -15677
  105. package/dist/fx.d.ts +0 -823
  106. package/dist/fx.js +0 -2
  107. package/dist/package.json +0 -1
  108. package/lib/a1.js +0 -348
  109. package/lib/a1.spec.js +0 -458
  110. package/lib/addTokenMeta.spec.js +0 -153
  111. package/lib/astTypes.js +0 -96
  112. package/lib/extraTypes.js +0 -74
  113. package/lib/fixRanges.js +0 -104
  114. package/lib/fixRanges.spec.js +0 -171
  115. package/lib/fromCol.spec.js +0 -11
  116. package/lib/index.js +0 -134
  117. package/lib/index.spec.js +0 -67
  118. package/lib/isType.spec.js +0 -168
  119. package/lib/lexer-srefs.spec.js +0 -324
  120. package/lib/lexer.js +0 -264
  121. package/lib/lexer.spec.js +0 -1953
  122. package/lib/lexers/lexRange.js +0 -8
  123. package/lib/lexers/sets.js +0 -38
  124. package/lib/mergeRefTokens.spec.js +0 -121
  125. package/lib/package.json +0 -1
  126. package/lib/parseRef.js +0 -157
  127. package/lib/parseRef.spec.js +0 -71
  128. package/lib/parseStructRef.js +0 -48
  129. package/lib/parseStructRef.spec.js +0 -164
  130. package/lib/parser.spec.js +0 -1208
  131. package/lib/rc.js +0 -341
  132. package/lib/rc.spec.js +0 -403
  133. package/lib/stringifyStructRef.js +0 -80
  134. package/lib/stringifyStructRef.spec.js +0 -182
  135. package/lib/toCol.spec.js +0 -11
  136. package/lib/translate-toA1.spec.js +0 -214
  137. package/lib/translate-toRC.spec.js +0 -197
  138. package/lib/translate.js +0 -239
  139. package/lib/translate.spec.js +0 -21
  140. package/rollup.config.mjs +0 -22
  141. package/tsd.json +0 -12
@@ -45,7 +45,7 @@ interface ReferenceIdentifier extends Node {
45
45
  }
46
46
  ```
47
47
 
48
- An identifier for a range or a named. The
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 as an option for this but it is off by default.
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.js';
2
- import { parseA1Ref } from './a1.js';
3
- import { parseStructRef } from './parseStructRef.js';
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 {Array<Token>} tokenlist An array of tokens (from `tokenize()`)
125
- * @param {object} [context={}] A contest used to match `A1` to `Sheet1!A1`.
126
- * @param {string} [context.sheetName=''] An implied sheet name ('Sheet1')
127
- * @param {string} [context.workbookName=''] An implied workbook name ('report.xlsx')
128
- * @returns {Array<TokenEnhanced>} The input array with the enchanced tokens
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.type === REF_STRUCT)
185
- ? parseStructRef(token.value, { xlsx: true })
186
- : parseA1Ref(token.value, { allowTernary: true, xlsx: true });
187
- if (ref && (ref.range || ref.columns)) {
188
- ref.source = token.value;
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
- const known = knownRefs.find(d => isEquivalent(d, ref));
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
- return tokenlist;
224
+
225
+ return tokenlist as TokenEnhanced[];
206
226
  }