@dra2020/baseclient 1.0.142 → 1.0.144
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/baseclient.js +62 -33
- package/dist/baseclient.js.map +1 -1
- package/dist/filterexpr/filterexpr.d.ts +1 -0
- package/lib/detail/detail.ts +36 -38
- package/lib/filterexpr/filterexpr.ts +39 -0
- package/package.json +1 -1
package/lib/detail/detail.ts
CHANGED
|
@@ -12,15 +12,19 @@
|
|
|
12
12
|
//
|
|
13
13
|
|
|
14
14
|
import * as Util from '../util/all';
|
|
15
|
+
//import { Util } from '@dra2020/baseclient';
|
|
15
16
|
|
|
16
17
|
const reIdentifier = /\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g;
|
|
17
|
-
const
|
|
18
|
+
const reIdentifierOrString = /([a-zA-Z_$][a-zA-Z0-9_$]*)|(['"])(?:(?=(\\?))\3.)*?\2/g;
|
|
19
|
+
const reParam = /^__\d+$/;
|
|
20
|
+
const reString = /^['"]/;
|
|
18
21
|
|
|
19
22
|
// Number format: (locale|general|integer|currency).precision
|
|
20
23
|
|
|
21
24
|
function formatNumber(n: number, format: string): string
|
|
22
25
|
{
|
|
23
26
|
if (!format) format = 'locale.0';
|
|
27
|
+
if (isNaN(n)) n = 0;
|
|
24
28
|
let parts = format.split('.');
|
|
25
29
|
let fmt = parts[0];
|
|
26
30
|
let precision = Number(parts[1] || '0');
|
|
@@ -37,18 +41,20 @@ const DefaultDetailOptions: DetailOptions = { numberFormat: 'locale.0' };
|
|
|
37
41
|
|
|
38
42
|
class Evaluator
|
|
39
43
|
{
|
|
44
|
+
options: DetailOptions;
|
|
40
45
|
expr: string;
|
|
41
46
|
_error: boolean;
|
|
42
47
|
|
|
43
|
-
constructor(expr: string)
|
|
48
|
+
constructor(expr: string, options?: DetailOptions)
|
|
44
49
|
{
|
|
50
|
+
this.options = Util.shallowAssignImmutable(DefaultDetailOptions, options);
|
|
45
51
|
this.expr = expr;
|
|
46
52
|
this._error = false;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
get error(): boolean { return this._error }
|
|
50
56
|
|
|
51
|
-
eval(o: any):
|
|
57
|
+
eval(o: any): any
|
|
52
58
|
{
|
|
53
59
|
this._error = false;
|
|
54
60
|
try
|
|
@@ -65,17 +71,25 @@ class Evaluator
|
|
|
65
71
|
let values = Object.values(o);
|
|
66
72
|
let safeexpr = this.expr;
|
|
67
73
|
let safenames = names.map(n => namemap[n]);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
|
|
75
|
+
// Replace valid identifiers with safe version
|
|
76
|
+
safeexpr = safeexpr.replace(reIdentifierOrString,
|
|
77
|
+
(match) => {
|
|
78
|
+
if (namemap[match])
|
|
79
|
+
return namemap[match];
|
|
80
|
+
else if (match === '__format' || reString.test(match))
|
|
81
|
+
return match;
|
|
82
|
+
else
|
|
83
|
+
{
|
|
84
|
+
this._error = true;
|
|
85
|
+
return 'invalid';
|
|
86
|
+
}
|
|
87
|
+
});
|
|
74
88
|
|
|
75
89
|
// Remove any identifiers that aren't the simple parameters to prevent out-of-sandbox execution
|
|
76
|
-
safeexpr = safeexpr.replace(
|
|
90
|
+
safeexpr = safeexpr.replace(reIdentifierOrString,
|
|
77
91
|
(match) => {
|
|
78
|
-
let valid = reParam.test(match);
|
|
92
|
+
let valid = reParam.test(match) || match === '__format' || reString.test(match);
|
|
79
93
|
if (valid)
|
|
80
94
|
return match;
|
|
81
95
|
else
|
|
@@ -84,6 +98,9 @@ class Evaluator
|
|
|
84
98
|
return 'invalid';
|
|
85
99
|
}
|
|
86
100
|
});
|
|
101
|
+
let __format = (n: number) => { return formatNumber(n, this.options.numberFormat) };
|
|
102
|
+
safenames.push('__format');
|
|
103
|
+
values.push(__format);
|
|
87
104
|
|
|
88
105
|
// Create a new function that accepts the variables as parameters
|
|
89
106
|
// and evaluates the expression
|
|
@@ -91,7 +108,7 @@ class Evaluator
|
|
|
91
108
|
|
|
92
109
|
// Call the function with the variable values
|
|
93
110
|
let r = func(...values);
|
|
94
|
-
return isNaN(r) ? 0 : r;
|
|
111
|
+
return typeof r === 'string' ? r : ((typeof r !== 'number' || isNaN(r)) ? 0 : r);
|
|
95
112
|
}
|
|
96
113
|
catch (err)
|
|
97
114
|
{
|
|
@@ -131,30 +148,8 @@ export class FormatDetail
|
|
|
131
148
|
let a = reExpr.exec(pattern);
|
|
132
149
|
if (a && a.length == 2)
|
|
133
150
|
{
|
|
134
|
-
this.items = [];
|
|
135
151
|
const expr = a[1];
|
|
136
|
-
|
|
137
|
-
let state = 'expr';
|
|
138
|
-
parse.forEach(subexpr => {
|
|
139
|
-
if (state === 'expr')
|
|
140
|
-
{
|
|
141
|
-
if (subexpr.length)
|
|
142
|
-
{
|
|
143
|
-
// Don't allow unsafe actions
|
|
144
|
-
if (reInvalidChars.test(subexpr))
|
|
145
|
-
this.items.push({ text: subexpr });
|
|
146
|
-
else
|
|
147
|
-
this.items.push({ expr: subexpr });
|
|
148
|
-
}
|
|
149
|
-
state = 'text';
|
|
150
|
-
}
|
|
151
|
-
else // state === 'text'
|
|
152
|
-
{
|
|
153
|
-
if (subexpr.length)
|
|
154
|
-
this.items.push({ text: subexpr });
|
|
155
|
-
state = 'expr';
|
|
156
|
-
}
|
|
157
|
-
});
|
|
152
|
+
this.items = [ { expr } ];
|
|
158
153
|
}
|
|
159
154
|
else
|
|
160
155
|
{
|
|
@@ -191,17 +186,20 @@ export class FormatDetail
|
|
|
191
186
|
this._error = true;
|
|
192
187
|
return { n: 0, v: '' };
|
|
193
188
|
}
|
|
194
|
-
let n:
|
|
189
|
+
let n: any = 0;
|
|
195
190
|
let av = this.items.map(di => {
|
|
196
191
|
if (di.text)
|
|
197
192
|
return di.text;
|
|
198
193
|
else
|
|
199
194
|
{
|
|
200
|
-
let e = new Evaluator(di.expr);
|
|
195
|
+
let e = new Evaluator(di.expr, this.options);
|
|
201
196
|
n = e.eval(o);
|
|
202
197
|
if (! this._error)
|
|
203
198
|
this._error = e.error;
|
|
204
|
-
|
|
199
|
+
if (typeof n === 'string')
|
|
200
|
+
return n;
|
|
201
|
+
else
|
|
202
|
+
return formatNumber(n, this.options.numberFormat);
|
|
205
203
|
}
|
|
206
204
|
});
|
|
207
205
|
return { n, v: av.join('') }
|
|
@@ -87,6 +87,26 @@ function tokIsUnary(tt: TokType): boolean
|
|
|
87
87
|
case TokType.And: return false;
|
|
88
88
|
case TokType.Or: return false;
|
|
89
89
|
case TokType.Colon: return false;
|
|
90
|
+
case TokType.GreaterThan: return false;
|
|
91
|
+
case TokType.GreaterThanEqual: return false;
|
|
92
|
+
case TokType.Equal: return false;
|
|
93
|
+
case TokType.LessThan: return false;
|
|
94
|
+
case TokType.LessThanEqual: return false;
|
|
95
|
+
case TokType.NotEqual: return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function tokIsArithmetic(tt: TokType): boolean
|
|
100
|
+
{
|
|
101
|
+
switch (tt)
|
|
102
|
+
{
|
|
103
|
+
case TokType.Text: return false;
|
|
104
|
+
case TokType.OpenParen: return false;
|
|
105
|
+
case TokType.CloseParen: return false;
|
|
106
|
+
case TokType.Not: return false;
|
|
107
|
+
case TokType.And: return false;
|
|
108
|
+
case TokType.Or: return false;
|
|
109
|
+
case TokType.Colon: return false;
|
|
90
110
|
// Nominally binary, but written as "prop: < 3" so appears as unary operator where test is comparative to prop value
|
|
91
111
|
case TokType.GreaterThan: return true;
|
|
92
112
|
case TokType.GreaterThanEqual: return true;
|
|
@@ -276,6 +296,8 @@ class Parser
|
|
|
276
296
|
this.clauses = clauses;
|
|
277
297
|
this.combineParenthetical();
|
|
278
298
|
this.convertOpToText();
|
|
299
|
+
this.combineArithmetic();
|
|
300
|
+
this.convertOpToText();
|
|
279
301
|
this.combineBinary(TokType.Colon);
|
|
280
302
|
this.convertOpToText();
|
|
281
303
|
this.combineUnary();
|
|
@@ -363,6 +385,23 @@ class Parser
|
|
|
363
385
|
}
|
|
364
386
|
}
|
|
365
387
|
|
|
388
|
+
combineArithmetic(): void
|
|
389
|
+
{
|
|
390
|
+
// go backwards to handle not not
|
|
391
|
+
for (let i: number = this.clauses.length-1; i >= 0; i--)
|
|
392
|
+
{
|
|
393
|
+
let c = this.clauses[i];
|
|
394
|
+
if (tokIsArithmetic(c.op.tt))
|
|
395
|
+
{
|
|
396
|
+
let argclause = (i < this.clauses.length-1) ? { op: c.op, operand1: this.clauses[i+1] } : undefined;
|
|
397
|
+
if (argclause)
|
|
398
|
+
this.clauses.splice(i, 2, argclause);
|
|
399
|
+
else
|
|
400
|
+
this.clauses.splice(i, 1);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
366
405
|
combineUnary(): void
|
|
367
406
|
{
|
|
368
407
|
// go backwards to handle not not
|