@borgar/fx 4.3.1 → 4.5.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/fx.d.ts +653 -0
- package/dist/fx.js +1 -1
- package/docs/API.md +363 -280
- package/lib/a1.js +18 -9
- package/lib/constants.js +2 -2
- package/lib/lexer-srefs.spec.js +5 -0
- package/lib/lexerParts.js +24 -12
- package/lib/parseRef.spec.js +22 -11
- package/lib/parser.js +1 -1
- package/lib/parser.spec.js +90 -39
- package/lib/rc.js +4 -2
- package/lib/sr.spec.js +7 -0
- package/lib/translate-toA1.spec.js +5 -1
- package/lib/translate-toRC.spec.js +4 -1
- package/lib/translate.js +8 -5
- package/lib/translate.spec.js +21 -0
- package/package.json +14 -10
- package/tsd.json +12 -0
- package/.jsdoc/config.json +0 -17
- package/.jsdoc/publish.js +0 -217
package/lib/translate.js
CHANGED
|
@@ -34,18 +34,19 @@ const settings = {
|
|
|
34
34
|
* @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
|
|
35
35
|
* @param {Object} [options={}] The options
|
|
36
36
|
* @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
|
|
37
|
+
* @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
|
|
37
38
|
* @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
|
|
38
39
|
*/
|
|
39
|
-
export function translateToR1C1 (fx, anchorCell, { xlsx = false } = {}) {
|
|
40
|
+
export function translateToR1C1 (fx, anchorCell, { xlsx = false, allowTernary = true } = {}) {
|
|
40
41
|
const { top, left } = fromA1(anchorCell);
|
|
41
42
|
const isString = typeof fx === 'string';
|
|
42
43
|
|
|
43
44
|
const tokens = isString
|
|
44
|
-
? tokenize(fx, { ...settings, xlsx })
|
|
45
|
+
? tokenize(fx, { ...settings, xlsx, allowTernary })
|
|
45
46
|
: fx;
|
|
46
47
|
|
|
47
48
|
let offsetSkew = 0;
|
|
48
|
-
const refOpts = { xlsx, allowTernary
|
|
49
|
+
const refOpts = { xlsx, allowTernary };
|
|
49
50
|
tokens.forEach(token => {
|
|
50
51
|
if (isRange(token)) {
|
|
51
52
|
const tokenValue = token.value;
|
|
@@ -108,6 +109,7 @@ function toFixed (val, abs, base, max, wrapEdges = true) {
|
|
|
108
109
|
const defaultOptions = {
|
|
109
110
|
wrapEdges: true,
|
|
110
111
|
mergeRefs: true,
|
|
112
|
+
allowTernary: true,
|
|
111
113
|
xlsx: false
|
|
112
114
|
};
|
|
113
115
|
|
|
@@ -148,6 +150,7 @@ const defaultOptions = {
|
|
|
148
150
|
* @param {boolean} [options.wrapEdges=true] Wrap out-of-bounds ranges around sheet edges rather than turning them to #REF! errors
|
|
149
151
|
* @param {boolean} [options.mergeRefs=true] Should ranges be treated as whole references (`Sheet1!A1:B2`) or as separate tokens for each part: (`Sheet1`,`!`,`A1`,`:`,`B2`).
|
|
150
152
|
* @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
|
|
153
|
+
* @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
|
|
151
154
|
* @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
|
|
152
155
|
*/
|
|
153
156
|
export function translateToA1 (formula, anchorCell, options = defaultOptions) {
|
|
@@ -160,13 +163,13 @@ export function translateToA1 (formula, anchorCell, options = defaultOptions) {
|
|
|
160
163
|
withLocation: false,
|
|
161
164
|
mergeRefs: opts.mergeRefs,
|
|
162
165
|
xlsx: opts.xlsx,
|
|
163
|
-
allowTernary:
|
|
166
|
+
allowTernary: opts.allowTernary,
|
|
164
167
|
r1c1: true
|
|
165
168
|
})
|
|
166
169
|
: formula;
|
|
167
170
|
|
|
168
171
|
let offsetSkew = 0;
|
|
169
|
-
const refOpts = { xlsx: opts.xlsx, allowTernary:
|
|
172
|
+
const refOpts = { xlsx: opts.xlsx, allowTernary: opts.allowTernary };
|
|
170
173
|
tokens.forEach(token => {
|
|
171
174
|
if (isRange(token)) {
|
|
172
175
|
const tokenValue = token.value;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { test, Test } from 'tape';
|
|
2
|
+
import { translateToR1C1, translateToA1 } from './translate.js';
|
|
3
|
+
|
|
4
|
+
Test.prototype.okayRoundTrip = function roundTrip (expr, anchor, options) {
|
|
5
|
+
const rc = translateToR1C1(expr, anchor, options);
|
|
6
|
+
const a1 = translateToA1(rc, anchor, options);
|
|
7
|
+
this.is(a1, expr, 'Round trip: ' + expr);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
test('translate absolute cells from A1 to RC', t => {
|
|
11
|
+
t.okayRoundTrip('=Sheet1!$1:$1048576', 'A1');
|
|
12
|
+
t.okayRoundTrip('=D$1:$BJ$1048576', 'A1');
|
|
13
|
+
t.okayRoundTrip('=VLOOKUP(C7,Röðun,4,0)', 'A1');
|
|
14
|
+
t.okayRoundTrip('=COUNTIF(B$1442:B$1048576,$G1442)', 'A1');
|
|
15
|
+
t.okayRoundTrip('=IF(p2m<=D5,10,0)*scene_spend', 'A1');
|
|
16
|
+
t.okayRoundTrip('=(kwh_used_daily*kwhbtu*co2btu)/1000000', 'A1');
|
|
17
|
+
t.okayRoundTrip('=NOPLATT1+g1_+ROIC1+WACC+G1+g1_+G130+ROIC2+WACC+g2_+WACC+N', 'A1');
|
|
18
|
+
// FIXME: translate needs to be be able to specify allowTernary=false
|
|
19
|
+
t.okayRoundTrip('=foo:C3:D4', 'A1', { allowTernary: false });
|
|
20
|
+
t.end();
|
|
21
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgar/fx",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Utilities for working with Excel formulas",
|
|
5
5
|
"main": "dist/fx.js",
|
|
6
|
+
"types": "dist/fx.d.ts",
|
|
6
7
|
"module": "lib/index.js",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
"version": "npm run build",
|
|
16
17
|
"lint": "eslint lib/*.js",
|
|
17
18
|
"test": "tape lib/*.spec.js | tap-min",
|
|
18
|
-
"build:
|
|
19
|
+
"build:types": "jsdoc -c tsd.json lib>dist/fx.d.ts",
|
|
20
|
+
"build:docs": "echo '# _Fx_ API\n'>docs/API.md; jsdoc -t node_modules/@borgar/jsdoc-tsmd -d console lib>>docs/API.md",
|
|
19
21
|
"build": "NODE_ENV=production rollup -c"
|
|
20
22
|
},
|
|
21
23
|
"repository": {
|
|
@@ -39,17 +41,19 @@
|
|
|
39
41
|
"author": "Borgar Þorsteinsson <borgar@borgar.net> (http://borgar.net/)",
|
|
40
42
|
"license": "MIT",
|
|
41
43
|
"devDependencies": {
|
|
42
|
-
"@babel/core": "~7.
|
|
43
|
-
"@babel/eslint-parser": "~7.
|
|
44
|
-
"@babel/preset-env": "~7.20
|
|
45
|
-
"@borgar/eslint-config": "~3.
|
|
44
|
+
"@babel/core": "~7.23.0",
|
|
45
|
+
"@babel/eslint-parser": "~7.22.15",
|
|
46
|
+
"@babel/preset-env": "~7.22.20",
|
|
47
|
+
"@borgar/eslint-config": "~3.1.0",
|
|
48
|
+
"@borgar/jsdoc-tsmd": "~0.1.0",
|
|
46
49
|
"@rollup/plugin-babel": "~6.0.3",
|
|
47
50
|
"babel-eslint": "~10.1.0",
|
|
48
|
-
"eslint": "~8.
|
|
51
|
+
"eslint": "~8.50.0",
|
|
49
52
|
"jsdoc": "~4.0.2",
|
|
50
|
-
"rollup": "~3.
|
|
53
|
+
"rollup": "~3.29.4",
|
|
51
54
|
"rollup-plugin-minification": "~0.2.0",
|
|
52
|
-
"tap-min": "~
|
|
53
|
-
"
|
|
55
|
+
"tap-min": "~3.0.0",
|
|
56
|
+
"typescript": "~5.2.2",
|
|
57
|
+
"tape": "~5.7.0"
|
|
54
58
|
}
|
|
55
59
|
}
|
package/tsd.json
ADDED
package/.jsdoc/config.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"source": {
|
|
3
|
-
"includePattern": ".+\\.js(doc|x)?$",
|
|
4
|
-
"excludePattern": "((^|\\/|\\\\)_|spec\\.js$)"
|
|
5
|
-
},
|
|
6
|
-
"opts": {
|
|
7
|
-
"template": "./.jsdoc",
|
|
8
|
-
"encoding": "utf8",
|
|
9
|
-
"destination": "./docs/"
|
|
10
|
-
},
|
|
11
|
-
"templates": {
|
|
12
|
-
"default": {
|
|
13
|
-
"includeDate": false,
|
|
14
|
-
"outputSourceFiles": false
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
package/.jsdoc/publish.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-undefined, no-console */
|
|
2
|
-
|
|
3
|
-
function formatType (d, inTable = false) {
|
|
4
|
-
return d.names?.map(n => `\`${n}\``).join(inTable ? ' \\| ' : ' | ') || '';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function formatArguments (args, showDefaults = true) {
|
|
8
|
-
let inOpt = false;
|
|
9
|
-
let r = args.reduce((a, d, i) => {
|
|
10
|
-
if (d.name.includes('.')) {
|
|
11
|
-
return a;
|
|
12
|
-
}
|
|
13
|
-
if (inOpt && !d.optional) {
|
|
14
|
-
a += ']_';
|
|
15
|
-
inOpt = false;
|
|
16
|
-
}
|
|
17
|
-
if (i) {
|
|
18
|
-
a += ', ';
|
|
19
|
-
}
|
|
20
|
-
if (!inOpt && d.optional) {
|
|
21
|
-
a += '_[';
|
|
22
|
-
inOpt = true;
|
|
23
|
-
}
|
|
24
|
-
a += d.name;
|
|
25
|
-
if (showDefaults && d.defaultvalue) {
|
|
26
|
-
a += ' = `' + d.defaultvalue + '`';
|
|
27
|
-
}
|
|
28
|
-
return a;
|
|
29
|
-
}, '');
|
|
30
|
-
if (inOpt) {
|
|
31
|
-
r += ']_';
|
|
32
|
-
}
|
|
33
|
-
return r;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function repValue (v) {
|
|
37
|
-
if (v === undefined) {
|
|
38
|
-
return '';
|
|
39
|
-
}
|
|
40
|
-
if (v === "''") {
|
|
41
|
-
// FIXME: should transform all '' strings to "" style
|
|
42
|
-
v = '""';
|
|
43
|
-
}
|
|
44
|
-
return '`' + v + '`';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function repArgName (name, optional = false) {
|
|
48
|
-
const names = name.split('.');
|
|
49
|
-
const nPath = names.length > 1 ? '.' + names.slice(1).join('.') : '';
|
|
50
|
-
return (optional ? '_[' : '') + names[0] + (optional ? ']_' : '') + nPath;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function listToTable (args) {
|
|
54
|
-
const useDefaults = args.some(d => !!d.defaultvalue);
|
|
55
|
-
const table = useDefaults
|
|
56
|
-
? [
|
|
57
|
-
'| Name | Type | Default | Description |',
|
|
58
|
-
'| ---- | ---- | ------- | ----------- |' ]
|
|
59
|
-
: [
|
|
60
|
-
'| Name | Type | Description |',
|
|
61
|
-
'| ---- | ---- | ----------- |' ];
|
|
62
|
-
args.forEach(d => {
|
|
63
|
-
const row = [ repArgName(d.name, d.optional) ];
|
|
64
|
-
row.push(formatType(d.type, true));
|
|
65
|
-
useDefaults && row.push(repValue(d.defaultvalue));
|
|
66
|
-
row.push(transformLinks(d.description));
|
|
67
|
-
table.push('| ' + row.join(' | ') + ' |');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return table.join('\n');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function adjustLineBreaks (md) {
|
|
74
|
-
return md.trim().replace(
|
|
75
|
-
/(?:```([^\0]*?)```|(\S)\n(?!\n))/g,
|
|
76
|
-
(a, b, c) => {
|
|
77
|
-
return (c ? (c + ' ') : a);
|
|
78
|
-
}
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function formatSee (see) {
|
|
83
|
-
if (see) {
|
|
84
|
-
if (/^[a-z][a-z0-9]*$/i.test(see)) {
|
|
85
|
-
see = `{@link ${see}}`;
|
|
86
|
-
}
|
|
87
|
-
return `\n**See also:** ${transformLinks(see)}.\n`;
|
|
88
|
-
}
|
|
89
|
-
return '';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const re_links = /(?:\[([^\]]+)\])?\{@link(?:code|plain)? ([^} ]+)([^}]+)?\}/g;
|
|
93
|
-
function transformLinks (md) {
|
|
94
|
-
return md.replace(re_links, (a, preLabel, href, postLabel) => {
|
|
95
|
-
const hash = /^[a-z][a-z0-9]*$/i.test(href) ? '#' : '';
|
|
96
|
-
return `[${preLabel || postLabel || href}](${hash}${href})`;
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function formatHeading (text, level = 2) {
|
|
101
|
-
return '#'.repeat(level || 1) + ' ' + text;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function formatIndexLink (name, text) {
|
|
105
|
-
return `[${text.replace(/([[\]])/g, '\\$1')}](#${name})`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const formatIndex = {
|
|
109
|
-
function: d => {
|
|
110
|
-
return '- ' + formatIndexLink(d.name,
|
|
111
|
-
`${d.name}( ${formatArguments(d.params, false)} )`
|
|
112
|
-
);
|
|
113
|
-
},
|
|
114
|
-
constant: d => {
|
|
115
|
-
return '- ' + formatIndexLink(d.name,
|
|
116
|
-
`${d.name}`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const format = {
|
|
122
|
-
function: d => {
|
|
123
|
-
return [
|
|
124
|
-
formatHeading(`<a name="${d.name}" href="#${d.name}">#</a> ${d.name}( ${formatArguments(d.params)} ) ⇒ ${formatType(d.returns[0].type)}`, 3),
|
|
125
|
-
adjustLineBreaks(transformLinks(d.description)),
|
|
126
|
-
formatSee(d.see),
|
|
127
|
-
formatHeading('Parameters', 4),
|
|
128
|
-
listToTable(d.params),
|
|
129
|
-
formatHeading('Returns', 4),
|
|
130
|
-
formatType(d.returns[0].type) + ' – ' + d.returns[0].description
|
|
131
|
-
];
|
|
132
|
-
},
|
|
133
|
-
constant: d => {
|
|
134
|
-
return [
|
|
135
|
-
formatHeading(`<a name="${d.name}" href="#${d.name}">#</a> ${d.name} ⇒ ${formatType(d.type)}`, 3),
|
|
136
|
-
adjustLineBreaks(transformLinks(d.description)),
|
|
137
|
-
formatSee(d.see),
|
|
138
|
-
formatHeading('Properties', 4),
|
|
139
|
-
listToTable(d.properties)
|
|
140
|
-
];
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const categoryHeadings = {
|
|
145
|
-
package: null,
|
|
146
|
-
function: 'Functions',
|
|
147
|
-
constant: 'Constants'
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
exports.publish = (data, { destination }) => {
|
|
151
|
-
data({ undocumented: true }).remove();
|
|
152
|
-
const docs = data().get();
|
|
153
|
-
|
|
154
|
-
const test = '';
|
|
155
|
-
if (test) {
|
|
156
|
-
const s = docs.find(d => d.name === test);
|
|
157
|
-
const o = format[s.kind](s);
|
|
158
|
-
console.log(o.join('\n\n'));
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const categories = {};
|
|
163
|
-
docs.forEach(d => {
|
|
164
|
-
if (!categories[d.kind]) {
|
|
165
|
-
categories[d.kind] = [];
|
|
166
|
-
}
|
|
167
|
-
categories[d.kind].push(d);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
let output = [];
|
|
171
|
-
const index = [];
|
|
172
|
-
|
|
173
|
-
Object.keys(categories)
|
|
174
|
-
.sort()
|
|
175
|
-
.forEach(kind => {
|
|
176
|
-
const heading = categoryHeadings[kind];
|
|
177
|
-
if (heading == null) { return; }
|
|
178
|
-
// emit category heading
|
|
179
|
-
if (index.length) { index.push(''); }
|
|
180
|
-
index.push('**' + heading + '**', '');
|
|
181
|
-
output.push(formatHeading(heading, 2));
|
|
182
|
-
// emit all members belonging to that category
|
|
183
|
-
docs
|
|
184
|
-
.filter(d => d.kind === kind)
|
|
185
|
-
.filter(d => d.access !== 'private')
|
|
186
|
-
.sort((a, b) => {
|
|
187
|
-
if (a.name < b.name) { return -1; }
|
|
188
|
-
if (a.name > b.name) { return 1; }
|
|
189
|
-
return NaN;
|
|
190
|
-
})
|
|
191
|
-
.forEach((d, i) => {
|
|
192
|
-
if (i) {
|
|
193
|
-
output.push('---');
|
|
194
|
-
}
|
|
195
|
-
if (!format[d.kind]) {
|
|
196
|
-
console.error('Unsupported member type ' + d.kind);
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
index.push(formatIndex[d.kind](d));
|
|
200
|
-
output.push(...format[d.kind](d));
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
if (destination === 'console') {
|
|
205
|
-
const outText = (
|
|
206
|
-
index.map(d => d.trim()).join('\n') +
|
|
207
|
-
'\n\n' +
|
|
208
|
-
output.map(d => d.trim()).filter(Boolean).join('\n\n') +
|
|
209
|
-
'\n'
|
|
210
|
-
);
|
|
211
|
-
console.log(outText);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
console.error('This template only supports output to the console. Use the option "-d console" when you run JSDoc.');
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
};
|