@borgar/fx 4.4.0 → 4.6.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.
@@ -227,7 +227,8 @@ test('parse array literals', t => {
227
227
  [ { type: 'Literal', value: 1234, raw: '1234' } ],
228
228
  [ { type: 'CallExpression', callee: { type: 'Identifier', name: 'UNIQUE' }, arguments: [
229
229
  { type: 'ReferenceIdentifier', value: 'A:A' }
230
- ] } ] ] },
230
+ ] } ]
231
+ ] },
231
232
  { permitArrayCalls: true });
232
233
  // permitArrayCalls can be nested
233
234
  t.isParsed('={SUM({1,2}),3}',
@@ -235,9 +236,11 @@ test('parse array literals', t => {
235
236
  [ { type: 'CallExpression', callee: { type: 'Identifier', name: 'SUM' }, arguments: [
236
237
  { type: 'ArrayExpression', elements: [ [
237
238
  { type: 'Literal', value: 1, raw: '1' },
238
- { type: 'Literal', value: 2, raw: '2' } ] ] }
239
+ { type: 'Literal', value: 2, raw: '2' }
240
+ ] ] }
239
241
  ] },
240
- { type: 'Literal', value: 3, raw: '3' } ] ] },
242
+ { type: 'Literal', value: 3, raw: '3' } ]
243
+ ] },
241
244
  { permitArrayCalls: true });
242
245
  t.end();
243
246
  });
@@ -259,7 +262,8 @@ test('parse function calls', t => {
259
262
  type: 'CallExpression', callee: { type: 'Identifier', name: 'FOO' },
260
263
  arguments: [
261
264
  { type: 'Literal', value: 1, raw: '1' },
262
- { type: 'Literal', value: 2, raw: '2' } ]
265
+ { type: 'Literal', value: 2, raw: '2' }
266
+ ]
263
267
  });
264
268
  const args = Array(300).fill('1');
265
269
  t.isParsed(`=FOO(${args.join(',')})`, {
@@ -270,20 +274,24 @@ test('parse function calls', t => {
270
274
  type: 'CallExpression', callee: { type: 'Identifier', name: 'FOO' },
271
275
  arguments: [
272
276
  { type: 'ReferenceIdentifier', value: 'A1' },
273
- { type: 'ReferenceIdentifier', value: 'B2' } ]
277
+ { type: 'ReferenceIdentifier', value: 'B2' }
278
+ ]
274
279
  });
275
280
  t.isParsed('=FOO((A1,B2))', {
276
281
  type: 'CallExpression', callee: { type: 'Identifier', name: 'FOO' },
277
282
  arguments: [
278
283
  { type: 'BinaryExpression', operator: ',', arguments: [
279
284
  { type: 'ReferenceIdentifier', value: 'A1' },
280
- { type: 'ReferenceIdentifier', value: 'B2' } ] } ]
285
+ { type: 'ReferenceIdentifier', value: 'B2' }
286
+ ] }
287
+ ]
281
288
  });
282
289
  t.isParsed('=FOO(BAR())', {
283
290
  type: 'CallExpression', callee: { type: 'Identifier', name: 'FOO' },
284
291
  arguments: [
285
292
  { type: 'CallExpression', callee: { type: 'Identifier', name: 'BAR' },
286
- arguments: [] } ]
293
+ arguments: [] }
294
+ ]
287
295
  });
288
296
  t.isParsed('=FOO(,)', {
289
297
  type: 'CallExpression', callee: { type: 'Identifier', name: 'FOO' },
@@ -479,8 +487,18 @@ test('parse unary operator @', t => {
479
487
  // parse binary operators
480
488
  // FIXME: add precedence & associativity tests (2+3*4)
481
489
  [
482
- '+', '-', '^', '*', '/', '&',
483
- '=', '<', '>', '<=', '>=', '<>'
490
+ '+',
491
+ '-',
492
+ '^',
493
+ '*',
494
+ '/',
495
+ '&',
496
+ '=',
497
+ '<',
498
+ '>',
499
+ '<=',
500
+ '>=',
501
+ '<>'
484
502
  ].forEach(op => {
485
503
  test('parse binary operator ' + op, t => {
486
504
  t.isParsed(`1${op}2`, {
@@ -496,8 +514,10 @@ test('parse unary operator @', t => {
496
514
  { type: 'BinaryExpression', operator: op,
497
515
  arguments: [
498
516
  { type: 'Literal', value: 1, raw: '1' },
499
- { type: 'Literal', value: 2, raw: '2' } ] },
500
- { type: 'Literal', value: 3, raw: '3' } ]
517
+ { type: 'Literal', value: 2, raw: '2' }
518
+ ] },
519
+ { type: 'Literal', value: 3, raw: '3' }
520
+ ]
501
521
  });
502
522
  t.isParsed(`"foo"${op}"bar"`, {
503
523
  type: 'BinaryExpression', operator: op,
@@ -518,10 +538,12 @@ test('parse unary operator @', t => {
518
538
  arguments: [
519
539
  { type: 'ArrayExpression', elements: [ [
520
540
  { type: 'Literal', value: 1, raw: '1' },
521
- { type: 'Literal', value: 2, raw: '2' } ] ] },
541
+ { type: 'Literal', value: 2, raw: '2' }
542
+ ] ] },
522
543
  { type: 'ArrayExpression', elements: [ [
523
544
  { type: 'Literal', value: 3, raw: '3' },
524
- { type: 'Literal', value: 4, raw: '4' } ] ] }
545
+ { type: 'Literal', value: 4, raw: '4' }
546
+ ] ] }
525
547
  ]
526
548
  });
527
549
  t.isParsed(`FOO()${op}BAR()`, {
@@ -552,22 +574,26 @@ test('parse unary operator @', t => {
552
574
  type: 'BinaryExpression', operator: op,
553
575
  arguments: [
554
576
  { type: 'ReferenceIdentifier', value: 'named1' },
555
- { type: 'ReferenceIdentifier', value: 'named2' } ] });
577
+ { type: 'ReferenceIdentifier', value: 'named2' }
578
+ ] });
556
579
  t.isParsed(`A1${op}named2`, {
557
580
  type: 'BinaryExpression', operator: op,
558
581
  arguments: [
559
582
  { type: 'ReferenceIdentifier', value: 'A1' },
560
- { type: 'ReferenceIdentifier', value: 'named2' } ] });
583
+ { type: 'ReferenceIdentifier', value: 'named2' }
584
+ ] });
561
585
  t.isParsed(`named1${op}B2`, {
562
586
  type: 'BinaryExpression', operator: op,
563
587
  arguments: [
564
588
  { type: 'ReferenceIdentifier', value: 'named1' },
565
- { type: 'ReferenceIdentifier', value: 'B2' } ] });
589
+ { type: 'ReferenceIdentifier', value: 'B2' }
590
+ ] });
566
591
  t.isParsed(`(A1)${op}(B2)`, {
567
592
  type: 'BinaryExpression', operator: op,
568
593
  arguments: [
569
594
  { type: 'ReferenceIdentifier', value: 'A1' },
570
- { type: 'ReferenceIdentifier', value: 'B2' } ] });
595
+ { type: 'ReferenceIdentifier', value: 'B2' }
596
+ ] });
571
597
  t.isInvalidExpr(`A1${op}0`);
572
598
  t.isInvalidExpr(`0${op}A1`);
573
599
  t.isInvalidExpr(`0${op}0`);
@@ -578,12 +604,14 @@ test('parse unary operator @', t => {
578
604
  type: 'BinaryExpression', operator: op,
579
605
  arguments: [
580
606
  { type: 'ReferenceIdentifier', value: 'A1' },
581
- { type: 'ErrorLiteral', value: '#REF!', raw: '#REF!' } ] });
607
+ { type: 'ErrorLiteral', value: '#REF!', raw: '#REF!' }
608
+ ] });
582
609
  t.isParsed(`#REF!${op}B2`, {
583
610
  type: 'BinaryExpression', operator: op,
584
611
  arguments: [
585
612
  { type: 'ErrorLiteral', value: '#REF!', raw: '#REF!' },
586
- { type: 'ReferenceIdentifier', value: 'B2' } ] });
613
+ { type: 'ReferenceIdentifier', value: 'B2' }
614
+ ] });
587
615
  t.isInvalidExpr(`A1${op}#NAME?`);
588
616
  t.isInvalidExpr(`A1${op}#VALUE!`);
589
617
  t.isInvalidExpr(`#NULL!${op}A1`);
@@ -595,8 +623,10 @@ test('parse unary operator @', t => {
595
623
  { type: 'BinaryExpression', operator: ',',
596
624
  arguments: [
597
625
  { type: 'ReferenceIdentifier', value: 'A1' },
598
- { type: 'ReferenceIdentifier', value: 'B2' } ] },
599
- { type: 'ReferenceIdentifier', value: 'C3' } ] });
626
+ { type: 'ReferenceIdentifier', value: 'B2' }
627
+ ] },
628
+ { type: 'ReferenceIdentifier', value: 'C3' }
629
+ ] });
600
630
  t.isParsed(`C3${op}(A1,B2)`, {
601
631
  type: 'BinaryExpression', operator: op,
602
632
  arguments: [
@@ -604,7 +634,8 @@ test('parse unary operator @', t => {
604
634
  { type: 'BinaryExpression', operator: ',',
605
635
  arguments: [
606
636
  { type: 'ReferenceIdentifier', value: 'A1' },
607
- { type: 'ReferenceIdentifier', value: 'B2' } ] }
637
+ { type: 'ReferenceIdentifier', value: 'B2' }
638
+ ] }
608
639
  ] });
609
640
  // intersection ops
610
641
  t.isParsed(`(A1 B2)${op}C3`, {
@@ -613,8 +644,10 @@ test('parse unary operator @', t => {
613
644
  { type: 'BinaryExpression', operator: ' ',
614
645
  arguments: [
615
646
  { type: 'ReferenceIdentifier', value: 'A1' },
616
- { type: 'ReferenceIdentifier', value: 'B2' } ] },
617
- { type: 'ReferenceIdentifier', value: 'C3' } ] });
647
+ { type: 'ReferenceIdentifier', value: 'B2' }
648
+ ] },
649
+ { type: 'ReferenceIdentifier', value: 'C3' }
650
+ ] });
618
651
  t.isParsed(`C3${op}(A1 B2)`, {
619
652
  type: 'BinaryExpression', operator: op,
620
653
  arguments: [
@@ -622,7 +655,8 @@ test('parse unary operator @', t => {
622
655
  { type: 'BinaryExpression', operator: ' ',
623
656
  arguments: [
624
657
  { type: 'ReferenceIdentifier', value: 'A1' },
625
- { type: 'ReferenceIdentifier', value: 'B2' } ] }
658
+ { type: 'ReferenceIdentifier', value: 'B2' }
659
+ ] }
626
660
  ] });
627
661
  // join ops
628
662
  t.isParsed(`(A1:(B2))${op}C3`, {
@@ -631,8 +665,10 @@ test('parse unary operator @', t => {
631
665
  { type: 'BinaryExpression', operator: ':',
632
666
  arguments: [
633
667
  { type: 'ReferenceIdentifier', value: 'A1' },
634
- { type: 'ReferenceIdentifier', value: 'B2' } ] },
635
- { type: 'ReferenceIdentifier', value: 'C3' } ] });
668
+ { type: 'ReferenceIdentifier', value: 'B2' }
669
+ ] },
670
+ { type: 'ReferenceIdentifier', value: 'C3' }
671
+ ] });
636
672
  t.isParsed(`C3${op}(A1:(B2))`, {
637
673
  type: 'BinaryExpression', operator: op,
638
674
  arguments: [
@@ -640,7 +676,8 @@ test('parse unary operator @', t => {
640
676
  { type: 'BinaryExpression', operator: ':',
641
677
  arguments: [
642
678
  { type: 'ReferenceIdentifier', value: 'A1' },
643
- { type: 'ReferenceIdentifier', value: 'B2' } ] }
679
+ { type: 'ReferenceIdentifier', value: 'B2' }
680
+ ] }
644
681
  ] });
645
682
  // ref calls
646
683
  ([
@@ -743,33 +780,38 @@ test('position information is correct', t => {
743
780
  t.isParsed(
744
781
  '=-A1',
745
782
  { type: 'UnaryExpression', loc: [ 1, 4 ], operator: '-', arguments: [
746
- { type: 'ReferenceIdentifier', value: 'A1', loc: [ 2, 4 ] } ] },
783
+ { type: 'ReferenceIdentifier', value: 'A1', loc: [ 2, 4 ] }
784
+ ] },
747
785
  { withLocation: true }
748
786
  );
749
787
  t.isParsed(
750
788
  '=10%',
751
789
  { type: 'UnaryExpression', loc: [ 1, 4 ], operator: '%', arguments: [
752
- { type: 'Literal', value: 10, loc: [ 1, 3 ], raw: '10' } ] },
790
+ { type: 'Literal', value: 10, loc: [ 1, 3 ], raw: '10' }
791
+ ] },
753
792
  { withLocation: true }
754
793
  );
755
794
  t.isParsed(
756
795
  '=-(123)',
757
796
  { type: 'UnaryExpression', loc: [ 1, 6 ], operator: '-', arguments: [
758
- { type: 'Literal', value: 123, loc: [ 3, 6 ], raw: '123' } ] },
797
+ { type: 'Literal', value: 123, loc: [ 3, 6 ], raw: '123' }
798
+ ] },
759
799
  { withLocation: true }
760
800
  );
761
801
  t.isParsed(
762
802
  '(123+(234))',
763
803
  { type: 'BinaryExpression', loc: [ 1, 9 ], operator: '+', arguments: [
764
804
  { type: 'Literal', value: 123, loc: [ 1, 4 ], raw: '123' },
765
- { type: 'Literal', value: 234, loc: [ 6, 9 ], raw: '234' } ] },
805
+ { type: 'Literal', value: 234, loc: [ 6, 9 ], raw: '234' }
806
+ ] },
766
807
  { withLocation: true }
767
808
  );
768
809
  t.isParsed(
769
810
  '=(A1 B2)',
770
811
  { type: 'BinaryExpression', loc: [ 2, 7 ], operator: ' ', arguments: [
771
812
  { type: 'ReferenceIdentifier', value: 'A1', loc: [ 2, 4 ] },
772
- { type: 'ReferenceIdentifier', value: 'B2', loc: [ 5, 7 ] } ] },
813
+ { type: 'ReferenceIdentifier', value: 'B2', loc: [ 5, 7 ] }
814
+ ] },
773
815
  { withLocation: true }
774
816
  );
775
817
  t.isParsed(
@@ -789,7 +831,8 @@ test('position information is correct', t => {
789
831
  [ { type: 'Literal', value: 1, loc: [ 3, 4 ], raw: '1' },
790
832
  { type: 'Literal', value: 2, loc: [ 6, 7 ], raw: '2' } ],
791
833
  [ { type: 'Literal', value: 3, loc: [ 9, 10 ], raw: '3' },
792
- { type: 'Literal', value: 4, loc: [ 12, 13 ], raw: '4' } ] ] },
834
+ { type: 'Literal', value: 4, loc: [ 12, 13 ], raw: '4' } ]
835
+ ] },
793
836
  { withLocation: true }
794
837
  );
795
838
  t.end();
@@ -802,7 +845,9 @@ test('does not tolerate unterminated tokens', t => {
802
845
  arguments: [
803
846
  { type: 'ArrayExpression', elements: [
804
847
  [ { type: 'ReferenceIdentifier', value: 'A:A' },
805
- { type: 'ReferenceIdentifier', value: 'B:B' } ] ] } ] },
848
+ { type: 'ReferenceIdentifier', value: 'B:B' } ]
849
+ ] }
850
+ ] },
806
851
  { permitArrayRanges: true });
807
852
  // whitespace in arguments
808
853
  t.isParsed('=A2:A5=XLOOKUP(B1,C:C, D:D)',
@@ -812,19 +857,23 @@ test('does not tolerate unterminated tokens', t => {
812
857
  arguments: [
813
858
  { type: 'ReferenceIdentifier', value: 'B1' },
814
859
  { type: 'ReferenceIdentifier', value: 'C:C' },
815
- { type: 'ReferenceIdentifier', value: 'D:D' } ] } ] },
860
+ { type: 'ReferenceIdentifier', value: 'D:D' }
861
+ ] }
862
+ ] },
816
863
  { permitArrayRanges: true });
817
864
  // whitespace surrounding comma
818
865
  t.isParsed('=SUM(12 , B:B)',
819
866
  { type: 'CallExpression', callee: { type: 'Identifier', name: 'SUM' }, arguments: [
820
867
  { type: 'Literal', value: 12, raw: '12' },
821
- { type: 'ReferenceIdentifier', value: 'B:B' } ] },
868
+ { type: 'ReferenceIdentifier', value: 'B:B' }
869
+ ] },
822
870
  { permitArrayCalls: true });
823
871
  // whitespace tailing operator
824
872
  t.isParsed('=A:A= C1',
825
873
  { type: 'BinaryExpression', operator: '=', arguments: [
826
874
  { type: 'ReferenceIdentifier', value: 'A:A' },
827
- { type: 'ReferenceIdentifier', value: 'C1' } ] },
875
+ { type: 'ReferenceIdentifier', value: 'C1' }
876
+ ] },
828
877
  { permitArrayCalls: true });
829
878
  t.end();
830
879
  });
@@ -835,7 +884,9 @@ test('parser can permit xlsx mode references', t => {
835
884
  { type: 'CallExpression', callee: { type: 'Identifier', name: 'SUM' }, arguments: [
836
885
  { type: 'BinaryExpression', operator: '+', arguments: [
837
886
  { type: 'ReferenceIdentifier', value: '[Workbook.xlsx]!A1' },
838
- { type: 'ReferenceIdentifier', value: '[Workbook.xlsx]!Table1[#Data]' } ] } ] },
887
+ { type: 'ReferenceIdentifier', value: '[Workbook.xlsx]!Table1[#Data]' }
888
+ ] }
889
+ ] },
839
890
  { xlsx: true });
840
891
  t.end();
841
892
  });
package/lib/rc.js CHANGED
@@ -24,8 +24,8 @@ function toCoord (value, isAbs) {
24
24
  *
25
25
  * @private
26
26
  * @see parseR1C1Ref
27
- * @param {Object} range A range object
28
- * @return {string} An R1C1-style string represenation of a range
27
+ * @param {RangeR1C1} range A range object
28
+ * @returns {string} An R1C1-style string represenation of a range
29
29
  */
30
30
  export function toR1C1 (range) {
31
31
  let { r0, c0, r1, c1 } = range;
@@ -47,13 +47,15 @@ export function toR1C1 (range) {
47
47
  c1 = clamp($c1 ? 0 : -MAX_COLS, c1 | 0, MAX_COLS);
48
48
  }
49
49
  // C:C
50
- if ((r0 === 0 && r1 >= MAX_ROWS) || (nullR0 && nullR1)) {
50
+ const allRows = r0 === 0 && r1 >= MAX_ROWS;
51
+ if ((allRows && !nullC0 && !nullC1) || (nullR0 && nullR1)) {
51
52
  const a = toCoord(c0, $c0);
52
53
  const b = toCoord(c1, $c1);
53
54
  return 'C' + (a === b ? a : a + ':C' + b);
54
55
  }
55
56
  // R:R
56
- if ((c0 === 0 && c1 >= MAX_COLS) || (nullC0 && nullC1)) {
57
+ const allCols = c0 === 0 && c1 >= MAX_COLS;
58
+ if ((allCols && !nullR0 && !nullR1) || (nullC0 && nullC1)) {
57
59
  const a = toCoord(r0, $r0);
58
60
  const b = toCoord(r1, $r1);
59
61
  return 'R' + (a === b ? a : a + ':R' + b);
@@ -132,11 +134,11 @@ function parseR1C1Part (ref) {
132
134
  * @private
133
135
  * @see parseA1Ref
134
136
  * @param {string} rangeString A range string
135
- * @return {(Object|null)} An object representing a valid reference or null if it is invalid.
137
+ * @returns {(RangeR1C1|null)} An object representing a valid reference or null if it is invalid.
136
138
  */
137
- export function fromR1C1 (ref) {
139
+ export function fromR1C1 (rangeString) {
138
140
  let final = null;
139
- const [ part1, part2 ] = ref.split(':', 2);
141
+ const [ part1, part2 ] = rangeString.split(':', 2);
140
142
  const range = parseR1C1Part(part1);
141
143
  if (range) {
142
144
  const [ r0, c0, $r0, $c0 ] = range;
@@ -265,11 +267,11 @@ export function fromR1C1 (ref) {
265
267
  * ```
266
268
  *
267
269
  * @param {string} refString An R1C1-style reference string
268
- * @param {Object} [options={}] Options
270
+ * @param {object} [options={}] Options
269
271
  * @param {boolean} [options.allowNamed=true] Enable parsing names as well as ranges.
270
272
  * @param {boolean} [options.allowTernary=false] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
271
273
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
272
- * @return {(Object|null)} An object representing a valid reference or null if it is invalid.
274
+ * @returns {(ReferenceR1C1|null)} An object representing a valid reference or null if it is invalid.
273
275
  */
274
276
  export function parseR1C1Ref (refString, { allowNamed = true, allowTernary = false, xlsx = false } = {}) {
275
277
  const d = parseRef(refString, { allowNamed, allowTernary, xlsx, r1c1: true });
@@ -310,10 +312,10 @@ export function parseR1C1Ref (refString, { allowNamed = true, allowTernary = fal
310
312
  * // => 'Sheet1!R[9]C9:R[9]C9'
311
313
  * ```
312
314
  *
313
- * @param {Object} refObject A reference object
314
- * @param {Object} [options={}] Options
315
+ * @param {ReferenceR1C1} refObject A reference object
316
+ * @param {object} [options={}] Options
315
317
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
316
- * @return {Object} The reference in R1C1-style string format
318
+ * @returns {string} The reference in R1C1-style string format
317
319
  */
318
320
  export function stringifyR1C1Ref (refObject, { xlsx = false } = {}) {
319
321
  const prefix = xlsx
package/lib/sr.js CHANGED
@@ -185,16 +185,16 @@ export function parseSRange (raw) {
185
185
  *
186
186
  * @tutorial References.md
187
187
  * @param {string} ref A structured reference string
188
- * @param {Object} [options={}] Options
188
+ * @param {object} [options={}] Options
189
189
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
190
- * @return {(Object|null)} An object representing a valid reference or null if it is invalid.
190
+ * @returns {(ReferenceStruct|null)} An object representing a valid reference or null if it is invalid.
191
191
  */
192
- export function parseStructRef (ref, opts = { xlsx: false }) {
193
- const r = parseRef(ref, opts);
192
+ export function parseStructRef (ref, options = { xlsx: false }) {
193
+ const r = parseRef(ref, options);
194
194
  if (r && r.struct) {
195
195
  const structData = parseSRange(r.struct);
196
196
  if (structData && structData.length === r.struct.length) {
197
- return opts.xlsx
197
+ return options.xlsx
198
198
  ? {
199
199
  workbookName: r.workbookName,
200
200
  sheetName: r.sheetName,
@@ -238,38 +238,38 @@ function toSentenceCase (str) {
238
238
  * // => 'workbook.xlsx!tableName[[#Data],[Column1]:[Column2]]'
239
239
  * ```
240
240
  *
241
- * @param {Object} refObject A structured reference object
242
- * @param {Object} [options={}] Options
241
+ * @param {ReferenceStruct} refObject A structured reference object
242
+ * @param {object} [options={}] Options
243
243
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
244
- * @return {Object} The structured reference in string format
244
+ * @returns {string} The structured reference in string format
245
245
  */
246
- export function stringifyStructRef (ref, { xlsx = false } = {}) {
246
+ export function stringifyStructRef (refObject, { xlsx = false } = {}) {
247
247
  let s = xlsx
248
- ? stringifyPrefixAlt(ref)
249
- : stringifyPrefix(ref);
248
+ ? stringifyPrefixAlt(refObject)
249
+ : stringifyPrefix(refObject);
250
250
 
251
- if (ref.table) {
252
- s += ref.table;
251
+ if (refObject.table) {
252
+ s += refObject.table;
253
253
  }
254
- const numColumns = ref.columns?.length ?? 0;
255
- const numSections = ref.sections?.length ?? 0;
254
+ const numColumns = refObject.columns?.length ?? 0;
255
+ const numSections = refObject.sections?.length ?? 0;
256
256
  // single section
257
257
  if (numSections === 1 && !numColumns) {
258
- s += `[#${toSentenceCase(ref.sections[0])}]`;
258
+ s += `[#${toSentenceCase(refObject.sections[0])}]`;
259
259
  }
260
260
  // single column
261
261
  else if (!numSections && numColumns === 1) {
262
- s += `[${quoteColname(ref.columns[0])}]`;
262
+ s += `[${quoteColname(refObject.columns[0])}]`;
263
263
  }
264
264
  else {
265
265
  s += '[';
266
266
  // single [#this row] sections get normalized to an @
267
- const singleAt = numSections === 1 && ref.sections[0].toLowerCase() === 'this row';
267
+ const singleAt = numSections === 1 && refObject.sections[0].toLowerCase() === 'this row';
268
268
  if (singleAt) {
269
269
  s += '@';
270
270
  }
271
271
  else if (numSections) {
272
- s += ref.sections
272
+ s += refObject.sections
273
273
  .map(d => `[#${toSentenceCase(d)}]`)
274
274
  .join(',');
275
275
  if (numColumns) {
@@ -277,11 +277,11 @@ export function stringifyStructRef (ref, { xlsx = false } = {}) {
277
277
  }
278
278
  }
279
279
  // a case of a single alphanumberic column with a [#this row] becomes [@col]
280
- if (singleAt && ref.columns.length === 1 && !needsBraces(ref.columns[0])) {
281
- s += quoteColname(ref.columns[0]);
280
+ if (singleAt && refObject.columns.length === 1 && !needsBraces(refObject.columns[0])) {
281
+ s += quoteColname(refObject.columns[0]);
282
282
  }
283
283
  else if (numColumns) {
284
- s += ref.columns.slice(0, 2)
284
+ s += refObject.columns.slice(0, 2)
285
285
  .map(d => (`[${quoteColname(d)}]`))
286
286
  .join(':');
287
287
  }
package/lib/sr.spec.js CHANGED
@@ -88,6 +88,13 @@ test('parse structured references', t => {
88
88
  context: [ 'Sheet1' ]
89
89
  });
90
90
 
91
+ t.isSREqual('[myworkbook.xlsx]Sheet1!TMP8w0habhr[#All]', {
92
+ columns: [],
93
+ table: 'TMP8w0habhr',
94
+ context: [ 'myworkbook.xlsx', 'Sheet1' ],
95
+ sections: [ 'all' ]
96
+ });
97
+
91
98
  t.end();
92
99
  });
93
100
 
@@ -100,7 +100,7 @@ test('translate partials from RC to A1', t => {
100
100
  t.end();
101
101
  });
102
102
 
103
- test('translate out of bounds coords from RC to A1', t => {
103
+ test('translate bounds coords from RC to A1', t => {
104
104
  t.isR2A('=C[-1]', 'A1', '=XFD:XFD');
105
105
  t.isR2A('=C[-2]', 'A1', '=XFC:XFC');
106
106
  t.isR2A('=RC[16383]', 'B1', '=A1');
@@ -110,6 +110,10 @@ test('translate out of bounds coords from RC to A1', t => {
110
110
  t.isR2A('=R[1048575]C', 'A2', '=A1');
111
111
  t.isR2A('=R[1048575]C', 'A3', '=A2');
112
112
 
113
+ t.isR2A('=R1:R1048576', 'A1', '=$1:$1048576');
114
+ t.isR2A('=C1:C16384', 'A1', '=$A:$XFD');
115
+ t.isR2A('=R1C1:R1048576C16384', 'A1', '=$A$1:$XFD$1048576');
116
+
113
117
  const f1 = '=R[-1]C[-1]';
114
118
  t.is(translateToA1(f1, 'A1', { wrapEdges: false }), '=#REF!', f1);
115
119
 
@@ -95,11 +95,14 @@ test('translate partials from A1 to RC', t => {
95
95
  t.end();
96
96
  });
97
97
 
98
- test('translate out of boundary coords from A1 to RC', t => {
98
+ test('translate boundary coords from A1 to RC', t => {
99
99
  t.isA2R('=XFD:XFD', 'A1', '=C[16383]');
100
100
  t.isA2R('=A1', 'B1', '=RC[-1]');
101
101
  t.isA2R('=B1', 'C1', '=RC[-1]');
102
102
  t.isA2R('=1048576:1048576', 'A1', '=R[1048575]');
103
+ t.isA2R('=$1:$1048576', 'A1', '=R1:R1048576');
104
+ t.isA2R('=$A:$XFD', 'A1', '=C1:C16384');
105
+ t.isA2R('=$A$1:$XFD$1048576', 'A1', '=R1C1:R1048576C16384');
103
106
  t.isA2R('=A1', 'A2', '=R[-1]C');
104
107
  t.isA2R('=A2', 'A3', '=R[-1]C');
105
108
  t.end();
package/lib/translate.js CHANGED
@@ -30,22 +30,23 @@ const settings = {
30
30
  * // => "=SUM(RC[1],R2C5,Sheet!R3C5)");
31
31
  * ```
32
32
  *
33
- * @param {(string | Array<Object>)} formula A string (an Excel formula) or a token list that should be adjusted.
33
+ * @param {(string | Array<Token>)} formula A string (an Excel formula) or a token list that should be adjusted.
34
34
  * @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
35
- * @param {Object} [options={}] The options
35
+ * @param {object} [options={}] The options
36
36
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
37
- * @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
37
+ * @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
38
+ * @returns {(string | Array<Token>)} A formula string or token list (depending on which was input)
38
39
  */
39
- export function translateToR1C1 (fx, anchorCell, { xlsx = false } = {}) {
40
+ export function translateToR1C1 (formula, anchorCell, { xlsx = false, allowTernary = true } = {}) {
40
41
  const { top, left } = fromA1(anchorCell);
41
- const isString = typeof fx === 'string';
42
+ const isString = typeof formula === 'string';
42
43
 
43
44
  const tokens = isString
44
- ? tokenize(fx, { ...settings, xlsx })
45
- : fx;
45
+ ? tokenize(formula, { ...settings, xlsx, allowTernary })
46
+ : formula;
46
47
 
47
48
  let offsetSkew = 0;
48
- const refOpts = { xlsx, allowTernary: true };
49
+ const refOpts = { xlsx, allowTernary };
49
50
  tokens.forEach(token => {
50
51
  if (isRange(token)) {
51
52
  const tokenValue = token.value;
@@ -108,6 +109,7 @@ function toFixed (val, abs, base, max, wrapEdges = true) {
108
109
  const defaultOptions = {
109
110
  wrapEdges: true,
110
111
  mergeRefs: true,
112
+ allowTernary: true,
111
113
  xlsx: false
112
114
  };
113
115
 
@@ -142,13 +144,14 @@ const defaultOptions = {
142
144
  * `=Sheet3!#REF!:F3`. These are valid formulas in the Excel formula language
143
145
  * and Excel will accept them, but they are not supported in Google Sheets.
144
146
  *
145
- * @param {(string | Array<Object>)} formula A string (an Excel formula) or a token list that should be adjusted.
147
+ * @param {(string | Array<Token>)} formula A string (an Excel formula) or a token list that should be adjusted.
146
148
  * @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
147
- * @param {Object} [options={}] The options
149
+ * @param {object} [options={}] The options
148
150
  * @param {boolean} [options.wrapEdges=true] Wrap out-of-bounds ranges around sheet edges rather than turning them to #REF! errors
149
151
  * @param {boolean} [options.mergeRefs=true] Should ranges be treated as whole references (`Sheet1!A1:B2`) or as separate tokens for each part: (`Sheet1`,`!`,`A1`,`:`,`B2`).
150
152
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
151
- * @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
153
+ * @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
154
+ * @returns {(string | Array<Token>)} A formula string or token list (depending on which was input)
152
155
  */
153
156
  export function translateToA1 (formula, anchorCell, options = defaultOptions) {
154
157
  const anchor = fromA1(anchorCell);
@@ -160,13 +163,13 @@ export function translateToA1 (formula, anchorCell, options = defaultOptions) {
160
163
  withLocation: false,
161
164
  mergeRefs: opts.mergeRefs,
162
165
  xlsx: opts.xlsx,
163
- allowTernary: true,
166
+ allowTernary: opts.allowTernary,
164
167
  r1c1: true
165
168
  })
166
169
  : formula;
167
170
 
168
171
  let offsetSkew = 0;
169
- const refOpts = { xlsx: opts.xlsx, allowTernary: true };
172
+ const refOpts = { xlsx: opts.xlsx, allowTernary: opts.allowTernary };
170
173
  tokens.forEach(token => {
171
174
  if (isRange(token)) {
172
175
  const tokenValue = token.value;
@@ -0,0 +1,21 @@
1
+ import { test, Test } from 'tape';
2
+ import { translateToR1C1, translateToA1 } from './translate.js';
3
+
4
+ Test.prototype.okayRoundTrip = function roundTrip (expr, anchor, options) {
5
+ const rc = translateToR1C1(expr, anchor, options);
6
+ const a1 = translateToA1(rc, anchor, options);
7
+ this.is(a1, expr, 'Round trip: ' + expr);
8
+ };
9
+
10
+ test('translate absolute cells from A1 to RC', t => {
11
+ t.okayRoundTrip('=Sheet1!$1:$1048576', 'A1');
12
+ t.okayRoundTrip('=D$1:$BJ$1048576', 'A1');
13
+ t.okayRoundTrip('=VLOOKUP(C7,Röðun,4,0)', 'A1');
14
+ t.okayRoundTrip('=COUNTIF(B$1442:B$1048576,$G1442)', 'A1');
15
+ t.okayRoundTrip('=IF(p2m<=D5,10,0)*scene_spend', 'A1');
16
+ t.okayRoundTrip('=(kwh_used_daily*kwhbtu*co2btu)/1000000', 'A1');
17
+ t.okayRoundTrip('=NOPLATT1+g1_+ROIC1+WACC+G1+g1_+G130+ROIC2+WACC+g2_+WACC+N', 'A1');
18
+ // FIXME: translate needs to be be able to specify allowTernary=false
19
+ t.okayRoundTrip('=foo:C3:D4', 'A1', { allowTernary: false });
20
+ t.end();
21
+ });