@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,191 @@
1
+ /*
2
+ ** RC notation works differently from A1 in that we can't merge static
3
+ ** references joined by `:`. Merging can only work between references
4
+ ** that are relative/absolute on the same axes, so:
5
+ ** - R1C1:R2C2 will work,
6
+ ** - R[1]C1:R[2]C2 will also work, but
7
+ ** - R[1]C[1]:R2C2 doesn't have a direct rectangle represention without context.
8
+ */
9
+ import type { RangeR1C1 } from './types.ts';
10
+
11
+ function trimDirection (head: boolean, tail: boolean): 'both' | 'head' | 'tail' | undefined {
12
+ if (head && tail) {
13
+ return 'both';
14
+ }
15
+ if (head) {
16
+ return 'head';
17
+ }
18
+ if (tail) {
19
+ return 'tail';
20
+ }
21
+ }
22
+
23
+ function parseR1C1Part (ref: string): [number, number, boolean, boolean] {
24
+ let r0 = null;
25
+ let c0 = null;
26
+ let $r0 = null;
27
+ let $c0 = null;
28
+ // R part
29
+ const rm = /^R(?:\[([+-]?\d+)\]|(\d+))?/.exec(ref);
30
+ if (rm) {
31
+ if (rm[1]) {
32
+ r0 = parseInt(rm[1], 10);
33
+ $r0 = false;
34
+ }
35
+ else if (rm[2]) {
36
+ r0 = parseInt(rm[2], 10) - 1;
37
+ $r0 = true;
38
+ }
39
+ else {
40
+ r0 = 0;
41
+ $r0 = false;
42
+ }
43
+ ref = ref.slice(rm[0].length);
44
+ }
45
+ // C part
46
+ const cm = /^C(?:\[([+-]?\d+)\]|(\d+))?/.exec(ref);
47
+ if (cm) {
48
+ if (cm[1]) {
49
+ c0 = parseInt(cm[1], 10);
50
+ $c0 = false;
51
+ }
52
+ else if (cm[2]) {
53
+ c0 = parseInt(cm[2], 10) - 1;
54
+ $c0 = true;
55
+ }
56
+ else {
57
+ c0 = 0;
58
+ $c0 = false;
59
+ }
60
+ ref = ref.slice(cm[0].length);
61
+ }
62
+ // must have at least one part (and nothing more)
63
+ if ((!rm && !cm) || ref.length) {
64
+ return null;
65
+ }
66
+ return [ r0, c0, $r0, $c0 ];
67
+ }
68
+
69
+ /**
70
+ * Parse R1C1-style range string into a RangeR1C1 object.
71
+ *
72
+ * @param rangeString R1C1-style range string.
73
+ * @return A reference object.
74
+ */
75
+ export function parseR1C1Range (rangeString: string): RangeR1C1 | null {
76
+ let final: RangeR1C1 | null = undefined;
77
+ const [ part1, op, part2, overflow ] = rangeString.split(/(\.?:\.?)/);
78
+ if (overflow) {
79
+ return null;
80
+ }
81
+ const range = parseR1C1Part(part1);
82
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
83
+ const trim = trimDirection(!!op && op[0] === '.', !!op && op[op.length - 1] === '.');
84
+ if (range) {
85
+ const [ r0, c0, $r0, $c0 ] = range;
86
+ if (part2) {
87
+ const extendTo = parseR1C1Part(part2);
88
+ if (extendTo) {
89
+ final = {};
90
+ const [ r1, c1, $r1, $c1 ] = extendTo;
91
+ // rows
92
+ if (r0 != null && r1 != null) {
93
+ final.r0 = $r0 === $r1 ? Math.min(r0, r1) : r0;
94
+ final.$r0 = $r0;
95
+ final.r1 = $r0 === $r1 ? Math.max(r0, r1) : r1;
96
+ final.$r1 = $r1;
97
+ }
98
+ else if (r0 != null && r1 == null) {
99
+ // partial RC:C
100
+ final.r0 = r0;
101
+ final.$r0 = $r0;
102
+ final.r1 = null;
103
+ final.$r1 = $r0;
104
+ }
105
+ else if (r0 == null && r1 != null) {
106
+ // partial C:RC
107
+ final.r0 = r1;
108
+ final.$r0 = $r1;
109
+ final.r1 = null;
110
+ final.$r1 = $r1;
111
+ }
112
+ else if (r0 == null && r1 == null) {
113
+ // C:C
114
+ final.r0 = null;
115
+ final.$r0 = false;
116
+ final.r1 = null;
117
+ final.$r1 = false;
118
+ }
119
+ // columns
120
+ if (c0 != null && c1 != null) {
121
+ final.c0 = $c0 === $c1 ? Math.min(c0, c1) : c0;
122
+ final.$c0 = $c0;
123
+ final.c1 = $c0 === $c1 ? Math.max(c0, c1) : c1;
124
+ final.$c1 = $c1;
125
+ }
126
+ else if (c0 != null && c1 == null) {
127
+ final.c0 = c0;
128
+ final.$c0 = $c0;
129
+ final.c1 = null;
130
+ final.$c1 = $c0;
131
+ }
132
+ else if (c0 == null && c1 != null) {
133
+ final.c0 = c1;
134
+ final.$c0 = $c1;
135
+ final.c1 = null;
136
+ final.$c1 = $c1;
137
+ }
138
+ else if (c0 == null && c1 == null) {
139
+ final.c0 = null;
140
+ final.$c0 = false;
141
+ final.c1 = null;
142
+ final.$c1 = false;
143
+ }
144
+ }
145
+ else {
146
+ return null;
147
+ }
148
+ }
149
+ // range only - no second part
150
+ else if (r0 != null && c0 == null) {
151
+ final = {
152
+ r0: r0,
153
+ c0: null,
154
+ r1: r0,
155
+ c1: null,
156
+ $r0: $r0,
157
+ $c0: false,
158
+ $r1: $r0,
159
+ $c1: false
160
+ };
161
+ }
162
+ else if (r0 == null && c0 != null) {
163
+ final = {
164
+ r0: null,
165
+ c0: c0,
166
+ r1: null,
167
+ c1: c0,
168
+ $r0: false,
169
+ $c0: $c0,
170
+ $r1: false,
171
+ $c1: $c0
172
+ };
173
+ }
174
+ else {
175
+ final = {
176
+ r0: r0 || 0,
177
+ c0: c0 || 0,
178
+ r1: r0 || 0,
179
+ c1: c0 || 0,
180
+ $r0: $r0 || false,
181
+ $c0: $c0 || false,
182
+ $r1: $r0 || false,
183
+ $c1: $c0 || false
184
+ };
185
+ }
186
+ }
187
+ if (final && trim) {
188
+ final.trim = trim;
189
+ }
190
+ return final;
191
+ }
@@ -0,0 +1,323 @@
1
+ /* eslint-disable @stylistic/object-property-newline */
2
+ import { describe, test, expect } from 'vitest';
3
+ import { parseR1C1Ref, parseR1C1RefXlsx } from './parseR1C1Ref.ts';
4
+
5
+ type OptsIsRCEqual = {
6
+ allowNamed?: boolean;
7
+ allowTernary?: boolean;
8
+ xlsx?: boolean;
9
+ };
10
+
11
+ function isRCEqual (expr: string, expected: any, opts?: OptsIsRCEqual) {
12
+ const xlsx = !!opts?.xlsx;
13
+ if (expected) {
14
+ expected = xlsx
15
+ ? { workbookName: '', sheetName: '', ...expected }
16
+ : { context: [], ...expected };
17
+ if (expected.range && typeof expected.range === 'object') {
18
+ // mix in some defaults so we don't have to write things out in full
19
+ expected.range = {
20
+ r0: null, c0: null, r1: null, c1: null,
21
+ $r0: false, $c0: false, $r1: false, $c1: false,
22
+ ...expected.range
23
+ };
24
+ }
25
+ }
26
+ expect(xlsx ? parseR1C1RefXlsx(expr, opts) : parseR1C1Ref(expr, opts)).toEqual(expected);
27
+ }
28
+
29
+ describe('parse single R1C1 references', () => {
30
+ test('current row references', () => {
31
+ isRCEqual('R', { range: { r0: 0, r1: 0 } });
32
+ isRCEqual('R[0]', { range: { r0: 0, r1: 0 } });
33
+ });
34
+
35
+ test('fixed row references', () => {
36
+ isRCEqual('R0', { name: 'R0' });
37
+ isRCEqual('R1', { range: { r0: 0, r1: 0, $r0: true, $r1: true } });
38
+ isRCEqual('R10', { range: { r0: 9, r1: 9, $r0: true, $r1: true } });
39
+ });
40
+
41
+ test('relative row references', () => {
42
+ isRCEqual('R[1]', { range: { r0: 1, r1: 1 } });
43
+ isRCEqual('R[-1]', { range: { r0: -1, r1: -1 } });
44
+ });
45
+
46
+ test('current column references', () => {
47
+ isRCEqual('C', { range: { c0: 0, c1: 0 } });
48
+ isRCEqual('C[0]', { range: { c0: 0, c1: 0 } });
49
+ });
50
+
51
+ test('fixed column references', () => {
52
+ isRCEqual('C0', { name: 'C0' });
53
+ isRCEqual('C1', { range: { c0: 0, c1: 0, $c0: true, $c1: true } });
54
+ isRCEqual('C10', { range: { c0: 9, c1: 9, $c0: true, $c1: true } });
55
+ });
56
+
57
+ test('relative column references', () => {
58
+ isRCEqual('C[1]', { range: { c0: 1, c1: 1 } });
59
+ isRCEqual('C[-1]', { range: { c0: -1, c1: -1 } });
60
+ });
61
+
62
+ test('cell references', () => {
63
+ isRCEqual('RC', { range: { r0: 0, c0: 0, r1: 0, c1: 0 } });
64
+ isRCEqual('R0C0', { name: 'R0C0' });
65
+ isRCEqual('R1C1', { range: { r0: 0, c0: 0, r1: 0, c1: 0, $c0: true, $c1: true, $r0: true, $r1: true } });
66
+ isRCEqual('R10C8', { range: { r0: 9, c0: 7, r1: 9, c1: 7, $c0: true, $c1: true, $r0: true, $r1: true } });
67
+ isRCEqual('R-10C-8', undefined);
68
+ });
69
+
70
+ test('relative cell parts', () => {
71
+ isRCEqual('R[2]C', { range: { r0: 2, c0: 0, r1: 2, c1: 0 } });
72
+ isRCEqual('R[-2]C', { range: { r0: -2, c0: 0, r1: -2, c1: 0 } });
73
+ isRCEqual('RC[3]', { range: { r0: 0, c0: 3, r1: 0, c1: 3 } });
74
+ isRCEqual('RC[-3]', { range: { r0: 0, c0: -3, r1: 0, c1: -3 } });
75
+ isRCEqual('R[2]C[4]', { range: { r0: 2, c0: 4, r1: 2, c1: 4 } });
76
+ isRCEqual('R[-2]C[-4]', { range: { r0: -2, c0: -4, r1: -2, c1: -4 } });
77
+ });
78
+
79
+ test('mixed fixed and relative references', () => {
80
+ isRCEqual('R[9]C9', { range: { r0: 9, c0: 8, r1: 9, c1: 8, $c0: true, $c1: true } });
81
+ isRCEqual('R9C[9]', { range: { r0: 8, c0: 9, r1: 8, c1: 9, $r0: true, $r1: true } });
82
+ });
83
+
84
+ test('out of bounds references', () => {
85
+ isRCEqual('R1048577', { name: 'R1048577' });
86
+ isRCEqual('R[1048576]', undefined);
87
+ isRCEqual('C16385', { name: 'C16385' });
88
+ isRCEqual('C[16384]', undefined);
89
+ });
90
+ });
91
+
92
+ describe('R1C1 partial ranges', () => {
93
+ const opts = { allowTernary: true };
94
+
95
+ test('partials are not allowed by default', () => {
96
+ isRCEqual('R[-5]C[-2]:C[-2]', undefined);
97
+ isRCEqual('R1:R1C1', undefined);
98
+ });
99
+
100
+ test('beam type partials', () => {
101
+ isRCEqual('R[-5]C[-2]:C[-2]', { range: { r0: -5, c0: -2, c1: -2 } }, opts);
102
+ isRCEqual('C[-2]:R[-5]C[-2]', { range: { r0: -5, c0: -2, c1: -2 } }, opts);
103
+ isRCEqual('R[-5]C[-3]:R[-5]', { range: { r0: -5, c0: -3, r1: -5 } }, opts);
104
+ isRCEqual('R[-5]:R[-5]C[-3]', { range: { r0: -5, c0: -3, r1: -5 } }, opts);
105
+ isRCEqual('R[-6]C1:C1', { range: { r0: -6, c0: 0, c1: 0, $c0: true, $c1: true } }, opts);
106
+ isRCEqual('C1:R[-6]C1', { range: { r0: -6, c0: 0, c1: 0, $c0: true, $c1: true } }, opts);
107
+ isRCEqual('R[-6]C1:R[-6]', { range: { r0: -6, c0: 0, r1: -6, $c0: true, $c1: true } }, opts);
108
+ isRCEqual('R[-6]:R[-6]C1', { range: { r0: -6, c0: 0, r1: -6, $c0: true, $c1: true } }, opts);
109
+ isRCEqual('R1C[-2]:C[-2]', { range: { r0: 0, c0: -2, c1: -2, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
110
+ isRCEqual('C[-2]:R1C[-2]', { range: { r0: 0, c0: -2, c1: -2, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
111
+ isRCEqual('R1C[-3]:R1', { range: { r0: 0, c0: -3, r1: 0, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
112
+ isRCEqual('R1:R1C[-3]', { range: { r0: 0, c0: -3, r1: 0, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
113
+ isRCEqual('R1C1:C1', { range: { r0: 0, c0: 0, c1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
114
+ isRCEqual('C1:R1C1', { range: { r0: 0, c0: 0, c1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
115
+ isRCEqual('R1C1:R1', { range: { r0: 0, c0: 0, r1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
116
+ isRCEqual('R1:R1C1', { range: { r0: 0, c0: 0, r1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
117
+ });
118
+
119
+ test('range type partials', () => {
120
+ isRCEqual('R[-5]C[10]:R[4]', { range: { r0: -5, c0: 10, r1: 4 } }, opts);
121
+ isRCEqual('R[4]:R[-5]C[10]', { range: { r0: -5, c0: 10, r1: 4 } }, opts);
122
+ isRCEqual('R[-6]C16:R[3]', { range: { r0: -6, c0: 15, r1: 3, $c0: true, $c1: true } }, opts);
123
+ isRCEqual('R[3]:R[-6]C16', { range: { r0: -6, c0: 15, r1: 3, $c0: true, $c1: true } }, opts);
124
+ isRCEqual('R1C[10]:R10', { range: { r0: 0, c0: 10, r1: 9, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
125
+ isRCEqual('R10:R1C[10]', { range: { r0: 0, c0: 10, r1: 9, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
126
+ isRCEqual('R1C16:R10', { range: { r0: 0, c0: 15, r1: 9, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
127
+ isRCEqual('R10:R1C16', { range: { r0: 0, c0: 15, r1: 9, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
128
+ });
129
+ });
130
+
131
+ describe('parse joined R1C1 references', () => {
132
+ test('equivalent mirrored references', () => {
133
+ isRCEqual('R:R', { range: { r0: 0, r1: 0 } });
134
+ isRCEqual('R[0]:R[0]', { range: { r0: 0, r1: 0 } });
135
+ isRCEqual('R1:R1', { range: { r0: 0, r1: 0, $r0: true, $r1: true } });
136
+ isRCEqual('R10:R10', { range: { r0: 9, r1: 9, $r0: true, $r1: true } });
137
+ isRCEqual('R[1]:R[1]', { range: { r0: 1, r1: 1 } });
138
+ isRCEqual('R[-1]:R[-1]', { range: { r0: -1, r1: -1 } });
139
+
140
+ isRCEqual('C:C', { range: { c0: 0, c1: 0 } });
141
+ isRCEqual('C[0]:C[0]', { range: { c0: 0, c1: 0 } });
142
+ isRCEqual('C1:C1', { range: { c0: 0, c1: 0, $c0: true, $c1: true } });
143
+ isRCEqual('C10:C10', { range: { c0: 9, c1: 9, $c0: true, $c1: true } });
144
+ isRCEqual('C[1]:C[1]', { range: { c0: 1, c1: 1 } });
145
+ isRCEqual('C[-1]:C[-1]', { range: { c0: -1, c1: -1 } });
146
+ });
147
+
148
+ test('invalid range combinations', () => {
149
+ isRCEqual('R0:R0', undefined);
150
+ isRCEqual('C0:C0', undefined);
151
+ });
152
+
153
+ test('complex range references', () => {
154
+ isRCEqual('R[9]C9:R[9]C9', { range: { r0: 9, c0: 8, r1: 9, c1: 8, $c0: true, $c1: true } });
155
+ isRCEqual('R9C[9]:R9C[9]', { range: { r0: 8, c0: 9, r1: 8, c1: 9, $r0: true, $r1: true } });
156
+ isRCEqual('R[1]C[1]:R1C1', { range: { r0: 1, c0: 1, r1: 0, c1: 0, $c1: true, $r1: true } });
157
+ isRCEqual('R1C1:R2C2', { range: { r0: 0, c0: 0, r1: 1, c1: 1, $c0: true, $c1: true, $r0: true, $r1: true } });
158
+ isRCEqual('R2C2:R1C1', { range: { r0: 0, c0: 0, r1: 1, c1: 1, $c0: true, $c1: true, $r0: true, $r1: true } });
159
+ });
160
+
161
+ test('single dimension ranges', () => {
162
+ isRCEqual('C1:C3', { range: { c0: 0, c1: 2, $c0: true, $c1: true } });
163
+ isRCEqual('R[1]:R3', { range: { r0: 1, r1: 2, $r1: true } });
164
+ isRCEqual('R[1]C1:R1C[-1]', { range: { r0: 1, c0: 0, r1: 0, c1: -1, $c0: true, $r1: true, $c1: false } });
165
+ });
166
+
167
+ test('invalid mixed references', () => {
168
+ isRCEqual('R:C', undefined);
169
+ isRCEqual('R:RC', undefined);
170
+ isRCEqual('RC:R', undefined);
171
+ isRCEqual('RC:C', undefined);
172
+ isRCEqual('C:R', undefined);
173
+ isRCEqual('C:RC', undefined);
174
+ });
175
+ });
176
+
177
+ describe('parse R1C1 ranges in XLSX mode', () => {
178
+ const opts = { xlsx: true };
179
+ const rcRange = { r0: 0, c0: 0, r1: 0, c1: 0 };
180
+
181
+ test('workbook-only references', () => {
182
+ isRCEqual('[1]!RC', {
183
+ workbookName: '1',
184
+ sheetName: '',
185
+ range: rcRange
186
+ }, opts);
187
+
188
+ isRCEqual('[Workbook.xlsx]!RC', {
189
+ workbookName: 'Workbook.xlsx',
190
+ sheetName: '',
191
+ range: rcRange
192
+ }, opts);
193
+ });
194
+
195
+ test('workbook and sheet references', () => {
196
+ isRCEqual('[1]Sheet1!RC', {
197
+ workbookName: '1',
198
+ sheetName: 'Sheet1',
199
+ range: rcRange
200
+ }, opts);
201
+
202
+ isRCEqual('[Workbook.xlsx]Sheet1!RC', {
203
+ workbookName: 'Workbook.xlsx',
204
+ sheetName: 'Sheet1',
205
+ range: rcRange
206
+ }, opts);
207
+ });
208
+
209
+ test('named references', () => {
210
+ isRCEqual('[4]!name', {
211
+ workbookName: '4',
212
+ sheetName: '',
213
+ name: 'name'
214
+ }, opts);
215
+
216
+ isRCEqual('[Workbook.xlsx]!name', {
217
+ workbookName: 'Workbook.xlsx',
218
+ sheetName: '',
219
+ name: 'name'
220
+ }, opts);
221
+
222
+ isRCEqual('[16]Sheet1!name', {
223
+ workbookName: '16',
224
+ sheetName: 'Sheet1',
225
+ name: 'name'
226
+ }, opts);
227
+
228
+ isRCEqual('[Workbook.xlsx]Sheet1!name', {
229
+ workbookName: 'Workbook.xlsx',
230
+ sheetName: 'Sheet1',
231
+ name: 'name'
232
+ }, opts);
233
+ });
234
+
235
+ test('quoted references', () => {
236
+ isRCEqual("='[1]'!RC", {
237
+ workbookName: '1',
238
+ sheetName: '',
239
+ range: rcRange
240
+ }, opts);
241
+
242
+ isRCEqual("='[Workbook.xlsx]'!RC", {
243
+ workbookName: 'Workbook.xlsx',
244
+ sheetName: '',
245
+ range: rcRange
246
+ }, opts);
247
+
248
+ isRCEqual("'[1]Sheet1'!RC", {
249
+ workbookName: '1',
250
+ sheetName: 'Sheet1',
251
+ range: rcRange
252
+ }, opts);
253
+
254
+ isRCEqual("'[Workbook.xlsx]Sheet1'!RC", {
255
+ workbookName: 'Workbook.xlsx',
256
+ sheetName: 'Sheet1',
257
+ range: rcRange
258
+ }, opts);
259
+
260
+ isRCEqual("'[4]'!name", {
261
+ workbookName: '4',
262
+ sheetName: '',
263
+ name: 'name'
264
+ }, opts);
265
+
266
+ isRCEqual("'[Workbook.xlsx]'!name", {
267
+ workbookName: 'Workbook.xlsx',
268
+ sheetName: '',
269
+ name: 'name'
270
+ }, opts);
271
+
272
+ isRCEqual("'[16]Sheet1'!name", {
273
+ workbookName: '16',
274
+ sheetName: 'Sheet1',
275
+ name: 'name'
276
+ }, opts);
277
+
278
+ isRCEqual("'[Workbook.xlsx]Sheet1'!name", {
279
+ workbookName: 'Workbook.xlsx',
280
+ sheetName: 'Sheet1',
281
+ name: 'name'
282
+ }, opts);
283
+ });
284
+ });
285
+
286
+ describe('R1C1 trimmed ranges', () => {
287
+ const locks = { $r0: true, $r1: true, $c0: true, $c1: true };
288
+ const opts = [ {}, { xlsx: true } ];
289
+
290
+ for (const opt of opts) {
291
+ test(`trimmed ranges with ${opt.xlsx ? 'XLSX' : 'default'} options`, () => {
292
+ isRCEqual('R[1]C[1]:R[2]C[2]', { range: { r0: 1, r1: 2, c0: 1, c1: 2 } }, opt);
293
+ isRCEqual('R[1]C[1].:R[2]C[2]', { range: { r0: 1, r1: 2, c0: 1, c1: 2, trim: 'head' } }, opt);
294
+ isRCEqual('R[1]C[1]:.R[2]C[2]', { range: { r0: 1, r1: 2, c0: 1, c1: 2, trim: 'tail' } }, opt);
295
+ isRCEqual('R[1]C[1].:.R[2]C[2]', { range: { r0: 1, r1: 2, c0: 1, c1: 2, trim: 'both' } }, opt);
296
+
297
+ isRCEqual('R2C2:R3C3', { range: { r0: 1, r1: 2, c0: 1, c1: 2, ...locks } }, opt);
298
+ isRCEqual('R2C2.:R3C3', { range: { r0: 1, r1: 2, c0: 1, c1: 2, trim: 'head', ...locks } }, opt);
299
+ isRCEqual('R2C2:.R3C3', { range: { r0: 1, r1: 2, c0: 1, c1: 2, trim: 'tail', ...locks } }, opt);
300
+ isRCEqual('R2C2.:.R3C3', { range: { r0: 1, r1: 2, c0: 1, c1: 2, trim: 'both', ...locks } }, opt);
301
+
302
+ isRCEqual('C[1]:C[2]', { range: { c0: 1, c1: 2 } }, opt);
303
+ isRCEqual('C[1].:C[2]', { range: { c0: 1, c1: 2, trim: 'head' } }, opt);
304
+ isRCEqual('C[1]:.C[2]', { range: { c0: 1, c1: 2, trim: 'tail' } }, opt);
305
+ isRCEqual('C[1].:.C[2]', { range: { c0: 1, c1: 2, trim: 'both' } }, opt);
306
+
307
+ isRCEqual('R[10]:R[10]', { range: { r0: 10, r1: 10 } }, opt);
308
+ isRCEqual('R[10].:R[10]', { range: { r0: 10, r1: 10, trim: 'head' } }, opt);
309
+ isRCEqual('R[10]:.R[10]', { range: { r0: 10, r1: 10, trim: 'tail' } }, opt);
310
+ isRCEqual('R[10].:.R[10]', { range: { r0: 10, r1: 10, trim: 'both' } }, opt);
311
+
312
+ isRCEqual('R[2]C[2]:R[4]', undefined, { ...opt });
313
+ isRCEqual('R[2]C[2]:C[4]', undefined, { ...opt });
314
+ isRCEqual('R[2]C[2].:.R[4]', undefined, { ...opt });
315
+ isRCEqual('R[2]C[2].:.C[4]', undefined, { ...opt });
316
+
317
+ isRCEqual('R[2]C[2]:R[4]', { range: { r0: 2, r1: 4, c0: 2 } }, { allowTernary: true, ...opt });
318
+ isRCEqual('R[2]C[2]:C[4]', { range: { r0: 2, c0: 2, c1: 4 } }, { allowTernary: true, ...opt });
319
+ isRCEqual('R[2]C[2].:.R[4]', { range: { r0: 2, r1: 4, c0: 2, trim: 'both' } }, { allowTernary: true, ...opt });
320
+ isRCEqual('R[2]C[2].:.C[4]', { range: { r0: 2, c0: 2, c1: 4, trim: 'both' } }, { allowTernary: true, ...opt });
321
+ });
322
+ }
323
+ });
@@ -0,0 +1,127 @@
1
+ /*
2
+ ** RC notation works differently from A1 in that we can't merge static
3
+ ** references joined by `:`. Merging can only work between references
4
+ ** that are relative/absolute on the same axes, so:
5
+ ** - R1C1:R2C2 will work,
6
+ ** - R[1]C1:R[2]C2 will also work, but
7
+ ** - R[1]C[1]:R2C2 doesn't have a direct rectangle represention without context.
8
+ */
9
+ import type { ReferenceName, ReferenceNameXlsx, ReferenceR1C1, ReferenceR1C1Xlsx } from './types.ts';
10
+ import { parseR1C1Range } from './parseR1C1Range.ts';
11
+ import { parseRefCtx, parseRefXlsx } from './parseRef.ts';
12
+
13
+ /**
14
+ * Options for {@link parseR1C1Ref}.
15
+ */
16
+ export type OptsParseR1C1Ref = {
17
+ /**
18
+ * Enable parsing names as well as ranges.
19
+ * @defaultValue true
20
+ */
21
+ allowNamed?: boolean;
22
+ /**
23
+ * Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`.
24
+ * These are supported by Google Sheets but not Excel.
25
+ * See: [References.md](./References.md).
26
+ * @defaultValue false
27
+ */
28
+ allowTernary?: boolean;
29
+ };
30
+
31
+ /**
32
+ * Parse a string reference into an object representing it.
33
+ *
34
+ * ```js
35
+ * parseR1C1Ref('Sheet1!R[9]C9:R[9]C9');
36
+ * // => {
37
+ * // context: [ 'Sheet1' ],
38
+ * // range: {
39
+ * // r0: 9,
40
+ * // c0: 8,
41
+ * // r1: 9,
42
+ * // c1: 8,
43
+ * // $c0: true,
44
+ * // $c1: true
45
+ * // $r0: false,
46
+ * // $r1: false
47
+ * // }
48
+ * // }
49
+ * ```
50
+ *
51
+ * @see {@link OptsParseR1C1Ref}
52
+ * @param refString An R1C1-style reference string.
53
+ * @param [options] Options.
54
+ * @returns An object representing a valid reference or `undefined` if it is invalid.
55
+ */
56
+ export function parseR1C1Ref (
57
+ refString: string,
58
+ options: OptsParseR1C1Ref = {}
59
+ ): ReferenceR1C1 | ReferenceName | undefined {
60
+ const {
61
+ allowNamed = true,
62
+ allowTernary = false
63
+ } = options;
64
+ const d = parseRefCtx(refString, { allowNamed, allowTernary, r1c1: true });
65
+ if (d) {
66
+ if (d.name) {
67
+ return { context: d.context, name: d.name } as ReferenceName;
68
+ }
69
+ else if (d.r0) {
70
+ const range = d.r1
71
+ ? parseR1C1Range(d.r0 + d.operator + d.r1)
72
+ : parseR1C1Range(d.r0);
73
+ if (range) {
74
+ return { context: d.context, range } as ReferenceR1C1;
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Parse a string reference into an object representing it.
82
+ *
83
+ * ```js
84
+ * parseR1C1Ref('Sheet1!R[9]C9:R[9]C9');
85
+ * // => {
86
+ * // context: [ 'Sheet1' ],
87
+ * // range: {
88
+ * // r0: 9,
89
+ * // c0: 8,
90
+ * // r1: 9,
91
+ * // c1: 8,
92
+ * // $c0: true,
93
+ * // $c1: true
94
+ * // $r0: false,
95
+ * // $r1: false
96
+ * // }
97
+ * // }
98
+ * ```
99
+ *
100
+ * @see {@link OptsParseR1C1Ref}
101
+ * @param refString An R1C1-style reference string.
102
+ * @param [options] Options.
103
+ * @returns An object representing a valid reference or `undefined` if it is invalid.
104
+ */
105
+ export function parseR1C1RefXlsx (
106
+ refString: string,
107
+ options: OptsParseR1C1Ref = {}
108
+ ): ReferenceR1C1Xlsx | ReferenceNameXlsx | undefined {
109
+ const {
110
+ allowNamed = true,
111
+ allowTernary = false
112
+ } = options;
113
+ const d = parseRefXlsx(refString, { allowNamed, allowTernary, r1c1: true });
114
+ if (d) {
115
+ if (d.name && allowNamed) {
116
+ return { workbookName: d.workbookName, sheetName: d.sheetName, name: d.name } as ReferenceNameXlsx;
117
+ }
118
+ else if (d.r0) {
119
+ const range = d.r1
120
+ ? parseR1C1Range(d.r0 + d.operator + d.r1)
121
+ : parseR1C1Range(d.r0);
122
+ if (range) {
123
+ return { workbookName: (d).workbookName, sheetName: (d).sheetName, range } as ReferenceR1C1Xlsx;
124
+ }
125
+ }
126
+ }
127
+ }