@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.
@@ -45,6 +45,7 @@ declare class Parser {
45
45
  initFromClauses(clauses: Clause[]): void;
46
46
  convertOpToText(): void;
47
47
  combineParenthetical(): void;
48
+ combineArithmetic(): void;
48
49
  combineUnary(): void;
49
50
  combineColon(): void;
50
51
  combineBinary(tt: TokType): void;
@@ -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 reParam = /^__\d+$/
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): number
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
- // replace longest field names first in case they contain substrings of short field names
69
- names.sort((n1: string, n2: string) => n2.length - n1.length);
70
- names.forEach((n: string, i: number) => {
71
- while (safeexpr.indexOf(n) >= 0)
72
- safeexpr = safeexpr.replace(n, namemap[n]);
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(reIdentifier,
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
- const parse = expr.split('"');
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: number;
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
- return formatNumber(n, this.options.numberFormat);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/baseclient",
3
- "version": "1.0.142",
3
+ "version": "1.0.144",
4
4
  "description": "Utility functions for Javascript projects.",
5
5
  "main": "dist/baseclient.js",
6
6
  "types": "./dist/all/all.d.ts",