@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
@@ -0,0 +1,90 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { splitPrefix } from './parseRef.ts';
3
+
4
+ function testStr (str: string, opt: boolean, expected: any) {
5
+ expect(splitPrefix(str, opt)).toEqual(expected);
6
+ }
7
+
8
+ describe('splitPrefix', () => {
9
+ describe('with simple mode (opt = true)', () => {
10
+ test('fully bracketed references', () => {
11
+ testStr('[foo][bar][baz]', true, [ 'foo', 'bar', 'baz' ]);
12
+ testStr('[foo][bar]', true, [ 'foo', 'bar' ]);
13
+ testStr('[foo]', true, [ 'foo' ]);
14
+ });
15
+
16
+ test('mixed bracketed and unbracketed references', () => {
17
+ testStr('foo[bar][baz]', true, [ 'foo', 'bar', 'baz' ]);
18
+ testStr('[foo]bar[baz]', true, [ 'foo', 'bar', 'baz' ]);
19
+ testStr('[foo][bar]baz', true, [ 'foo', 'bar', 'baz' ]);
20
+ testStr('foo[bar]baz', true, [ 'foo', 'bar', 'baz' ]);
21
+ testStr('[foo]bar', true, [ 'foo', 'bar' ]);
22
+ testStr('foo[bar]', true, [ 'foo', 'bar' ]);
23
+ });
24
+
25
+ test('unbracketed references', () => {
26
+ testStr('foo', true, [ 'foo' ]);
27
+ });
28
+ });
29
+
30
+ describe('with detailed mode (opt = false)', () => {
31
+ test('fully bracketed references', () => {
32
+ testStr('[foo][bar][baz]', false, [
33
+ { value: 'foo', braced: true },
34
+ { value: 'bar', braced: true },
35
+ { value: 'baz', braced: true }
36
+ ]);
37
+
38
+ testStr('[foo][bar]', false, [
39
+ { value: 'foo', braced: true },
40
+ { value: 'bar', braced: true }
41
+ ]);
42
+
43
+ testStr('[foo]', false, [
44
+ { value: 'foo', braced: true }
45
+ ]);
46
+ });
47
+
48
+ test('mixed bracketed and unbracketed references', () => {
49
+ testStr('foo[bar][baz]', false, [
50
+ { value: 'foo', braced: false },
51
+ { value: 'bar', braced: true },
52
+ { value: 'baz', braced: true }
53
+ ]);
54
+
55
+ testStr('[foo]bar[baz]', false, [
56
+ { value: 'foo', braced: true },
57
+ { value: 'bar', braced: false },
58
+ { value: 'baz', braced: true }
59
+ ]);
60
+
61
+ testStr('[foo][bar]baz', false, [
62
+ { value: 'foo', braced: true },
63
+ { value: 'bar', braced: true },
64
+ { value: 'baz', braced: false }
65
+ ]);
66
+
67
+ testStr('foo[bar]baz', false, [
68
+ { value: 'foo', braced: false },
69
+ { value: 'bar', braced: true },
70
+ { value: 'baz', braced: false }
71
+ ]);
72
+
73
+ testStr('[foo]bar', false, [
74
+ { value: 'foo', braced: true },
75
+ { value: 'bar', braced: false }
76
+ ]);
77
+
78
+ testStr('foo[bar]', false, [
79
+ { value: 'foo', braced: false },
80
+ { value: 'bar', braced: true }
81
+ ]);
82
+ });
83
+
84
+ test('unbracketed references', () => {
85
+ testStr('foo', false, [
86
+ { value: 'foo', braced: false }
87
+ ]);
88
+ });
89
+ });
90
+ });
@@ -0,0 +1,240 @@
1
+ import {
2
+ FX_PREFIX,
3
+ CONTEXT,
4
+ CONTEXT_QUOTE,
5
+ REF_RANGE,
6
+ REF_TERNARY,
7
+ REF_NAMED,
8
+ REF_BEAM,
9
+ REF_STRUCT,
10
+ OPERATOR
11
+ } from './constants.ts';
12
+ import { lexersRefs } from './lexers/sets.ts';
13
+ import { getTokens } from './tokenize.ts';
14
+ import type { Token } from './types.ts';
15
+
16
+ // Liberally split a context string up into parts.
17
+ // Permits any combination of braced and unbraced items.
18
+ export function splitPrefix (str: string, stringsOnly = false) {
19
+ let inBrace = false;
20
+ let currStr = '';
21
+ const parts = [];
22
+ const flush = () => {
23
+ if (currStr) {
24
+ parts.push(
25
+ stringsOnly
26
+ ? currStr
27
+ : { value: currStr, braced: inBrace }
28
+ );
29
+ }
30
+ currStr = '';
31
+ };
32
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
33
+ for (let i = 0; i < str.length; i++) {
34
+ const char = str[i];
35
+ if (char === '[') {
36
+ flush();
37
+ inBrace = true;
38
+ }
39
+ else if (char === ']') {
40
+ flush();
41
+ inBrace = false;
42
+ }
43
+ else {
44
+ currStr += char;
45
+ }
46
+ }
47
+ flush();
48
+ return parts;
49
+ }
50
+
51
+ export function splitContext (contextString, data, xlsx) {
52
+ const ctx = splitPrefix(contextString, !xlsx);
53
+ if (xlsx) {
54
+ if (ctx.length > 1) {
55
+ data.workbookName = ctx[ctx.length - 2].value;
56
+ data.sheetName = ctx[ctx.length - 1].value;
57
+ }
58
+ else if (ctx.length === 1) {
59
+ const item = ctx[0];
60
+ if (item.braced) {
61
+ data.workbookName = item.value;
62
+ }
63
+ else {
64
+ data.sheetName = item.value;
65
+ }
66
+ }
67
+ }
68
+ else {
69
+ data.context = ctx;
70
+ }
71
+ }
72
+
73
+ type RefParseData = {
74
+ operator: string,
75
+ r0: string,
76
+ r1: string,
77
+ name: string,
78
+ struct: string,
79
+ };
80
+ type RefParserPart = (t: Token | undefined, data: Partial<RefParseData>, xlsx?: boolean) => 1 | undefined;
81
+
82
+ const unquote = d => d.slice(1, -1).replace(/''/g, "'");
83
+
84
+ const pRangeOp: RefParserPart = (t, data) => {
85
+ const value = t?.value;
86
+ if (value === ':' || value === '.:' || value === ':.' || value === '.:.') {
87
+ data.operator = value;
88
+ return 1;
89
+ }
90
+ };
91
+ const pRange: RefParserPart = (t, data) => {
92
+ if (t?.type === REF_RANGE) {
93
+ data.r0 = t.value;
94
+ return 1;
95
+ }
96
+ };
97
+ const pPartial: RefParserPart = (t, data) => {
98
+ if (t?.type === REF_TERNARY) {
99
+ data.r0 = t.value;
100
+ return 1;
101
+ }
102
+ };
103
+ const pRange2: RefParserPart = (t, data) => {
104
+ if (t?.type === REF_RANGE) {
105
+ data.r1 = t.value;
106
+ return 1;
107
+ }
108
+ };
109
+ const pBang: RefParserPart = t => {
110
+ if (t?.type === OPERATOR && t.value === '!') {
111
+ return 1;
112
+ }
113
+ };
114
+ const pBeam: RefParserPart = (t, data) => {
115
+ if (t?.type === REF_BEAM) {
116
+ data.r0 = t.value;
117
+ return 1;
118
+ }
119
+ };
120
+ const pStrucured: RefParserPart = (t, data) => {
121
+ if (t.type === REF_STRUCT) {
122
+ data.struct = t.value;
123
+ return 1;
124
+ }
125
+ };
126
+ const pContext: RefParserPart = (t, data, xlsx) => {
127
+ const type = t?.type;
128
+ if (type === CONTEXT) {
129
+ splitContext(t.value, data, xlsx);
130
+ return 1;
131
+ }
132
+ if (type === CONTEXT_QUOTE) {
133
+ splitContext(unquote(t.value), data, xlsx);
134
+ return 1;
135
+ }
136
+ };
137
+ const pNamed: RefParserPart = (t, data) => {
138
+ if (t?.type === REF_NAMED) {
139
+ data.name = t.value;
140
+ return 1;
141
+ }
142
+ };
143
+
144
+ const validRuns = [
145
+ [ pPartial ],
146
+ [ pRange, pRangeOp, pRange2 ],
147
+ [ pRange ],
148
+ [ pBeam ],
149
+ [ pContext, pBang, pPartial ],
150
+ [ pContext, pBang, pRange, pRangeOp, pRange2 ],
151
+ [ pContext, pBang, pRange ],
152
+ [ pContext, pBang, pBeam ]
153
+ ];
154
+
155
+ const validRunsNamed = validRuns.concat([
156
+ [ pNamed ],
157
+ [ pContext, pBang, pNamed ],
158
+ [ pStrucured ],
159
+ [ pNamed, pStrucured ],
160
+ [ pContext, pBang, pNamed, pStrucured ]
161
+ ]);
162
+
163
+ type ParseRefOptions = {
164
+ withLocation?: boolean,
165
+ mergeRefs?: boolean,
166
+ allowTernary?: boolean,
167
+ allowNamed?: boolean,
168
+ r1c1?: boolean,
169
+ };
170
+ export type RefParseDataXls = RefParseData & { workbookName: string, sheetName: string };
171
+ export type RefParseDataCtx = RefParseData & { context: string[] };
172
+
173
+ export function parseRefCtx (ref: string, opts: ParseRefOptions = {}): RefParseDataCtx | null {
174
+ const options = {
175
+ withLocation: opts.withLocation ?? false,
176
+ mergeRefs: opts.mergeRefs ?? false,
177
+ allowTernary: opts.allowTernary ?? false,
178
+ allowNamed: opts.allowNamed ?? true,
179
+ r1c1: opts.r1c1 ?? false
180
+ };
181
+ const tokens = getTokens(ref, lexersRefs, options);
182
+
183
+ // discard the "="-prefix if it is there
184
+ if (tokens.length && tokens[0].type === FX_PREFIX) {
185
+ tokens.shift();
186
+ }
187
+ const runs = options.allowNamed ? validRunsNamed : validRuns;
188
+ for (const run of runs) {
189
+ // const len = run.length;
190
+ if (run.length === tokens.length) {
191
+ const data: RefParseDataCtx = {
192
+ context: [],
193
+ r0: '',
194
+ r1: '',
195
+ name: '',
196
+ struct: '',
197
+ operator: ''
198
+ };
199
+ const valid = run.every((parse, i) => parse(tokens[i], data, false));
200
+ if (valid) {
201
+ return data;
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ export function parseRefXlsx (ref: string, opts: ParseRefOptions = {}): RefParseDataXls | null {
208
+ const options = {
209
+ withLocation: opts.withLocation ?? false,
210
+ mergeRefs: opts.mergeRefs ?? false,
211
+ allowTernary: opts.allowTernary ?? false,
212
+ allowNamed: opts.allowNamed ?? true,
213
+ r1c1: opts.r1c1 ?? false,
214
+ xlsx: true
215
+ };
216
+ const tokens = getTokens(ref, lexersRefs, options);
217
+
218
+ // discard the "="-prefix if it is there
219
+ if (tokens.length && tokens[0].type === FX_PREFIX) {
220
+ tokens.shift();
221
+ }
222
+ const runs = options.allowNamed ? validRunsNamed : validRuns;
223
+ for (const run of runs) {
224
+ if (run.length === tokens.length) {
225
+ const data: RefParseDataXls = {
226
+ workbookName: '',
227
+ sheetName: '',
228
+ r0: '',
229
+ r1: '',
230
+ name: '',
231
+ struct: '',
232
+ operator: ''
233
+ };
234
+ const valid = run.every((parse, i) => parse(tokens[i], data, true));
235
+ if (valid) {
236
+ return data as any;
237
+ }
238
+ }
239
+ }
240
+ }
@@ -1,6 +1,4 @@
1
- /* eslint-disable no-multi-spaces */
2
- /* eslint-disable no-undefined */
3
- import { isWS } from './lexers/lexWhitespace.js';
1
+ import { isWS } from './lexers/lexWhitespace.ts';
4
2
 
5
3
  const AT = 64; // @
6
4
  const BR_CLOSE = 93; // ]
@@ -20,7 +18,7 @@ const keyTerms = {
20
18
  };
21
19
 
22
20
  // only combinations allowed are: #data + (#headers | #totals | #data)
23
- const fz = (...a) => Object.freeze(a);
21
+ const fz = (...a: string[]) => Object.freeze(a);
24
22
  const sectionMap = {
25
23
  // no terms
26
24
  0: fz(),
@@ -36,7 +34,7 @@ const sectionMap = {
36
34
  6: fz('data', 'totals')
37
35
  };
38
36
 
39
- function matchKeyword (str, pos) {
37
+ function matchKeyword (str: string, pos: number): number {
40
38
  let p = pos;
41
39
  if (str.charCodeAt(p++) !== BR_OPEN) {
42
40
  return;
@@ -64,13 +62,13 @@ function matchKeyword (str, pos) {
64
62
  return p - pos;
65
63
  }
66
64
 
67
- function skipWhitespace (str, pos) {
65
+ function skipWhitespace (str: string, pos: number): number {
68
66
  let p = pos;
69
67
  while (isWS(str.charCodeAt(p))) { p++; }
70
68
  return p - pos;
71
69
  }
72
70
 
73
- function matchColumn (str, pos, allowUnbraced = true) {
71
+ function matchColumn (str: string, pos: number, allowUnbraced = true): [ string, string ] {
74
72
  let p = pos;
75
73
  let column = '';
76
74
  if (str.charCodeAt(p) === BR_OPEN) {
@@ -125,8 +123,15 @@ function matchColumn (str, pos, allowUnbraced = true) {
125
123
  }
126
124
  }
127
125
 
128
- export function parseSRange (str, pos = 0) {
129
- const columns = [];
126
+ export type SRange = {
127
+ columns: string[],
128
+ sections: string[],
129
+ length: number,
130
+ token: string
131
+ };
132
+
133
+ export function parseSRange (str: string, pos: number = 0): SRange {
134
+ const columns: string[] = [];
130
135
  const start = pos;
131
136
  let m;
132
137
  let terms = 0;
@@ -225,7 +230,7 @@ export function parseSRange (str, pos = 0) {
225
230
  pos++;
226
231
  }
227
232
 
228
- const sections = sectionMap[terms];
233
+ const sections: string[] = sectionMap[terms];
229
234
  return {
230
235
  columns,
231
236
  sections: sections ? sections.concat() : sections,
@@ -0,0 +1,168 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { parseStructRef, parseStructRefXlsx } from './parseStructRef.ts';
3
+
4
+ function isSREqual (expr: string, expected: any, opts?: any) {
5
+ if (expected) {
6
+ expected = opts?.xlsx
7
+ ? {
8
+ workbookName: '',
9
+ sheetName: '',
10
+ table: '',
11
+ columns: [],
12
+ sections: [],
13
+ ...expected
14
+ }
15
+ : {
16
+ context: [],
17
+ table: '',
18
+ columns: [],
19
+ sections: [],
20
+ ...expected
21
+ };
22
+ }
23
+ expect(opts?.xlsx ? parseStructRefXlsx(expr) : parseStructRef(expr)).toEqual(expected);
24
+ }
25
+
26
+ describe('parse structured references', () => {
27
+ test('basic table and column references', () => {
28
+ isSREqual('table[col]', {
29
+ table: 'table',
30
+ columns: [ 'col' ]
31
+ });
32
+
33
+ isSREqual('table[]', {
34
+ table: 'table'
35
+ });
36
+ });
37
+
38
+ test('section references', () => {
39
+ isSREqual('[#All]', {
40
+ sections: [ 'all' ]
41
+ });
42
+ });
43
+
44
+ test('column name references', () => {
45
+ isSREqual('[column name]', {
46
+ columns: [ 'column name' ]
47
+ });
48
+
49
+ isSREqual('[[my column]]', {
50
+ columns: [ 'my column' ]
51
+ });
52
+ });
53
+
54
+ test('invalid references', () => {
55
+ isSREqual('[column name]!foo', undefined);
56
+ isSREqual('[foo]bar', undefined);
57
+ });
58
+
59
+ test('column range references', () => {
60
+ isSREqual('[[my column]:otherColumn]', {
61
+ columns: [ 'my column', 'otherColumn' ]
62
+ });
63
+
64
+ isSREqual('[ [my column]:otherColumn ]', {
65
+ columns: [ 'my column', 'otherColumn ' ]
66
+ });
67
+
68
+ isSREqual('[ [my column]: otherColumn ]', {
69
+ columns: [ 'my column', ' otherColumn ' ]
70
+ });
71
+ });
72
+
73
+ test('this row references', () => {
74
+ isSREqual('[ @[ my column ]: otherColumn ]', {
75
+ columns: [ ' my column ', ' otherColumn ' ],
76
+ sections: [ 'this row' ]
77
+ });
78
+ });
79
+
80
+ test('section and column combinations', () => {
81
+ isSREqual('[[#Data], [my column]:otherColumn]', {
82
+ columns: [ 'my column', 'otherColumn' ],
83
+ sections: [ 'data' ]
84
+ });
85
+
86
+ isSREqual('[ [#Data], [my column]:[\'@foo] ]', {
87
+ columns: [ 'my column', '@foo' ],
88
+ sections: [ 'data' ]
89
+ });
90
+ });
91
+
92
+ test('context-qualified references', () => {
93
+ isSREqual('workbook.xlsx!tableName[ [#Data], [my column]:[\'@foo] ]', {
94
+ columns: [ 'my column', '@foo' ],
95
+ sections: [ 'data' ],
96
+ table: 'tableName',
97
+ context: [ 'workbook.xlsx' ]
98
+ });
99
+
100
+ isSREqual("'Sheet'!Table[Column]", {
101
+ columns: [ 'Column' ],
102
+ table: 'Table',
103
+ context: [ 'Sheet' ]
104
+ });
105
+
106
+ isSREqual("Sheet1!Table1[foo '[bar']]", {
107
+ columns: [ 'foo [bar]' ],
108
+ table: 'Table1',
109
+ context: [ 'Sheet1' ]
110
+ });
111
+
112
+ isSREqual('[myworkbook.xlsx]Sheet1!TMP8w0habhr[#All]', {
113
+ columns: [],
114
+ table: 'TMP8w0habhr',
115
+ context: [ 'myworkbook.xlsx', 'Sheet1' ],
116
+ sections: [ 'all' ]
117
+ });
118
+ });
119
+
120
+ test('duplicate section handling', () => {
121
+ isSREqual('[[#Data],[#data],[#Data],[#Data],[#Totals],[#Totals],[#Totals],foo]', {
122
+ columns: [ 'foo' ],
123
+ sections: [ 'data', 'totals' ]
124
+ });
125
+ });
126
+ });
127
+
128
+ describe('structured references parse in xlsx mode', () => {
129
+ test('workbook-only references', () => {
130
+ isSREqual('[Workbook.xlsx]!Table[#Data]', {
131
+ workbookName: 'Workbook.xlsx',
132
+ table: 'Table',
133
+ sections: [ 'data' ]
134
+ }, { xlsx: true });
135
+ });
136
+
137
+ test('workbook and sheet references', () => {
138
+ isSREqual('[Workbook.xlsx]Sheet1!Table[#Data]', {
139
+ workbookName: 'Workbook.xlsx',
140
+ sheetName: 'Sheet1',
141
+ table: 'Table',
142
+ sections: [ 'data' ]
143
+ }, { xlsx: true });
144
+ });
145
+
146
+ test('sheet-only references', () => {
147
+ isSREqual('Sheet1!Table[#Data]', {
148
+ sheetName: 'Sheet1',
149
+ table: 'Table',
150
+ sections: [ 'data' ]
151
+ }, { xlsx: true });
152
+ });
153
+ });
154
+
155
+ describe('longform parse (in xlsx mode)', () => {
156
+ test('thisRow option should have no effect when parsing', () => {
157
+ const expectedResult = {
158
+ table: 'Table2',
159
+ columns: [ 'col1' ],
160
+ sections: [ 'this row' ]
161
+ };
162
+
163
+ isSREqual('Table2[[#This Row],[col1]]', expectedResult, { xlsx: true, thisRow: true });
164
+ isSREqual('Table2[[#This Row],[col1]]', expectedResult, { xlsx: true, thisRow: false });
165
+ isSREqual('Table2[[#This Row],[col1]]', expectedResult, { xlsx: false, thisRow: true });
166
+ isSREqual('Table2[[#This Row],[col1]]', expectedResult, { xlsx: false, thisRow: false });
167
+ });
168
+ });
@@ -0,0 +1,76 @@
1
+ import type { ReferenceStruct, ReferenceStructXlsx } from './types.ts';
2
+ import { parseRefCtx, parseRefXlsx } from './parseRef.ts';
3
+ import { parseSRange } from './parseSRange.ts';
4
+
5
+ /**
6
+ * Parse a structured reference string into an object representing it.
7
+ *
8
+ * ```js
9
+ * parseStructRef('workbook.xlsx!tableName[[#Data],[Column1]:[Column2]]');
10
+ * // => {
11
+ * // context: [ 'workbook.xlsx' ],
12
+ * // sections: [ 'data' ],
13
+ * // columns: [ 'my column', '@foo' ],
14
+ * // table: 'tableName',
15
+ * // }
16
+ * ```
17
+ *
18
+ * For A:A or A1:A style ranges, `null` will be used for any dimensions that the
19
+ * syntax does not specify:
20
+ *
21
+ * See [References.md](./References.md).
22
+ *
23
+ * @param ref A structured reference string
24
+ * @returns An object representing a valid reference or `undefined` if it is invalid.
25
+ */
26
+ export function parseStructRef (ref: string): ReferenceStruct | undefined {
27
+ const r = parseRefCtx(ref);
28
+ if (r && r.struct) {
29
+ const structData = parseSRange(r.struct);
30
+ if (structData && structData.length === r.struct.length) {
31
+ return {
32
+ context: r.context,
33
+ table: r.name,
34
+ columns: structData.columns,
35
+ sections: structData.sections
36
+ };
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Parse a structured reference string into an object representing it.
43
+ *
44
+ * ```js
45
+ * parseStructRef('[workbook.xlsx]!tableName[[#Data],[Column1]:[Column2]]');
46
+ * // => {
47
+ * // workbookName: 'workbook.xlsx',
48
+ * // sections: [ 'data' ],
49
+ * // columns: [ 'my column', '@foo' ],
50
+ * // table: 'tableName',
51
+ * // }
52
+ * ```
53
+ *
54
+ * For A:A or A1:A style ranges, `null` will be used for any dimensions that the
55
+ * syntax does not specify:
56
+ *
57
+ * See [References.md](./References.md).
58
+ *
59
+ * @param ref A structured reference string
60
+ * @returns An object representing a valid reference or null if it is invalid.
61
+ */
62
+ export function parseStructRefXlsx (ref: string): ReferenceStructXlsx | undefined {
63
+ const r = parseRefXlsx(ref);
64
+ if (r && r.struct) {
65
+ const structData = parseSRange(r.struct);
66
+ if (structData && structData.length === r.struct.length) {
67
+ return {
68
+ workbookName: r.workbookName,
69
+ sheetName: r.sheetName,
70
+ table: r.name,
71
+ columns: structData.columns,
72
+ sections: structData.sections
73
+ };
74
+ }
75
+ }
76
+ }