@lhncbc/ucum-lhc 4.1.8 → 5.0.0

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.
@@ -86,14 +86,14 @@ var Ucum = {
86
86
  * displaying messages on a web site; should be blank when output is
87
87
  * to a file.
88
88
  */
89
- openEmphHTML_: '<span class="emphSpan">',
89
+ openEmphHTML_: ' <span class="emphSpan">',
90
90
 
91
91
  /**
92
92
  * Closing HTML used to emphasize portions of error messages. Used when
93
93
  * displaying messages on a web site; should be blank when output is
94
94
  * to a file.
95
95
  */
96
- closeEmphHTML_: '</span>',
96
+ closeEmphHTML_: '</span> ',
97
97
 
98
98
  /**
99
99
  * Message that is displayed when annotations are included in a unit
@@ -1617,24 +1617,17 @@ var UcumLhcUtils = /*#__PURE__*/function () {
1617
1617
  if (valConv === undefined) valConv = 'validate';
1618
1618
  var resp = this.getSpecifiedUnit(uStr, valConv, suggest);
1619
1619
  var theUnit = resp['unit'];
1620
- var retObj = {};
1621
-
1622
- if (!theUnit) {
1623
- retObj = {
1624
- 'status': !resp['origString'] || resp['origString'] === null ? 'error' : 'invalid',
1625
- 'ucumCode': null
1626
- };
1627
- } else {
1628
- retObj = {
1629
- 'status': resp['origString'] === uStr ? 'valid' : 'invalid',
1630
- 'ucumCode': resp['origString'],
1631
- 'unit': {
1632
- 'code': theUnit.csCode_,
1633
- 'name': theUnit.name_,
1634
- 'guidance': theUnit.guidance_
1635
- }
1636
- };
1637
- }
1620
+ var retObj = !theUnit ? {
1621
+ 'ucumCode': null
1622
+ } : {
1623
+ 'ucumCode': resp['origString'],
1624
+ 'unit': {
1625
+ 'code': theUnit.csCode_,
1626
+ 'name': theUnit.name_,
1627
+ 'guidance': theUnit.guidance_
1628
+ }
1629
+ };
1630
+ retObj.status = resp.status;
1638
1631
 
1639
1632
  if (resp['suggestions']) {
1640
1633
  retObj['suggestions'] = resp['suggestions'];
@@ -1723,10 +1716,7 @@ var UcumLhcUtils = /*#__PURE__*/function () {
1723
1716
  returnObj['msg'].push('No "from" unit expression specified.');
1724
1717
  }
1725
1718
 
1726
- if (fromVal === null || isNaN(fromVal) || typeof fromVal !== 'number' && !intUtils_.isNumericString(fromVal)) {
1727
- returnObj['status'] = 'error';
1728
- returnObj['msg'].push('No "from" value, or an invalid "from" value, ' + 'was specified.');
1729
- }
1719
+ this._checkFromVal(fromVal, returnObj);
1730
1720
 
1731
1721
  if (toUnitCode) {
1732
1722
  toUnitCode = toUnitCode.trim();
@@ -1817,6 +1807,120 @@ var UcumLhcUtils = /*#__PURE__*/function () {
1817
1807
  return returnObj;
1818
1808
  } // end convertUnitTo
1819
1809
 
1810
+ /**
1811
+ * Converts the given unit string into its base units, their exponents, and
1812
+ * a magnitude, and returns that data.
1813
+ * @param fromUnit the unit string to be converted to base units information
1814
+ * @param fromVal the number of "from" units to be converted
1815
+ * @returns an object with the properties:
1816
+ * 'status' indicates whether the result succeeded. The value will be one of:
1817
+ * 'succeeded': the conversion was successfully calculated (which can be
1818
+ * true even if it was already in base units);
1819
+ * 'invalid': fromUnit is not a valid UCUM code;
1820
+ * 'failed': the conversion could not be made (e.g., if it is an "arbitrary" unit);
1821
+ * 'error': if an error occurred (an input or programming error)
1822
+ * 'msg': an array of one or more messages, if the string is invalid or
1823
+ * an error occurred, indicating the problem, or a suggestion of a
1824
+ * substitution such as the substitution of 'G' for 'Gauss', or
1825
+ * an empty array if no messages were generated. There can also be a
1826
+ * message that is just informational or warning.
1827
+ * 'magnitude': the new value when fromVal units of fromUnits is expressed in the base units.
1828
+ * 'fromUnitIsSpecial': whether the input unit fromUnit is a "special unit"
1829
+ * as defined in UCUM. This means there is some function applied to convert
1830
+ * between fromUnit and the base units, so the returned magnitude is likely not
1831
+ * useful as a scale factor for other conversions (i.e., it only has validity
1832
+ * and usefulness for the input values that produced it).
1833
+ * 'unitToExp': a map of base units in uStr to their exponent
1834
+ */
1835
+
1836
+ }, {
1837
+ key: "convertToBaseUnits",
1838
+ value: function convertToBaseUnits(fromUnit, fromVal) {
1839
+ var retObj = {};
1840
+
1841
+ this._checkFromVal(fromVal, retObj);
1842
+
1843
+ if (!retObj.status) {
1844
+ // could be set to 'error' by _checkFromVal
1845
+ var inputUnitLookup = this.getSpecifiedUnit(fromUnit, 'validate');
1846
+ retObj = {
1847
+ status: inputUnitLookup.status == 'valid' ? 'succeeded' : inputUnitLookup.status
1848
+ };
1849
+ var unit = inputUnitLookup.unit;
1850
+ retObj.msg = inputUnitLookup.retMsg || [];
1851
+
1852
+ if (!unit) {
1853
+ var _inputUnitLookup$retM;
1854
+
1855
+ if (((_inputUnitLookup$retM = inputUnitLookup.retMsg) === null || _inputUnitLookup$retM === void 0 ? void 0 : _inputUnitLookup$retM.length) == 0) retObj.msg.push('Could not find unit information for ' + fromUnit);
1856
+ } else if (unit.isArbitrary_) {
1857
+ retObj.msg.push('Arbitrary units cannot be converted to base units or other units.');
1858
+ retObj.status = 'failed';
1859
+ } else if (retObj.status == 'succeeded') {
1860
+ var _unit$dim_;
1861
+
1862
+ var unitToExp = {};
1863
+ var dimVec = (_unit$dim_ = unit.dim_) === null || _unit$dim_ === void 0 ? void 0 : _unit$dim_.dimVec_;
1864
+ var baseUnitString = '1';
1865
+
1866
+ if (dimVec) {
1867
+ var dimVecIndexToBaseUnit = UnitTables.getInstance().dimVecIndexToBaseUnit_;
1868
+
1869
+ for (var i = 0, len = dimVec.length; i < len; ++i) {
1870
+ var exp = dimVec[i];
1871
+
1872
+ if (exp) {
1873
+ unitToExp[dimVecIndexToBaseUnit[i]] = exp;
1874
+ baseUnitString += '.' + dimVecIndexToBaseUnit[i] + exp;
1875
+ }
1876
+ }
1877
+ } // The unit might have a conversion function, which has to be applied; we
1878
+ // cannot just assume unit_.magnitude_ is the magnitude in base units.
1879
+
1880
+
1881
+ var retUnitLookup = this.getSpecifiedUnit(baseUnitString, 'validate'); // There should not be any error in retUnitLookup, unless there is a bug.
1882
+
1883
+ var retUnit = retUnitLookup.unit;
1884
+
1885
+ if (retUnitLookup.status !== 'valid') {
1886
+ retObj.msg.push('Unable construct base unit string; tried ' + baseUnitString);
1887
+ retObj.status = 'error';
1888
+ } else {
1889
+ try {
1890
+ retObj.magnitude = retUnit.convertFrom(fromVal, unit);
1891
+ } catch (e) {
1892
+ retObj.msg.push(e.toString());
1893
+ retObj.status = 'error';
1894
+ }
1895
+
1896
+ if (retObj.status == 'succeeded') {
1897
+ retObj.unitToExp = unitToExp;
1898
+ retObj.fromUnitIsSpecial = unit.isSpecial_;
1899
+ }
1900
+ }
1901
+ }
1902
+ }
1903
+
1904
+ return retObj;
1905
+ }
1906
+ /**
1907
+ * Checks the given value as to whether it is suitable as a "from" value in a
1908
+ * unit conversion. If it is not, the responseObj will have its status set
1909
+ * to 'error' and a message added.
1910
+ * @param fromVal The value to check
1911
+ * @param responseObj the object that will be updated if the value is not
1912
+ * usable.
1913
+ */
1914
+
1915
+ }, {
1916
+ key: "_checkFromVal",
1917
+ value: function _checkFromVal(fromVal, responseObj) {
1918
+ if (fromVal === null || isNaN(fromVal) || typeof fromVal !== 'number' && !intUtils_.isNumericString(fromVal)) {
1919
+ responseObj.status = 'error';
1920
+ if (!responseObj.msg) responseObj.msg = [];
1921
+ responseObj.msg.push('No "from" value, or an invalid "from" value, ' + 'was specified.');
1922
+ }
1923
+ }
1820
1924
  /**
1821
1925
  * This method accepts a term and looks for units that include it as
1822
1926
  * a synonym - or that include the term in its name.
@@ -1862,13 +1966,18 @@ var UcumLhcUtils = /*#__PURE__*/function () {
1862
1966
  * true indicates suggestions are wanted; false indicates they are not,
1863
1967
  * and is the default if the parameter is not specified;
1864
1968
  * @returns a hash containing:
1969
+ * 'status' will be 'valid' (uName is a valid UCUM code), 'invalid'
1970
+ * (the uStr is not a valid UCUM code, and substitutions or
1971
+ * suggestions may or may not be returned, depending on what was
1972
+ * requested and found); or 'error' (an input or programming error
1973
+ * occurred);
1865
1974
  * 'unit' the unit object (or null if there were problems creating the
1866
1975
  * unit);
1867
1976
  * 'origString' the possibly updated unit string passed in;
1868
1977
  * 'retMsg' an array of user messages (informational, error or warning) if
1869
1978
  * any were generated (IF any were generated, otherwise will be an
1870
1979
  * empty array); and
1871
- * 'suggestions' is an array of 1 or more hash objects. Each hash
1980
+ * 'suggestions' is an array of 1 or more hash objects. Each hash
1872
1981
  * contains three elements:
1873
1982
  * 'msg' which is a message indicating what unit expression the
1874
1983
  * suggestions are for;
@@ -1915,8 +2024,18 @@ var UcumLhcUtils = /*#__PURE__*/function () {
1915
2024
  } // end if the unit was not found as a unit name
1916
2025
 
1917
2026
  } // end if a unit expression was specified
2027
+ // Set the status field
1918
2028
 
1919
2029
 
2030
+ if (!retObj.unit) {
2031
+ // No unit was found; check whether origString has a value
2032
+ retObj.status = !retObj.origString ? 'error' : 'invalid';
2033
+ } else {
2034
+ // Check whether substitutions were made to the unit string in order to
2035
+ // find the unit
2036
+ retObj.status = retObj.origString === uName ? 'valid' : 'invalid';
2037
+ }
2038
+
1920
2039
  return retObj;
1921
2040
  } // end getSpecifiedUnit
1922
2041
 
@@ -2419,8 +2538,8 @@ var Unit = /*#__PURE__*/function () {
2419
2538
  key: "convertFrom",
2420
2539
  value: function convertFrom(num, fromUnit) {
2421
2540
  var newNum = 0.0;
2422
- if (this.isArbitrary_) throw new Error("Attempt to convert arbitrary unit ".concat(this.name_));
2423
- if (fromUnit.isArbitrary_) throw new Error("Attempt to convert to arbitrary unit ".concat(fromUnit.name_)); // reject request if both units have dimensions that are not equal
2541
+ if (this.isArbitrary_) throw new Error("Attempt to convert to arbitrary unit \"".concat(this.csCode_, "\""));
2542
+ if (fromUnit.isArbitrary_) throw new Error("Attempt to convert arbitrary unit \"".concat(fromUnit.csCode_, "\"")); // reject request if both units have dimensions that are not equal
2424
2543
 
2425
2544
  if (fromUnit.dim_ && this.dim_ && !fromUnit.dim_.equals(this.dim_)) {
2426
2545
  // check first to see if a mole<->mass conversion is appropriate
@@ -2442,40 +2561,28 @@ var Unit = /*#__PURE__*/function () {
2442
2561
  }
2443
2562
 
2444
2563
  var fromCnv = fromUnit.cnv_;
2445
- var fromMag = fromUnit.magnitude_; // If the same conversion function is specified for both units, which
2446
- // includes neither unit having a conversion function, multiply the
2447
- // "from" unit's magnitude by the number passed in and then divide
2448
- // that result by this unit's magnitude. Do this for units with
2449
- // and without dimension vectors. PROBLEM with 2 non-commensurable
2450
- // units with no dimension vector or function, e.g., byte to mol
2451
-
2452
- if (fromCnv === this.cnv_) {
2453
- newNum = num * fromMag / this.magnitude_;
2454
- } // else use a function to get the number to be returned
2455
- else {
2456
- var x = 0.0;
2564
+ var fromMag = fromUnit.magnitude_;
2565
+ var x;
2457
2566
 
2458
- if (fromCnv != null) {
2459
- // turn num * fromUnit.magnitude into its ratio scale equivalent,
2460
- // e.g., convert Celsius to Kelvin
2461
- var fromFunc = _ucumFunctions.default.forName(fromCnv);
2567
+ if (fromCnv != null) {
2568
+ // turn num * fromUnit.magnitude into its ratio scale equivalent,
2569
+ // e.g., convert Celsius to Kelvin
2570
+ var fromFunc = _ucumFunctions.default.forName(fromCnv);
2462
2571
 
2463
- x = fromFunc.cnvFrom(num * fromUnit.cnvPfx_) * fromMag; //x = fromFunc.cnvFrom(num * fromMag) * fromUnit.cnvPfx_;
2464
- } else {
2465
- x = num * fromMag;
2466
- }
2467
-
2468
- if (this.cnv_ != null) {
2469
- // turn mag * origUnit on ratio scale into a non-ratio unit,
2470
- // e.g. convert Kelvin to Fahrenheit
2471
- var toFunc = _ucumFunctions.default.forName(this.cnv_);
2572
+ x = fromFunc.cnvFrom(num * fromUnit.cnvPfx_) * fromMag; //x = fromFunc.cnvFrom(num * fromMag) * fromUnit.cnvPfx_;
2573
+ } else {
2574
+ x = num * fromMag;
2575
+ }
2472
2576
 
2473
- newNum = toFunc.cnvTo(x / this.magnitude_) / this.cnvPfx_;
2474
- } else {
2475
- newNum = x / this.magnitude_;
2476
- }
2477
- } // end if either unit has a conversion function
2577
+ if (this.cnv_ != null) {
2578
+ // turn mag * origUnit on ratio scale into a non-ratio unit,
2579
+ // e.g. convert Kelvin to Fahrenheit
2580
+ var toFunc = _ucumFunctions.default.forName(this.cnv_);
2478
2581
 
2582
+ newNum = toFunc.cnvTo(x / this.magnitude_) / this.cnvPfx_;
2583
+ } else {
2584
+ newNum = x / this.magnitude_;
2585
+ }
2479
2586
 
2480
2587
  return newNum;
2481
2588
  } // end convertFrom
@@ -2690,6 +2797,7 @@ var Unit = /*#__PURE__*/function () {
2690
2797
  else if (unit2.cnv_ != null) {
2691
2798
  if (!retUnit.dim_ || retUnit.dim_.isZero()) {
2692
2799
  retUnit.cnvPfx_ = unit2.cnvPfx_ * retUnit.magnitude_;
2800
+ retUnit.magnitude_ = unit2.magnitude_;
2693
2801
  retUnit.cnv_ = unit2.cnv_;
2694
2802
  } else throw new Error("Attempt to multiply non-ratio unit ".concat(unit2.name_));
2695
2803
  } // end if unit2 has a conversion function
@@ -2723,7 +2831,9 @@ var Unit = /*#__PURE__*/function () {
2723
2831
  // if (!retUnit.isMole_)
2724
2832
  // retUnit.isMole_ = unit2.isMole_ ;
2725
2833
 
2726
- if (!retUnit.isArbitrary_) retUnit.isArbitrary_ = unit2.isArbitrary_;
2834
+ if (!retUnit.isArbitrary_) retUnit.isArbitrary_ = unit2.isArbitrary_; // Likewise for special units
2835
+
2836
+ if (!retUnit.isSpecial_) retUnit.isSpecial_ = unit2.isSpecial_;
2727
2837
  return retUnit;
2728
2838
  } // end multiplyThese
2729
2839
 
@@ -3280,6 +3390,7 @@ var UnitString = /*#__PURE__*/function () {
3280
3390
  if (intUtils_.isIntegerUnit(finalUnit) || typeof finalUnit === 'number') {
3281
3391
  finalUnit = new Unit({
3282
3392
  'csCode_': origString,
3393
+ 'ciCode_': origString,
3283
3394
  'magnitude_': finalUnit,
3284
3395
  'name_': origString
3285
3396
  });
@@ -4392,24 +4503,30 @@ var UnitString = /*#__PURE__*/function () {
4392
4503
  if (!befAnnoText && !aftAnnoText) {
4393
4504
  var tryBrackets = '[' + annoText.substring(1, annoText.length - 1) + ']';
4394
4505
 
4395
- var mkUnitRet = this._makeUnit(tryBrackets, origString); // If we got back a unit, assign it to the returned unit, and add
4396
- // a message to advise the user that brackets should enclose the code
4506
+ var mkUnitRet = this._makeUnit(tryBrackets, origString); // Nearly anything inside braces is valid, so we don't want to change the
4507
+ // unit, but we can put the found unit in the message as a sort of
4508
+ // warning.
4397
4509
 
4398
4510
 
4399
4511
  if (mkUnitRet[0]) {
4400
- retUnit = mkUnitRet[0];
4401
- origString = origString.replace(annoText, tryBrackets);
4402
- this.retMsg_.push("".concat(annoText, " is not a valid unit expression, but ") + "".concat(tryBrackets, " is.\n") + this.vcMsgStart_ + "".concat(tryBrackets, " (").concat(retUnit.name_, ")").concat(this.vcMsgEnd_));
4403
- } // Otherwise assume that this should be interpreted as a 1
4404
- else {
4405
- // remove error message generated for trybrackets
4406
- if (this.retMsg_.length > msgLen) {
4407
- this.retMsg_.pop();
4408
- }
4409
-
4410
- uCode = 1;
4411
- retUnit = 1;
4512
+ retUnit = uCode;
4513
+ this.retMsg_.push("".concat(annoText, " is a valid unit expression, but ") + "did you mean ".concat(tryBrackets, " (").concat(mkUnitRet[0].name_, ")?"));
4514
+ } else {
4515
+ // remove error message generated for trybrackets
4516
+ if (this.retMsg_.length > msgLen) {
4517
+ this.retMsg_.pop();
4412
4518
  }
4519
+ } // This is the case where the string is only this annotation.
4520
+ // Create and return a unit object, as we do for numeric units in
4521
+ // parseString.
4522
+
4523
+
4524
+ retUnit = new Unit({
4525
+ 'csCode_': annoText,
4526
+ 'ciCode_': annoText,
4527
+ 'magnitude_': 1,
4528
+ 'name_': annoText
4529
+ });
4413
4530
  } // end if it's only an annotation
4414
4531
  else {
4415
4532
  // if there's text before and no text after, assume the text before
@@ -4494,6 +4611,7 @@ var UnitString = /*#__PURE__*/function () {
4494
4611
  if (intUtils_.isIntegerUnit(finalUnit)) {
4495
4612
  finalUnit = new Unit({
4496
4613
  'csCode_': finalUnit,
4614
+ 'ciCode_': finalUnit,
4497
4615
  'magnitude_': Number(finalUnit),
4498
4616
  'name_': finalUnit
4499
4617
  });
@@ -4509,6 +4627,7 @@ var UnitString = /*#__PURE__*/function () {
4509
4627
  if (intUtils_.isIntegerUnit(nextUnit)) {
4510
4628
  nextUnit = new Unit({
4511
4629
  'csCode_': nextUnit,
4630
+ 'ciCode_': nextUnit,
4512
4631
  'magnitude_': Number(nextUnit),
4513
4632
  'name_': nextUnit
4514
4633
  });
@@ -4734,6 +4853,11 @@ var UnitTablesFactory = /*#__PURE__*/function () {
4734
4853
  */
4735
4854
 
4736
4855
  this.massDimIndex_ = 0;
4856
+ /**
4857
+ * Map of indices in the dimension vector to base unit symbols.
4858
+ */
4859
+
4860
+ this.dimVecIndexToBaseUnit_ = {};
4737
4861
  }
4738
4862
  /**
4739
4863
  * Provides the number of unit objects written to the tables, using the
@@ -4774,6 +4898,17 @@ var UnitTablesFactory = /*#__PURE__*/function () {
4774
4898
  } catch (err) {// do nothing - throws error if the property is null
4775
4899
  // and that's OK here.
4776
4900
  }
4901
+
4902
+ if (theUnit.isBase_) {
4903
+ var dimVec = theUnit.dim_.dimVec_;
4904
+ var nonZeroIndex;
4905
+
4906
+ for (var i = 0, len = dimVec.length; nonZeroIndex == undefined && i < len; ++i) {
4907
+ if (dimVec[i] != 0) nonZeroIndex = i;
4908
+ }
4909
+
4910
+ this.dimVecIndexToBaseUnit_[nonZeroIndex] = theUnit.csCode_;
4911
+ }
4777
4912
  } // end addUnit
4778
4913
 
4779
4914
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lhncbc/ucum-lhc",
3
- "version": "4.1.8",
3
+ "version": "5.0.0",
4
4
  "description": "Implements Unified Code for Units of Measure (UCUM) functions in a javascript library",
5
5
  "main": "source-cjs/ucumPkg.js",
6
6
  "homepage": "https://lhncbc.github.io/ucum-lhc/",
package/source/config.js CHANGED
@@ -24,6 +24,7 @@ export var Ucum = {
24
24
  */
25
25
  dimLen_: 7,
26
26
 
27
+
27
28
  /**
28
29
  * The characters used as valid operators in a UCUM unit expression,
29
30
  * where '.' is for multiplication and '/' is for division.
@@ -63,14 +64,14 @@ export var Ucum = {
63
64
  * displaying messages on a web site; should be blank when output is
64
65
  * to a file.
65
66
  */
66
- openEmphHTML_ : '<span class="emphSpan">',
67
+ openEmphHTML_ : ' <span class="emphSpan">',
67
68
 
68
69
  /**
69
70
  * Closing HTML used to emphasize portions of error messages. Used when
70
71
  * displaying messages on a web site; should be blank when output is
71
72
  * to a file.
72
73
  */
73
- closeEmphHTML_ : '</span>' ,
74
+ closeEmphHTML_ : '</span> ' ,
74
75
 
75
76
  /**
76
77
  * Message that is displayed when annotations are included in a unit
@@ -130,19 +130,12 @@ export class UcumLhcUtils {
130
130
 
131
131
  let resp = this.getSpecifiedUnit(uStr, valConv, suggest);
132
132
  let theUnit = resp['unit'];
133
- let retObj = {};
134
- if (!theUnit) {
135
- retObj = {'status': (!resp['origString'] || resp['origString'] === null) ?
136
- 'error' : 'invalid',
137
- 'ucumCode': null};
138
- }
139
- else {
140
- retObj = {'status': resp['origString'] === uStr ? 'valid': 'invalid',
141
- 'ucumCode': resp['origString'],
142
- 'unit': {'code': theUnit.csCode_,
143
- 'name': theUnit.name_,
144
- 'guidance': theUnit.guidance_ }};
145
- }
133
+ let retObj = !theUnit ? {'ucumCode': null} :
134
+ {'ucumCode': resp['origString'],
135
+ 'unit': {'code': theUnit.csCode_,
136
+ 'name': theUnit.name_,
137
+ 'guidance': theUnit.guidance_ }};
138
+ retObj.status = resp.status;
146
139
  if (resp['suggestions']) {
147
140
  retObj['suggestions'] = resp['suggestions'];
148
141
  }
@@ -228,12 +221,7 @@ export class UcumLhcUtils {
228
221
  returnObj['status'] = 'error';
229
222
  returnObj['msg'].push('No "from" unit expression specified.');
230
223
  }
231
- if (fromVal === null || isNaN(fromVal) || (typeof fromVal !== 'number' &&
232
- !intUtils_.isNumericString(fromVal))) {
233
- returnObj['status'] = 'error';
234
- returnObj['msg'].push('No "from" value, or an invalid "from" value, ' +
235
- 'was specified.');
236
- }
224
+ this._checkFromVal(fromVal, returnObj);
237
225
  if (toUnitCode) {
238
226
  toUnitCode = toUnitCode.trim();
239
227
  }
@@ -337,6 +325,110 @@ export class UcumLhcUtils {
337
325
  } // end convertUnitTo
338
326
 
339
327
 
328
+ /**
329
+ * Converts the given unit string into its base units, their exponents, and
330
+ * a magnitude, and returns that data.
331
+ * @param fromUnit the unit string to be converted to base units information
332
+ * @param fromVal the number of "from" units to be converted
333
+ * @returns an object with the properties:
334
+ * 'status' indicates whether the result succeeded. The value will be one of:
335
+ * 'succeeded': the conversion was successfully calculated (which can be
336
+ * true even if it was already in base units);
337
+ * 'invalid': fromUnit is not a valid UCUM code;
338
+ * 'failed': the conversion could not be made (e.g., if it is an "arbitrary" unit);
339
+ * 'error': if an error occurred (an input or programming error)
340
+ * 'msg': an array of one or more messages, if the string is invalid or
341
+ * an error occurred, indicating the problem, or a suggestion of a
342
+ * substitution such as the substitution of 'G' for 'Gauss', or
343
+ * an empty array if no messages were generated. There can also be a
344
+ * message that is just informational or warning.
345
+ * 'magnitude': the new value when fromVal units of fromUnits is expressed in the base units.
346
+ * 'fromUnitIsSpecial': whether the input unit fromUnit is a "special unit"
347
+ * as defined in UCUM. This means there is some function applied to convert
348
+ * between fromUnit and the base units, so the returned magnitude is likely not
349
+ * useful as a scale factor for other conversions (i.e., it only has validity
350
+ * and usefulness for the input values that produced it).
351
+ * 'unitToExp': a map of base units in uStr to their exponent
352
+ */
353
+ convertToBaseUnits(fromUnit, fromVal) {
354
+ let retObj = {};
355
+ this._checkFromVal(fromVal, retObj);
356
+ if (!retObj.status) { // could be set to 'error' by _checkFromVal
357
+ let inputUnitLookup = this.getSpecifiedUnit(fromUnit, 'validate');
358
+ retObj = {status: inputUnitLookup.status == 'valid' ? 'succeeded' : inputUnitLookup.status};
359
+ let unit = inputUnitLookup.unit;
360
+ retObj.msg = inputUnitLookup.retMsg || [];
361
+ if (!unit) {
362
+ if (inputUnitLookup.retMsg?.length == 0)
363
+ retObj.msg.push('Could not find unit information for '+fromUnit);
364
+ }
365
+ else if (unit.isArbitrary_) {
366
+ retObj.msg.push('Arbitrary units cannot be converted to base units or other units.');
367
+ retObj.status = 'failed';
368
+ }
369
+ else if (retObj.status == 'succeeded') {
370
+ let unitToExp = {};
371
+ let dimVec = unit.dim_?.dimVec_
372
+ let baseUnitString = '1';
373
+ if (dimVec) {
374
+ let dimVecIndexToBaseUnit = UnitTables.getInstance().dimVecIndexToBaseUnit_;
375
+ for (let i=0, len=dimVec.length; i<len; ++i) {
376
+ let exp = dimVec[i];
377
+ if (exp) {
378
+ unitToExp[dimVecIndexToBaseUnit[i]] = exp;
379
+ baseUnitString += '.' + dimVecIndexToBaseUnit[i] + exp;
380
+ }
381
+ }
382
+ }
383
+
384
+ // The unit might have a conversion function, which has to be applied; we
385
+ // cannot just assume unit_.magnitude_ is the magnitude in base units.
386
+ let retUnitLookup = this.getSpecifiedUnit(baseUnitString, 'validate');
387
+ // There should not be any error in retUnitLookup, unless there is a bug.
388
+ let retUnit = retUnitLookup.unit;
389
+ if (retUnitLookup.status !== 'valid') {
390
+ retObj.msg.push('Unable construct base unit string; tried '+baseUnitString);
391
+ retObj.status = 'error';
392
+ }
393
+ else {
394
+ try {
395
+ retObj.magnitude = retUnit.convertFrom(fromVal, unit);
396
+ }
397
+ catch (e) {
398
+ retObj.msg.push(e.toString());
399
+ retObj.status = 'error';
400
+ }
401
+ if (retObj.status == 'succeeded') {
402
+ retObj.unitToExp = unitToExp;
403
+ retObj.fromUnitIsSpecial = unit.isSpecial_;
404
+ }
405
+ }
406
+ }
407
+ }
408
+ return retObj;
409
+ }
410
+
411
+
412
+ /**
413
+ * Checks the given value as to whether it is suitable as a "from" value in a
414
+ * unit conversion. If it is not, the responseObj will have its status set
415
+ * to 'error' and a message added.
416
+ * @param fromVal The value to check
417
+ * @param responseObj the object that will be updated if the value is not
418
+ * usable.
419
+ */
420
+ _checkFromVal(fromVal, responseObj) {
421
+ if (fromVal === null || isNaN(fromVal) || (typeof fromVal !== 'number' &&
422
+ !intUtils_.isNumericString(fromVal))) {
423
+ responseObj.status = 'error';
424
+ if (!responseObj.msg)
425
+ responseObj.msg = [];
426
+ responseObj.msg.push('No "from" value, or an invalid "from" value, ' +
427
+ 'was specified.');
428
+ }
429
+ }
430
+
431
+
340
432
  /**
341
433
  * This method accepts a term and looks for units that include it as
342
434
  * a synonym - or that include the term in its name.
@@ -380,13 +472,18 @@ export class UcumLhcUtils {
380
472
  * true indicates suggestions are wanted; false indicates they are not,
381
473
  * and is the default if the parameter is not specified;
382
474
  * @returns a hash containing:
475
+ * 'status' will be 'valid' (uName is a valid UCUM code), 'invalid'
476
+ * (the uStr is not a valid UCUM code, and substitutions or
477
+ * suggestions may or may not be returned, depending on what was
478
+ * requested and found); or 'error' (an input or programming error
479
+ * occurred);
383
480
  * 'unit' the unit object (or null if there were problems creating the
384
481
  * unit);
385
482
  * 'origString' the possibly updated unit string passed in;
386
483
  * 'retMsg' an array of user messages (informational, error or warning) if
387
484
  * any were generated (IF any were generated, otherwise will be an
388
485
  * empty array); and
389
- * 'suggestions' is an array of 1 or more hash objects. Each hash
486
+ * 'suggestions' is an array of 1 or more hash objects. Each hash
390
487
  * contains three elements:
391
488
  * 'msg' which is a message indicating what unit expression the
392
489
  * suggestions are for;
@@ -441,6 +538,17 @@ export class UcumLhcUtils {
441
538
  } // end if the unit was not found as a unit name
442
539
  } // end if a unit expression was specified
443
540
 
541
+ // Set the status field
542
+ if (!retObj.unit) {
543
+ // No unit was found; check whether origString has a value
544
+ retObj.status = !retObj.origString ? 'error' : 'invalid';
545
+ }
546
+ else {
547
+ // Check whether substitutions were made to the unit string in order to
548
+ // find the unit
549
+ retObj.status = retObj.origString === uName ? 'valid': 'invalid';
550
+ }
551
+
444
552
  return retObj;
445
553
 
446
554
  } // end getSpecifiedUnit
@@ -512,7 +620,3 @@ export class UcumLhcUtils {
512
620
  UcumLhcUtils.getInstance = function(){
513
621
  return new UcumLhcUtils();
514
622
  } ;
515
-
516
-
517
-
518
-