@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,72 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { stringifyA1Range } from './stringifyA1Range.ts';
3
+ import { MAX_COLS, MAX_ROWS } from './constants.ts';
4
+
5
+ describe('stringifyA1Range', () => {
6
+ test('cell references: A1', () => {
7
+ expect(stringifyA1Range({ top: 9, bottom: 9, left: 2, right: 2 })).toBe('C10');
8
+ expect(stringifyA1Range({ top: 9, bottom: 9, left: 2, right: 2, $top: true, $bottom: true })).toBe('C$10');
9
+ expect(stringifyA1Range({ top: 9, bottom: 9, left: 2, right: 2, $left: true, $right: true })).toBe('$C10');
10
+ expect(stringifyA1Range({ top: 9, bottom: 9, left: 2, right: 2, $top: true, $bottom: true, $left: true, $right: true })).toBe('$C$10');
11
+ });
12
+
13
+ test('rectangle references: A1:B2', () => {
14
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4 })).toBe('E3');
15
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4, $right: true })).toBe('E3:$E3');
16
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4, $top: true })).toBe('E$3:E3');
17
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4, $left: true })).toBe('$E3:E3');
18
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4, $bottom: true })).toBe('E3:E$3');
19
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4, $bottom: true, $right: true })).toBe('E3:$E$3');
20
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 5 })).toBe('E3:F3');
21
+ expect(stringifyA1Range({ top: 2, bottom: 3, left: 4, right: 4 })).toBe('E3:E4');
22
+ expect(stringifyA1Range({ top: 2, bottom: 3, left: 4, right: 5 })).toBe('E3:F4');
23
+ });
24
+
25
+ test('beam references: A:A 1:1', () => {
26
+ expect(stringifyA1Range({ left: 0, right: 0 })).toBe('A:A');
27
+ expect(stringifyA1Range({ top: 0, bottom: MAX_ROWS, left: 0, right: 0 })).toBe('A:A');
28
+ expect(stringifyA1Range({ left: 10, right: 15 })).toBe('K:P');
29
+ expect(stringifyA1Range({ left: 10, right: 15, $left: true })).toBe('$K:P');
30
+ expect(stringifyA1Range({ left: 10, right: 15, $right: true })).toBe('K:$P');
31
+ expect(stringifyA1Range({ left: 10, right: 15, $left: true, $right: true })).toBe('$K:$P');
32
+ expect(stringifyA1Range({ top: 0, bottom: 0 })).toBe('1:1');
33
+ expect(stringifyA1Range({ top: 0, bottom: 0, left: 0, right: MAX_COLS })).toBe('1:1');
34
+ expect(stringifyA1Range({ top: 10, bottom: 15 })).toBe('11:16');
35
+ expect(stringifyA1Range({ top: 10, bottom: 15, $top: true })).toBe('$11:16');
36
+ expect(stringifyA1Range({ top: 10, bottom: 15, $bottom: true })).toBe('11:$16');
37
+ expect(stringifyA1Range({ top: 10, bottom: 15, $top: true, $bottom: true })).toBe('$11:$16');
38
+ });
39
+
40
+ test('partial references: B1:C B2:3', () => {
41
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0 })).toBe('A10:A');
42
+ expect(stringifyA1Range({ bottom: 9, left: 0, right: 0 })).toBe('A10:A');
43
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0, $top: true })).toBe('A$10:A');
44
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0, $left: true })).toBe('$A10:A');
45
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0, $right: true })).toBe('A10:$A');
46
+ expect(stringifyA1Range({ top: 0, left: 3, bottom: 0 })).toBe('D1:1');
47
+ expect(stringifyA1Range({ top: 0, right: 3, bottom: 0 })).toBe('D1:1');
48
+ expect(stringifyA1Range({ top: 0, left: 3, bottom: 0, $top: true })).toBe('D$1:1');
49
+ expect(stringifyA1Range({ top: 0, left: 3, bottom: 0, $left: true })).toBe('$D1:1');
50
+ expect(stringifyA1Range({ top: 0, left: 3, bottom: 0, $left: true })).toBe('$D1:1');
51
+ });
52
+
53
+ test('edge cases', () => {
54
+ // allow skipping right/bottom for cells
55
+ expect(stringifyA1Range({ top: 0, left: 0 })).toBe('A1');
56
+ // clamp the range at min/max dimensions
57
+ expect(stringifyA1Range({ top: -10, bottom: -5, left: -10, right: -5 })).toBe('A1');
58
+ expect(stringifyA1Range({ top: 15e5, bottom: 15e5, left: 20000, right: 20000 })).toBe('XFD1048576');
59
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 2.5, right: 2.5 })).toBe('C3');
60
+ expect(stringifyA1Range({ top: 1.5, bottom: 2.5, left: 4.5, right: 8.5 })).toBe('E2:I3');
61
+ });
62
+
63
+ test('trimming', () => {
64
+ expect(stringifyA1Range({ top: 2, bottom: 2, left: 4, right: 4, trim: 'both' })).toBe('E3');
65
+ expect(stringifyA1Range({ top: 2, bottom: 3, left: 4, right: 6, trim: 'both' })).toBe('E3.:.G4');
66
+ expect(stringifyA1Range({ top: 2, bottom: 3, trim: 'both' })).toBe('3.:.4');
67
+ expect(stringifyA1Range({ left: 4, right: 6, trim: 'both' })).toBe('E.:.G');
68
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0, trim: 'tail' })).toBe('A10:.A');
69
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0, trim: 'head' })).toBe('A10.:A');
70
+ expect(stringifyA1Range({ top: 9, left: 0, right: 0, trim: 'both' })).toBe('A10.:.A');
71
+ });
72
+ });
@@ -0,0 +1,72 @@
1
+ import { MAX_ROWS, MAX_COLS } from './constants.ts';
2
+ import { toCol } from './toCol.ts';
3
+ import type { RangeA1 } from './types.ts';
4
+ import { rangeOperator } from './a1.ts';
5
+
6
+ const clamp = (min: number, val: number, max: number) => Math.min(Math.max(val, min), max);
7
+ const toColStr = (c: number, a: boolean) => (a ? '$' : '') + toCol(c);
8
+ const toRowStr = (r: number, a: boolean) => (a ? '$' : '') + toRow(r);
9
+ const toRow = (top: number): string => String(top + 1);
10
+
11
+ /**
12
+ * Stringify a range object into A1 syntax.
13
+ * @param range A range object
14
+ * @returns An A1-style string represenation of a range
15
+ * @internal
16
+ */
17
+ export function stringifyA1Range (range: Partial<RangeA1>): string {
18
+ // eslint-disable-next-line prefer-const
19
+ let { top, left, bottom, right, trim } = range;
20
+ const { $left, $right, $top, $bottom } = range;
21
+ const noLeft = left == null;
22
+ const noRight = right == null;
23
+ const noTop = top == null;
24
+ const noBottom = bottom == null;
25
+ // allow skipping right and bottom to define a cell
26
+ top = clamp(0, top | 0, MAX_ROWS);
27
+ left = clamp(0, left | 0, MAX_COLS);
28
+ if (!noLeft && !noTop && noRight && noBottom) {
29
+ bottom = top;
30
+ right = left;
31
+ }
32
+ else {
33
+ bottom = clamp(0, bottom | 0, MAX_ROWS);
34
+ right = clamp(0, right | 0, MAX_COLS);
35
+ }
36
+ const op = rangeOperator(trim);
37
+ // A:A
38
+ const allRows = top === 0 && bottom >= MAX_ROWS;
39
+ const haveAbsCol = ($left && !noLeft) || ($right && !noRight);
40
+ if ((allRows && !noLeft && !noRight && (!haveAbsCol || left === right)) || (noTop && noBottom)) {
41
+ return toColStr(left, $left) + op + toColStr(right, $right);
42
+ }
43
+ // 1:1
44
+ const allCols = left === 0 && right >= MAX_COLS;
45
+ const haveAbsRow = ($top && !noTop) || ($bottom && !noBottom);
46
+ if ((allCols && !noTop && !noBottom && (!haveAbsRow || top === bottom)) || (noLeft && noRight)) {
47
+ return toRowStr(top, $top) + op + toRowStr(bottom, $bottom);
48
+ }
49
+ // A1:1
50
+ if (!noLeft && !noTop && !noRight && noBottom) {
51
+ return toColStr(left, $left) + toRowStr(top, $top) + op + toColStr(right, $right);
52
+ }
53
+ // A:A1 => A1:1
54
+ if (!noLeft && noTop && !noRight && !noBottom) {
55
+ return toColStr(left, $left) + toRowStr(bottom, $bottom) + op + toColStr(right, $right);
56
+ }
57
+ // A1:A
58
+ if (!noLeft && !noTop && noRight && !noBottom) {
59
+ return toColStr(left, $left) + toRowStr(top, $top) + op + toRowStr(bottom, $bottom);
60
+ }
61
+ // A:A1 => A1:A
62
+ if (noLeft && !noTop && !noRight && !noBottom) {
63
+ return toColStr(right, $right) + toRowStr(top, $top) + op + toRowStr(bottom, $bottom);
64
+ }
65
+ // A1:A1
66
+ if (right !== left || bottom !== top || $right !== $left || $bottom !== $top) {
67
+ return toColStr(left, $left) + toRowStr(top, $top) + op +
68
+ toColStr(right, $right) + toRowStr(bottom, $bottom);
69
+ }
70
+ // A1
71
+ return toColStr(left, $left) + toRowStr(top, $top);
72
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { stringifyA1Ref, stringifyA1RefXlsx } from './stringifyA1Ref.ts';
3
+
4
+ describe('stringifyA1Ref', () => {
5
+ const rangeA1 = { top: 0, bottom: 0, left: 0, right: 0 };
6
+
7
+ test('basic stringification', () => {
8
+ expect(stringifyA1Ref({ range: rangeA1 })).toBe('A1');
9
+ expect(stringifyA1Ref({ context: [ 'Sheet1' ], range: rangeA1 })).toBe('Sheet1!A1');
10
+ expect(stringifyA1Ref({ context: [ 'Sheet 1' ], range: rangeA1 })).toBe("'Sheet 1'!A1");
11
+ expect(stringifyA1Ref({ context: [ 'MyFile.xlsx', 'Sheet1' ], range: rangeA1 })).toBe('[MyFile.xlsx]Sheet1!A1');
12
+ expect(stringifyA1Ref({ context: [ 'My File.xlsx', 'Sheet1' ], range: rangeA1 })).toBe("'[My File.xlsx]Sheet1'!A1");
13
+ expect(stringifyA1Ref({ context: [ 'MyFile.xlsx' ], range: rangeA1 })).toBe('MyFile.xlsx!A1');
14
+ expect(stringifyA1Ref({ context: [ 'My File.xlsx' ], range: rangeA1 })).toBe("'My File.xlsx'!A1");
15
+ });
16
+
17
+ test('named ranges', () => {
18
+ expect(stringifyA1Ref({ name: 'foo' })).toBe('foo');
19
+ expect(stringifyA1Ref({ context: [ 'Sheet1' ], name: 'foo' })).toBe('Sheet1!foo');
20
+ expect(stringifyA1Ref({ context: [ 'Sheet 1' ], name: 'foo' })).toBe("'Sheet 1'!foo");
21
+ expect(stringifyA1Ref({ context: [ 'MyFile.xlsx', 'Sheet1' ], name: 'foo' })).toBe('[MyFile.xlsx]Sheet1!foo');
22
+ expect(stringifyA1Ref({ context: [ 'My File.xlsx', 'Sheet1' ], name: 'foo' })).toBe("'[My File.xlsx]Sheet1'!foo");
23
+ expect(stringifyA1Ref({ context: [ 'MyFile.xlsx' ], name: 'foo' })).toBe('MyFile.xlsx!foo');
24
+ expect(stringifyA1Ref({ context: [ 'My File.xlsx' ], name: 'foo' })).toBe("'My File.xlsx'!foo");
25
+ });
26
+
27
+ test('ignore workbookName/sheetName in non-XLSX mode', () => {
28
+ // @ts-expect-error -- testing invalid input
29
+ expect(stringifyA1Ref({ workbookName: 'MyFile.xlsx', sheetName: 'Sheet1', range: rangeA1 })).toBe('A1');
30
+ // @ts-expect-error -- testing invalid input
31
+ expect(stringifyA1Ref({ workbookName: 'MyFile.xlsx', sheetName: 'Sheet1', name: 'foo' })).toBe('foo');
32
+ });
33
+ });
34
+
35
+ describe('stringifyA1Ref in XLSX mode', () => {
36
+ const rangeA1 = { top: 0, bottom: 0, left: 0, right: 0 };
37
+
38
+ test('basic stringification', () => {
39
+ expect(stringifyA1RefXlsx({ range: rangeA1 })).toBe('A1');
40
+ expect(stringifyA1RefXlsx({ sheetName: 'Sheet1', range: rangeA1 })).toBe('Sheet1!A1');
41
+ expect(stringifyA1RefXlsx({ sheetName: 'Sheet 1', range: rangeA1 })).toBe("'Sheet 1'!A1");
42
+ expect(stringifyA1RefXlsx({ workbookName: 'MyFile.xlsx', sheetName: 'Sheet1', range: rangeA1 })).toBe('[MyFile.xlsx]Sheet1!A1');
43
+ expect(stringifyA1RefXlsx({ workbookName: 'My File.xlsx', sheetName: 'Sheet1', range: rangeA1 })).toBe("'[My File.xlsx]Sheet1'!A1");
44
+ expect(stringifyA1RefXlsx({ workbookName: 'MyFile.xlsx', range: rangeA1 })).toBe('[MyFile.xlsx]!A1');
45
+ expect(stringifyA1RefXlsx({ workbookName: 'My File.xlsx', range: rangeA1 })).toBe("'[My File.xlsx]'!A1");
46
+ });
47
+
48
+ test('named ranges', () => {
49
+ expect(stringifyA1RefXlsx({ name: 'foo' })).toBe('foo');
50
+ expect(stringifyA1RefXlsx({ sheetName: 'Sheet1', name: 'foo' })).toBe('Sheet1!foo');
51
+ expect(stringifyA1RefXlsx({ sheetName: 'Sheet 1', name: 'foo' })).toBe("'Sheet 1'!foo");
52
+ expect(stringifyA1RefXlsx({ workbookName: 'MyFile.xlsx', sheetName: 'Sheet1', name: 'foo' })).toBe('[MyFile.xlsx]Sheet1!foo');
53
+ expect(stringifyA1RefXlsx({ workbookName: 'My File.xlsx', sheetName: 'Sheet1', name: 'foo' })).toBe("'[My File.xlsx]Sheet1'!foo");
54
+ expect(stringifyA1RefXlsx({ workbookName: 'MyFile.xlsx', name: 'foo' })).toBe('[MyFile.xlsx]!foo');
55
+ expect(stringifyA1RefXlsx({ workbookName: 'My File.xlsx', name: 'foo' })).toBe("'[My File.xlsx]'!foo");
56
+ });
57
+
58
+ test('ignore context in XLSX mode', () => {
59
+ // @ts-expect-error -- testing invalid input
60
+ expect(stringifyA1RefXlsx({ context: [ 'MyFile.xlsx', 'Sheet1' ], range: rangeA1 })).toBe('A1');
61
+ // @ts-expect-error -- testing invalid input
62
+ expect(stringifyA1RefXlsx({ context: [ 'MyFile.xlsx', 'Sheet1' ], name: 'foo' })).toBe('foo');
63
+ });
64
+ });
@@ -0,0 +1,59 @@
1
+ import { stringifyPrefix, stringifyPrefixXlsx } from './stringifyPrefix.ts';
2
+ import type { ReferenceA1, ReferenceA1Xlsx, ReferenceName, ReferenceNameXlsx } from './types.ts';
3
+ import { stringifyA1Range } from './stringifyA1Range.ts';
4
+
5
+ /**
6
+ * Get an A1-style string representation of a reference object.
7
+ *
8
+ * ```js
9
+ * stringifyA1Ref({
10
+ * context: [ 'Sheet1' ],
11
+ * range: {
12
+ * top: 0,
13
+ * left: 0,
14
+ * bottom: 1,
15
+ * right: 1,
16
+ * $top: true,
17
+ * $left: false,
18
+ * $bottom: false,
19
+ * $right: true
20
+ * }
21
+ * });
22
+ * // => 'Sheet1!A$1:$B2'
23
+ * ```
24
+ *
25
+ * @param refObject A reference object.
26
+ * @returns The reference in A1-style string format.
27
+ */
28
+ export function stringifyA1Ref (refObject: ReferenceA1 | ReferenceName): string {
29
+ const prefix = stringifyPrefix(refObject);
30
+ return prefix + ('name' in refObject ? refObject.name : stringifyA1Range(refObject.range));
31
+ }
32
+
33
+ /**
34
+ * Get an A1-style string representation of a reference object.
35
+ *
36
+ * ```js
37
+ * stringifyA1Ref({
38
+ * sheetName: 'Sheet1',
39
+ * range: {
40
+ * top: 0,
41
+ * left: 0,
42
+ * bottom: 1,
43
+ * right: 1,
44
+ * $top: true,
45
+ * $left: false,
46
+ * $bottom: false,
47
+ * $right: true
48
+ * }
49
+ * });
50
+ * // => 'Sheet1!A$1:$B2'
51
+ * ```
52
+ *
53
+ * @param refObject A reference object.
54
+ * @returns The reference in A1-style string format.
55
+ */
56
+ export function stringifyA1RefXlsx (refObject: ReferenceA1Xlsx | ReferenceNameXlsx): string {
57
+ const prefix = stringifyPrefixXlsx(refObject);
58
+ return prefix + ('name' in refObject ? refObject.name : stringifyA1Range(refObject.range));
59
+ }
@@ -1,6 +1,19 @@
1
+ import type {
2
+ ReferenceA1,
3
+ ReferenceStruct,
4
+ ReferenceR1C1,
5
+ ReferenceA1Xlsx,
6
+ ReferenceStructXlsx,
7
+ ReferenceR1C1Xlsx,
8
+ ReferenceName,
9
+ ReferenceNameXlsx
10
+ } from './types.ts';
11
+
1
12
  const reBannedChars = /[^0-9A-Za-z._¡¤§¨ª\u00ad¯-\uffff]/;
2
13
 
3
- export function stringifyPrefix (ref) {
14
+ export function stringifyPrefix (
15
+ ref: ReferenceA1 | ReferenceName | ReferenceStruct | ReferenceR1C1
16
+ ): string {
4
17
  let pre = '';
5
18
  let quote = 0;
6
19
  let nth = 0;
@@ -20,7 +33,9 @@ export function stringifyPrefix (ref) {
20
33
  return pre ? pre + '!' : pre;
21
34
  }
22
35
 
23
- export function stringifyPrefixAlt (ref) {
36
+ export function stringifyPrefixXlsx (
37
+ ref: ReferenceA1Xlsx | ReferenceNameXlsx | ReferenceStructXlsx | ReferenceR1C1Xlsx
38
+ ): string {
24
39
  let pre = '';
25
40
  let quote = 0;
26
41
  const { workbookName, sheetName } = ref;
@@ -0,0 +1,92 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { MAX_COLS, MAX_ROWS } from './constants.ts';
3
+ import { stringifyR1C1Range } from './stringifyR1C1Range.ts';
4
+
5
+ function isR1C1Rendered (range: any, expected: string) {
6
+ expect(stringifyR1C1Range(range)).toBe(expected);
7
+ }
8
+
9
+ describe('R1C1 serialization', () => {
10
+ test('ray serialization', () => {
11
+ isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: MAX_COLS }, 'R');
12
+ isR1C1Rendered({ r0: 0, r1: 0 }, 'R');
13
+ isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: MAX_COLS, $r0: true, $r1: true }, 'R1');
14
+ isR1C1Rendered({ r0: 0, r1: 0, $r0: true, $r1: true }, 'R1');
15
+ isR1C1Rendered({ r0: 1, c0: 0, r1: 1, c1: MAX_COLS }, 'R[1]');
16
+ isR1C1Rendered({ r0: 1, r1: 1 }, 'R[1]');
17
+
18
+ isR1C1Rendered({ r0: 0, c0: 0, r1: MAX_ROWS, c1: 0 }, 'C');
19
+ isR1C1Rendered({ c0: 0, c1: 0 }, 'C');
20
+ isR1C1Rendered({ r0: 0, c0: 0, r1: MAX_ROWS, c1: 0, $c0: true, $c1: true }, 'C1');
21
+ isR1C1Rendered({ c0: 0, c1: 0, $c0: true, $c1: true }, 'C1');
22
+ isR1C1Rendered({ r0: 0, c0: 1, r1: MAX_ROWS, c1: 1 }, 'C[1]');
23
+ isR1C1Rendered({ c0: 1, c1: 1 }, 'C[1]');
24
+ });
25
+
26
+ test('rectangle serialization', () => {
27
+ isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: 0 }, 'RC');
28
+ isR1C1Rendered({ r0: 0, c0: 0, r1: 0, c1: 0, $c0: true, $c1: true, $r0: true, $r1: true }, 'R1C1');
29
+ isR1C1Rendered({ r0: 9, c0: 7, r1: 9, c1: 7, $c0: true, $c1: true, $r0: true, $r1: true }, 'R10C8');
30
+ isR1C1Rendered({ r0: 2, c0: 0, r1: 2, c1: 0 }, 'R[2]C');
31
+ isR1C1Rendered({ r0: -2, c0: 0, r1: -2, c1: 0 }, 'R[-2]C');
32
+ isR1C1Rendered({ r0: 0, c0: 3, r1: 0, c1: 3 }, 'RC[3]');
33
+ isR1C1Rendered({ r0: 0, c0: -3, r1: 0, c1: -3 }, 'RC[-3]');
34
+ isR1C1Rendered({ r0: 2, c0: 4, r1: 2, c1: 4 }, 'R[2]C[4]');
35
+ isR1C1Rendered({ r0: -2, c0: -4, r1: -2, c1: -4 }, 'R[-2]C[-4]');
36
+ isR1C1Rendered({ r0: 9, c0: 8, r1: 9, c1: 8, $c0: true, $c1: true }, 'R[9]C9');
37
+ isR1C1Rendered({ r0: 8, c0: 9, r1: 8, c1: 9, $r0: true, $r1: true }, 'R9C[9]');
38
+ });
39
+
40
+ test('range serialization', () => {
41
+ isR1C1Rendered({ r0: 1, c0: 1, r1: 0, c1: 0, $c1: true, $r1: true }, 'R[1]C[1]:R1C1');
42
+ isR1C1Rendered({ r0: 0, c0: 0, r1: 1, c1: 1, $c0: true, $c1: true, $r0: true, $r1: true }, 'R1C1:R2C2');
43
+ isR1C1Rendered({ c0: 0, c1: 2, $c0: true, $c1: true }, 'C1:C3');
44
+ isR1C1Rendered({ r0: 1, r1: 2, $r1: true }, 'R[1]:R3');
45
+ isR1C1Rendered({ r0: 1, c0: 0, r1: 0, c1: -1, $c0: true, $r1: true }, 'R[1]C1:R1C[-1]');
46
+ });
47
+
48
+ test('partial serialization', () => {
49
+ isR1C1Rendered({ r0: -5, c0: -2, c1: -2 }, 'R[-5]C[-2]:C[-2]');
50
+ isR1C1Rendered({ r0: -5, c0: -3, r1: -5 }, 'R[-5]C[-3]:R[-5]');
51
+ isR1C1Rendered({ r0: -6, c0: 0, c1: 0, $c0: true, $c1: true }, 'R[-6]C1:C1');
52
+ isR1C1Rendered({ r0: -6, c0: 0, r1: -6, $c0: true, $c1: true }, 'R[-6]C1:R[-6]');
53
+ isR1C1Rendered({ r0: 0, c0: -2, c1: -2, $r0: true, $r1: true }, 'R1C[-2]:C[-2]');
54
+ isR1C1Rendered({ r0: 0, c0: -3, r1: 0, $r0: true, $r1: true }, 'R1C[-3]:R1');
55
+ isR1C1Rendered({ r0: 0, c0: 0, c1: 0, $r0: true, $c0: true, $r1: true, $c1: true }, 'R1C1:C1');
56
+ isR1C1Rendered({ r0: 0, c0: 0, r1: 0, $r0: true, $c0: true, $r1: true, $c1: true }, 'R1C1:R1');
57
+ isR1C1Rendered({ r0: -5, c0: 10, r1: 4 }, 'R[-5]C[10]:R[4]');
58
+ isR1C1Rendered({ r0: -6, c0: 15, r1: 3, $c0: true, $c1: true }, 'R[-6]C16:R[3]');
59
+ isR1C1Rendered({ r0: 0, c0: 10, r1: 9, $r0: true, $r1: true }, 'R1C[10]:R10');
60
+ isR1C1Rendered({ r0: 0, c0: 15, r1: 9, $r0: true, $c0: true, $r1: true, $c1: true }, 'R1C16:R10');
61
+ });
62
+
63
+ test('edge cases', () => {
64
+ // allow skipping right/bottom for cells
65
+ isR1C1Rendered({ r0: -5, c0: -2 }, 'R[-5]C[-2]');
66
+
67
+ // clamp the range at min/max dimensions
68
+ const abs = { $r0: true, $c0: true, $r1: true, $c1: true };
69
+ isR1C1Rendered({ r0: 1, c0: -20000, r1: 1, c1: 20000, ...abs }, 'R2');
70
+ isR1C1Rendered({ r0: -15e5, c0: 1, r1: 15e5, c1: 1, ...abs }, 'C2');
71
+ isR1C1Rendered({ r0: -5, c0: -2, r1: -8, c1: -7, ...abs }, 'R1C1');
72
+ isR1C1Rendered({ r0: 0, c0: -20000, r1: 0, c1: 20000 }, 'RC[-16383]:RC[16383]');
73
+ isR1C1Rendered({ r0: -15e5, c0: 0, r1: 15e5, c1: 0 }, 'R[-1048575]C:R[1048575]C');
74
+ isR1C1Rendered({ r0: 0.5, c0: 0.5, r1: 0.5, c1: 0.5, ...abs }, 'R1C1');
75
+ isR1C1Rendered({ r0: 0.5, c0: 0.5, r1: 0.5, c1: 0.5 }, 'RC');
76
+ });
77
+
78
+ test('trimming', () => {
79
+ isR1C1Rendered({ r0: 1, c0: 1, r1: 2, c1: 2 }, 'R[1]C[1]:R[2]C[2]');
80
+ isR1C1Rendered({ r0: 1, c0: 1, r1: 2, c1: 2, trim: 'head' }, 'R[1]C[1].:R[2]C[2]');
81
+ isR1C1Rendered({ r0: 1, c0: 1, r1: 2, c1: 2, trim: 'tail' }, 'R[1]C[1]:.R[2]C[2]');
82
+ isR1C1Rendered({ r0: 1, c0: 1, r1: 2, c1: 2, trim: 'both' }, 'R[1]C[1].:.R[2]C[2]');
83
+ isR1C1Rendered({ r0: 1, c0: 1, r1: 1, c1: 1, trim: 'both' }, 'R[1]C[1]');
84
+ isR1C1Rendered({ r0: 1, r1: 1 }, 'R[1]');
85
+ isR1C1Rendered({ r0: 1, r1: 1, trim: 'head' }, 'R[1].:R[1]');
86
+ isR1C1Rendered({ r0: 1, r1: 1, trim: 'both' }, 'R[1].:.R[1]');
87
+ isR1C1Rendered({ c0: 1, c1: 1 }, 'C[1]');
88
+ isR1C1Rendered({ c0: 1, c1: 1, trim: 'tail' }, 'C[1]:.C[1]');
89
+ isR1C1Rendered({ c0: 1, c1: 1, trim: 'both' }, 'C[1].:.C[1]');
90
+ isR1C1Rendered({ r0: -5, c0: -2, c1: -2, trim: 'both' }, 'R[-5]C[-2].:.C[-2]');
91
+ });
92
+ });
@@ -0,0 +1,73 @@
1
+ import { rangeOperator } from './a1.ts';
2
+ import { MAX_ROWS, MAX_COLS } from './constants.ts';
3
+ import type { RangeR1C1 } from './types.ts';
4
+
5
+ const clamp = (min: number, val: number, max: number) => Math.min(Math.max(val, min), max);
6
+
7
+ function toCoord (value: number, isAbs: boolean): string {
8
+ if (isAbs) {
9
+ return String(value + 1);
10
+ }
11
+ return value ? '[' + value + ']' : '';
12
+ }
13
+
14
+ /**
15
+ * Stringify a range object into R1C1 syntax.
16
+ * @internal
17
+ */
18
+ export function stringifyR1C1Range (range: RangeR1C1): string {
19
+ let { r0, c0, r1, c1 } = range;
20
+ const { $c0, $c1, $r0, $r1 } = range;
21
+ const nullR0 = r0 == null;
22
+ const nullC0 = c0 == null;
23
+ let nullR1 = r1 == null;
24
+ let nullC1 = c1 == null;
25
+ const op = rangeOperator(range.trim);
26
+ const hasTrim = !!range.trim;
27
+ r0 = clamp($r0 ? 0 : -MAX_ROWS, r0 | 0, MAX_ROWS);
28
+ c0 = clamp($c0 ? 0 : -MAX_COLS, c0 | 0, MAX_COLS);
29
+ if (!nullR0 && nullR1 && !nullC0 && nullC1) {
30
+ r1 = r0;
31
+ nullR1 = false;
32
+ c1 = c0;
33
+ nullC1 = false;
34
+ }
35
+ else {
36
+ r1 = clamp($r1 ? 0 : -MAX_ROWS, r1 | 0, MAX_ROWS);
37
+ c1 = clamp($c1 ? 0 : -MAX_COLS, c1 | 0, MAX_COLS);
38
+ }
39
+ // C:C
40
+ const allRows = r0 === 0 && r1 >= MAX_ROWS;
41
+ if ((allRows && !nullC0 && !nullC1) || (nullR0 && nullR1)) {
42
+ const a = toCoord(c0, $c0);
43
+ const b = toCoord(c1, $c1);
44
+ return 'C' + (a === b && !hasTrim ? a : a + op + 'C' + b);
45
+ }
46
+ // R:R
47
+ const allCols = c0 === 0 && c1 >= MAX_COLS;
48
+ if ((allCols && !nullR0 && !nullR1) || (nullC0 && nullC1)) {
49
+ const a = toCoord(r0, $r0);
50
+ const b = toCoord(r1, $r1);
51
+ return 'R' + (a === b && !hasTrim ? a : a + op + 'R' + b);
52
+ }
53
+ const s_r0 = toCoord(r0, $r0);
54
+ const s_r1 = toCoord(r1, $r1);
55
+ const s_c0 = toCoord(c0, $c0);
56
+ const s_c1 = toCoord(c1, $c1);
57
+ // RC:R, RC:C
58
+ if (nullR0 || nullR1 || nullC0 || nullC1) {
59
+ return (
60
+ (nullR0 ? '' : 'R' + s_r0) +
61
+ (nullC0 ? '' : 'C' + s_c0) +
62
+ op +
63
+ (nullR1 ? '' : 'R' + s_r1) +
64
+ (nullC1 ? '' : 'C' + s_c1)
65
+ );
66
+ }
67
+ // RC:RC
68
+ if (s_r0 !== s_r1 || s_c0 !== s_c1) {
69
+ return 'R' + s_r0 + 'C' + s_c0 + op + 'R' + s_r1 + 'C' + s_c1;
70
+ }
71
+ // RC
72
+ return 'R' + s_r0 + 'C' + s_c0;
73
+ }
@@ -0,0 +1,63 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { stringifyR1C1Ref, stringifyR1C1RefXlsx } from './stringifyR1C1Ref.ts';
3
+
4
+ describe('stringifyR1C1Ref', () => {
5
+ const rangeA1 = { r0: 2, c0: 4, r1: 2, c1: 4 };
6
+
7
+ function testRef (ref: any, expected: string) {
8
+ expect(stringifyR1C1Ref(ref)).toBe(expected);
9
+ }
10
+
11
+ test('basic stringification', () => {
12
+ testRef({ range: rangeA1 }, 'R[2]C[4]');
13
+ testRef({ context: [ 'Sheet1' ], range: rangeA1 }, 'Sheet1!R[2]C[4]');
14
+ testRef({ context: [ 'Sheet 1' ], range: rangeA1 }, "'Sheet 1'!R[2]C[4]");
15
+ testRef({ context: [ 'MyFile.xlsx', 'Sheet1' ], range: rangeA1 }, '[MyFile.xlsx]Sheet1!R[2]C[4]');
16
+ testRef({ context: [ 'My File.xlsx', 'Sheet1' ], range: rangeA1 }, "'[My File.xlsx]Sheet1'!R[2]C[4]");
17
+ testRef({ context: [ 'MyFile.xlsx' ], range: rangeA1 }, 'MyFile.xlsx!R[2]C[4]');
18
+ testRef({ context: [ 'My File.xlsx' ], range: rangeA1 }, "'My File.xlsx'!R[2]C[4]");
19
+ });
20
+
21
+ test('named ranges', () => {
22
+ testRef({ name: 'foo' }, 'foo');
23
+ testRef({ context: [ 'Sheet1' ], name: 'foo' }, 'Sheet1!foo');
24
+ testRef({ context: [ 'Sheet 1' ], name: 'foo' }, "'Sheet 1'!foo");
25
+ testRef({ context: [ 'MyFile.xlsx', 'Sheet1' ], name: 'foo' }, '[MyFile.xlsx]Sheet1!foo');
26
+ testRef({ context: [ 'My File.xlsx', 'Sheet1' ], name: 'foo' }, "'[My File.xlsx]Sheet1'!foo");
27
+ testRef({ context: [ 'MyFile.xlsx' ], name: 'foo' }, 'MyFile.xlsx!foo');
28
+ testRef({ context: [ 'My File.xlsx' ], name: 'foo' }, "'My File.xlsx'!foo");
29
+ });
30
+ });
31
+
32
+ describe('stringifyR1C1Ref in XLSX mode', () => {
33
+ const rangeA1 = { r0: 2, c0: 4, r1: 2, c1: 4 };
34
+
35
+ function testRef (ref: any, expected: string) {
36
+ expect(stringifyR1C1RefXlsx(ref)).toBe(expected);
37
+ }
38
+
39
+ test('basic stringification', () => {
40
+ testRef({ range: rangeA1 }, 'R[2]C[4]');
41
+ testRef({ sheetName: 'Sheet1', range: rangeA1 }, 'Sheet1!R[2]C[4]');
42
+ testRef({ sheetName: 'Sheet 1', range: rangeA1 }, "'Sheet 1'!R[2]C[4]");
43
+ testRef({ workbookName: 'MyFile.xlsx', sheetName: 'Sheet1', range: rangeA1 }, '[MyFile.xlsx]Sheet1!R[2]C[4]');
44
+ testRef({ workbookName: 'My File.xlsx', sheetName: 'Sheet1', range: rangeA1 }, "'[My File.xlsx]Sheet1'!R[2]C[4]");
45
+ testRef({ workbookName: 'MyFile.xlsx', range: rangeA1 }, '[MyFile.xlsx]!R[2]C[4]');
46
+ testRef({ workbookName: 'My File.xlsx', range: rangeA1 }, "'[My File.xlsx]'!R[2]C[4]");
47
+ });
48
+
49
+ test('named ranges', () => {
50
+ testRef({ name: 'foo' }, 'foo');
51
+ testRef({ sheetName: 'Sheet1', name: 'foo' }, 'Sheet1!foo');
52
+ testRef({ sheetName: 'Sheet 1', name: 'foo' }, "'Sheet 1'!foo");
53
+ testRef({ workbookName: 'MyFile.xlsx', sheetName: 'Sheet1', name: 'foo' }, '[MyFile.xlsx]Sheet1!foo');
54
+ testRef({ workbookName: 'My File.xlsx', sheetName: 'Sheet1', name: 'foo' }, "'[My File.xlsx]Sheet1'!foo");
55
+ testRef({ workbookName: 'MyFile.xlsx', name: 'foo' }, '[MyFile.xlsx]!foo');
56
+ testRef({ workbookName: 'My File.xlsx', name: 'foo' }, "'[My File.xlsx]'!foo");
57
+ });
58
+
59
+ test('ignores context in XLSX mode', () => {
60
+ testRef({ context: [ 'MyFile.xlsx', 'Sheet1' ], range: rangeA1 }, 'R[2]C[4]');
61
+ testRef({ context: [ 'MyFile.xlsx', 'Sheet1' ], name: 'foo' }, 'foo');
62
+ });
63
+ });
@@ -0,0 +1,67 @@
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 { stringifyPrefix, stringifyPrefixXlsx } from './stringifyPrefix.ts';
11
+ import { stringifyR1C1Range } from './stringifyR1C1Range.ts';
12
+
13
+ /**
14
+ * Get an R1C1-style string representation of a reference object.
15
+ *
16
+ * ```js
17
+ * stringifyR1C1Ref({
18
+ * context: [ 'Sheet1' ],
19
+ * range: {
20
+ * r0: 9,
21
+ * c0: 8,
22
+ * r1: 9,
23
+ * c1: 8,
24
+ * $c0: true,
25
+ * $c1: true
26
+ * $r0: false,
27
+ * $r1: false
28
+ * }
29
+ * });
30
+ * // => 'Sheet1!R[9]C9:R[9]C9'
31
+ * ```
32
+ *
33
+ * @param refObject A reference object.
34
+ * @returns The reference in R1C1-style string format.
35
+ */
36
+ export function stringifyR1C1Ref (refObject: ReferenceR1C1 | ReferenceName): string {
37
+ const prefix = stringifyPrefix(refObject);
38
+ return prefix + ('name' in refObject ? refObject.name : stringifyR1C1Range(refObject.range));
39
+ }
40
+
41
+ /**
42
+ * Get an R1C1-style string representation of a reference object.
43
+ *
44
+ * ```js
45
+ * stringifyR1C1Ref({
46
+ * sheetName: 'Sheet1',
47
+ * range: {
48
+ * r0: 9,
49
+ * c0: 8,
50
+ * r1: 9,
51
+ * c1: 8,
52
+ * $c0: true,
53
+ * $c1: true
54
+ * $r0: false,
55
+ * $r1: false
56
+ * }
57
+ * });
58
+ * // => 'Sheet1!R[9]C9:R[9]C9'
59
+ * ```
60
+ *
61
+ * @param refObject A reference object.
62
+ * @returns The reference in R1C1-style string format.
63
+ */
64
+ export function stringifyR1C1RefXlsx (refObject: ReferenceR1C1Xlsx | ReferenceNameXlsx): string {
65
+ const prefix = stringifyPrefixXlsx(refObject);
66
+ return prefix + ('name' in refObject ? refObject.name : stringifyR1C1Range(refObject.range));
67
+ }