@borgar/fx 2.1.1 → 3.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/lib/rc.js CHANGED
@@ -1,12 +1,14 @@
1
1
  /*
2
- RC notation works differently from A1 in that we can't merge static references joined by `:`.
3
- Merging can only work between references that are relative/absolute on the same axes, so:
4
- R1C1:R2C2 will work, R[1]C1:R[2]C2 will also work, but R[1]C[1]:R2C2 cannot be represented
5
- simply by a rectangle as without knowing the cell context.
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.
6
8
  */
7
-
8
- import { MAX_ROWS, MAX_COLS, tokenHandlersRefsRC } from './constants.js';
9
+ import { MAX_ROWS, MAX_COLS } from './constants.js';
9
10
  import { parseRef } from './parseRef.js';
11
+ import { stringifyPrefix } from './stringifyPrefix.js';
10
12
 
11
13
  function toCoord (value, isAbs) {
12
14
  if (isAbs) {
@@ -17,23 +19,37 @@ function toCoord (value, isAbs) {
17
19
 
18
20
  export function toRC (range) {
19
21
  const { r0, c0, r1, c1, $c0, $c1, $r0, $r1 } = range;
22
+ const nullR0 = r0 == null;
23
+ const nullR1 = r1 == null;
24
+ const nullC0 = c0 == null;
25
+ const nullC1 = c1 == null;
20
26
  // C:C
21
- if (r0 === 0 && r1 === MAX_ROWS) {
27
+ if ((r0 === 0 && r1 >= MAX_ROWS) || (nullR0 && nullR1)) {
22
28
  const a = toCoord(c0, $c0);
23
29
  const b = toCoord(c1, $c1);
24
30
  return 'C' + (a === b ? a : a + ':C' + b);
25
31
  }
26
32
  // R:R
27
- if (c0 === 0 && c1 === MAX_COLS) {
33
+ if ((c0 === 0 && c1 >= MAX_COLS) || (nullC0 && nullC1)) {
28
34
  const a = toCoord(r0, $r0);
29
35
  const b = toCoord(r1, $r1);
30
36
  return 'R' + (a === b ? a : a + ':R' + b);
31
37
  }
32
- // RC:RC
33
38
  const s_r0 = toCoord(r0, $r0);
34
39
  const s_r1 = toCoord(r1, $r1);
35
40
  const s_c0 = toCoord(c0, $c0);
36
41
  const s_c1 = toCoord(c1, $c1);
42
+ // RC:R, RC:C
43
+ if (nullR0 || nullR1 || nullC0 || nullC1) {
44
+ return (
45
+ (nullR0 ? '' : 'R' + s_r0) +
46
+ (nullC0 ? '' : 'C' + s_c0) +
47
+ ':' +
48
+ (nullR1 ? '' : 'R' + s_r1) +
49
+ (nullC1 ? '' : 'C' + s_c1)
50
+ );
51
+ }
52
+ // RC:RC
37
53
  if (s_r0 !== s_r1 || s_c0 !== s_c1) {
38
54
  return 'R' + s_r0 + 'C' + s_c0 + ':R' + s_r1 + 'C' + s_c1;
39
55
  }
@@ -42,85 +58,167 @@ export function toRC (range) {
42
58
  }
43
59
 
44
60
  function parseRCPart (ref) {
45
- let r0 = 0;
46
- let c0 = 0;
47
- let r1 = MAX_ROWS;
48
- let c1 = MAX_COLS;
49
- let $r0 = false;
50
- let $c0 = false;
51
- let $r1 = false;
52
- let $c1 = false;
61
+ let r0 = null;
62
+ let c0 = null;
63
+ let $r0 = null;
64
+ let $c0 = null;
53
65
  // R part
54
66
  const rm = /^R(?:\[([+-]?\d+)\]|(\d+))?/.exec(ref);
55
67
  if (rm) {
56
68
  if (rm[1]) {
57
69
  r0 = parseInt(rm[1], 10);
70
+ $r0 = false;
58
71
  }
59
72
  else if (rm[2]) {
60
73
  r0 = parseInt(rm[2], 10) - 1;
61
74
  $r0 = true;
62
75
  }
63
- r1 = r0;
64
- $r1 = $r0;
76
+ else {
77
+ r0 = 0;
78
+ $r0 = false;
79
+ }
65
80
  ref = ref.slice(rm[0].length);
66
81
  }
67
- else {
68
- // r0 = 0, r1 = MAX_ROWS
69
- $r0 = true;
70
- $r1 = true;
71
- }
72
82
  // C part
73
83
  const cm = /^C(?:\[([+-]?\d+)\]|(\d+))?/.exec(ref);
74
84
  if (cm) {
75
85
  if (cm[1]) {
76
86
  c0 = parseInt(cm[1], 10);
87
+ $c0 = false;
77
88
  }
78
89
  else if (cm[2]) {
79
90
  c0 = parseInt(cm[2], 10) - 1;
80
91
  $c0 = true;
81
92
  }
82
- c1 = c0;
83
- $c1 = $c0;
93
+ else {
94
+ c0 = 0;
95
+ $c0 = false;
96
+ }
84
97
  ref = ref.slice(cm[0].length);
85
98
  }
86
- else {
87
- // c0 = 0, c1 = MAX_COLS
88
- $c0 = true;
89
- $c1 = true;
90
- }
91
99
  // must have at least one part (and nothing more)
92
100
  if ((!rm && !cm) || ref.length) {
93
101
  return null;
94
102
  }
95
- return { r0, c0, r1, c1, $r0, $c0, $r1, $c1 };
103
+ return [ r0, c0, $r0, $c0 ];
96
104
  }
97
105
 
98
106
  export function fromRC (ref) {
107
+ let final = null;
99
108
  const [ part1, part2 ] = ref.split(':', 2);
100
109
  const range = parseRCPart(part1);
101
- if (!range) {
102
- return null;
103
- }
104
- if (range && part2) {
105
- const extendTo = parseRCPart(part2);
106
- if (extendTo) {
107
- // Note: R[-1]C[-1]:R1C1 is only meaningful once we have an anchor
108
- range.r1 = extendTo.r1;
109
- range.c1 = extendTo.c1;
110
- range.$r1 = extendTo.$r1;
111
- range.$c1 = extendTo.$c1;
110
+ if (range) {
111
+ const [ r0, c0, $r0, $c0 ] = range;
112
+ if (part2) {
113
+ const extendTo = parseRCPart(part2);
114
+ if (extendTo) {
115
+ final = {};
116
+ const [ r1, c1, $r1, $c1 ] = extendTo;
117
+ // rows
118
+ if (r0 != null && r1 != null) {
119
+ final.r0 = $r0 === $r1 ? Math.min(r0, r1) : r0;
120
+ final.$r0 = $r0;
121
+ final.r1 = $r0 === $r1 ? Math.max(r0, r1) : r1;
122
+ final.$r1 = $r1;
123
+ }
124
+ else if (r0 != null && r1 == null) {
125
+ // partial RC:C
126
+ final.r0 = r0;
127
+ final.$r0 = $r0;
128
+ final.r1 = null;
129
+ final.$r1 = $r0;
130
+ }
131
+ else if (r0 == null && r1 != null) {
132
+ // partial C:RC
133
+ final.r0 = r1;
134
+ final.$r0 = $r1;
135
+ final.r1 = null;
136
+ final.$r1 = $r1;
137
+ }
138
+ else if (r0 == null && r1 == null) {
139
+ // C:C
140
+ final.r0 = null;
141
+ final.$r0 = false;
142
+ final.r1 = null;
143
+ final.$r1 = false;
144
+ }
145
+ // columns
146
+ if (c0 != null && c1 != null) {
147
+ final.c0 = $c0 === $c1 ? Math.min(c0, c1) : c0;
148
+ final.$c0 = $c0;
149
+ final.c1 = $c0 === $c1 ? Math.max(c0, c1) : c1;
150
+ final.$c1 = $c1;
151
+ }
152
+ else if (c0 != null && c1 == null) {
153
+ final.c0 = c0;
154
+ final.$c0 = $c0;
155
+ final.c1 = null;
156
+ final.$c1 = $c0;
157
+ }
158
+ else if (c0 == null && c1 != null) {
159
+ final.c0 = c1;
160
+ final.$c0 = $c1;
161
+ final.c1 = null;
162
+ final.$c1 = $c1;
163
+ }
164
+ else if (c0 == null && c1 == null) {
165
+ final.c0 = null;
166
+ final.$c0 = false;
167
+ final.c1 = null;
168
+ final.$c1 = false;
169
+ }
170
+ }
171
+ else {
172
+ return null;
173
+ }
174
+ }
175
+ // range only - no second part
176
+ else if (r0 != null && c0 == null) {
177
+ return {
178
+ r0: r0,
179
+ c0: null,
180
+ r1: r0,
181
+ c1: null,
182
+ $r0: $r0,
183
+ $c0: false,
184
+ $r1: $r0,
185
+ $c1: false
186
+ };
187
+ }
188
+ else if (r0 == null && c0 != null) {
189
+ return {
190
+ r0: null,
191
+ c0: c0,
192
+ r1: null,
193
+ c1: c0,
194
+ $r0: false,
195
+ $c0: $c0,
196
+ $r1: false,
197
+ $c1: $c0
198
+ };
112
199
  }
113
200
  else {
114
- return null;
201
+ return {
202
+ r0: r0 || 0,
203
+ c0: c0 || 0,
204
+ r1: r0 || 0,
205
+ c1: c0 || 0,
206
+ $r0: $r0 || false,
207
+ $c0: $c0 || false,
208
+ $r1: $r0 || false,
209
+ $c1: $c0 || false
210
+ };
115
211
  }
116
212
  }
117
- return range;
213
+ return final;
118
214
  }
119
215
 
120
- export function parseRCRef (ref, allow_named = true) {
121
- const d = parseRef(ref, allow_named, tokenHandlersRefsRC);
216
+ export function parseRCRef (ref, { allowNamed = true, allowTernary = false } = {}) {
217
+ const d = parseRef(ref, { allowNamed, allowTernary, r1c1: true });
122
218
  if (d && (d.r0 || d.name)) {
123
- const range = d.r1 ? fromRC(d.r0 + ':' + d.r1) : fromRC(d.r0);
219
+ const range = d.r1
220
+ ? fromRC(d.r0 + ':' + d.r1)
221
+ : fromRC(d.r0);
124
222
  if (d.name || range) {
125
223
  d.range = range;
126
224
  delete d.r0;
@@ -134,8 +232,15 @@ export function parseRCRef (ref, allow_named = true) {
134
232
  return null;
135
233
  }
136
234
 
235
+ export function stringifyRCRef (ref) {
236
+ return stringifyPrefix(ref) + (
237
+ ref.name ? ref.name : toRC(ref.range)
238
+ );
239
+ }
240
+
137
241
  export default {
138
242
  to: toRC,
139
243
  from: fromRC,
140
- parse: parseRCRef
244
+ parse: parseRCRef,
245
+ stringify: stringifyRCRef
141
246
  };
package/lib/rc.spec.js ADDED
@@ -0,0 +1,220 @@
1
+ /* eslint-disable object-property-newline, object-curly-newline */
2
+ import { test, Test } from 'tape';
3
+ import { MAX_COLS, MAX_ROWS } from './constants.js';
4
+ import { parseRCRef, stringifyRCRef, toRC } from './rc.js';
5
+
6
+ Test.prototype.isRCEqual = function isTokens (expr, result, opts) {
7
+ if (result) {
8
+ result = {
9
+ context: [],
10
+ name: '',
11
+ range: null,
12
+ ...result
13
+ };
14
+ if (result.range && typeof result.range === 'object') {
15
+ // mix in some defaults so we don't have to write things out in full
16
+ result.range = {
17
+ r0: null, c0: null, r1: null, c1: null,
18
+ $r0: false, $c0: false, $r1: false, $c1: false,
19
+ ...result.range
20
+ };
21
+ }
22
+ }
23
+ this.deepEqual(parseRCRef(expr, opts), result, expr);
24
+ };
25
+
26
+ Test.prototype.isR1C1Rendered = function isTokens (range, expect, d) {
27
+ this.is(toRC(range, d), expect, expect);
28
+ };
29
+
30
+ test('parse single R1C1 references', t => {
31
+ // current row
32
+ t.isRCEqual('R', { range: { r0: 0, r1: 0 } });
33
+ t.isRCEqual('R[0]', { range: { r0: 0, r1: 0 } });
34
+ // row N (equivalent to 1:1)
35
+ t.isRCEqual('R0', { name: 'R0' });
36
+ t.isRCEqual('R1', { range: { r0: 0, r1: 0, $r0: true, $r1: true } });
37
+ t.isRCEqual('R10', { range: { r0: 9, r1: 9, $r0: true, $r1: true } });
38
+ // row following current
39
+ t.isRCEqual('R[1]', { range: { r0: 1, r1: 1 } });
40
+ // row preceding current
41
+ t.isRCEqual('R[-1]', { range: { r0: -1, r1: -1 } });
42
+
43
+ // current column
44
+ t.isRCEqual('C', { range: { c0: 0, c1: 0 } });
45
+ t.isRCEqual('C[0]', { range: { c0: 0, c1: 0 } });
46
+ // column N (equivalent to A:A)
47
+ t.isRCEqual('C0', { name: 'C0' });
48
+ t.isRCEqual('C1', { range: { c0: 0, c1: 0, $c0: true, $c1: true } });
49
+ t.isRCEqual('C10', { range: { c0: 9, c1: 9, $c0: true, $c1: true } });
50
+ // column following current
51
+ t.isRCEqual('C[1]', { range: { c0: 1, c1: 1 } });
52
+ // column preceding current
53
+ t.isRCEqual('C[-1]', { range: { c0: -1, c1: -1 } });
54
+ // current cell
55
+ t.isRCEqual('RC', { range: { r0: 0, c0: 0, r1: 0, c1: 0 } });
56
+ t.isRCEqual('R0C0', { name: 'R0C0' });
57
+ // fixed cell
58
+ t.isRCEqual('R1C1', { range: { r0: 0, c0: 0, r1: 0, c1: 0, $c0: true, $c1: true, $r0: true, $r1: true } });
59
+ t.isRCEqual('R10C8', { range: { r0: 9, c0: 7, r1: 9, c1: 7, $c0: true, $c1: true, $r0: true, $r1: true } });
60
+ t.isRCEqual('R-10C-8', null);
61
+
62
+ // relative parts
63
+ t.isRCEqual('R[2]C', { range: { r0: 2, c0: 0, r1: 2, c1: 0 } });
64
+ t.isRCEqual('R[-2]C', { range: { r0: -2, c0: 0, r1: -2, c1: 0 } });
65
+ t.isRCEqual('RC[3]', { range: { r0: 0, c0: 3, r1: 0, c1: 3 } });
66
+ t.isRCEqual('RC[-3]', { range: { r0: 0, c0: -3, r1: 0, c1: -3 } });
67
+ t.isRCEqual('R[2]C[4]', { range: { r0: 2, c0: 4, r1: 2, c1: 4 } });
68
+ t.isRCEqual('R[-2]C[-4]', { range: { r0: -2, c0: -4, r1: -2, c1: -4 } });
69
+ // mixed fixed and relative
70
+ t.isRCEqual('R[9]C9', { range: { r0: 9, c0: 8, r1: 9, c1: 8, $c0: true, $c1: true } });
71
+ t.isRCEqual('R9C[9]', { range: { r0: 8, c0: 9, r1: 8, c1: 9, $r0: true, $r1: true } });
72
+
73
+ // out of bounds
74
+ t.isRCEqual('R1048577', { name: 'R1048577' });
75
+ t.isRCEqual('R[1048576]', null);
76
+ t.isRCEqual('C16385', { name: 'C16385' });
77
+ t.isRCEqual('C[16384]', null);
78
+
79
+ t.end();
80
+ });
81
+
82
+ test('R1C1 partial ranges', t => {
83
+ const opts = { allowTernary: true };
84
+ // partials are not allowed by defult
85
+ t.isRCEqual('R[-5]C[-2]:C[-2]', null);
86
+ t.isRCEqual('R1:R1C1', null);
87
+ // beam type partials A1:A @ C6
88
+ t.isRCEqual('R[-5]C[-2]:C[-2]', { range: { r0: -5, c0: -2, c1: -2 } }, opts);
89
+ t.isRCEqual('C[-2]:R[-5]C[-2]', { range: { r0: -5, c0: -2, c1: -2 } }, opts);
90
+ t.isRCEqual('R[-5]C[-3]:R[-5]', { range: { r0: -5, c0: -3, r1: -5 } }, opts);
91
+ t.isRCEqual('R[-5]:R[-5]C[-3]', { range: { r0: -5, c0: -3, r1: -5 } }, opts);
92
+ t.isRCEqual('R[-6]C1:C1', { range: { r0: -6, c0: 0, c1: 0, $c0: true, $c1: true } }, opts);
93
+ t.isRCEqual('C1:R[-6]C1', { range: { r0: -6, c0: 0, c1: 0, $c0: true, $c1: true } }, opts);
94
+ t.isRCEqual('R[-6]C1:R[-6]', { range: { r0: -6, c0: 0, r1: -6, $c0: true, $c1: true } }, opts);
95
+ t.isRCEqual('R[-6]:R[-6]C1', { range: { r0: -6, c0: 0, r1: -6, $c0: true, $c1: true } }, opts);
96
+ t.isRCEqual('R1C[-2]:C[-2]', { range: { r0: 0, c0: -2, c1: -2, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
97
+ t.isRCEqual('C[-2]:R1C[-2]', { range: { r0: 0, c0: -2, c1: -2, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
98
+ t.isRCEqual('R1C[-3]:R1', { range: { r0: 0, c0: -3, r1: 0, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
99
+ t.isRCEqual('R1:R1C[-3]', { range: { r0: 0, c0: -3, r1: 0, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
100
+ t.isRCEqual('R1C1:C1', { range: { r0: 0, c0: 0, c1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
101
+ t.isRCEqual('C1:R1C1', { range: { r0: 0, c0: 0, c1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
102
+ t.isRCEqual('R1C1:R1', { range: { r0: 0, c0: 0, r1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
103
+ t.isRCEqual('R1:R1C1', { range: { r0: 0, c0: 0, r1: 0, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
104
+ // range type partials P1:10 @ F6
105
+ t.isRCEqual('R[-5]C[10]:R[4]', { range: { r0: -5, c0: 10, r1: 4 } }, opts);
106
+ t.isRCEqual('R[4]:R[-5]C[10]', { range: { r0: -5, c0: 10, r1: 4 } }, opts);
107
+ t.isRCEqual('R[-6]C16:R[3]', { range: { r0: -6, c0: 15, r1: 3, $c0: true, $c1: true } }, opts);
108
+ t.isRCEqual('R[3]:R[-6]C16', { range: { r0: -6, c0: 15, r1: 3, $c0: true, $c1: true } }, opts);
109
+ t.isRCEqual('R1C[10]:R10', { range: { r0: 0, c0: 10, r1: 9, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
110
+ t.isRCEqual('R10:R1C[10]', { range: { r0: 0, c0: 10, r1: 9, $r0: true, $c0: false, $r1: true, $c1: false } }, opts);
111
+ t.isRCEqual('R1C16:R10', { range: { r0: 0, c0: 15, r1: 9, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
112
+ t.isRCEqual('R10:R1C16', { range: { r0: 0, c0: 15, r1: 9, $r0: true, $c0: true, $r1: true, $c1: true } }, opts);
113
+ t.end();
114
+ });
115
+
116
+ test('parse joined R1C1 references', t => {
117
+ // all "mirrored" refs are equivalent of the non mirrored counterparts...
118
+ t.isRCEqual('R:R', { range: { r0: 0, r1: 0 } });
119
+ t.isRCEqual('R[0]:R[0]', { range: { r0: 0, r1: 0 } });
120
+ t.isRCEqual('R1:R1', { range: { r0: 0, r1: 0, $r0: true, $r1: true } });
121
+ t.isRCEqual('R10:R10', { range: { r0: 9, r1: 9, $r0: true, $r1: true } });
122
+ t.isRCEqual('R[1]:R[1]', { range: { r0: 1, r1: 1 } });
123
+ t.isRCEqual('R[-1]:R[-1]', { range: { r0: -1, r1: -1 } });
124
+
125
+ t.isRCEqual('C:C', { range: { c0: 0, c1: 0 } });
126
+ t.isRCEqual('C[0]:C[0]', { range: { c0: 0, c1: 0 } });
127
+ t.isRCEqual('C1:C1', { range: { c0: 0, c1: 0, $c0: true, $c1: true } });
128
+ t.isRCEqual('C10:C10', { range: { c0: 9, c1: 9, $c0: true, $c1: true } });
129
+ t.isRCEqual('C[1]:C[1]', { range: { c0: 1, c1: 1 } });
130
+ t.isRCEqual('C[-1]:C[-1]', { range: { c0: -1, c1: -1 } });
131
+
132
+ t.isRCEqual('R0:R0', null);
133
+ t.isRCEqual('C0:C0', null);
134
+ t.isRCEqual('R[9]C9:R[9]C9', { range: { r0: 9, c0: 8, r1: 9, c1: 8, $c0: true, $c1: true } });
135
+ t.isRCEqual('R9C[9]:R9C[9]', { range: { r0: 8, c0: 9, r1: 8, c1: 9, $r0: true, $r1: true } });
136
+ t.isRCEqual('R[1]C[1]:R1C1', { range: { r0: 1, c0: 1, r1: 0, c1: 0, $c1: true, $r1: true } });
137
+ t.isRCEqual('R[1]C[1]:R1C1', { range: { r0: 1, c0: 1, r1: 0, c1: 0, $c1: true, $r1: true } });
138
+ t.isRCEqual('R1C1:R2C2', { range: { r0: 0, c0: 0, r1: 1, c1: 1, $c0: true, $c1: true, $r0: true, $r1: true } });
139
+ t.isRCEqual('R2C2:R1C1', { range: { r0: 0, c0: 0, r1: 1, c1: 1, $c0: true, $c1: true, $r0: true, $r1: true } });
140
+ // single thing
141
+ t.isRCEqual('C1:C3', { range: { c0: 0, c1: 2, $c0: true, $c1: true } });
142
+ t.isRCEqual('R[1]:R3', { range: { r0: 1, r1: 2, $r1: true } });
143
+ t.isRCEqual('R[1]C1:R1C[-1]', { range: { r0: 1, c0: 0, r1: 0, c1: -1, $c0: true, $r1: true, $c1: false } });
144
+ // many things
145
+ t.isRCEqual('R:C', null);
146
+ t.isRCEqual('R:RC', null);
147
+ t.isRCEqual('RC:R', null);
148
+ t.isRCEqual('RC:C', null);
149
+ t.isRCEqual('C:R', null);
150
+ t.isRCEqual('C:RC', null);
151
+ t.end();
152
+ });
153
+
154
+ test('R1C1 serialization', t => {
155
+ // ray
156
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: MAX_COLS }, 'R');
157
+ t.isR1C1Rendered({ r0: 0, r1: 0 }, 'R');
158
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: MAX_COLS, $r0: true, $r1: true }, 'R1');
159
+ t.isR1C1Rendered({ r0: 0, r1: 0, $r0: true, $r1: true }, 'R1');
160
+ t.isR1C1Rendered({ r0: 1, c0: 0, r1: 1, c1: MAX_COLS }, 'R[1]');
161
+ t.isR1C1Rendered({ r0: 1, r1: 1 }, 'R[1]');
162
+ // ray
163
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: MAX_ROWS, c1: 0 }, 'C');
164
+ t.isR1C1Rendered({ c0: 0, c1: 0 }, 'C');
165
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: MAX_ROWS, c1: 0, $c0: true, $c1: true }, 'C1');
166
+ t.isR1C1Rendered({ c0: 0, c1: 0, $c0: true, $c1: true }, 'C1');
167
+ t.isR1C1Rendered({ r0: 0, c0: 1, r1: MAX_ROWS, c1: 1 }, 'C[1]');
168
+ t.isR1C1Rendered({ c0: 1, c1: 1 }, 'C[1]');
169
+ // rect
170
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: 0 }, 'RC');
171
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: 0, $c0: true, $c1: true, $r0: true, $r1: true }, 'R1C1');
172
+ t.isR1C1Rendered({ r0: 9, c0: 7, r1: 9, c1: 7, $c0: true, $c1: true, $r0: true, $r1: true }, 'R10C8');
173
+ t.isR1C1Rendered({ r0: 2, c0: 0, r1: 2, c1: 0 }, 'R[2]C');
174
+ t.isR1C1Rendered({ r0: -2, c0: 0, r1: -2, c1: 0 }, 'R[-2]C');
175
+ t.isR1C1Rendered({ r0: 0, c0: 3, r1: 0, c1: 3 }, 'RC[3]');
176
+ t.isR1C1Rendered({ r0: 0, c0: -3, r1: 0, c1: -3 }, 'RC[-3]');
177
+ t.isR1C1Rendered({ r0: 2, c0: 4, r1: 2, c1: 4 }, 'R[2]C[4]');
178
+ t.isR1C1Rendered({ r0: -2, c0: -4, r1: -2, c1: -4 }, 'R[-2]C[-4]');
179
+ t.isR1C1Rendered({ r0: 9, c0: 8, r1: 9, c1: 8, $c0: true, $c1: true }, 'R[9]C9');
180
+ t.isR1C1Rendered({ r0: 8, c0: 9, r1: 8, c1: 9, $r0: true, $r1: true }, 'R9C[9]');
181
+ t.isR1C1Rendered({ r0: 1, c0: 1, r1: 0, c1: 0, $c1: true, $r1: true }, 'R[1]C[1]:R1C1');
182
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: 1, c1: 1, $c0: true, $c1: true, $r0: true, $r1: true }, 'R1C1:R2C2');
183
+ t.isR1C1Rendered({ c0: 0, c1: 2, $c0: true, $c1: true }, 'C1:C3');
184
+ t.isR1C1Rendered({ r0: 1, r1: 2, $r1: true }, 'R[1]:R3');
185
+ t.isR1C1Rendered({ r0: 1, c0: 0, r1: 0, c1: -1, $c0: true, $r1: true }, 'R[1]C1:R1C[-1]');
186
+ // partial
187
+ t.isR1C1Rendered({ r0: -5, c0: -2, c1: -2 }, 'R[-5]C[-2]:C[-2]');
188
+ t.isR1C1Rendered({ r0: -5, c0: -3, r1: -5 }, 'R[-5]C[-3]:R[-5]');
189
+ t.isR1C1Rendered({ r0: -6, c0: 0, c1: 0, $c0: true, $c1: true }, 'R[-6]C1:C1');
190
+ t.isR1C1Rendered({ r0: -6, c0: 0, r1: -6, $c0: true, $c1: true }, 'R[-6]C1:R[-6]');
191
+ t.isR1C1Rendered({ r0: 0, c0: -2, c1: -2, $r0: true, $r1: true }, 'R1C[-2]:C[-2]');
192
+ t.isR1C1Rendered({ r0: 0, c0: -3, r1: 0, $r0: true, $r1: true }, 'R1C[-3]:R1');
193
+ t.isR1C1Rendered({ r0: 0, c0: 0, c1: 0, $r0: true, $c0: true, $r1: true, $c1: true }, 'R1C1:C1');
194
+ t.isR1C1Rendered({ r0: 0, c0: 0, r1: 0, $r0: true, $c0: true, $r1: true, $c1: true }, 'R1C1:R1');
195
+ t.isR1C1Rendered({ r0: -5, c0: 10, r1: 4 }, 'R[-5]C[10]:R[4]');
196
+ t.isR1C1Rendered({ r0: -6, c0: 15, r1: 3, $c0: true, $c1: true }, 'R[-6]C16:R[3]');
197
+ t.isR1C1Rendered({ r0: 0, c0: 10, r1: 9, $r0: true, $r1: true }, 'R1C[10]:R10');
198
+ t.isR1C1Rendered({ r0: 0, c0: 15, r1: 9, $r0: true, $c0: true, $r1: true, $c1: true }, 'R1C16:R10');
199
+ t.end();
200
+ });
201
+
202
+ test('stringifyRCRef', t => {
203
+ const rangeA1 = { r0: 2, c0: 4, r1: 2, c1: 4 };
204
+ const testRef = (ref, expect) => t.is(stringifyRCRef(ref), expect, expect);
205
+ testRef({ range: rangeA1 }, 'R[2]C[4]');
206
+ testRef({ context: [ 'Sheet1' ], range: rangeA1 }, 'Sheet1!R[2]C[4]');
207
+ testRef({ context: [ 'Sheet 1' ], range: rangeA1 }, "'Sheet 1'!R[2]C[4]");
208
+ testRef({ context: [ 'MyFile.xlsx', 'Sheet1' ], range: rangeA1 }, '[MyFile.xlsx]Sheet1!R[2]C[4]');
209
+ testRef({ context: [ 'My File.xlsx', 'Sheet1' ], range: rangeA1 }, "'[My File.xlsx]Sheet1'!R[2]C[4]");
210
+ testRef({ context: [ 'MyFile.xlsx' ], range: rangeA1 }, 'MyFile.xlsx!R[2]C[4]');
211
+ testRef({ context: [ 'My File.xlsx' ], range: rangeA1 }, "'My File.xlsx'!R[2]C[4]");
212
+ testRef({ name: 'foo' }, 'foo');
213
+ testRef({ context: [ 'Sheet1' ], name: 'foo' }, 'Sheet1!foo');
214
+ testRef({ context: [ 'Sheet 1' ], name: 'foo' }, "'Sheet 1'!foo");
215
+ testRef({ context: [ 'MyFile.xlsx', 'Sheet1' ], name: 'foo' }, '[MyFile.xlsx]Sheet1!foo');
216
+ testRef({ context: [ 'My File.xlsx', 'Sheet1' ], name: 'foo' }, "'[My File.xlsx]Sheet1'!foo");
217
+ testRef({ context: [ 'MyFile.xlsx' ], name: 'foo' }, 'MyFile.xlsx!foo');
218
+ testRef({ context: [ 'My File.xlsx' ], name: 'foo' }, "'My File.xlsx'!foo");
219
+ t.end();
220
+ });
@@ -0,0 +1,21 @@
1
+ const reBannedChars = /[^0-9A-Za-z._¡¤§¨ª\u00ad¯-\uffff]/;
2
+
3
+ export function stringifyPrefix (ref) {
4
+ let pre = '';
5
+ let quote = 0;
6
+ let nth = 0;
7
+ const context = ref.context || [];
8
+ for (let i = context.length; i > -1; i--) {
9
+ const scope = context[i];
10
+ if (scope) {
11
+ const part = (nth % 2) ? '[' + scope + ']' : scope;
12
+ pre = part + pre;
13
+ quote += reBannedChars.test(scope);
14
+ nth++;
15
+ }
16
+ }
17
+ if (quote) {
18
+ pre = "'" + pre.replace(/'/g, "''") + "'";
19
+ }
20
+ return pre ? pre + '!' : pre;
21
+ }
@@ -1,8 +1,8 @@
1
1
  import { test, Test } from 'tape';
2
2
  import { translateToA1 } from './translate.js';
3
3
 
4
- Test.prototype.isR2A = function isTokens (expr, anchor, result) {
5
- this.is(translateToA1(expr, anchor), result, expr);
4
+ Test.prototype.isR2A = function isTokens (expr, anchor, result, debug) {
5
+ this.is(translateToA1(expr, anchor, debug), result, expr);
6
6
  };
7
7
 
8
8
  test('translate absolute cells from RC to A1', t => {
@@ -79,6 +79,24 @@ test('translate cols from RC to A1', t => {
79
79
  t.end();
80
80
  });
81
81
 
82
+ test('translate partials from RC to A1', t => {
83
+ t.isR2A('=R[-5]C[-2]:C[-2]', 'C6', '=A1:A');
84
+ t.isR2A('=R[-5]C[-3]:R[-5]', 'D6', '=A1:1');
85
+ t.isR2A('=R[-6]C1:C1', 'C7', '=$A1:$A');
86
+ t.isR2A('=C1:R[-6]C1', 'D7', '=$A1:$A');
87
+ t.isR2A('=R[-6]C1:R[-6]', 'C7', '=$A1:1');
88
+ t.isR2A('=R[-6]:R[-6]C1', 'C7', '=$A1:1');
89
+ t.isR2A('=R1C[-2]:C[-2]', 'C6', '=A$1:A');
90
+ t.isR2A('=C[-2]:R1C[-2]', 'C6', '=A$1:A');
91
+ t.isR2A('=R1C[-3]:R1', 'D6', '=A$1:$1');
92
+ t.isR2A('=R1C[-3]:R1', 'D6', '=A$1:$1');
93
+ t.isR2A('=R1C1:C1', 'D6', '=$A$1:$A');
94
+ t.isR2A('=C1:R1C1', 'D6', '=$A$1:$A');
95
+ t.isR2A('=R1C1:R1', 'D6', '=$A$1:$1');
96
+ t.isR2A('=R1:R1C1', 'D6', '=$A$1:$1');
97
+ t.end();
98
+ });
99
+
82
100
  test('translate out of bounds coords from RC to A1', t => {
83
101
  t.isR2A('=C[-1]', 'A1', '=XFD:XFD');
84
102
  t.isR2A('=C[-2]', 'A1', '=XFC:XFC');
@@ -74,6 +74,24 @@ test('translate cols from A1 to RC', t => {
74
74
  t.end();
75
75
  });
76
76
 
77
+ test('translate partials from A1 to RC', t => {
78
+ t.isA2R('=A1:A', 'C6', '=R[-5]C[-2]:C[-2]');
79
+ t.isA2R('=A1:1', 'D6', '=R[-5]C[-3]:R[-5]');
80
+ t.isA2R('=$A1:$A', 'C7', '=R[-6]C1:C1');
81
+ t.isA2R('=$A:$A1', 'D7', '=R[-6]C1:C1');
82
+ t.isA2R('=$A1:1', 'C7', '=R[-6]C1:R[-6]');
83
+ t.isA2R('=1:$A1', 'C7', '=R[-6]C1:R[-6]');
84
+ t.isA2R('=A$1:A', 'C6', '=R1C[-2]:C[-2]');
85
+ t.isA2R('=A:A$1', 'C6', '=R1C[-2]:C[-2]');
86
+ t.isA2R('=A$1:$1', 'D6', '=R1C[-3]:R1');
87
+ t.isA2R('=$1:A$1', 'D6', '=R1C[-3]:R1');
88
+ t.isA2R('=$A$1:$A', 'D6', '=R1C1:C1');
89
+ t.isA2R('=$A:$A$1', 'D6', '=R1C1:C1');
90
+ t.isA2R('=$A$1:$1', 'D6', '=R1C1:R1');
91
+ t.isA2R('=$1:$A$1', 'D6', '=R1C1:R1');
92
+ t.end();
93
+ });
94
+
77
95
  test('translate out of boundary coords from A1 to RC', t => {
78
96
  t.isA2R('=XFD:XFD', 'A1', '=C[16383]');
79
97
  t.isA2R('=A1', 'B1', '=RC[-1]');
@@ -99,4 +117,3 @@ test('translate involved formula from A1 to RC', t => {
99
117
  '=SUM(IF(RC[1],R2C5,R3C5),Sheet1!R2*Sheet2!C[-2])');
100
118
  t.end();
101
119
  });
102
-