@borgar/fx 4.12.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 (139) 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.ts +18 -0
  36. package/lib/lexers/canEndRange.ts +25 -0
  37. package/lib/lexers/lexBoolean.ts +55 -0
  38. package/lib/lexers/lexContext.ts +104 -0
  39. package/lib/lexers/lexError.ts +15 -0
  40. package/lib/lexers/lexFunction.ts +37 -0
  41. package/lib/lexers/lexNameFuncCntx.ts +112 -0
  42. package/lib/lexers/lexNamed.ts +60 -0
  43. package/lib/lexers/lexNewLine.ts +12 -0
  44. package/lib/lexers/lexNumber.ts +48 -0
  45. package/lib/lexers/lexOperator.ts +26 -0
  46. package/lib/lexers/lexRange.ts +15 -0
  47. package/lib/lexers/lexRangeA1.ts +134 -0
  48. package/lib/lexers/lexRangeR1C1.ts +146 -0
  49. package/lib/lexers/lexRangeTrim.ts +26 -0
  50. package/lib/lexers/lexRefOp.ts +19 -0
  51. package/lib/lexers/lexString.ts +22 -0
  52. package/lib/lexers/lexStructured.ts +25 -0
  53. package/lib/lexers/lexWhitespace.ts +31 -0
  54. package/lib/lexers/sets.ts +51 -0
  55. package/lib/mergeRefTokens.spec.ts +141 -0
  56. package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +47 -32
  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.ts +240 -0
  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 +46 -30
  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/dist/fx.d.ts +0 -823
  104. package/dist/fx.js +0 -2
  105. package/dist/package.json +0 -1
  106. package/lib/a1.js +0 -348
  107. package/lib/a1.spec.js +0 -458
  108. package/lib/addTokenMeta.spec.js +0 -153
  109. package/lib/astTypes.js +0 -96
  110. package/lib/extraTypes.js +0 -74
  111. package/lib/fixRanges.js +0 -104
  112. package/lib/fixRanges.spec.js +0 -170
  113. package/lib/fromCol.spec.js +0 -11
  114. package/lib/index.js +0 -134
  115. package/lib/index.spec.js +0 -67
  116. package/lib/isType.spec.js +0 -168
  117. package/lib/lexer-srefs.spec.js +0 -324
  118. package/lib/lexer.js +0 -283
  119. package/lib/lexer.spec.js +0 -1953
  120. package/lib/lexerParts.js +0 -228
  121. package/lib/mergeRefTokens.spec.js +0 -121
  122. package/lib/package.json +0 -1
  123. package/lib/parseRef.js +0 -157
  124. package/lib/parseRef.spec.js +0 -71
  125. package/lib/parseSRange.js +0 -167
  126. package/lib/parseStructRef.js +0 -48
  127. package/lib/parseStructRef.spec.js +0 -164
  128. package/lib/parser.spec.js +0 -1208
  129. package/lib/rc.js +0 -341
  130. package/lib/rc.spec.js +0 -403
  131. package/lib/stringifyStructRef.js +0 -80
  132. package/lib/stringifyStructRef.spec.js +0 -182
  133. package/lib/toCol.spec.js +0 -11
  134. package/lib/translate-toA1.spec.js +0 -214
  135. package/lib/translate-toRC.spec.js +0 -197
  136. package/lib/translate.js +0 -239
  137. package/lib/translate.spec.js +0 -21
  138. package/rollup.config.mjs +0 -22
  139. package/tsd.json +0 -12
@@ -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
+ }
@@ -0,0 +1,124 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { stringifyStructRef, stringifyStructRefXlsx } from './stringifyStructRef.ts';
3
+
4
+ describe('serialize structured references', () => {
5
+ test('simple column references', () => {
6
+ expect(stringifyStructRef({
7
+ columns: [ 'foo' ]
8
+ })).toBe('[foo]');
9
+
10
+ expect(stringifyStructRef({
11
+ columns: [ 'foo' ],
12
+ table: 'tableName'
13
+ })).toBe('tableName[foo]');
14
+
15
+ expect(stringifyStructRef({
16
+ columns: [ 'lorem ipsum' ],
17
+ table: 'tableName'
18
+ })).toBe('tableName[lorem ipsum]');
19
+ });
20
+
21
+ test('column range references', () => {
22
+ expect(stringifyStructRef({
23
+ columns: [ 'foo', 'sævör' ],
24
+ table: 'tableName'
25
+ })).toBe('tableName[[foo]:[sævör]]');
26
+
27
+ expect(stringifyStructRef({
28
+ columns: [ 'lorem ipsum', 'sævör' ],
29
+ table: 'tableName'
30
+ })).toBe('tableName[[lorem ipsum]:[sævör]]');
31
+ });
32
+
33
+ test('section references', () => {
34
+ expect(stringifyStructRef({
35
+ sections: [ 'data' ],
36
+ table: 'tableName'
37
+ })).toBe('tableName[#Data]');
38
+
39
+ expect(stringifyStructRef({
40
+ sections: [ 'data', 'headers' ],
41
+ table: 'table'
42
+ })).toBe('table[[#Data],[#Headers]]');
43
+ });
44
+
45
+ test('complex references with context', () => {
46
+ expect(stringifyStructRef({
47
+ columns: [ 'my column', 'fo@o' ],
48
+ sections: [ 'data' ],
49
+ table: 'tableName',
50
+ context: [ 'workbook.xlsx' ]
51
+ })).toBe('workbook.xlsx!tableName[[#Data],[my column]:[fo\'@o]]');
52
+ });
53
+
54
+ test('this row references', () => {
55
+ expect(stringifyStructRef({
56
+ columns: [ 'bar' ],
57
+ sections: [ 'this row' ],
58
+ table: 'foo'
59
+ })).toBe('foo[@bar]');
60
+
61
+ expect(stringifyStructRef({
62
+ columns: [ 'bar', 'baz' ],
63
+ sections: [ 'this row' ],
64
+ table: 'foo'
65
+ })).toBe('foo[@[bar]:[baz]]');
66
+
67
+ expect(stringifyStructRef({
68
+ columns: [ 'lorem ipsum', 'baz' ],
69
+ sections: [ 'this row' ],
70
+ table: 'foo'
71
+ })).toBe('foo[@[lorem ipsum]:[baz]]');
72
+ });
73
+ });
74
+
75
+ describe('structured references serialize in xlsx mode', () => {
76
+ test('context vs workbookName/sheetName handling', () => {
77
+ expect(stringifyStructRefXlsx({
78
+ // @ts-expect-error -- this is testing invalid input
79
+ context: [ 'Lorem', 'Ipsum' ],
80
+ columns: [ 'foo' ]
81
+ })).toBe('[foo]');
82
+
83
+ expect(stringifyStructRef({
84
+ // @ts-expect-error -- this is testing invalid input
85
+ workbookName: 'Lorem',
86
+ sheetName: 'Ipsum',
87
+ columns: [ 'foo' ]
88
+ })).toBe('[foo]');
89
+
90
+ expect(stringifyStructRefXlsx({
91
+ workbookName: 'Lorem',
92
+ sheetName: 'Ipsum',
93
+ columns: [ 'foo' ]
94
+ })).toBe('[Lorem]Ipsum![foo]');
95
+ });
96
+
97
+ test('workbook and sheet name handling', () => {
98
+ expect(stringifyStructRefXlsx({
99
+ workbookName: 'Lorem',
100
+ columns: [ 'foo' ]
101
+ })).toBe('[Lorem]![foo]');
102
+
103
+ expect(stringifyStructRefXlsx({
104
+ sheetName: 'Ipsum',
105
+ columns: [ 'foo' ]
106
+ })).toBe('Ipsum![foo]');
107
+ });
108
+ });
109
+
110
+ describe('longform serialize (in xlsx mode)', () => {
111
+ test('thisRow option affects output format', () => {
112
+ expect(stringifyStructRefXlsx({
113
+ table: 'Table2',
114
+ columns: [ 'col1' ],
115
+ sections: [ 'this row' ]
116
+ }, { thisRow: true })).toBe('Table2[[#This row],[col1]]');
117
+
118
+ expect(stringifyStructRef({
119
+ table: 'Table2',
120
+ columns: [ 'col1' ],
121
+ sections: [ 'this row' ]
122
+ }, { thisRow: true })).toBe('Table2[[#This row],[col1]]');
123
+ });
124
+ });
@@ -0,0 +1,113 @@
1
+ import type { ReferenceStruct, ReferenceStructXlsx } from './types.ts';
2
+ import { stringifyPrefix, stringifyPrefixXlsx } from './stringifyPrefix.ts';
3
+
4
+ function quoteColname (str: string): string {
5
+ return str.replace(/([[\]#'@])/g, '\'$1');
6
+ }
7
+
8
+ function needsBraces (str: string): boolean {
9
+ return /[^a-zA-Z0-9\u00a1-\uffff]/.test(str);
10
+ }
11
+
12
+ function toSentenceCase (str: string): string {
13
+ return str[0].toUpperCase() + str.slice(1).toLowerCase();
14
+ }
15
+
16
+ export function stringifySRef (refObject: ReferenceStruct, thisRow = false) {
17
+ let s = '';
18
+ if (refObject.table) {
19
+ s += refObject.table;
20
+ }
21
+ const numColumns = refObject.columns?.length ?? 0;
22
+ const numSections = refObject.sections?.length ?? 0;
23
+ // single section
24
+ if (numSections === 1 && !numColumns) {
25
+ s += `[#${toSentenceCase(refObject.sections[0])}]`;
26
+ }
27
+ // single column
28
+ else if (!numSections && numColumns === 1) {
29
+ s += `[${quoteColname(refObject.columns[0])}]`;
30
+ }
31
+ else {
32
+ s += '[';
33
+ // single [#this row] sections get normalized to an @ by default
34
+ const singleAt = !thisRow && numSections === 1 && refObject.sections[0].toLowerCase() === 'this row';
35
+ if (singleAt) {
36
+ s += '@';
37
+ }
38
+ else if (numSections) {
39
+ s += refObject.sections
40
+ .map(d => `[#${toSentenceCase(d)}]`)
41
+ .join(',');
42
+ if (numColumns) {
43
+ s += ',';
44
+ }
45
+ }
46
+ // a case of a single alphanumberic column with a [#this row] becomes [@col]
47
+ if (singleAt && refObject.columns.length === 1 && !needsBraces(refObject.columns[0])) {
48
+ s += quoteColname(refObject.columns[0]);
49
+ }
50
+ else if (numColumns) {
51
+ s += refObject.columns.slice(0, 2)
52
+ .map(d => (`[${quoteColname(d)}]`))
53
+ .join(':');
54
+ }
55
+ s += ']';
56
+ }
57
+ return s;
58
+ }
59
+
60
+ /**
61
+ * Options for {@link stringifyStructRef}
62
+ */
63
+ export type OptsStringifyStructRef = {
64
+ /**
65
+ * Enforces using the `[#This Row]` instead of the `@` shorthand when serializing structured ranges.
66
+ * @defaultValue false
67
+ */
68
+ thisRow?: boolean;
69
+ };
70
+
71
+ /**
72
+ * Returns a string representation of a structured reference object.
73
+ *
74
+ * ```js
75
+ * stringifyStructRef({
76
+ * context: [ 'workbook.xlsx' ],
77
+ * sections: [ 'data' ],
78
+ * columns: [ 'my column', '@foo' ],
79
+ * table: 'tableName',
80
+ * });
81
+ * // => 'workbook.xlsx!tableName[[#Data],[Column1]:[Column2]]'
82
+ * ```
83
+ *
84
+ * @see {@link OptsStringifyStructRef}
85
+ * @param refObject A structured reference object.
86
+ * @param [options={}] Options.
87
+ * @returns The given structured reference in string format.
88
+ */
89
+ export function stringifyStructRef (refObject: ReferenceStruct, options: OptsStringifyStructRef = {}): string {
90
+ return stringifyPrefix(refObject) + stringifySRef(refObject, !!options.thisRow);
91
+ }
92
+
93
+ /**
94
+ * Returns a string representation of a structured reference object.
95
+ *
96
+ * ```js
97
+ * stringifyStructRef({
98
+ * workbookName: 'workbook.xlsx',
99
+ * sheetName: '',
100
+ * sections: [ 'data' ],
101
+ * columns: [ 'my column', '@foo' ],
102
+ * table: 'tableName',
103
+ * });
104
+ * // => 'workbook.xlsx!tableName[[#Data],[Column1]:[Column2]]'
105
+ * ```
106
+ *
107
+ * @param refObject A structured reference object.
108
+ * @param [options] Options.
109
+ * @returns The given structured reference in string format.
110
+ */
111
+ export function stringifyStructRefXlsx (refObject: ReferenceStructXlsx, options: OptsStringifyStructRef = {}): string {
112
+ return stringifyPrefixXlsx(refObject) + stringifySRef(refObject, !!options.thisRow);
113
+ }
@@ -0,0 +1,15 @@
1
+ import type { Token } from './types.ts';
2
+
3
+ /**
4
+ * Collapses a list of tokens into a formula string.
5
+ *
6
+ * @param tokens A list of tokens.
7
+ * @returns A formula string.
8
+ */
9
+ export function stringifyTokens (tokens: Token[]): string {
10
+ let s = '';
11
+ for (const token of tokens) {
12
+ s += token.value;
13
+ }
14
+ return s;
15
+ }
@@ -0,0 +1,11 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { toCol } from './toCol.ts';
3
+
4
+ describe('toCol', () => {
5
+ it('toCol converts integers to column ids', () => {
6
+ expect(toCol(0)).toBe('A');
7
+ expect(toCol(26)).toBe('AA');
8
+ expect(toCol(701)).toBe('ZZ');
9
+ expect(toCol(18277)).toBe('ZZZ');
10
+ });
11
+ });
@@ -2,15 +2,15 @@ const charFrom = String.fromCharCode;
2
2
 
3
3
  /**
4
4
  * Convert a 0 based offset number to a column string
5
- * representation (`2` = `"C"`).
5
+ * representation (`0` = `"A"`, `2` = `"C"`).
6
6
  *
7
7
  * The method expects a number between 0 and 16383. Other input will
8
8
  * return garbage.
9
9
  *
10
- * @param {number} columnIndex Zero based column index number
11
- * @returns {string} The column string identifier
10
+ * @param columnIndex Zero based column index number
11
+ * @returns The column string identifier
12
12
  */
13
- export function toCol (columnIndex) {
13
+ export function toCol (columnIndex: number): string {
14
14
  return (
15
15
  (columnIndex >= 702
16
16
  ? charFrom(((((columnIndex - 702) / 676) - 0) % 26) + 65)
@@ -0,0 +1,76 @@
1
+ import {
2
+ OPERATOR,
3
+ BOOLEAN,
4
+ ERROR,
5
+ NUMBER,
6
+ FUNCTION,
7
+ NEWLINE,
8
+ WHITESPACE,
9
+ STRING,
10
+ CONTEXT,
11
+ CONTEXT_QUOTE,
12
+ REF_RANGE,
13
+ REF_BEAM,
14
+ REF_TERNARY,
15
+ REF_NAMED,
16
+ REF_STRUCT,
17
+ FX_PREFIX,
18
+ UNKNOWN
19
+ } from './constants.ts';
20
+
21
+ /**
22
+ * A dictionary of the types used to identify token variants.
23
+ * @prop OPERATOR - Unary or binary operator (`+`, `%`)
24
+ * @prop BOOLEAN - Boolean literal (`TRUE`)
25
+ * @prop ERROR - Error literal (`#VALUE!`)
26
+ * @prop NUMBER - Number literal (`123.4`, `-1.5e+2`)
27
+ * @prop FUNCTION - Function name (`SUM`)
28
+ * @prop NEWLINE - Newline character (`\n`)
29
+ * @prop WHITESPACE - Whitespace character sequence (` `)
30
+ * @prop STRING - String literal (`"Lorem ipsum"`)
31
+ * @prop CONTEXT - Reference context ([Workbook.xlsx]Sheet1)
32
+ * @prop CONTEXT_QUOTE - Quoted reference context (`'[My workbook.xlsx]Sheet1'`)
33
+ * @prop REF_RANGE - A range identifier (`A1`)
34
+ * @prop REF_BEAM - A range "beam" identifier (`A:A` or `1:1`)
35
+ * @prop REF_TERNARY - A ternary range identifier (`B2:B`)
36
+ * @prop REF_NAMED - A name / named range identifier (`income`)
37
+ * @prop REF_STRUCT - A structured reference identifier (`table[[Column1]:[Column2]]`)
38
+ * @prop FX_PREFIX - A leading equals sign at the start of a formula (`=`)
39
+ * @prop UNKNOWN - Any unidentifiable range of characters.
40
+ */
41
+ export const tokenTypes = Object.freeze({
42
+ /** Unary or binary operator (`+`, `%`) */
43
+ OPERATOR: OPERATOR,
44
+ /** Boolean literal (`TRUE`) */
45
+ BOOLEAN: BOOLEAN,
46
+ /** Error literal (`#VALUE!`) */
47
+ ERROR: ERROR,
48
+ /** Number literal (`123.4`, `-1.5e+2`) */
49
+ NUMBER: NUMBER,
50
+ /** Function name (`SUM`) */
51
+ FUNCTION: FUNCTION,
52
+ /** Newline character (`\n`) */
53
+ NEWLINE: NEWLINE,
54
+ /** Whitespace character sequence (` `) */
55
+ WHITESPACE: WHITESPACE,
56
+ /** String literal (`"Lorem ipsum"`) */
57
+ STRING: STRING,
58
+ /** Reference context ([Workbook.xlsx]Sheet1) */
59
+ CONTEXT: CONTEXT,
60
+ /** Quoted reference context (`'[My workbook.xlsx]Sheet1'`) */
61
+ CONTEXT_QUOTE: CONTEXT_QUOTE,
62
+ /** A range identifier (`A1`) */
63
+ REF_RANGE: REF_RANGE,
64
+ /** A range "beam" identifier (`A:A` or `1:1`) */
65
+ REF_BEAM: REF_BEAM,
66
+ /** A ternary range identifier (`B2:B`) */
67
+ REF_TERNARY: REF_TERNARY,
68
+ /** A name / named range identifier (`income`) */
69
+ REF_NAMED: REF_NAMED,
70
+ /** A structured reference identifier (`table[[Column1]:[Column2]]`) */
71
+ REF_STRUCT: REF_STRUCT,
72
+ /** A leading equals sign at the start of a formula (`=`) */
73
+ FX_PREFIX: FX_PREFIX,
74
+ /** Any unidentifiable range of characters. */
75
+ UNKNOWN: UNKNOWN
76
+ });