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