@itwin/core-quantity 4.0.0-dev.48 → 4.0.0-dev.50

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.
Files changed (79) hide show
  1. package/lib/cjs/Constants.d.ts +27 -27
  2. package/lib/cjs/Constants.js +52 -52
  3. package/lib/cjs/Constants.js.map +1 -1
  4. package/lib/cjs/Exception.d.ts +26 -26
  5. package/lib/cjs/Exception.js +38 -38
  6. package/lib/cjs/Exception.js.map +1 -1
  7. package/lib/cjs/Formatter/Format.d.ts +91 -91
  8. package/lib/cjs/Formatter/Format.js +328 -328
  9. package/lib/cjs/Formatter/Format.js.map +1 -1
  10. package/lib/cjs/Formatter/FormatEnums.d.ts +133 -133
  11. package/lib/cjs/Formatter/FormatEnums.js +318 -318
  12. package/lib/cjs/Formatter/FormatEnums.js.map +1 -1
  13. package/lib/cjs/Formatter/Formatter.d.ts +44 -44
  14. package/lib/cjs/Formatter/Formatter.js +371 -371
  15. package/lib/cjs/Formatter/Formatter.js.map +1 -1
  16. package/lib/cjs/Formatter/FormatterSpec.d.ts +39 -39
  17. package/lib/cjs/Formatter/FormatterSpec.js +101 -101
  18. package/lib/cjs/Formatter/FormatterSpec.js.map +1 -1
  19. package/lib/cjs/Formatter/Interfaces.d.ts +62 -62
  20. package/lib/cjs/Formatter/Interfaces.js +17 -17
  21. package/lib/cjs/Formatter/Interfaces.js.map +1 -1
  22. package/lib/cjs/Interfaces.d.ts +86 -86
  23. package/lib/cjs/Interfaces.js +9 -9
  24. package/lib/cjs/Interfaces.js.map +1 -1
  25. package/lib/cjs/Parser.d.ts +93 -93
  26. package/lib/cjs/Parser.js +592 -592
  27. package/lib/cjs/Parser.js.map +1 -1
  28. package/lib/cjs/ParserSpec.d.ts +34 -34
  29. package/lib/cjs/ParserSpec.js +47 -47
  30. package/lib/cjs/ParserSpec.js.map +1 -1
  31. package/lib/cjs/Quantity.d.ts +27 -27
  32. package/lib/cjs/Quantity.js +46 -46
  33. package/lib/cjs/Quantity.js.map +1 -1
  34. package/lib/cjs/Unit.d.ts +25 -25
  35. package/lib/cjs/Unit.js +44 -44
  36. package/lib/cjs/Unit.js.map +1 -1
  37. package/lib/cjs/core-quantity.d.ts +19 -19
  38. package/lib/cjs/core-quantity.js +35 -35
  39. package/lib/cjs/core-quantity.js.map +1 -1
  40. package/lib/esm/Constants.d.ts +27 -27
  41. package/lib/esm/Constants.js +48 -48
  42. package/lib/esm/Constants.js.map +1 -1
  43. package/lib/esm/Exception.d.ts +26 -26
  44. package/lib/esm/Exception.js +34 -34
  45. package/lib/esm/Exception.js.map +1 -1
  46. package/lib/esm/Formatter/Format.d.ts +91 -91
  47. package/lib/esm/Formatter/Format.js +323 -323
  48. package/lib/esm/Formatter/Format.js.map +1 -1
  49. package/lib/esm/Formatter/FormatEnums.d.ts +133 -133
  50. package/lib/esm/Formatter/FormatEnums.js +302 -302
  51. package/lib/esm/Formatter/FormatEnums.js.map +1 -1
  52. package/lib/esm/Formatter/Formatter.d.ts +44 -44
  53. package/lib/esm/Formatter/Formatter.js +367 -367
  54. package/lib/esm/Formatter/Formatter.js.map +1 -1
  55. package/lib/esm/Formatter/FormatterSpec.d.ts +39 -39
  56. package/lib/esm/Formatter/FormatterSpec.js +97 -97
  57. package/lib/esm/Formatter/FormatterSpec.js.map +1 -1
  58. package/lib/esm/Formatter/Interfaces.d.ts +62 -62
  59. package/lib/esm/Formatter/Interfaces.js +13 -13
  60. package/lib/esm/Formatter/Interfaces.js.map +1 -1
  61. package/lib/esm/Interfaces.d.ts +86 -86
  62. package/lib/esm/Interfaces.js +8 -8
  63. package/lib/esm/Interfaces.js.map +1 -1
  64. package/lib/esm/Parser.d.ts +93 -93
  65. package/lib/esm/Parser.js +588 -588
  66. package/lib/esm/Parser.js.map +1 -1
  67. package/lib/esm/ParserSpec.d.ts +34 -34
  68. package/lib/esm/ParserSpec.js +43 -43
  69. package/lib/esm/ParserSpec.js.map +1 -1
  70. package/lib/esm/Quantity.d.ts +27 -27
  71. package/lib/esm/Quantity.js +42 -42
  72. package/lib/esm/Quantity.js.map +1 -1
  73. package/lib/esm/Unit.d.ts +25 -25
  74. package/lib/esm/Unit.js +39 -39
  75. package/lib/esm/Unit.js.map +1 -1
  76. package/lib/esm/core-quantity.d.ts +19 -19
  77. package/lib/esm/core-quantity.js +23 -23
  78. package/lib/esm/core-quantity.js.map +1 -1
  79. package/package.json +4 -4
package/lib/cjs/Parser.js CHANGED
@@ -1,593 +1,593 @@
1
- "use strict";
2
- /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
- * See LICENSE.md in the project root for license terms and full copyright notice.
5
- *--------------------------------------------------------------------------------------------*/
6
- /** @packageDocumentation
7
- * @module Quantity
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.Parser = exports.ParseError = void 0;
11
- const Constants_1 = require("./Constants");
12
- const FormatEnums_1 = require("./Formatter/FormatEnums");
13
- const Quantity_1 = require("./Quantity");
14
- /** Possible parser errors
15
- * @beta
16
- */
17
- var ParseError;
18
- (function (ParseError) {
19
- ParseError[ParseError["UnableToGenerateParseTokens"] = 1] = "UnableToGenerateParseTokens";
20
- ParseError[ParseError["NoValueOrUnitFoundInString"] = 2] = "NoValueOrUnitFoundInString";
21
- ParseError[ParseError["UnitLabelSuppliedButNotMatched"] = 3] = "UnitLabelSuppliedButNotMatched";
22
- ParseError[ParseError["UnknownUnit"] = 4] = "UnknownUnit";
23
- ParseError[ParseError["UnableToConvertParseTokensToQuantity"] = 5] = "UnableToConvertParseTokensToQuantity";
24
- ParseError[ParseError["InvalidParserSpec"] = 6] = "InvalidParserSpec";
25
- })(ParseError = exports.ParseError || (exports.ParseError = {}));
26
- /** A ParseToken holds either a numeric or string token extracted from a string that represents a quantity value.
27
- * @beta
28
- */
29
- class ParseToken {
30
- constructor(value) {
31
- if (typeof value === "string")
32
- this.value = value.trim();
33
- else
34
- this.value = value;
35
- }
36
- get isString() { return typeof this.value === "string"; }
37
- get isNumber() { return typeof this.value === "number"; }
38
- }
39
- /** A ScientificToken holds an index and string representing the exponent.
40
- * @beta
41
- */
42
- class ScientificToken {
43
- constructor(index, exponent) {
44
- this.exponent = "";
45
- this.index = index;
46
- if (exponent)
47
- this.exponent = exponent;
48
- }
49
- }
50
- /** A FractionToken holds an index and the fraction value of numerator / denominator.
51
- * @beta
52
- */
53
- class FractionToken {
54
- constructor(index, fraction) {
55
- this.fraction = 0.0;
56
- this.exponent = "";
57
- this.index = index;
58
- if (fraction)
59
- this.fraction = fraction;
60
- }
61
- }
62
- /** A Parser class that is used to break a string that represents a quantity value into tokens.
63
- * @beta
64
- */
65
- class Parser {
66
- static isParsedQuantity(item) {
67
- return item.ok;
68
- }
69
- static isParseError(item) {
70
- return !item.ok;
71
- }
72
- static checkForScientificNotation(index, stringToParse, uomSeparatorToIgnore) {
73
- let exponentString = "";
74
- let i = index + 1;
75
- for (; i < stringToParse.length; i++) {
76
- const charCode = stringToParse.charCodeAt(i);
77
- if (Parser.isDigit(charCode) || ((charCode === Constants_1.QuantityConstants.CHAR_MINUS || charCode === Constants_1.QuantityConstants.CHAR_PLUS) && (i === (index + 1)))) {
78
- exponentString = exponentString.concat(stringToParse[i]);
79
- }
80
- else {
81
- i = uomSeparatorToIgnore === charCode ? i : i - 1;
82
- break;
83
- }
84
- }
85
- if (exponentString.length > 1 || ((exponentString.length === 1) && (exponentString.charCodeAt(0) !== Constants_1.QuantityConstants.CHAR_MINUS) && (exponentString.charCodeAt(0) !== Constants_1.QuantityConstants.CHAR_PLUS)))
86
- return new ScientificToken(i, exponentString);
87
- return new ScientificToken(index);
88
- }
89
- static checkForFractions(index, stringToParse, uomSeparatorToIgnore, numeratorStr) {
90
- let numeratorToken = "";
91
- let denominatorToken = "";
92
- let processingNumerator = true;
93
- let i = index;
94
- if (numeratorStr && numeratorStr.length > 0) {
95
- numeratorToken = numeratorStr;
96
- processingNumerator = false;
97
- }
98
- for (; i < stringToParse.length; i++) {
99
- const charCode = stringToParse.charCodeAt(i);
100
- if (Parser.isDigit(charCode)) {
101
- if (processingNumerator) {
102
- numeratorToken = numeratorToken.concat(stringToParse[i]);
103
- }
104
- else {
105
- denominatorToken = denominatorToken.concat(stringToParse[i]);
106
- }
107
- }
108
- else {
109
- if (processingNumerator && (charCode === Constants_1.QuantityConstants.CHAR_SLASH || charCode === Constants_1.QuantityConstants.CHAR_DIVISION_SLASH || charCode === Constants_1.QuantityConstants.CHAR_DIVISION_SLASH)) {
110
- processingNumerator = false;
111
- }
112
- else {
113
- if (uomSeparatorToIgnore !== charCode)
114
- i = i - 1; // skip over uom separator after fraction
115
- break;
116
- }
117
- }
118
- }
119
- if (numeratorToken.length > 0 && denominatorToken.length > 0) {
120
- const numerator = parseInt(numeratorToken, 10);
121
- const denominator = parseInt(denominatorToken, 10);
122
- if (denominator > 0)
123
- return new FractionToken(i, numerator / denominator);
124
- return new FractionToken(i);
125
- }
126
- return new FractionToken(index + 1);
127
- }
128
- static isDigit(charCode) {
129
- return (charCode >= Constants_1.QuantityConstants.CHAR_DIGIT_ZERO) && (charCode <= Constants_1.QuantityConstants.CHAR_DIGIT_NINE);
130
- }
131
- static isDigitOrDecimalSeparator(charCode, format) {
132
- return (charCode === format.decimalSeparator.charCodeAt(0)) || Parser.isDigit(charCode);
133
- }
134
- /** Parse the quantity string and return and array of ParseTokens that represent the component invariant values and unit labels.
135
- * @param quantitySpecification The quantity string to ba parsed.
136
- */
137
- static parseQuantitySpecification(quantitySpecification, format) {
138
- const tokens = [];
139
- const str = quantitySpecification.trim();
140
- let processingNumber = false;
141
- let wipToken = "";
142
- let signToken = "";
143
- let uomSeparatorToIgnore = 0;
144
- let fractionDashCode = 0;
145
- const skipCodes = [format.thousandSeparator.charCodeAt(0)];
146
- if (format.type === FormatEnums_1.FormatType.Station && format.stationSeparator && format.stationSeparator.length === 1)
147
- skipCodes.push(format.stationSeparator.charCodeAt(0));
148
- if (format.type === FormatEnums_1.FormatType.Fractional && format.hasFormatTraitSet(FormatEnums_1.FormatTraits.FractionDash)) {
149
- fractionDashCode = Constants_1.QuantityConstants.CHAR_MINUS;
150
- }
151
- if (format.uomSeparator && format.uomSeparator !== " " && format.uomSeparator.length === 1) {
152
- uomSeparatorToIgnore = format.uomSeparator.charCodeAt(0);
153
- skipCodes.push(uomSeparatorToIgnore);
154
- }
155
- // eslint-disable-next-line @typescript-eslint/prefer-for-of
156
- for (let i = 0; i < str.length; i++) {
157
- const charCode = str.charCodeAt(i);
158
- if (Parser.isDigitOrDecimalSeparator(charCode, format)) {
159
- if (!processingNumber) {
160
- if (wipToken.length > 0) {
161
- tokens.push(new ParseToken(wipToken));
162
- wipToken = "";
163
- }
164
- processingNumber = true;
165
- }
166
- wipToken = wipToken.concat(str[i]);
167
- }
168
- else {
169
- if (processingNumber) {
170
- if (charCode === Constants_1.QuantityConstants.CHAR_SLASH || charCode === Constants_1.QuantityConstants.CHAR_FRACTION_SLASH || charCode === Constants_1.QuantityConstants.CHAR_DIVISION_SLASH) {
171
- const fractSymbol = Parser.checkForFractions(i + 1, str, uomSeparatorToIgnore, wipToken);
172
- let fraction = fractSymbol.fraction;
173
- i = fractSymbol.index;
174
- if (fractSymbol.fraction !== 0.0) {
175
- wipToken = "";
176
- if (signToken.length > 0) {
177
- if (signToken === "-")
178
- fraction = 0 - fraction;
179
- signToken = "";
180
- }
181
- tokens.push(new ParseToken(fraction));
182
- processingNumber = false;
183
- continue;
184
- }
185
- }
186
- else {
187
- // a space may signify end of number or start of decimal
188
- if (charCode === Constants_1.QuantityConstants.CHAR_SPACE || charCode === fractionDashCode) {
189
- const fractSymbol = Parser.checkForFractions(i + 1, str, uomSeparatorToIgnore);
190
- let fraction = fractSymbol.fraction;
191
- if (fractSymbol.fraction !== 0.0) {
192
- i = fractSymbol.index;
193
- if (signToken.length > 0) {
194
- wipToken = signToken + wipToken;
195
- if (signToken === "-")
196
- fraction = 0 - fraction;
197
- signToken = "";
198
- }
199
- const valueWithFraction = parseFloat(wipToken) + fraction;
200
- tokens.push(new ParseToken(valueWithFraction));
201
- processingNumber = false;
202
- wipToken = "";
203
- }
204
- continue;
205
- }
206
- else {
207
- // an "E" or "e" may signify scientific notation
208
- if (charCode === Constants_1.QuantityConstants.CHAR_UPPER_E || charCode === Constants_1.QuantityConstants.CHAR_LOWER_E) {
209
- const exponentSymbol = Parser.checkForScientificNotation(i, str, uomSeparatorToIgnore);
210
- i = exponentSymbol.index;
211
- if (exponentSymbol.exponent && exponentSymbol.exponent.length > 0) {
212
- if (signToken.length > 0) {
213
- wipToken = signToken + wipToken;
214
- signToken = "";
215
- }
216
- wipToken = `${wipToken}e${exponentSymbol.exponent}`;
217
- const scientificValue = Number(wipToken);
218
- tokens.push(new ParseToken(scientificValue));
219
- processingNumber = false;
220
- wipToken = "";
221
- continue;
222
- }
223
- }
224
- }
225
- }
226
- // ignore any codes in skipCodes
227
- if (skipCodes.findIndex((ref) => ref === charCode) !== -1)
228
- continue;
229
- if (signToken.length > 0) {
230
- wipToken = signToken + wipToken;
231
- signToken = "";
232
- }
233
- tokens.push(new ParseToken(parseFloat(wipToken)));
234
- wipToken = (i < str.length) ? str[i] : "";
235
- processingNumber = false;
236
- }
237
- else {
238
- // not processing a number
239
- if ((charCode === Constants_1.QuantityConstants.CHAR_PLUS || charCode === Constants_1.QuantityConstants.CHAR_MINUS)) {
240
- if (0 === tokens.length) // sign token only needed for left most value
241
- signToken = str[i];
242
- continue;
243
- }
244
- wipToken = wipToken.concat(str[i]);
245
- }
246
- }
247
- }
248
- // handle case where end of input string is reached.
249
- if (wipToken.length > 0) {
250
- if (processingNumber) {
251
- if (signToken.length > 0) {
252
- wipToken = signToken + wipToken;
253
- }
254
- tokens.push(new ParseToken(parseFloat(wipToken)));
255
- }
256
- else {
257
- tokens.push(new ParseToken(wipToken));
258
- }
259
- }
260
- return tokens;
261
- }
262
- static async lookupUnitByLabel(unitLabel, format, unitsProvider, altUnitLabelsProvider) {
263
- const defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
264
- const labelToFind = unitLabel.toLowerCase();
265
- // First look in format for a label and matches
266
- if (format.units && format.units.length > 0) {
267
- const formatUnit = format.units.find(([unit, label]) => {
268
- if (label && label.toLowerCase() === labelToFind)
269
- return true;
270
- const alternateLabels = altUnitLabelsProvider?.getAlternateUnitLabels(unit);
271
- // check any alternate labels that may be defined for the Unit
272
- if (alternateLabels && alternateLabels.find((lbl) => lbl.toLowerCase() === labelToFind))
273
- return true;
274
- return false;
275
- });
276
- if (formatUnit)
277
- return formatUnit[0];
278
- }
279
- // now try to find a unit from the same family and system
280
- let foundUnit = await unitsProvider.findUnit(unitLabel, defaultUnit ? defaultUnit.phenomenon : undefined, defaultUnit ? defaultUnit.system : undefined);
281
- // if nothing found yet just limit to family
282
- if (!foundUnit.isValid && defaultUnit)
283
- foundUnit = await unitsProvider.findUnit(unitLabel, defaultUnit ? defaultUnit.phenomenon : undefined);
284
- return foundUnit;
285
- }
286
- static async createQuantityFromParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider) {
287
- let defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
288
- // common case where single value is supplied
289
- if (tokens.length === 1) {
290
- if (tokens[0].isNumber) {
291
- return new Quantity_1.Quantity(defaultUnit, tokens[0].value);
292
- }
293
- else {
294
- const unit = await this.lookupUnitByLabel(tokens[0].value, format, unitsProvider, altUnitLabelsProvider);
295
- return new Quantity_1.Quantity(unit);
296
- }
297
- }
298
- // common case where single value and single label are supplied
299
- if (tokens.length === 2) {
300
- if (tokens[0].isNumber && tokens[1].isString) {
301
- const unit = await this.lookupUnitByLabel(tokens[1].value, format, unitsProvider, altUnitLabelsProvider);
302
- if (undefined === defaultUnit)
303
- defaultUnit = unit;
304
- if (defaultUnit && defaultUnit.name === unit.name) {
305
- return new Quantity_1.Quantity(defaultUnit, tokens[0].value);
306
- }
307
- else if (defaultUnit) {
308
- const conversion = await unitsProvider.getConversion(unit, defaultUnit);
309
- const mag = ((tokens[0].value * conversion.factor)) + conversion.offset;
310
- return new Quantity_1.Quantity(defaultUnit, mag);
311
- }
312
- }
313
- else { // unit specification comes before value (like currency)
314
- if (tokens[1].isNumber && tokens[0].isString) {
315
- const unit = await this.lookupUnitByLabel(tokens[0].value, format, unitsProvider, altUnitLabelsProvider);
316
- if (undefined === defaultUnit)
317
- defaultUnit = unit;
318
- if (defaultUnit && defaultUnit.name === unit.name) {
319
- return new Quantity_1.Quantity(defaultUnit, tokens[1].value);
320
- }
321
- else if (defaultUnit) {
322
- const conversion = await unitsProvider.getConversion(unit, defaultUnit);
323
- const mag = ((tokens[1].value * conversion.factor)) + conversion.offset;
324
- return new Quantity_1.Quantity(defaultUnit, mag);
325
- }
326
- }
327
- }
328
- }
329
- // common case where there are multiple value/label pairs
330
- if (tokens.length % 2 === 0) {
331
- let mag = 0.0;
332
- for (let i = 0; i < tokens.length; i = i + 2) {
333
- if (tokens[i].isNumber && tokens[i + 1].isString) {
334
- const value = tokens[i].value;
335
- const unit = await this.lookupUnitByLabel(tokens[i + 1].value, format, unitsProvider, altUnitLabelsProvider);
336
- if (undefined === defaultUnit)
337
- defaultUnit = unit;
338
- if (0 === i) {
339
- if (defaultUnit.name === unit.name)
340
- mag = value;
341
- else {
342
- const conversion = await unitsProvider.getConversion(unit, defaultUnit);
343
- mag = ((value * conversion.factor)) + conversion.offset;
344
- }
345
- }
346
- else {
347
- if (defaultUnit) {
348
- const conversion = await unitsProvider.getConversion(unit, defaultUnit);
349
- if (mag < 0.0)
350
- mag = mag - ((value * conversion.factor)) + conversion.offset;
351
- else
352
- mag = mag + ((value * conversion.factor)) + conversion.offset;
353
- }
354
- }
355
- }
356
- }
357
- return new Quantity_1.Quantity(defaultUnit, mag);
358
- }
359
- return new Quantity_1.Quantity(defaultUnit);
360
- }
361
- /** Async method to generate a Quantity given a string that represents a quantity value and likely a unit label.
362
- * @param inString A string that contains text represent a quantity.
363
- * @param format Defines the likely format of inString.
364
- * @param unitsProvider required to look up units that may be specified in inString
365
- */
366
- static async parseIntoQuantity(inString, format, unitsProvider, altUnitLabelsProvider) {
367
- const tokens = Parser.parseQuantitySpecification(inString, format);
368
- if (tokens.length === 0)
369
- return new Quantity_1.Quantity();
370
- return Parser.createQuantityFromParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider);
371
- }
372
- /** method to get the Unit Conversion given a unit label */
373
- static tryFindUnitConversion(unitLabel, unitsConversions, preferredUnit) {
374
- if (unitsConversions.length > 0) {
375
- const label = unitLabel.toLocaleLowerCase();
376
- /* A preferred unit is used to target a unit if a unit label is used in more that one unit definition from the same unit family.
377
- * An example is if "ft" is used as the unitLabel and the preferredUnit is "SURVEY_FT" since that unit has an alternate label of "ft" the
378
- * conversion to "SURVEY_FT" is returned. If no preferredUnit is specified then the unit "FT" would likely to have been found first.
379
- * If "in" is the unit label and "SURVEY_FT" is the preferredUnit then conversion to "SURVEY_IN" would be returned.
380
- */
381
- if (preferredUnit) {
382
- // if there is a preferred unit defined see if unit label matched it or one of its alternates
383
- const preferredConversion = unitsConversions.find((conversion) => conversion.name === preferredUnit.name);
384
- if (preferredConversion && preferredConversion.parseLabels) {
385
- if (-1 !== preferredConversion.parseLabels.findIndex((lbl) => lbl === label))
386
- return preferredConversion.conversion;
387
- }
388
- // see if we can find a matching unitLabel in any unit within the same system as the preferred unit
389
- const preferredSystemConversions = unitsConversions.filter((conversion) => conversion.system === preferredUnit.system);
390
- for (const conversion of preferredSystemConversions) {
391
- if (conversion.parseLabels) {
392
- if (-1 !== conversion.parseLabels.findIndex((lbl) => lbl === label))
393
- return conversion.conversion;
394
- }
395
- }
396
- }
397
- // if no unit found based on preferredUnit see if an unit label matches
398
- for (const conversion of unitsConversions) {
399
- if (conversion.parseLabels) {
400
- if (-1 !== conversion.parseLabels.findIndex((lbl) => lbl === label))
401
- return conversion.conversion;
402
- }
403
- else {
404
- // eslint-disable-next-line no-console
405
- console.log("ERROR: Parser expects to find parseLabels array populate with all possible unit labels for the unit.");
406
- }
407
- }
408
- }
409
- return undefined;
410
- }
411
- static getQuantityValueFromParseTokens(tokens, format, unitsConversions) {
412
- const defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
413
- // common case where single value is supplied
414
- if (tokens.length === 1) {
415
- if (tokens[0].isNumber) {
416
- if (defaultUnit) {
417
- const conversion = Parser.tryFindUnitConversion(defaultUnit.label, unitsConversions, defaultUnit);
418
- if (conversion) {
419
- const value = tokens[0].value * conversion.factor + conversion.offset;
420
- return { ok: true, value };
421
- }
422
- }
423
- else {
424
- // if no conversion or no defaultUnit, just return parsed number
425
- return { ok: true, value: tokens[0].value };
426
- }
427
- }
428
- else {
429
- // only the unit label was specified so assume magnitude of 1
430
- const conversion = Parser.tryFindUnitConversion(tokens[0].value, unitsConversions, defaultUnit);
431
- if (undefined !== conversion)
432
- return { ok: true, value: conversion.factor + conversion.offset };
433
- else
434
- return { ok: false, error: ParseError.NoValueOrUnitFoundInString };
435
- }
436
- }
437
- // common case where single value and single label are supplied
438
- if (tokens.length === 2) {
439
- if (tokens[0].isNumber && tokens[1].isString) {
440
- const conversion = Parser.tryFindUnitConversion(tokens[1].value, unitsConversions, defaultUnit);
441
- if (conversion) {
442
- const value = tokens[0].value * conversion.factor + conversion.offset;
443
- return { ok: true, value };
444
- }
445
- // if no conversion, just return parsed number and ignore value in second token
446
- return { ok: true, value: tokens[0].value };
447
- }
448
- else { // unit specification comes before value (like currency)
449
- if (tokens[1].isNumber && tokens[0].isString) {
450
- const conversion = Parser.tryFindUnitConversion(tokens[0].value, unitsConversions, defaultUnit);
451
- if (conversion) {
452
- const value = tokens[1].value * conversion.factor + conversion.offset;
453
- return { ok: true, value };
454
- }
455
- // if no conversion, just return parsed number and ignore value in second token
456
- return { ok: true, value: tokens[1].value };
457
- }
458
- }
459
- }
460
- // common case where there are multiple value/label pairs
461
- if (tokens.length % 2 === 0) {
462
- let mag = 0.0;
463
- for (let i = 0; i < tokens.length; i = i + 2) {
464
- if (tokens[i].isNumber && tokens[i + 1].isString) {
465
- const value = tokens[i].value;
466
- const conversion = Parser.tryFindUnitConversion(tokens[i + 1].value, unitsConversions, defaultUnit);
467
- if (conversion) {
468
- if (mag < 0.0)
469
- mag = mag - ((value * conversion.factor)) + conversion.offset;
470
- else
471
- mag = mag + ((value * conversion.factor)) + conversion.offset;
472
- }
473
- }
474
- }
475
- return { ok: true, value: mag };
476
- }
477
- return { ok: false, error: ParseError.UnableToConvertParseTokensToQuantity };
478
- }
479
- /** Method to generate a Quantity given a string that represents a quantity value.
480
- * @param inString A string that contains text represent a quantity.
481
- * @param parserSpec unit label if not explicitly defined by user. Must have matching entry in supplied array of unitsConversions.
482
- * @param defaultValue default value to return if parsing is un successful
483
- */
484
- static parseQuantityString(inString, parserSpec) {
485
- return Parser.parseToQuantityValue(inString, parserSpec.format, parserSpec.unitConversions);
486
- }
487
- /** Method to generate a Quantity given a string that represents a quantity value and likely a unit label.
488
- * @param inString A string that contains text represent a quantity.
489
- * @param format Defines the likely format of inString. Primary unit serves as a default unit if no unit label found in string.
490
- * @param unitsConversions dictionary of conversions used to convert from unit used in inString to output quantity
491
- */
492
- static parseToQuantityValue(inString, format, unitsConversions) {
493
- // ensure any labels defined in composite unit definition are specified in unitConversions
494
- if (format.units) {
495
- format.units.forEach(([unit, label]) => {
496
- if (label) {
497
- if (unit.label !== label) { // if default unit label does not match composite label ensure the label is in the list of parse labels for the conversion
498
- const unitConversion = unitsConversions.find((conversion) => conversion.name === unit.name);
499
- if (unitConversion && unitConversion.parseLabels && !unitConversion.parseLabels.find((entry) => entry === label))
500
- unitConversion.parseLabels.push(label);
501
- }
502
- }
503
- });
504
- }
505
- const tokens = Parser.parseQuantitySpecification(inString, format);
506
- if (tokens.length === 0)
507
- return { ok: false, error: ParseError.UnableToGenerateParseTokens };
508
- if (Parser._log) {
509
- // eslint-disable-next-line no-console
510
- console.log(`Parse tokens`);
511
- let i = 0;
512
- for (const token of tokens) {
513
- // eslint-disable-next-line no-console
514
- console.log(` [${i++}] isNumber=${token.isNumber} isString=${token.isString} token=${token.value}`);
515
- }
516
- }
517
- return Parser.getQuantityValueFromParseTokens(tokens, format, unitsConversions);
518
- }
519
- /** Async Method used to create an array of UnitConversionSpec entries that can be used in synchronous calls to parse units. */
520
- static async createUnitConversionSpecsForUnit(unitsProvider, outUnit, altUnitLabelsProvider) {
521
- const unitConversionSpecs = [];
522
- const familyUnits = await unitsProvider.getUnitsByFamily(outUnit.phenomenon);
523
- for (const unit of familyUnits) {
524
- const conversion = await unitsProvider.getConversion(unit, outUnit);
525
- const parseLabels = [unit.label.toLocaleLowerCase()];
526
- const alternateLabels = altUnitLabelsProvider?.getAlternateUnitLabels(unit);
527
- // add any alternate labels that may be defined for the Unit
528
- if (alternateLabels) {
529
- alternateLabels.forEach((label) => {
530
- const potentialLabel = label.toLocaleLowerCase();
531
- if (-1 === parseLabels.findIndex((lbl) => lbl === potentialLabel))
532
- parseLabels.push(label.toLocaleLowerCase());
533
- });
534
- }
535
- unitConversionSpecs.push({
536
- name: unit.name,
537
- label: unit.label,
538
- conversion,
539
- parseLabels,
540
- system: unit.system,
541
- });
542
- }
543
- return unitConversionSpecs;
544
- }
545
- /** Async Method used to create an array of UnitConversionSpec entries that can be used in synchronous calls to parse units. */
546
- static async createUnitConversionSpecs(unitsProvider, outUnitName, potentialParseUnits, altUnitLabelsProvider) {
547
- const unitConversionSpecs = [];
548
- const outUnit = await unitsProvider.findUnitByName(outUnitName);
549
- if (!outUnit || !outUnit.name || 0 === outUnit.name.length) {
550
- // eslint-disable-next-line no-console
551
- console.log(`[Parser.createUnitConversionSpecs] ERROR: Unable to locate out unit ${outUnitName}.`);
552
- return unitConversionSpecs;
553
- }
554
- for (const potentialParseUnit of potentialParseUnits) {
555
- const unit = await unitsProvider.findUnitByName(potentialParseUnit.unitName);
556
- if (!unit || !unit.name || 0 === unit.name.length) {
557
- // eslint-disable-next-line no-console
558
- console.log(`[Parser.createUnitConversionSpecs] ERROR: Unable to locate potential unit ${potentialParseUnit.unitName}.`);
559
- continue;
560
- }
561
- const conversion = await unitsProvider.getConversion(unit, outUnit);
562
- const parseLabels = [unit.label.toLocaleLowerCase()];
563
- const alternateLabels = altUnitLabelsProvider?.getAlternateUnitLabels(unit);
564
- // add any alternate labels that may be defined for the Unit
565
- if (alternateLabels) {
566
- alternateLabels.forEach((label) => {
567
- const potentialLabel = label.toLocaleLowerCase();
568
- if (-1 === parseLabels.findIndex((lbl) => lbl === potentialLabel))
569
- parseLabels.push(label.toLocaleLowerCase());
570
- });
571
- }
572
- // add any alternate labels that where provided by caller
573
- if (potentialParseUnit.altLabels) {
574
- potentialParseUnit.altLabels.forEach((label) => {
575
- const potentialLabel = label.toLocaleLowerCase();
576
- if (-1 === parseLabels.findIndex((lbl) => lbl === potentialLabel))
577
- parseLabels.push(label.toLocaleLowerCase());
578
- });
579
- }
580
- unitConversionSpecs.push({
581
- name: unit.name,
582
- label: unit.label,
583
- conversion,
584
- parseLabels,
585
- system: unit.system,
586
- });
587
- }
588
- return unitConversionSpecs;
589
- }
590
- }
591
- exports.Parser = Parser;
592
- Parser._log = false;
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ /** @packageDocumentation
7
+ * @module Quantity
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.Parser = exports.ParseError = void 0;
11
+ const Constants_1 = require("./Constants");
12
+ const FormatEnums_1 = require("./Formatter/FormatEnums");
13
+ const Quantity_1 = require("./Quantity");
14
+ /** Possible parser errors
15
+ * @beta
16
+ */
17
+ var ParseError;
18
+ (function (ParseError) {
19
+ ParseError[ParseError["UnableToGenerateParseTokens"] = 1] = "UnableToGenerateParseTokens";
20
+ ParseError[ParseError["NoValueOrUnitFoundInString"] = 2] = "NoValueOrUnitFoundInString";
21
+ ParseError[ParseError["UnitLabelSuppliedButNotMatched"] = 3] = "UnitLabelSuppliedButNotMatched";
22
+ ParseError[ParseError["UnknownUnit"] = 4] = "UnknownUnit";
23
+ ParseError[ParseError["UnableToConvertParseTokensToQuantity"] = 5] = "UnableToConvertParseTokensToQuantity";
24
+ ParseError[ParseError["InvalidParserSpec"] = 6] = "InvalidParserSpec";
25
+ })(ParseError = exports.ParseError || (exports.ParseError = {}));
26
+ /** A ParseToken holds either a numeric or string token extracted from a string that represents a quantity value.
27
+ * @beta
28
+ */
29
+ class ParseToken {
30
+ constructor(value) {
31
+ if (typeof value === "string")
32
+ this.value = value.trim();
33
+ else
34
+ this.value = value;
35
+ }
36
+ get isString() { return typeof this.value === "string"; }
37
+ get isNumber() { return typeof this.value === "number"; }
38
+ }
39
+ /** A ScientificToken holds an index and string representing the exponent.
40
+ * @beta
41
+ */
42
+ class ScientificToken {
43
+ constructor(index, exponent) {
44
+ this.exponent = "";
45
+ this.index = index;
46
+ if (exponent)
47
+ this.exponent = exponent;
48
+ }
49
+ }
50
+ /** A FractionToken holds an index and the fraction value of numerator / denominator.
51
+ * @beta
52
+ */
53
+ class FractionToken {
54
+ constructor(index, fraction) {
55
+ this.fraction = 0.0;
56
+ this.exponent = "";
57
+ this.index = index;
58
+ if (fraction)
59
+ this.fraction = fraction;
60
+ }
61
+ }
62
+ /** A Parser class that is used to break a string that represents a quantity value into tokens.
63
+ * @beta
64
+ */
65
+ class Parser {
66
+ static isParsedQuantity(item) {
67
+ return item.ok;
68
+ }
69
+ static isParseError(item) {
70
+ return !item.ok;
71
+ }
72
+ static checkForScientificNotation(index, stringToParse, uomSeparatorToIgnore) {
73
+ let exponentString = "";
74
+ let i = index + 1;
75
+ for (; i < stringToParse.length; i++) {
76
+ const charCode = stringToParse.charCodeAt(i);
77
+ if (Parser.isDigit(charCode) || ((charCode === Constants_1.QuantityConstants.CHAR_MINUS || charCode === Constants_1.QuantityConstants.CHAR_PLUS) && (i === (index + 1)))) {
78
+ exponentString = exponentString.concat(stringToParse[i]);
79
+ }
80
+ else {
81
+ i = uomSeparatorToIgnore === charCode ? i : i - 1;
82
+ break;
83
+ }
84
+ }
85
+ if (exponentString.length > 1 || ((exponentString.length === 1) && (exponentString.charCodeAt(0) !== Constants_1.QuantityConstants.CHAR_MINUS) && (exponentString.charCodeAt(0) !== Constants_1.QuantityConstants.CHAR_PLUS)))
86
+ return new ScientificToken(i, exponentString);
87
+ return new ScientificToken(index);
88
+ }
89
+ static checkForFractions(index, stringToParse, uomSeparatorToIgnore, numeratorStr) {
90
+ let numeratorToken = "";
91
+ let denominatorToken = "";
92
+ let processingNumerator = true;
93
+ let i = index;
94
+ if (numeratorStr && numeratorStr.length > 0) {
95
+ numeratorToken = numeratorStr;
96
+ processingNumerator = false;
97
+ }
98
+ for (; i < stringToParse.length; i++) {
99
+ const charCode = stringToParse.charCodeAt(i);
100
+ if (Parser.isDigit(charCode)) {
101
+ if (processingNumerator) {
102
+ numeratorToken = numeratorToken.concat(stringToParse[i]);
103
+ }
104
+ else {
105
+ denominatorToken = denominatorToken.concat(stringToParse[i]);
106
+ }
107
+ }
108
+ else {
109
+ if (processingNumerator && (charCode === Constants_1.QuantityConstants.CHAR_SLASH || charCode === Constants_1.QuantityConstants.CHAR_DIVISION_SLASH || charCode === Constants_1.QuantityConstants.CHAR_DIVISION_SLASH)) {
110
+ processingNumerator = false;
111
+ }
112
+ else {
113
+ if (uomSeparatorToIgnore !== charCode)
114
+ i = i - 1; // skip over uom separator after fraction
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ if (numeratorToken.length > 0 && denominatorToken.length > 0) {
120
+ const numerator = parseInt(numeratorToken, 10);
121
+ const denominator = parseInt(denominatorToken, 10);
122
+ if (denominator > 0)
123
+ return new FractionToken(i, numerator / denominator);
124
+ return new FractionToken(i);
125
+ }
126
+ return new FractionToken(index + 1);
127
+ }
128
+ static isDigit(charCode) {
129
+ return (charCode >= Constants_1.QuantityConstants.CHAR_DIGIT_ZERO) && (charCode <= Constants_1.QuantityConstants.CHAR_DIGIT_NINE);
130
+ }
131
+ static isDigitOrDecimalSeparator(charCode, format) {
132
+ return (charCode === format.decimalSeparator.charCodeAt(0)) || Parser.isDigit(charCode);
133
+ }
134
+ /** Parse the quantity string and return and array of ParseTokens that represent the component invariant values and unit labels.
135
+ * @param quantitySpecification The quantity string to ba parsed.
136
+ */
137
+ static parseQuantitySpecification(quantitySpecification, format) {
138
+ const tokens = [];
139
+ const str = quantitySpecification.trim();
140
+ let processingNumber = false;
141
+ let wipToken = "";
142
+ let signToken = "";
143
+ let uomSeparatorToIgnore = 0;
144
+ let fractionDashCode = 0;
145
+ const skipCodes = [format.thousandSeparator.charCodeAt(0)];
146
+ if (format.type === FormatEnums_1.FormatType.Station && format.stationSeparator && format.stationSeparator.length === 1)
147
+ skipCodes.push(format.stationSeparator.charCodeAt(0));
148
+ if (format.type === FormatEnums_1.FormatType.Fractional && format.hasFormatTraitSet(FormatEnums_1.FormatTraits.FractionDash)) {
149
+ fractionDashCode = Constants_1.QuantityConstants.CHAR_MINUS;
150
+ }
151
+ if (format.uomSeparator && format.uomSeparator !== " " && format.uomSeparator.length === 1) {
152
+ uomSeparatorToIgnore = format.uomSeparator.charCodeAt(0);
153
+ skipCodes.push(uomSeparatorToIgnore);
154
+ }
155
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
156
+ for (let i = 0; i < str.length; i++) {
157
+ const charCode = str.charCodeAt(i);
158
+ if (Parser.isDigitOrDecimalSeparator(charCode, format)) {
159
+ if (!processingNumber) {
160
+ if (wipToken.length > 0) {
161
+ tokens.push(new ParseToken(wipToken));
162
+ wipToken = "";
163
+ }
164
+ processingNumber = true;
165
+ }
166
+ wipToken = wipToken.concat(str[i]);
167
+ }
168
+ else {
169
+ if (processingNumber) {
170
+ if (charCode === Constants_1.QuantityConstants.CHAR_SLASH || charCode === Constants_1.QuantityConstants.CHAR_FRACTION_SLASH || charCode === Constants_1.QuantityConstants.CHAR_DIVISION_SLASH) {
171
+ const fractSymbol = Parser.checkForFractions(i + 1, str, uomSeparatorToIgnore, wipToken);
172
+ let fraction = fractSymbol.fraction;
173
+ i = fractSymbol.index;
174
+ if (fractSymbol.fraction !== 0.0) {
175
+ wipToken = "";
176
+ if (signToken.length > 0) {
177
+ if (signToken === "-")
178
+ fraction = 0 - fraction;
179
+ signToken = "";
180
+ }
181
+ tokens.push(new ParseToken(fraction));
182
+ processingNumber = false;
183
+ continue;
184
+ }
185
+ }
186
+ else {
187
+ // a space may signify end of number or start of decimal
188
+ if (charCode === Constants_1.QuantityConstants.CHAR_SPACE || charCode === fractionDashCode) {
189
+ const fractSymbol = Parser.checkForFractions(i + 1, str, uomSeparatorToIgnore);
190
+ let fraction = fractSymbol.fraction;
191
+ if (fractSymbol.fraction !== 0.0) {
192
+ i = fractSymbol.index;
193
+ if (signToken.length > 0) {
194
+ wipToken = signToken + wipToken;
195
+ if (signToken === "-")
196
+ fraction = 0 - fraction;
197
+ signToken = "";
198
+ }
199
+ const valueWithFraction = parseFloat(wipToken) + fraction;
200
+ tokens.push(new ParseToken(valueWithFraction));
201
+ processingNumber = false;
202
+ wipToken = "";
203
+ }
204
+ continue;
205
+ }
206
+ else {
207
+ // an "E" or "e" may signify scientific notation
208
+ if (charCode === Constants_1.QuantityConstants.CHAR_UPPER_E || charCode === Constants_1.QuantityConstants.CHAR_LOWER_E) {
209
+ const exponentSymbol = Parser.checkForScientificNotation(i, str, uomSeparatorToIgnore);
210
+ i = exponentSymbol.index;
211
+ if (exponentSymbol.exponent && exponentSymbol.exponent.length > 0) {
212
+ if (signToken.length > 0) {
213
+ wipToken = signToken + wipToken;
214
+ signToken = "";
215
+ }
216
+ wipToken = `${wipToken}e${exponentSymbol.exponent}`;
217
+ const scientificValue = Number(wipToken);
218
+ tokens.push(new ParseToken(scientificValue));
219
+ processingNumber = false;
220
+ wipToken = "";
221
+ continue;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ // ignore any codes in skipCodes
227
+ if (skipCodes.findIndex((ref) => ref === charCode) !== -1)
228
+ continue;
229
+ if (signToken.length > 0) {
230
+ wipToken = signToken + wipToken;
231
+ signToken = "";
232
+ }
233
+ tokens.push(new ParseToken(parseFloat(wipToken)));
234
+ wipToken = (i < str.length) ? str[i] : "";
235
+ processingNumber = false;
236
+ }
237
+ else {
238
+ // not processing a number
239
+ if ((charCode === Constants_1.QuantityConstants.CHAR_PLUS || charCode === Constants_1.QuantityConstants.CHAR_MINUS)) {
240
+ if (0 === tokens.length) // sign token only needed for left most value
241
+ signToken = str[i];
242
+ continue;
243
+ }
244
+ wipToken = wipToken.concat(str[i]);
245
+ }
246
+ }
247
+ }
248
+ // handle case where end of input string is reached.
249
+ if (wipToken.length > 0) {
250
+ if (processingNumber) {
251
+ if (signToken.length > 0) {
252
+ wipToken = signToken + wipToken;
253
+ }
254
+ tokens.push(new ParseToken(parseFloat(wipToken)));
255
+ }
256
+ else {
257
+ tokens.push(new ParseToken(wipToken));
258
+ }
259
+ }
260
+ return tokens;
261
+ }
262
+ static async lookupUnitByLabel(unitLabel, format, unitsProvider, altUnitLabelsProvider) {
263
+ const defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
264
+ const labelToFind = unitLabel.toLowerCase();
265
+ // First look in format for a label and matches
266
+ if (format.units && format.units.length > 0) {
267
+ const formatUnit = format.units.find(([unit, label]) => {
268
+ if (label && label.toLowerCase() === labelToFind)
269
+ return true;
270
+ const alternateLabels = altUnitLabelsProvider?.getAlternateUnitLabels(unit);
271
+ // check any alternate labels that may be defined for the Unit
272
+ if (alternateLabels && alternateLabels.find((lbl) => lbl.toLowerCase() === labelToFind))
273
+ return true;
274
+ return false;
275
+ });
276
+ if (formatUnit)
277
+ return formatUnit[0];
278
+ }
279
+ // now try to find a unit from the same family and system
280
+ let foundUnit = await unitsProvider.findUnit(unitLabel, defaultUnit ? defaultUnit.phenomenon : undefined, defaultUnit ? defaultUnit.system : undefined);
281
+ // if nothing found yet just limit to family
282
+ if (!foundUnit.isValid && defaultUnit)
283
+ foundUnit = await unitsProvider.findUnit(unitLabel, defaultUnit ? defaultUnit.phenomenon : undefined);
284
+ return foundUnit;
285
+ }
286
+ static async createQuantityFromParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider) {
287
+ let defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
288
+ // common case where single value is supplied
289
+ if (tokens.length === 1) {
290
+ if (tokens[0].isNumber) {
291
+ return new Quantity_1.Quantity(defaultUnit, tokens[0].value);
292
+ }
293
+ else {
294
+ const unit = await this.lookupUnitByLabel(tokens[0].value, format, unitsProvider, altUnitLabelsProvider);
295
+ return new Quantity_1.Quantity(unit);
296
+ }
297
+ }
298
+ // common case where single value and single label are supplied
299
+ if (tokens.length === 2) {
300
+ if (tokens[0].isNumber && tokens[1].isString) {
301
+ const unit = await this.lookupUnitByLabel(tokens[1].value, format, unitsProvider, altUnitLabelsProvider);
302
+ if (undefined === defaultUnit)
303
+ defaultUnit = unit;
304
+ if (defaultUnit && defaultUnit.name === unit.name) {
305
+ return new Quantity_1.Quantity(defaultUnit, tokens[0].value);
306
+ }
307
+ else if (defaultUnit) {
308
+ const conversion = await unitsProvider.getConversion(unit, defaultUnit);
309
+ const mag = ((tokens[0].value * conversion.factor)) + conversion.offset;
310
+ return new Quantity_1.Quantity(defaultUnit, mag);
311
+ }
312
+ }
313
+ else { // unit specification comes before value (like currency)
314
+ if (tokens[1].isNumber && tokens[0].isString) {
315
+ const unit = await this.lookupUnitByLabel(tokens[0].value, format, unitsProvider, altUnitLabelsProvider);
316
+ if (undefined === defaultUnit)
317
+ defaultUnit = unit;
318
+ if (defaultUnit && defaultUnit.name === unit.name) {
319
+ return new Quantity_1.Quantity(defaultUnit, tokens[1].value);
320
+ }
321
+ else if (defaultUnit) {
322
+ const conversion = await unitsProvider.getConversion(unit, defaultUnit);
323
+ const mag = ((tokens[1].value * conversion.factor)) + conversion.offset;
324
+ return new Quantity_1.Quantity(defaultUnit, mag);
325
+ }
326
+ }
327
+ }
328
+ }
329
+ // common case where there are multiple value/label pairs
330
+ if (tokens.length % 2 === 0) {
331
+ let mag = 0.0;
332
+ for (let i = 0; i < tokens.length; i = i + 2) {
333
+ if (tokens[i].isNumber && tokens[i + 1].isString) {
334
+ const value = tokens[i].value;
335
+ const unit = await this.lookupUnitByLabel(tokens[i + 1].value, format, unitsProvider, altUnitLabelsProvider);
336
+ if (undefined === defaultUnit)
337
+ defaultUnit = unit;
338
+ if (0 === i) {
339
+ if (defaultUnit.name === unit.name)
340
+ mag = value;
341
+ else {
342
+ const conversion = await unitsProvider.getConversion(unit, defaultUnit);
343
+ mag = ((value * conversion.factor)) + conversion.offset;
344
+ }
345
+ }
346
+ else {
347
+ if (defaultUnit) {
348
+ const conversion = await unitsProvider.getConversion(unit, defaultUnit);
349
+ if (mag < 0.0)
350
+ mag = mag - ((value * conversion.factor)) + conversion.offset;
351
+ else
352
+ mag = mag + ((value * conversion.factor)) + conversion.offset;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ return new Quantity_1.Quantity(defaultUnit, mag);
358
+ }
359
+ return new Quantity_1.Quantity(defaultUnit);
360
+ }
361
+ /** Async method to generate a Quantity given a string that represents a quantity value and likely a unit label.
362
+ * @param inString A string that contains text represent a quantity.
363
+ * @param format Defines the likely format of inString.
364
+ * @param unitsProvider required to look up units that may be specified in inString
365
+ */
366
+ static async parseIntoQuantity(inString, format, unitsProvider, altUnitLabelsProvider) {
367
+ const tokens = Parser.parseQuantitySpecification(inString, format);
368
+ if (tokens.length === 0)
369
+ return new Quantity_1.Quantity();
370
+ return Parser.createQuantityFromParseTokens(tokens, format, unitsProvider, altUnitLabelsProvider);
371
+ }
372
+ /** method to get the Unit Conversion given a unit label */
373
+ static tryFindUnitConversion(unitLabel, unitsConversions, preferredUnit) {
374
+ if (unitsConversions.length > 0) {
375
+ const label = unitLabel.toLocaleLowerCase();
376
+ /* A preferred unit is used to target a unit if a unit label is used in more that one unit definition from the same unit family.
377
+ * An example is if "ft" is used as the unitLabel and the preferredUnit is "SURVEY_FT" since that unit has an alternate label of "ft" the
378
+ * conversion to "SURVEY_FT" is returned. If no preferredUnit is specified then the unit "FT" would likely to have been found first.
379
+ * If "in" is the unit label and "SURVEY_FT" is the preferredUnit then conversion to "SURVEY_IN" would be returned.
380
+ */
381
+ if (preferredUnit) {
382
+ // if there is a preferred unit defined see if unit label matched it or one of its alternates
383
+ const preferredConversion = unitsConversions.find((conversion) => conversion.name === preferredUnit.name);
384
+ if (preferredConversion && preferredConversion.parseLabels) {
385
+ if (-1 !== preferredConversion.parseLabels.findIndex((lbl) => lbl === label))
386
+ return preferredConversion.conversion;
387
+ }
388
+ // see if we can find a matching unitLabel in any unit within the same system as the preferred unit
389
+ const preferredSystemConversions = unitsConversions.filter((conversion) => conversion.system === preferredUnit.system);
390
+ for (const conversion of preferredSystemConversions) {
391
+ if (conversion.parseLabels) {
392
+ if (-1 !== conversion.parseLabels.findIndex((lbl) => lbl === label))
393
+ return conversion.conversion;
394
+ }
395
+ }
396
+ }
397
+ // if no unit found based on preferredUnit see if an unit label matches
398
+ for (const conversion of unitsConversions) {
399
+ if (conversion.parseLabels) {
400
+ if (-1 !== conversion.parseLabels.findIndex((lbl) => lbl === label))
401
+ return conversion.conversion;
402
+ }
403
+ else {
404
+ // eslint-disable-next-line no-console
405
+ console.log("ERROR: Parser expects to find parseLabels array populate with all possible unit labels for the unit.");
406
+ }
407
+ }
408
+ }
409
+ return undefined;
410
+ }
411
+ static getQuantityValueFromParseTokens(tokens, format, unitsConversions) {
412
+ const defaultUnit = format.units && format.units.length > 0 ? format.units[0][0] : undefined;
413
+ // common case where single value is supplied
414
+ if (tokens.length === 1) {
415
+ if (tokens[0].isNumber) {
416
+ if (defaultUnit) {
417
+ const conversion = Parser.tryFindUnitConversion(defaultUnit.label, unitsConversions, defaultUnit);
418
+ if (conversion) {
419
+ const value = tokens[0].value * conversion.factor + conversion.offset;
420
+ return { ok: true, value };
421
+ }
422
+ }
423
+ else {
424
+ // if no conversion or no defaultUnit, just return parsed number
425
+ return { ok: true, value: tokens[0].value };
426
+ }
427
+ }
428
+ else {
429
+ // only the unit label was specified so assume magnitude of 1
430
+ const conversion = Parser.tryFindUnitConversion(tokens[0].value, unitsConversions, defaultUnit);
431
+ if (undefined !== conversion)
432
+ return { ok: true, value: conversion.factor + conversion.offset };
433
+ else
434
+ return { ok: false, error: ParseError.NoValueOrUnitFoundInString };
435
+ }
436
+ }
437
+ // common case where single value and single label are supplied
438
+ if (tokens.length === 2) {
439
+ if (tokens[0].isNumber && tokens[1].isString) {
440
+ const conversion = Parser.tryFindUnitConversion(tokens[1].value, unitsConversions, defaultUnit);
441
+ if (conversion) {
442
+ const value = tokens[0].value * conversion.factor + conversion.offset;
443
+ return { ok: true, value };
444
+ }
445
+ // if no conversion, just return parsed number and ignore value in second token
446
+ return { ok: true, value: tokens[0].value };
447
+ }
448
+ else { // unit specification comes before value (like currency)
449
+ if (tokens[1].isNumber && tokens[0].isString) {
450
+ const conversion = Parser.tryFindUnitConversion(tokens[0].value, unitsConversions, defaultUnit);
451
+ if (conversion) {
452
+ const value = tokens[1].value * conversion.factor + conversion.offset;
453
+ return { ok: true, value };
454
+ }
455
+ // if no conversion, just return parsed number and ignore value in second token
456
+ return { ok: true, value: tokens[1].value };
457
+ }
458
+ }
459
+ }
460
+ // common case where there are multiple value/label pairs
461
+ if (tokens.length % 2 === 0) {
462
+ let mag = 0.0;
463
+ for (let i = 0; i < tokens.length; i = i + 2) {
464
+ if (tokens[i].isNumber && tokens[i + 1].isString) {
465
+ const value = tokens[i].value;
466
+ const conversion = Parser.tryFindUnitConversion(tokens[i + 1].value, unitsConversions, defaultUnit);
467
+ if (conversion) {
468
+ if (mag < 0.0)
469
+ mag = mag - ((value * conversion.factor)) + conversion.offset;
470
+ else
471
+ mag = mag + ((value * conversion.factor)) + conversion.offset;
472
+ }
473
+ }
474
+ }
475
+ return { ok: true, value: mag };
476
+ }
477
+ return { ok: false, error: ParseError.UnableToConvertParseTokensToQuantity };
478
+ }
479
+ /** Method to generate a Quantity given a string that represents a quantity value.
480
+ * @param inString A string that contains text represent a quantity.
481
+ * @param parserSpec unit label if not explicitly defined by user. Must have matching entry in supplied array of unitsConversions.
482
+ * @param defaultValue default value to return if parsing is un successful
483
+ */
484
+ static parseQuantityString(inString, parserSpec) {
485
+ return Parser.parseToQuantityValue(inString, parserSpec.format, parserSpec.unitConversions);
486
+ }
487
+ /** Method to generate a Quantity given a string that represents a quantity value and likely a unit label.
488
+ * @param inString A string that contains text represent a quantity.
489
+ * @param format Defines the likely format of inString. Primary unit serves as a default unit if no unit label found in string.
490
+ * @param unitsConversions dictionary of conversions used to convert from unit used in inString to output quantity
491
+ */
492
+ static parseToQuantityValue(inString, format, unitsConversions) {
493
+ // ensure any labels defined in composite unit definition are specified in unitConversions
494
+ if (format.units) {
495
+ format.units.forEach(([unit, label]) => {
496
+ if (label) {
497
+ if (unit.label !== label) { // if default unit label does not match composite label ensure the label is in the list of parse labels for the conversion
498
+ const unitConversion = unitsConversions.find((conversion) => conversion.name === unit.name);
499
+ if (unitConversion && unitConversion.parseLabels && !unitConversion.parseLabels.find((entry) => entry === label))
500
+ unitConversion.parseLabels.push(label);
501
+ }
502
+ }
503
+ });
504
+ }
505
+ const tokens = Parser.parseQuantitySpecification(inString, format);
506
+ if (tokens.length === 0)
507
+ return { ok: false, error: ParseError.UnableToGenerateParseTokens };
508
+ if (Parser._log) {
509
+ // eslint-disable-next-line no-console
510
+ console.log(`Parse tokens`);
511
+ let i = 0;
512
+ for (const token of tokens) {
513
+ // eslint-disable-next-line no-console
514
+ console.log(` [${i++}] isNumber=${token.isNumber} isString=${token.isString} token=${token.value}`);
515
+ }
516
+ }
517
+ return Parser.getQuantityValueFromParseTokens(tokens, format, unitsConversions);
518
+ }
519
+ /** Async Method used to create an array of UnitConversionSpec entries that can be used in synchronous calls to parse units. */
520
+ static async createUnitConversionSpecsForUnit(unitsProvider, outUnit, altUnitLabelsProvider) {
521
+ const unitConversionSpecs = [];
522
+ const familyUnits = await unitsProvider.getUnitsByFamily(outUnit.phenomenon);
523
+ for (const unit of familyUnits) {
524
+ const conversion = await unitsProvider.getConversion(unit, outUnit);
525
+ const parseLabels = [unit.label.toLocaleLowerCase()];
526
+ const alternateLabels = altUnitLabelsProvider?.getAlternateUnitLabels(unit);
527
+ // add any alternate labels that may be defined for the Unit
528
+ if (alternateLabels) {
529
+ alternateLabels.forEach((label) => {
530
+ const potentialLabel = label.toLocaleLowerCase();
531
+ if (-1 === parseLabels.findIndex((lbl) => lbl === potentialLabel))
532
+ parseLabels.push(label.toLocaleLowerCase());
533
+ });
534
+ }
535
+ unitConversionSpecs.push({
536
+ name: unit.name,
537
+ label: unit.label,
538
+ conversion,
539
+ parseLabels,
540
+ system: unit.system,
541
+ });
542
+ }
543
+ return unitConversionSpecs;
544
+ }
545
+ /** Async Method used to create an array of UnitConversionSpec entries that can be used in synchronous calls to parse units. */
546
+ static async createUnitConversionSpecs(unitsProvider, outUnitName, potentialParseUnits, altUnitLabelsProvider) {
547
+ const unitConversionSpecs = [];
548
+ const outUnit = await unitsProvider.findUnitByName(outUnitName);
549
+ if (!outUnit || !outUnit.name || 0 === outUnit.name.length) {
550
+ // eslint-disable-next-line no-console
551
+ console.log(`[Parser.createUnitConversionSpecs] ERROR: Unable to locate out unit ${outUnitName}.`);
552
+ return unitConversionSpecs;
553
+ }
554
+ for (const potentialParseUnit of potentialParseUnits) {
555
+ const unit = await unitsProvider.findUnitByName(potentialParseUnit.unitName);
556
+ if (!unit || !unit.name || 0 === unit.name.length) {
557
+ // eslint-disable-next-line no-console
558
+ console.log(`[Parser.createUnitConversionSpecs] ERROR: Unable to locate potential unit ${potentialParseUnit.unitName}.`);
559
+ continue;
560
+ }
561
+ const conversion = await unitsProvider.getConversion(unit, outUnit);
562
+ const parseLabels = [unit.label.toLocaleLowerCase()];
563
+ const alternateLabels = altUnitLabelsProvider?.getAlternateUnitLabels(unit);
564
+ // add any alternate labels that may be defined for the Unit
565
+ if (alternateLabels) {
566
+ alternateLabels.forEach((label) => {
567
+ const potentialLabel = label.toLocaleLowerCase();
568
+ if (-1 === parseLabels.findIndex((lbl) => lbl === potentialLabel))
569
+ parseLabels.push(label.toLocaleLowerCase());
570
+ });
571
+ }
572
+ // add any alternate labels that where provided by caller
573
+ if (potentialParseUnit.altLabels) {
574
+ potentialParseUnit.altLabels.forEach((label) => {
575
+ const potentialLabel = label.toLocaleLowerCase();
576
+ if (-1 === parseLabels.findIndex((lbl) => lbl === potentialLabel))
577
+ parseLabels.push(label.toLocaleLowerCase());
578
+ });
579
+ }
580
+ unitConversionSpecs.push({
581
+ name: unit.name,
582
+ label: unit.label,
583
+ conversion,
584
+ parseLabels,
585
+ system: unit.system,
586
+ });
587
+ }
588
+ return unitConversionSpecs;
589
+ }
590
+ }
591
+ exports.Parser = Parser;
592
+ Parser._log = false;
593
593
  //# sourceMappingURL=Parser.js.map