@dra2020/baseclient 1.0.129 → 1.0.131

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 @@
1
+ export * from './detail';
@@ -0,0 +1,17 @@
1
+ interface DetailItem {
2
+ expr?: string;
3
+ text?: string;
4
+ }
5
+ export interface DetailResult {
6
+ n: number;
7
+ v: string;
8
+ }
9
+ export declare class FormatDetail {
10
+ valid: boolean;
11
+ pattern: string;
12
+ items: DetailItem[];
13
+ constructor(pattern: string);
14
+ prepare(o: any): any;
15
+ format(o: any): DetailResult;
16
+ }
17
+ export {};
package/lib/all/all.ts CHANGED
@@ -27,3 +27,5 @@ import * as Colors from '../colors/colors';
27
27
  export { Colors };
28
28
  import * as DataFlow from '../dataflow/all';
29
29
  export { DataFlow }
30
+ import * as Detail from '../detail/all';
31
+ export { Detail }
@@ -0,0 +1 @@
1
+ export * from './detail';
@@ -0,0 +1,166 @@
1
+ //
2
+ // FormatDetail will take an expression that specifies a format detail lines.
3
+ // Given an object with a set of integer properties, it will evaluate the expression and produce
4
+ // a result { k: string, n: number, v: string } results for displaying the contents of the object.
5
+ //
6
+ // The formatting string is a statement of the form:
7
+ // =expr
8
+ // Expr can be an arithmetic expression using +-/*()?: as operators and variables are the field
9
+ // names of properties on the object passed in. The special field name _tot represents the
10
+ // total of all properties. The expression may also include double-quoted strings that are
11
+ // passed through (e.g. for use as labels = area" sqm")
12
+ //
13
+
14
+ import * as Util from '../util/all';
15
+
16
+ const reIdentifier = /\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g;
17
+ const reParam = /^__\d+$/
18
+
19
+ class Evaluator
20
+ {
21
+ expr: string;
22
+
23
+ constructor(expr: string)
24
+ {
25
+ this.expr = expr;
26
+ }
27
+
28
+ eval(o: any): number
29
+ {
30
+ try
31
+ {
32
+ // Convert property names to valid Javascript to ensure expression safety
33
+ o = Util.shallowCopy(o);
34
+ let namemap: any = {};
35
+ Object.keys(o).forEach((n: string, i: number) => {
36
+ namemap[n] = `__${i}`;
37
+ });
38
+
39
+ // Extract variable names and values from the object
40
+ let names = Object.keys(o);
41
+ let values = Object.values(o);
42
+ let safeexpr = this.expr;
43
+ let safenames = names.map(n => namemap[n]);
44
+ // replace longest field names first in case they contain substrings of short field names
45
+ names.sort((n1: string, n2: string) => n2.length - n1.length);
46
+ names.forEach((n: string, i: number) => {
47
+ while (safeexpr.indexOf(n) >= 0)
48
+ safeexpr = safeexpr.replace(n, namemap[n]);
49
+ });
50
+
51
+ // Remove any identifiers that aren't the simple parameters to prevent out-of-sandbox execution
52
+ safeexpr = safeexpr.replace(reIdentifier, (match) => { return reParam.test(match) ? match : "invalid" });
53
+
54
+ // Create a new function that accepts the variables as parameters
55
+ // and evaluates the expression
56
+ const func = new Function(...safenames, `return ${safeexpr};`);
57
+
58
+ // Call the function with the variable values
59
+ let r = func(...values);
60
+ return isNaN(r) ? 0 : r;
61
+ }
62
+ catch (err)
63
+ {
64
+ return 0;
65
+ }
66
+ }
67
+ }
68
+
69
+ interface DetailItem
70
+ {
71
+ expr?: string,
72
+ text?: string,
73
+ }
74
+
75
+ export interface DetailResult
76
+ {
77
+ n: number, // value before formatting
78
+ v: string, // value
79
+ }
80
+
81
+ const reInvalidChars = /[\.\[\]\\]/;
82
+ const reExpr = /^=(.*)$/
83
+
84
+ export class FormatDetail
85
+ {
86
+ valid: boolean;
87
+ pattern: string;
88
+ items: DetailItem[];
89
+
90
+ constructor(pattern: string)
91
+ {
92
+ this.valid = true;
93
+ this.pattern = pattern.trim();
94
+ let a = reExpr.exec(pattern);
95
+ if (a && a.length == 2)
96
+ {
97
+ this.items = [];
98
+ const expr = a[1];
99
+ const parse = expr.split('"');
100
+ let state = 'expr';
101
+ parse.forEach(subexpr => {
102
+ if (state === 'expr')
103
+ {
104
+ if (subexpr.length)
105
+ {
106
+ // Don't allow unsafe actions
107
+ if (reInvalidChars.test(subexpr))
108
+ this.items.push({ text: subexpr });
109
+ else
110
+ this.items.push({ expr: subexpr });
111
+ }
112
+ state = 'text';
113
+ }
114
+ else // state === 'text'
115
+ {
116
+ if (subexpr.length)
117
+ this.items.push({ text: subexpr });
118
+ state = 'expr';
119
+ }
120
+ });
121
+ }
122
+ else
123
+ {
124
+ this.valid = false;
125
+ this.items = [ { text: 'invalid' } ];
126
+ }
127
+ }
128
+
129
+ prepare(o: any): any
130
+ {
131
+ if (o)
132
+ {
133
+ // Make sure there is a total field
134
+ o = Util.deepCopy(o);
135
+ if (o['Tot'] !== undefined)
136
+ o['_tot'] = o['Tot'];
137
+ else
138
+ {
139
+ let t = 0;
140
+ Object.values(o).forEach((v: any) => {
141
+ if (!isNaN(v) && typeof v === 'number')
142
+ t += v;
143
+ });
144
+ o['_tot'] = t;
145
+ }
146
+ }
147
+ return o;
148
+ }
149
+
150
+ format(o: any): DetailResult
151
+ {
152
+ if (!o) return { n: 0, v: '' };
153
+ let n: number;
154
+ let av = this.items.map(di => {
155
+ if (di.text)
156
+ return di.text;
157
+ else
158
+ {
159
+ let e = new Evaluator(di.expr);
160
+ n = e.eval(o);
161
+ return Util.precisionRound(n, 0).toLocaleString();
162
+ }
163
+ });
164
+ return { n, v: av.join('') }
165
+ }
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/baseclient",
3
- "version": "1.0.129",
3
+ "version": "1.0.131",
4
4
  "description": "Utility functions for Javascript projects.",
5
5
  "main": "dist/baseclient.js",
6
6
  "types": "./dist/all/all.d.ts",