@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.
- package/dist/index-BMr6cTgc.d.cts +1444 -0
- package/dist/index-BMr6cTgc.d.ts +1444 -0
- package/dist/index.cjs +3054 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2984 -0
- package/dist/index.js.map +1 -0
- package/dist/xlsx/index.cjs +3120 -0
- package/dist/xlsx/index.cjs.map +1 -0
- package/dist/xlsx/index.d.cts +55 -0
- package/dist/xlsx/index.d.ts +55 -0
- package/dist/xlsx/index.js +3049 -0
- package/dist/xlsx/index.js.map +1 -0
- package/docs/API.md +2959 -718
- package/docs/AST_format.md +2 -2
- package/eslint.config.mjs +40 -0
- package/lib/a1.spec.ts +32 -0
- package/lib/a1.ts +26 -0
- package/lib/addA1RangeBounds.ts +50 -0
- package/lib/addTokenMeta.spec.ts +166 -0
- package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
- package/lib/astTypes.ts +211 -0
- package/lib/cloneToken.ts +29 -0
- package/lib/{constants.js → constants.ts} +6 -3
- package/lib/fixRanges.spec.ts +220 -0
- package/lib/fixRanges.ts +260 -0
- package/lib/fromCol.spec.ts +15 -0
- package/lib/{fromCol.js → fromCol.ts} +1 -1
- package/lib/index.spec.ts +119 -0
- package/lib/index.ts +76 -0
- package/lib/isNodeType.ts +151 -0
- package/lib/isType.spec.ts +208 -0
- package/lib/{isType.js → isType.ts} +26 -25
- package/lib/lexers/advRangeOp.ts +18 -0
- package/lib/lexers/canEndRange.ts +25 -0
- package/lib/lexers/lexBoolean.ts +55 -0
- package/lib/lexers/lexContext.ts +104 -0
- package/lib/lexers/lexError.ts +15 -0
- package/lib/lexers/lexFunction.ts +37 -0
- package/lib/lexers/lexNameFuncCntx.ts +112 -0
- package/lib/lexers/lexNamed.ts +60 -0
- package/lib/lexers/lexNewLine.ts +12 -0
- package/lib/lexers/lexNumber.ts +48 -0
- package/lib/lexers/lexOperator.ts +26 -0
- package/lib/lexers/lexRange.ts +15 -0
- package/lib/lexers/lexRangeA1.ts +134 -0
- package/lib/lexers/lexRangeR1C1.ts +146 -0
- package/lib/lexers/lexRangeTrim.ts +26 -0
- package/lib/lexers/lexRefOp.ts +19 -0
- package/lib/lexers/lexString.ts +22 -0
- package/lib/lexers/lexStructured.ts +25 -0
- package/lib/lexers/lexWhitespace.ts +31 -0
- package/lib/lexers/sets.ts +51 -0
- package/lib/mergeRefTokens.spec.ts +141 -0
- package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +47 -32
- package/lib/nodeTypes.ts +54 -0
- package/lib/parse.spec.ts +1410 -0
- package/lib/{parser.js → parse.ts} +81 -63
- package/lib/parseA1Range.spec.ts +233 -0
- package/lib/parseA1Range.ts +206 -0
- package/lib/parseA1Ref.spec.ts +337 -0
- package/lib/parseA1Ref.ts +115 -0
- package/lib/parseR1C1Range.ts +191 -0
- package/lib/parseR1C1Ref.spec.ts +323 -0
- package/lib/parseR1C1Ref.ts +127 -0
- package/lib/parseRef.spec.ts +90 -0
- package/lib/parseRef.ts +240 -0
- package/lib/parseSRange.ts +240 -0
- package/lib/parseStructRef.spec.ts +168 -0
- package/lib/parseStructRef.ts +76 -0
- package/lib/stringifyA1Range.spec.ts +72 -0
- package/lib/stringifyA1Range.ts +72 -0
- package/lib/stringifyA1Ref.spec.ts +64 -0
- package/lib/stringifyA1Ref.ts +59 -0
- package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
- package/lib/stringifyR1C1Range.spec.ts +92 -0
- package/lib/stringifyR1C1Range.ts +73 -0
- package/lib/stringifyR1C1Ref.spec.ts +63 -0
- package/lib/stringifyR1C1Ref.ts +67 -0
- package/lib/stringifyStructRef.spec.ts +124 -0
- package/lib/stringifyStructRef.ts +113 -0
- package/lib/stringifyTokens.ts +15 -0
- package/lib/toCol.spec.ts +11 -0
- package/lib/{toCol.js → toCol.ts} +4 -4
- package/lib/tokenTypes.ts +76 -0
- package/lib/tokenize-srefs.spec.ts +429 -0
- package/lib/tokenize.spec.ts +2103 -0
- package/lib/tokenize.ts +346 -0
- package/lib/translate.spec.ts +35 -0
- package/lib/translateToA1.spec.ts +247 -0
- package/lib/translateToA1.ts +231 -0
- package/lib/translateToR1C1.spec.ts +227 -0
- package/lib/translateToR1C1.ts +145 -0
- package/lib/types.ts +179 -0
- package/lib/xlsx/index.spec.ts +27 -0
- package/lib/xlsx/index.ts +32 -0
- package/package.json +46 -30
- package/tsconfig.json +28 -0
- package/typedoc-ignore-links.ts +17 -0
- package/typedoc.json +41 -0
- package/.eslintrc +0 -22
- package/dist/fx.d.ts +0 -823
- package/dist/fx.js +0 -2
- package/dist/package.json +0 -1
- package/lib/a1.js +0 -348
- package/lib/a1.spec.js +0 -458
- package/lib/addTokenMeta.spec.js +0 -153
- package/lib/astTypes.js +0 -96
- package/lib/extraTypes.js +0 -74
- package/lib/fixRanges.js +0 -104
- package/lib/fixRanges.spec.js +0 -170
- package/lib/fromCol.spec.js +0 -11
- package/lib/index.js +0 -134
- package/lib/index.spec.js +0 -67
- package/lib/isType.spec.js +0 -168
- package/lib/lexer-srefs.spec.js +0 -324
- package/lib/lexer.js +0 -283
- package/lib/lexer.spec.js +0 -1953
- package/lib/lexerParts.js +0 -228
- package/lib/mergeRefTokens.spec.js +0 -121
- package/lib/package.json +0 -1
- package/lib/parseRef.js +0 -157
- package/lib/parseRef.spec.js +0 -71
- package/lib/parseSRange.js +0 -167
- package/lib/parseStructRef.js +0 -48
- package/lib/parseStructRef.spec.js +0 -164
- package/lib/parser.spec.js +0 -1208
- package/lib/rc.js +0 -341
- package/lib/rc.spec.js +0 -403
- package/lib/stringifyStructRef.js +0 -80
- package/lib/stringifyStructRef.spec.js +0 -182
- package/lib/toCol.spec.js +0 -11
- package/lib/translate-toA1.spec.js +0 -214
- package/lib/translate-toRC.spec.js +0 -197
- package/lib/translate.js +0 -239
- package/lib/translate.spec.js +0 -21
- package/rollup.config.mjs +0 -22
- 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
|
|
11
|
-
* @returns
|
|
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
|
+
});
|