@borgar/fx 4.10.2 → 4.11.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.
@@ -0,0 +1,160 @@
1
+ /* eslint-disable object-property-newline, object-curly-newline */
2
+ import { test, Test } from 'tape';
3
+ import { parseStructRef } from './parseStructRef.js';
4
+
5
+ Test.prototype.isSREqual = function isSREqual (expr, expect, opts) {
6
+ if (expect) {
7
+ expect = opts?.xlsx
8
+ ? {
9
+ workbookName: '',
10
+ sheetName: '',
11
+ table: '',
12
+ columns: [],
13
+ sections: [],
14
+ ...expect
15
+ }
16
+ : {
17
+ context: [],
18
+ table: '',
19
+ columns: [],
20
+ sections: [],
21
+ ...expect
22
+ };
23
+ }
24
+ this.deepEqual(parseStructRef(expr, opts), expect, expr);
25
+ };
26
+
27
+ test('parse structured references', t => {
28
+ t.isSREqual('table[col]', {
29
+ table: 'table',
30
+ columns: [ 'col' ]
31
+ });
32
+
33
+ t.isSREqual('[#All]', {
34
+ sections: [ 'all' ]
35
+ });
36
+
37
+ t.isSREqual('[column name]', {
38
+ columns: [ 'column name' ]
39
+ });
40
+
41
+ t.isSREqual('[column name]!foo', null);
42
+ t.isSREqual('[foo]bar', null);
43
+
44
+ t.isSREqual('[[my column]]', {
45
+ columns: [ 'my column' ]
46
+ });
47
+
48
+ t.isSREqual('[[my column]:otherColumn]', {
49
+ columns: [ 'my column', 'otherColumn' ]
50
+ });
51
+
52
+ t.isSREqual('[ [my column]:otherColumn ]', {
53
+ columns: [ 'my column', 'otherColumn ' ]
54
+ });
55
+
56
+ t.isSREqual('[ [my column]: otherColumn ]', {
57
+ columns: [ 'my column', ' otherColumn ' ]
58
+ });
59
+
60
+ t.isSREqual('[ @[ my column ]: otherColumn ]', {
61
+ columns: [ ' my column ', ' otherColumn ' ],
62
+ sections: [ 'this row' ]
63
+ });
64
+
65
+ t.isSREqual('[[#Data], [my column]:otherColumn]', {
66
+ columns: [ 'my column', 'otherColumn' ],
67
+ sections: [ 'data' ]
68
+ });
69
+
70
+ t.isSREqual('[ [#Data], [my column]:[\'@foo] ]', {
71
+ columns: [ 'my column', '@foo' ],
72
+ sections: [ 'data' ]
73
+ });
74
+
75
+ t.isSREqual('workbook.xlsx!tableName[ [#Data], [my column]:[\'@foo] ]', {
76
+ columns: [ 'my column', '@foo' ],
77
+ sections: [ 'data' ],
78
+ table: 'tableName',
79
+ context: [ 'workbook.xlsx' ]
80
+ });
81
+
82
+ t.isSREqual('[[#Data],[#data],[#Data],[#Data],[#Totals],[#Totals],[#Totals],foo]', {
83
+ columns: [ 'foo' ],
84
+ sections: [ 'data', 'totals' ]
85
+ });
86
+
87
+ t.isSREqual("'Sheet'!Table[Column]", {
88
+ columns: [ 'Column' ],
89
+ table: 'Table',
90
+ context: [ 'Sheet' ]
91
+ });
92
+
93
+ t.isSREqual("Sheet1!Table1[foo '[bar']]", {
94
+ columns: [ 'foo [bar]' ],
95
+ table: 'Table1',
96
+ context: [ 'Sheet1' ]
97
+ });
98
+
99
+ t.isSREqual('[myworkbook.xlsx]Sheet1!TMP8w0habhr[#All]', {
100
+ columns: [],
101
+ table: 'TMP8w0habhr',
102
+ context: [ 'myworkbook.xlsx', 'Sheet1' ],
103
+ sections: [ 'all' ]
104
+ });
105
+
106
+ t.end();
107
+ });
108
+
109
+ test('structured references parse in xlsx mode', t => {
110
+ t.isSREqual('[Workbook.xlsx]!Table[#Data]', {
111
+ workbookName: 'Workbook.xlsx',
112
+ table: 'Table',
113
+ sections: [ 'data' ]
114
+ }, { xlsx: true });
115
+
116
+ t.isSREqual('[Workbook.xlsx]Sheet1!Table[#Data]', {
117
+ workbookName: 'Workbook.xlsx',
118
+ sheetName: 'Sheet1',
119
+ table: 'Table',
120
+ sections: [ 'data' ]
121
+ }, { xlsx: true });
122
+
123
+ t.isSREqual('Sheet1!Table[#Data]', {
124
+ sheetName: 'Sheet1',
125
+ table: 'Table',
126
+ sections: [ 'data' ]
127
+ }, { xlsx: true });
128
+
129
+ t.end();
130
+ });
131
+
132
+ test('longform prase (in xlsx mode)', t => {
133
+ // thisRow should have no effect when parsing
134
+ t.isSREqual('Table2[[#This Row],[col1]]', {
135
+ table: 'Table2',
136
+ columns: [ 'col1' ],
137
+ sections: [ 'this row' ]
138
+ }, { xlsx: true, thisRow: true });
139
+
140
+ t.isSREqual('Table2[[#This Row],[col1]]', {
141
+ table: 'Table2',
142
+ columns: [ 'col1' ],
143
+ sections: [ 'this row' ]
144
+ }, { xlsx: true, thisRow: false });
145
+
146
+ t.isSREqual('Table2[[#This Row],[col1]]', {
147
+ table: 'Table2',
148
+ columns: [ 'col1' ],
149
+ sections: [ 'this row' ]
150
+ }, { xlsx: false, thisRow: true });
151
+
152
+ t.isSREqual('Table2[[#This Row],[col1]]', {
153
+ table: 'Table2',
154
+ columns: [ 'col1' ],
155
+ sections: [ 'this row' ]
156
+ }, { xlsx: false, thisRow: false });
157
+
158
+ t.end();
159
+ });
160
+
@@ -0,0 +1,80 @@
1
+ import { stringifyPrefix, stringifyPrefixAlt } from './stringifyPrefix.js';
2
+
3
+ function quoteColname (str) {
4
+ return str.replace(/([[\]#'@])/g, '\'$1');
5
+ }
6
+
7
+ function needsBraces (str) {
8
+ return !/^[a-zA-Z0-9\u00a1-\uffff]+$/.test(str);
9
+ }
10
+
11
+ function toSentenceCase (str) {
12
+ return str[0].toUpperCase() + str.slice(1).toLowerCase();
13
+ }
14
+
15
+ /**
16
+ * Get a string representation of a structured reference object.
17
+ *
18
+ * ```js
19
+ * stringifyStructRef({
20
+ * context: [ 'workbook.xlsx' ],
21
+ * sections: [ 'data' ],
22
+ * columns: [ 'my column', '@foo' ],
23
+ * table: 'tableName',
24
+ * });
25
+ * // => 'workbook.xlsx!tableName[[#Data],[Column1]:[Column2]]'
26
+ * ```
27
+ *
28
+ * @param {ReferenceStruct} refObject A structured reference object
29
+ * @param {object} [options={}] Options
30
+ * @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)
31
+ * @param {boolean} [options.thisRow=false] Enforces using the `[#This Row]` instead of the `@` shorthand when serializing structured ranges.
32
+ * @returns {string} The structured reference in string format
33
+ */
34
+ export function stringifyStructRef (refObject, options = {}) {
35
+ const { xlsx, thisRow } = options;
36
+ let s = xlsx
37
+ ? stringifyPrefixAlt(refObject)
38
+ : stringifyPrefix(refObject);
39
+
40
+ if (refObject.table) {
41
+ s += refObject.table;
42
+ }
43
+ const numColumns = refObject.columns?.length ?? 0;
44
+ const numSections = refObject.sections?.length ?? 0;
45
+ // single section
46
+ if (numSections === 1 && !numColumns) {
47
+ s += `[#${toSentenceCase(refObject.sections[0])}]`;
48
+ }
49
+ // single column
50
+ else if (!numSections && numColumns === 1) {
51
+ s += `[${quoteColname(refObject.columns[0])}]`;
52
+ }
53
+ else {
54
+ s += '[';
55
+ // single [#this row] sections get normalized to an @ by default
56
+ const singleAt = !thisRow && numSections === 1 && refObject.sections[0].toLowerCase() === 'this row';
57
+ if (singleAt) {
58
+ s += '@';
59
+ }
60
+ else if (numSections) {
61
+ s += refObject.sections
62
+ .map(d => `[#${toSentenceCase(d)}]`)
63
+ .join(',');
64
+ if (numColumns) {
65
+ s += ',';
66
+ }
67
+ }
68
+ // a case of a single alphanumberic column with a [#this row] becomes [@col]
69
+ if (singleAt && refObject.columns.length === 1 && !needsBraces(refObject.columns[0])) {
70
+ s += quoteColname(refObject.columns[0]);
71
+ }
72
+ else if (numColumns) {
73
+ s += refObject.columns.slice(0, 2)
74
+ .map(d => (`[${quoteColname(d)}]`))
75
+ .join(':');
76
+ }
77
+ s += ']';
78
+ }
79
+ return s;
80
+ }
@@ -1,110 +1,6 @@
1
1
  /* eslint-disable object-property-newline, object-curly-newline */
2
- import { test, Test } from 'tape';
3
- import { parseStructRef, stringifyStructRef } from './sr.js';
4
-
5
- Test.prototype.isSREqual = function isSREqual (expr, expect, opts) {
6
- if (expect) {
7
- expect = opts?.xlsx
8
- ? {
9
- workbookName: '',
10
- sheetName: '',
11
- table: '',
12
- columns: [],
13
- sections: [],
14
- ...expect
15
- }
16
- : {
17
- context: [],
18
- table: '',
19
- columns: [],
20
- sections: [],
21
- ...expect
22
- };
23
- }
24
- this.deepEqual(parseStructRef(expr, opts), expect, expr);
25
- };
26
-
27
- test('parse structured references', t => {
28
- t.isSREqual('table[col]', {
29
- table: 'table',
30
- columns: [ 'col' ]
31
- });
32
-
33
- t.isSREqual('[#All]', {
34
- sections: [ 'all' ]
35
- });
36
-
37
- t.isSREqual('[column name]', {
38
- columns: [ 'column name' ]
39
- });
40
-
41
- t.isSREqual('[column name]!foo', null);
42
- t.isSREqual('[foo]bar', null);
43
-
44
- t.isSREqual('[[my column]]', {
45
- columns: [ 'my column' ]
46
- });
47
-
48
- t.isSREqual('[[my column]:otherColumn]', {
49
- columns: [ 'my column', 'otherColumn' ]
50
- });
51
-
52
- t.isSREqual('[ [my column]:otherColumn ]', {
53
- columns: [ 'my column', 'otherColumn ' ]
54
- });
55
-
56
- t.isSREqual('[ [my column]: otherColumn ]', {
57
- columns: [ 'my column', ' otherColumn ' ]
58
- });
59
-
60
- t.isSREqual('[ @[ my column ]: otherColumn ]', {
61
- columns: [ ' my column ', ' otherColumn ' ],
62
- sections: [ 'this row' ]
63
- });
64
-
65
- t.isSREqual('[[#Data], [my column]:otherColumn]', {
66
- columns: [ 'my column', 'otherColumn' ],
67
- sections: [ 'data' ]
68
- });
69
-
70
- t.isSREqual('[ [#Data], [my column]:[\'@foo] ]', {
71
- columns: [ 'my column', '@foo' ],
72
- sections: [ 'data' ]
73
- });
74
-
75
- t.isSREqual('workbook.xlsx!tableName[ [#Data], [my column]:[\'@foo] ]', {
76
- columns: [ 'my column', '@foo' ],
77
- sections: [ 'data' ],
78
- table: 'tableName',
79
- context: [ 'workbook.xlsx' ]
80
- });
81
-
82
- t.isSREqual('[[#Data],[#data],[#Data],[#Data],[#Totals],[#Totals],[#Totals],foo]', {
83
- columns: [ 'foo' ],
84
- sections: [ 'data', 'totals' ]
85
- });
86
-
87
- t.isSREqual("'Sheet'!Table[Column]", {
88
- columns: [ 'Column' ],
89
- table: 'Table',
90
- context: [ 'Sheet' ]
91
- });
92
-
93
- t.isSREqual("Sheet1!Table1[foo '[bar']]", {
94
- columns: [ 'foo [bar]' ],
95
- table: 'Table1',
96
- context: [ 'Sheet1' ]
97
- });
98
-
99
- t.isSREqual('[myworkbook.xlsx]Sheet1!TMP8w0habhr[#All]', {
100
- columns: [],
101
- table: 'TMP8w0habhr',
102
- context: [ 'myworkbook.xlsx', 'Sheet1' ],
103
- sections: [ 'all' ]
104
- });
105
-
106
- t.end();
107
- });
2
+ import { test } from 'tape';
3
+ import { stringifyStructRef } from './stringifyStructRef.js';
108
4
 
109
5
  test('serialize structured references', t => {
110
6
  t.is(
@@ -213,26 +109,7 @@ test('serialize structured references', t => {
213
109
  t.end();
214
110
  });
215
111
 
216
- test('structured references parse and serialize in xlsx mode', t => {
217
- t.isSREqual('[Workbook.xlsx]!Table[#Data]', {
218
- workbookName: 'Workbook.xlsx',
219
- table: 'Table',
220
- sections: [ 'data' ]
221
- }, { xlsx: true });
222
-
223
- t.isSREqual('[Workbook.xlsx]Sheet1!Table[#Data]', {
224
- workbookName: 'Workbook.xlsx',
225
- sheetName: 'Sheet1',
226
- table: 'Table',
227
- sections: [ 'data' ]
228
- }, { xlsx: true });
229
-
230
- t.isSREqual('Sheet1!Table[#Data]', {
231
- sheetName: 'Sheet1',
232
- table: 'Table',
233
- sections: [ 'data' ]
234
- }, { xlsx: true });
235
-
112
+ test('structured references serialize in xlsx mode', t => {
236
113
  t.is(
237
114
  stringifyStructRef({
238
115
  context: [ 'Lorem', 'Ipsum' ],
@@ -279,31 +156,6 @@ test('structured references parse and serialize in xlsx mode', t => {
279
156
  });
280
157
 
281
158
  test('longform serialize (in xlsx mode)', t => {
282
- // thisRow should have no effect when parsing
283
- t.isSREqual('Table2[[#This Row],[col1]]', {
284
- table: 'Table2',
285
- columns: [ 'col1' ],
286
- sections: [ 'this row' ]
287
- }, { xlsx: true, thisRow: true });
288
-
289
- t.isSREqual('Table2[[#This Row],[col1]]', {
290
- table: 'Table2',
291
- columns: [ 'col1' ],
292
- sections: [ 'this row' ]
293
- }, { xlsx: true, thisRow: false });
294
-
295
- t.isSREqual('Table2[[#This Row],[col1]]', {
296
- table: 'Table2',
297
- columns: [ 'col1' ],
298
- sections: [ 'this row' ]
299
- }, { xlsx: false, thisRow: true });
300
-
301
- t.isSREqual('Table2[[#This Row],[col1]]', {
302
- table: 'Table2',
303
- columns: [ 'col1' ],
304
- sections: [ 'this row' ]
305
- }, { xlsx: false, thisRow: false });
306
-
307
159
  // thisRow should mean we don't see @'s in output
308
160
  t.is(
309
161
  stringifyStructRef({
package/lib/toCol.js ADDED
@@ -0,0 +1,23 @@
1
+ const charFrom = String.fromCharCode;
2
+
3
+ /**
4
+ * Convert a 0 based offset number to a column string
5
+ * representation (`2` = `"C"`).
6
+ *
7
+ * The method expects a number between 0 and 16383. Other input will
8
+ * return garbage.
9
+ *
10
+ * @param {number} columnIndex Zero based column index number
11
+ * @returns {string} The column string identifier
12
+ */
13
+ export function toCol (columnIndex) {
14
+ return (
15
+ (columnIndex >= 702
16
+ ? charFrom(((((columnIndex - 702) / 676) - 0) % 26) + 65)
17
+ : '') +
18
+ (columnIndex >= 26
19
+ ? charFrom(((((columnIndex / 26) - 1) % 26) + 65))
20
+ : '') +
21
+ charFrom((columnIndex % 26) + 65)
22
+ );
23
+ }
@@ -0,0 +1,11 @@
1
+ import { test } from 'tape';
2
+ import { toCol } from './toCol.js';
3
+
4
+ // What happens when B2:A1 -> should work!
5
+ test('toCol converts integers to column ids', t => {
6
+ t.is(toCol(0), 'A');
7
+ t.is(toCol(26), 'AA');
8
+ t.is(toCol(701), 'ZZ');
9
+ t.is(toCol(18277), 'ZZZ');
10
+ t.end();
11
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borgar/fx",
3
- "version": "4.10.2",
3
+ "version": "4.11.0",
4
4
  "description": "Utilities for working with Excel formulas",
5
5
  "main": "dist/fx.js",
6
6
  "types": "dist/fx.d.ts",
@@ -48,12 +48,12 @@
48
48
  "@borgar/eslint-config": "~3.1.0",
49
49
  "@borgar/jsdoc-tsmd": "~0.2.1",
50
50
  "@rollup/plugin-babel": "~6.0.4",
51
+ "@rollup/plugin-terser": "~0.4.4",
51
52
  "babel-eslint": "~10.1.0",
52
53
  "eslint": "~8.56.0",
53
54
  "eslint-plugin-jsdoc": "~48.1.0",
54
55
  "jsdoc": "~4.0.2",
55
56
  "rollup": "~4.12.0",
56
- "rollup-plugin-minification": "~0.2.0",
57
57
  "tap-min": "~3.0.0",
58
58
  "tape": "~5.7.5",
59
59
  "typescript": "~5.3.3"
package/rollup.config.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import babel from '@rollup/plugin-babel';
3
- import { terser } from 'rollup-plugin-minification';
3
+ import terser from '@rollup/plugin-terser';
4
4
 
5
- const pkg = JSON.parse(fs.readFileSync('./package.json'));
5
+ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
6
6
  const isProd = process.env.NODE_ENV === 'production';
7
7
 
8
8
  export default {
@@ -11,12 +11,12 @@ export default {
11
11
  babel({
12
12
  babelHelpers: 'bundled',
13
13
  presets: [ '@babel/preset-env' ]
14
- })
14
+ }),
15
+ isProd ? terser({ maxWorkers: 4 }) : null
15
16
  ],
16
17
  output: {
17
18
  file: pkg.main,
18
19
  format: 'cjs',
19
- sourcemap: 'inline',
20
- plugins: [ isProd && terser() ]
20
+ sourcemap: 'inline'
21
21
  }
22
22
  };