@itwin/core-quantity 4.8.0-dev.9 → 4.9.0-dev.2
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/CHANGELOG.md +36 -1
- package/lib/cjs/Constants.js.map +1 -1
- package/lib/cjs/Exception.js.map +1 -1
- package/lib/cjs/Formatter/Format.d.ts +3 -0
- package/lib/cjs/Formatter/Format.d.ts.map +1 -1
- package/lib/cjs/Formatter/Format.js +8 -0
- package/lib/cjs/Formatter/Format.js.map +1 -1
- package/lib/cjs/Formatter/FormatEnums.js.map +1 -1
- package/lib/cjs/Formatter/Formatter.js.map +1 -1
- package/lib/cjs/Formatter/FormatterSpec.js.map +1 -1
- package/lib/cjs/Formatter/Interfaces.d.ts +1 -0
- package/lib/cjs/Formatter/Interfaces.d.ts.map +1 -1
- package/lib/cjs/Formatter/Interfaces.js.map +1 -1
- package/lib/cjs/Interfaces.js.map +1 -1
- package/lib/cjs/Parser.d.ts +25 -4
- package/lib/cjs/Parser.d.ts.map +1 -1
- package/lib/cjs/Parser.js +184 -117
- package/lib/cjs/Parser.js.map +1 -1
- package/lib/cjs/ParserSpec.js.map +1 -1
- package/lib/cjs/Quantity.js.map +1 -1
- package/lib/cjs/Unit.js.map +1 -1
- package/lib/cjs/core-quantity.js.map +1 -1
- package/lib/esm/Constants.js.map +1 -1
- package/lib/esm/Exception.js.map +1 -1
- package/lib/esm/Formatter/Format.d.ts +3 -0
- package/lib/esm/Formatter/Format.d.ts.map +1 -1
- package/lib/esm/Formatter/Format.js +8 -0
- package/lib/esm/Formatter/Format.js.map +1 -1
- package/lib/esm/Formatter/FormatEnums.js.map +1 -1
- package/lib/esm/Formatter/Formatter.js.map +1 -1
- package/lib/esm/Formatter/FormatterSpec.js.map +1 -1
- package/lib/esm/Formatter/Interfaces.d.ts +1 -0
- package/lib/esm/Formatter/Interfaces.d.ts.map +1 -1
- package/lib/esm/Formatter/Interfaces.js.map +1 -1
- package/lib/esm/Interfaces.js.map +1 -1
- package/lib/esm/Parser.d.ts +25 -4
- package/lib/esm/Parser.d.ts.map +1 -1
- package/lib/esm/Parser.js +184 -117
- package/lib/esm/Parser.js.map +1 -1
- package/lib/esm/ParserSpec.js.map +1 -1
- package/lib/esm/Quantity.js.map +1 -1
- package/lib/esm/Unit.js.map +1 -1
- package/lib/esm/core-quantity.js.map +1 -1
- package/package.json +4 -4
package/lib/esm/Parser.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export declare enum ParseError {
|
|
|
13
13
|
UnitLabelSuppliedButNotMatched = 3,
|
|
14
14
|
UnknownUnit = 4,
|
|
15
15
|
UnableToConvertParseTokensToQuantity = 5,
|
|
16
|
-
InvalidParserSpec = 6
|
|
16
|
+
InvalidParserSpec = 6,
|
|
17
|
+
MathematicOperationFoundButIsNotAllowed = 7
|
|
17
18
|
}
|
|
18
19
|
/** Parse error result from [[Parser.parseToQuantityValue]] or [[Parser.parseToQuantityValue]].
|
|
19
20
|
* @beta
|
|
@@ -33,6 +34,10 @@ export interface ParsedQuantity {
|
|
|
33
34
|
/** The magnitude of the parsed quantity. */
|
|
34
35
|
value: number;
|
|
35
36
|
}
|
|
37
|
+
declare enum Operator {
|
|
38
|
+
addition = "+",
|
|
39
|
+
subtraction = "-"
|
|
40
|
+
}
|
|
36
41
|
/**
|
|
37
42
|
* Defines Results of parsing a string input by a user into its desired value type
|
|
38
43
|
* @beta
|
|
@@ -42,8 +47,9 @@ export type QuantityParseResult = ParsedQuantity | ParseQuantityError;
|
|
|
42
47
|
* @beta
|
|
43
48
|
*/
|
|
44
49
|
declare class ParseToken {
|
|
45
|
-
value: number | string;
|
|
46
|
-
|
|
50
|
+
value: number | string | Operator;
|
|
51
|
+
isOperator: boolean;
|
|
52
|
+
constructor(value: string | number | Operator);
|
|
47
53
|
get isString(): boolean;
|
|
48
54
|
get isNumber(): boolean;
|
|
49
55
|
}
|
|
@@ -62,7 +68,15 @@ export declare class Parser {
|
|
|
62
68
|
* @param quantitySpecification The quantity string to ba parsed.
|
|
63
69
|
*/
|
|
64
70
|
static parseQuantitySpecification(quantitySpecification: string, format: Format): ParseToken[];
|
|
71
|
+
private static isMathematicOperation;
|
|
65
72
|
private static lookupUnitByLabel;
|
|
73
|
+
/**
|
|
74
|
+
* Get the output unit and all the conversion specs required to parse a given list of tokens.
|
|
75
|
+
*/
|
|
76
|
+
private static getRequiredUnitsConversionsToParseTokens;
|
|
77
|
+
/**
|
|
78
|
+
* Get the units information asynchronously, then convert the tokens into quantity using the synchronous tokens -> value.
|
|
79
|
+
*/
|
|
66
80
|
private static createQuantityFromParseTokens;
|
|
67
81
|
/** Async method to generate a Quantity given a string that represents a quantity value and likely a unit label.
|
|
68
82
|
* @param inString A string that contains text represent a quantity.
|
|
@@ -72,11 +86,18 @@ export declare class Parser {
|
|
|
72
86
|
static parseIntoQuantity(inString: string, format: Format, unitsProvider: UnitsProvider, altUnitLabelsProvider?: AlternateUnitLabelsProvider): Promise<QuantityProps>;
|
|
73
87
|
/** method to get the Unit Conversion given a unit label */
|
|
74
88
|
private static tryFindUnitConversion;
|
|
89
|
+
/**
|
|
90
|
+
* Get what the unit conversion is for a unitless value.
|
|
91
|
+
*/
|
|
92
|
+
private static getDefaultUnitConversion;
|
|
93
|
+
private static getNextTokenPair;
|
|
94
|
+
/**
|
|
95
|
+
* Accumulate the given list of tokens into a single quantity value. Formatting the tokens along the way.
|
|
96
|
+
*/
|
|
75
97
|
private static getQuantityValueFromParseTokens;
|
|
76
98
|
/** Method to generate a Quantity given a string that represents a quantity value.
|
|
77
99
|
* @param inString A string that contains text represent a quantity.
|
|
78
100
|
* @param parserSpec unit label if not explicitly defined by user. Must have matching entry in supplied array of unitsConversions.
|
|
79
|
-
* @param defaultValue default value to return if parsing is un successful
|
|
80
101
|
*/
|
|
81
102
|
static parseQuantityString(inString: string, parserSpec: ParserSpec): QuantityParseResult;
|
|
82
103
|
/** Method to generate a Quantity given a string that represents a quantity value and likely a unit label.
|
package/lib/esm/Parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Parser.d.ts","sourceRoot":"","sources":["../../src/Parser.ts"],"names":[],"mappings":"AAIA;;GAEG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,aAAa,EAAuB,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjK,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C;;GAEG;AACH,oBAAY,UAAU;IACpB,2BAA2B,IAAI;IAC/B,0BAA0B,IAAA;IAC1B,8BAA8B,IAAA;IAC9B,WAAW,IAAA;IACX,oCAAoC,IAAA;IACpC,iBAAiB,IAAA;
|
|
1
|
+
{"version":3,"file":"Parser.d.ts","sourceRoot":"","sources":["../../src/Parser.ts"],"names":[],"mappings":"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"}
|
package/lib/esm/Parser.js
CHANGED
|
@@ -19,18 +19,35 @@ export var ParseError;
|
|
|
19
19
|
ParseError[ParseError["UnknownUnit"] = 4] = "UnknownUnit";
|
|
20
20
|
ParseError[ParseError["UnableToConvertParseTokensToQuantity"] = 5] = "UnableToConvertParseTokensToQuantity";
|
|
21
21
|
ParseError[ParseError["InvalidParserSpec"] = 6] = "InvalidParserSpec";
|
|
22
|
+
ParseError[ParseError["MathematicOperationFoundButIsNotAllowed"] = 7] = "MathematicOperationFoundButIsNotAllowed";
|
|
22
23
|
})(ParseError || (ParseError = {}));
|
|
24
|
+
var Operator;
|
|
25
|
+
(function (Operator) {
|
|
26
|
+
Operator["addition"] = "+";
|
|
27
|
+
Operator["subtraction"] = "-";
|
|
28
|
+
})(Operator || (Operator = {}));
|
|
29
|
+
function isOperator(char) {
|
|
30
|
+
if (typeof char === "number") {
|
|
31
|
+
// Convert the charcode to string.
|
|
32
|
+
char = String.fromCharCode(char);
|
|
33
|
+
}
|
|
34
|
+
return Object.values(Operator).includes(char);
|
|
35
|
+
}
|
|
23
36
|
/** A ParseToken holds either a numeric or string token extracted from a string that represents a quantity value.
|
|
24
37
|
* @beta
|
|
25
38
|
*/
|
|
26
39
|
class ParseToken {
|
|
27
40
|
constructor(value) {
|
|
28
|
-
|
|
41
|
+
this.isOperator = false;
|
|
42
|
+
if (typeof value === "string") {
|
|
29
43
|
this.value = value.trim();
|
|
30
|
-
|
|
44
|
+
this.isOperator = isOperator(this.value);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
31
47
|
this.value = value;
|
|
48
|
+
}
|
|
32
49
|
}
|
|
33
|
-
get isString() { return typeof this.value === "string"; }
|
|
50
|
+
get isString() { return !this.isOperator && typeof this.value === "string"; }
|
|
34
51
|
get isNumber() { return typeof this.value === "number"; }
|
|
35
52
|
}
|
|
36
53
|
/** A ScientificToken holds an index and string representing the exponent.
|
|
@@ -137,6 +154,7 @@ export class Parser {
|
|
|
137
154
|
let processingNumber = false;
|
|
138
155
|
let wipToken = "";
|
|
139
156
|
let signToken = "";
|
|
157
|
+
let isStationSeparatorAdded = false;
|
|
140
158
|
let uomSeparatorToIgnore = 0;
|
|
141
159
|
let fractionDashCode = 0;
|
|
142
160
|
const skipCodes = [format.thousandSeparator.charCodeAt(0)];
|
|
@@ -222,9 +240,17 @@ export class Parser {
|
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
242
|
}
|
|
225
|
-
|
|
226
|
-
|
|
243
|
+
if (format.type === FormatType.Station && charCode === format.stationSeparator.charCodeAt(0)) {
|
|
244
|
+
if (!isStationSeparatorAdded) {
|
|
245
|
+
isStationSeparatorAdded = true;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
isStationSeparatorAdded = false;
|
|
249
|
+
}
|
|
250
|
+
else if (skipCodes.findIndex((ref) => ref === charCode) !== -1) {
|
|
251
|
+
// ignore any codes in skipCodes
|
|
227
252
|
continue;
|
|
253
|
+
}
|
|
228
254
|
if (signToken.length > 0) {
|
|
229
255
|
wipToken = signToken + wipToken;
|
|
230
256
|
signToken = "";
|
|
@@ -232,12 +258,24 @@ export class Parser {
|
|
|
232
258
|
tokens.push(new ParseToken(parseFloat(wipToken)));
|
|
233
259
|
wipToken = (i < str.length) ? str[i] : "";
|
|
234
260
|
processingNumber = false;
|
|
261
|
+
if (wipToken.length === 1 && isOperator(wipToken)) {
|
|
262
|
+
tokens.push(new ParseToken(wipToken)); // Push operator token.
|
|
263
|
+
wipToken = "";
|
|
264
|
+
}
|
|
235
265
|
}
|
|
236
266
|
else {
|
|
237
267
|
// not processing a number
|
|
238
|
-
if ((charCode
|
|
239
|
-
if (
|
|
240
|
-
|
|
268
|
+
if (isOperator(charCode)) {
|
|
269
|
+
if (wipToken.length > 0) {
|
|
270
|
+
// There is a token is progress, process it now, before adding the new operator token.
|
|
271
|
+
tokens.push(new ParseToken(wipToken));
|
|
272
|
+
wipToken = "";
|
|
273
|
+
}
|
|
274
|
+
tokens.push(new ParseToken(str[i])); // Push an Operator Token in the list.
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (wipToken.length === 0 && charCode === QuantityConstants.CHAR_SPACE) {
|
|
278
|
+
// Dont add space when the wip token is empty.
|
|
241
279
|
continue;
|
|
242
280
|
}
|
|
243
281
|
wipToken = wipToken.concat(str[i]);
|
|
@@ -258,6 +296,17 @@ export class Parser {
|
|
|
258
296
|
}
|
|
259
297
|
return tokens;
|
|
260
298
|
}
|
|
299
|
+
static isMathematicOperation(tokens) {
|
|
300
|
+
if (tokens.length > 1) {
|
|
301
|
+
// The loop starts at one because the first token can be a operator without it being maths. Ex: "-5FT"
|
|
302
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
303
|
+
if (tokens[i].isOperator)
|
|
304
|
+
// Operator found, it's a math operation.
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
261
310
|
static async lookupUnitByLabel(unitLabel, format, unitsProvider, altUnitLabelsProvider) {
|
|
262
311
|
const defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
|
|
263
312
|
const labelToFind = unitLabel.toLowerCase();
|
|
@@ -282,69 +331,53 @@ export class Parser {
|
|
|
282
331
|
foundUnit = await unitsProvider.findUnit(unitLabel, defaultUnit ? defaultUnit.phenomenon : undefined);
|
|
283
332
|
return foundUnit;
|
|
284
333
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Get the output unit and all the conversion specs required to parse a given list of tokens.
|
|
336
|
+
*/
|
|
337
|
+
static async getRequiredUnitsConversionsToParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider) {
|
|
338
|
+
let outUnit = (format.units && format.units.length > 0 ? format.units[0][0] : undefined);
|
|
339
|
+
const unitConversions = [];
|
|
340
|
+
const uniqueUnitLabels = [...new Set(tokens.filter((token) => token.isString).map((token) => token.value))];
|
|
341
|
+
for (const label of uniqueUnitLabels) {
|
|
342
|
+
const unitProps = await this.lookupUnitByLabel(label, format, unitsProvider, altUnitLabelsProvider);
|
|
343
|
+
if (!outUnit) {
|
|
344
|
+
// No default unit, assume that the first unit found is the desired output unit.
|
|
345
|
+
outUnit = unitProps;
|
|
291
346
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
347
|
+
let spec = unitConversions.find((specB) => specB.name === unitProps.name);
|
|
348
|
+
if (spec) {
|
|
349
|
+
// Already in the list, just add the label.
|
|
350
|
+
spec.parseLabels?.push(label.toLocaleLowerCase());
|
|
295
351
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return new Quantity(defaultUnit, tokens[0].value);
|
|
309
|
-
}
|
|
310
|
-
else if (defaultUnit) {
|
|
311
|
-
const conversion = await unitsProvider.getConversion(unit, defaultUnit);
|
|
312
|
-
const mag = ((tokens[0].value * conversion.factor)) + conversion.offset;
|
|
313
|
-
return new Quantity(defaultUnit, mag);
|
|
352
|
+
else {
|
|
353
|
+
// Add new conversion to the list.
|
|
354
|
+
const conversion = await unitsProvider.getConversion(unitProps, outUnit);
|
|
355
|
+
if (conversion) {
|
|
356
|
+
spec = {
|
|
357
|
+
conversion,
|
|
358
|
+
label: unitProps.label,
|
|
359
|
+
system: unitProps.system,
|
|
360
|
+
name: unitProps.name,
|
|
361
|
+
parseLabels: [label.toLocaleLowerCase()],
|
|
362
|
+
};
|
|
363
|
+
unitConversions.push(spec);
|
|
314
364
|
}
|
|
315
365
|
}
|
|
316
366
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
mag = value;
|
|
329
|
-
else {
|
|
330
|
-
const conversion = await unitsProvider.getConversion(unit, defaultUnit);
|
|
331
|
-
mag = ((value * conversion.factor)) + conversion.offset;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
if (defaultUnit) {
|
|
336
|
-
const conversion = await unitsProvider.getConversion(unit, defaultUnit);
|
|
337
|
-
if (mag < 0.0)
|
|
338
|
-
mag = mag - ((value * conversion.factor)) + conversion.offset;
|
|
339
|
-
else
|
|
340
|
-
mag = mag + ((value * conversion.factor)) + conversion.offset;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
367
|
+
return { outUnit, specs: unitConversions };
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get the units information asynchronously, then convert the tokens into quantity using the synchronous tokens -> value.
|
|
371
|
+
*/
|
|
372
|
+
static async createQuantityFromParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider) {
|
|
373
|
+
const unitConversionInfos = await this.getRequiredUnitsConversionsToParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider);
|
|
374
|
+
if (unitConversionInfos.outUnit) {
|
|
375
|
+
const value = Parser.getQuantityValueFromParseTokens(tokens, format, unitConversionInfos.specs, await unitsProvider.getConversion(unitConversionInfos.outUnit, unitConversionInfos.outUnit));
|
|
376
|
+
if (value.ok) {
|
|
377
|
+
return new Quantity(unitConversionInfos.outUnit, value.value);
|
|
344
378
|
}
|
|
345
|
-
return new Quantity(defaultUnit, mag);
|
|
346
379
|
}
|
|
347
|
-
return new Quantity(
|
|
380
|
+
return new Quantity();
|
|
348
381
|
}
|
|
349
382
|
/** Async method to generate a Quantity given a string that represents a quantity value and likely a unit label.
|
|
350
383
|
* @param inString A string that contains text represent a quantity.
|
|
@@ -353,7 +386,7 @@ export class Parser {
|
|
|
353
386
|
*/
|
|
354
387
|
static async parseIntoQuantity(inString, format, unitsProvider, altUnitLabelsProvider) {
|
|
355
388
|
const tokens = Parser.parseQuantitySpecification(inString, format);
|
|
356
|
-
if (tokens.length === 0)
|
|
389
|
+
if (tokens.length === 0 || (!format.allowMathematicOperations && Parser.isMathematicOperation(tokens)))
|
|
357
390
|
return new Quantity();
|
|
358
391
|
return Parser.createQuantityFromParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider);
|
|
359
392
|
}
|
|
@@ -396,75 +429,106 @@ export class Parser {
|
|
|
396
429
|
}
|
|
397
430
|
return undefined;
|
|
398
431
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Get what the unit conversion is for a unitless value.
|
|
434
|
+
*/
|
|
435
|
+
static getDefaultUnitConversion(tokens, unitsConversions, defaultUnit) {
|
|
436
|
+
let unitConversion = defaultUnit ? Parser.tryFindUnitConversion(defaultUnit.label, unitsConversions, defaultUnit) : undefined;
|
|
437
|
+
if (!unitConversion) {
|
|
438
|
+
// No default unit conversion, take the first valid unit.
|
|
439
|
+
const uniqueUnitLabels = [...new Set(tokens.filter((token) => token.isString).map((token) => token.value))];
|
|
440
|
+
for (const label of uniqueUnitLabels) {
|
|
441
|
+
unitConversion = Parser.tryFindUnitConversion(label, unitsConversions, defaultUnit);
|
|
442
|
+
if (unitConversion !== undefined)
|
|
443
|
+
return unitConversion;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return unitConversion;
|
|
447
|
+
}
|
|
448
|
+
// Get the next token pair to parse into a quantity.
|
|
449
|
+
static getNextTokenPair(index, tokens) {
|
|
450
|
+
if (index >= tokens.length)
|
|
451
|
+
return;
|
|
452
|
+
// 6 possible combination of token pair.
|
|
453
|
+
// Stringified to ease comparison later.
|
|
454
|
+
const validCombinations = [
|
|
455
|
+
JSON.stringify(["string"]), // ['FT']
|
|
456
|
+
JSON.stringify(["string", "number"]), // ['$', 5] unit specification comes before value (like currency)
|
|
457
|
+
JSON.stringify(["number"]), // [5]
|
|
458
|
+
JSON.stringify(["number", "string"]), // [5, 'FT']
|
|
459
|
+
JSON.stringify(["operator", "number"]), // ['-', 5]
|
|
460
|
+
JSON.stringify(["operator", "number", "string"]), // ['-', 5, 'FT']
|
|
461
|
+
];
|
|
462
|
+
// Push up to 3 tokens in the list, if the length allows it.
|
|
463
|
+
const maxNbrTokensInThePair = Math.min(tokens.length - index, 3);
|
|
464
|
+
const tokenPair = tokens.slice(index, index + maxNbrTokensInThePair);
|
|
465
|
+
const currentCombination = tokenPair.map((token) => token.isOperator ? "operator" : (token.isNumber ? "number" : "string"));
|
|
466
|
+
// Check if the token pair is valid. If not, try again by removing the last token util empty.
|
|
467
|
+
// Ex: ['5', 'FT', '7'] invalid => ['5', 'FT'] valid returned
|
|
468
|
+
for (let i = currentCombination.length - 1; i >= 0; i--) {
|
|
469
|
+
if (validCombinations.includes(JSON.stringify(currentCombination))) {
|
|
470
|
+
break;
|
|
415
471
|
}
|
|
416
472
|
else {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (undefined !== conversion)
|
|
420
|
-
return { ok: true, value: conversion.factor + conversion.offset };
|
|
421
|
-
else
|
|
422
|
-
return { ok: false, error: ParseError.NoValueOrUnitFoundInString };
|
|
473
|
+
currentCombination.pop();
|
|
474
|
+
tokenPair.pop();
|
|
423
475
|
}
|
|
424
476
|
}
|
|
425
|
-
|
|
426
|
-
|
|
477
|
+
return tokenPair.length > 0 ? tokenPair : undefined;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Accumulate the given list of tokens into a single quantity value. Formatting the tokens along the way.
|
|
481
|
+
*/
|
|
482
|
+
static getQuantityValueFromParseTokens(tokens, format, unitsConversions, defaultUnitConversion) {
|
|
483
|
+
const defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
|
|
484
|
+
defaultUnitConversion = defaultUnitConversion ? defaultUnitConversion : Parser.getDefaultUnitConversion(tokens, unitsConversions, defaultUnit);
|
|
485
|
+
let tokenPair;
|
|
486
|
+
let increment = 1;
|
|
487
|
+
let mag = 0.0;
|
|
488
|
+
// The sign is saved outside from the loop for cases like this. '-1m 50cm 10mm + 2m 30cm 40mm' => -1.51m + 2.34m
|
|
489
|
+
let sign = 1;
|
|
490
|
+
for (let i = 0; i < tokens.length; i = i + increment) {
|
|
491
|
+
tokenPair = this.getNextTokenPair(i, tokens);
|
|
492
|
+
if (!tokenPair || tokenPair.length === 0) {
|
|
493
|
+
return { ok: false, error: ParseError.UnableToConvertParseTokensToQuantity };
|
|
494
|
+
}
|
|
495
|
+
increment = tokenPair.length;
|
|
496
|
+
// Keep the sign so its applied to the next tokens.
|
|
497
|
+
if (tokenPair[0].isOperator) {
|
|
498
|
+
sign = tokenPair[0].value === Operator.addition ? 1 : -1;
|
|
499
|
+
tokenPair.shift();
|
|
500
|
+
}
|
|
427
501
|
// unit specification comes before value (like currency)
|
|
428
|
-
if (
|
|
429
|
-
|
|
502
|
+
if (tokenPair.length === 2 && tokenPair[0].isString) {
|
|
503
|
+
// Invert it so the currency sign comes second.
|
|
504
|
+
tokenPair = [tokenPair[1], tokenPair[0]];
|
|
430
505
|
}
|
|
431
|
-
if (
|
|
432
|
-
let
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
conversion = Parser.tryFindUnitConversion(
|
|
506
|
+
if (tokenPair[0].isNumber) {
|
|
507
|
+
let value = sign * tokenPair[0].value;
|
|
508
|
+
let conversion;
|
|
509
|
+
if (tokenPair.length === 2 && tokenPair[1].isString) {
|
|
510
|
+
conversion = Parser.tryFindUnitConversion(tokenPair[1].value, unitsConversions, defaultUnit);
|
|
436
511
|
}
|
|
512
|
+
conversion = conversion ? conversion : defaultUnitConversion;
|
|
437
513
|
if (conversion) {
|
|
438
|
-
|
|
439
|
-
return { ok: true, value };
|
|
514
|
+
value = (value * conversion.factor) + conversion.offset;
|
|
440
515
|
}
|
|
441
|
-
|
|
442
|
-
return { ok: true, value: tokens[0].value };
|
|
516
|
+
mag = mag + value;
|
|
443
517
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const value = tokens[i].value;
|
|
451
|
-
const conversion = Parser.tryFindUnitConversion(tokens[i + 1].value, unitsConversions, defaultUnit);
|
|
452
|
-
if (conversion) {
|
|
453
|
-
if (mag < 0.0)
|
|
454
|
-
mag = mag - ((value * conversion.factor)) + conversion.offset;
|
|
455
|
-
else
|
|
456
|
-
mag = mag + ((value * conversion.factor)) + conversion.offset;
|
|
457
|
-
}
|
|
518
|
+
else {
|
|
519
|
+
// only the unit label was specified so assume magnitude of 0
|
|
520
|
+
const conversion = Parser.tryFindUnitConversion(tokenPair[0].value, unitsConversions, defaultUnit);
|
|
521
|
+
if (conversion === undefined) {
|
|
522
|
+
// Unknown unit label
|
|
523
|
+
return { ok: false, error: ParseError.NoValueOrUnitFoundInString };
|
|
458
524
|
}
|
|
459
525
|
}
|
|
460
|
-
return { ok: true, value: mag };
|
|
461
526
|
}
|
|
462
|
-
return { ok:
|
|
527
|
+
return { ok: true, value: mag };
|
|
463
528
|
}
|
|
464
529
|
/** Method to generate a Quantity given a string that represents a quantity value.
|
|
465
530
|
* @param inString A string that contains text represent a quantity.
|
|
466
531
|
* @param parserSpec unit label if not explicitly defined by user. Must have matching entry in supplied array of unitsConversions.
|
|
467
|
-
* @param defaultValue default value to return if parsing is un successful
|
|
468
532
|
*/
|
|
469
533
|
static parseQuantityString(inString, parserSpec) {
|
|
470
534
|
return Parser.parseToQuantityValue(inString, parserSpec.format, parserSpec.unitConversions);
|
|
@@ -490,6 +554,9 @@ export class Parser {
|
|
|
490
554
|
const tokens = Parser.parseQuantitySpecification(inString, format);
|
|
491
555
|
if (tokens.length === 0)
|
|
492
556
|
return { ok: false, error: ParseError.UnableToGenerateParseTokens };
|
|
557
|
+
if (!format.allowMathematicOperations && Parser.isMathematicOperation(tokens)) {
|
|
558
|
+
return { ok: false, error: ParseError.MathematicOperationFoundButIsNotAllowed };
|
|
559
|
+
}
|
|
493
560
|
if (Parser._log) {
|
|
494
561
|
// eslint-disable-next-line no-console
|
|
495
562
|
console.log(`Parse tokens`);
|