@lhncbc/ucum-lhc 4.1.7 → 4.2.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
@@ -1817,6 +1817,81 @@ var UcumLhcUtils = /*#__PURE__*/function () {
1817
1817
  return returnObj;
1818
1818
  } // end convertUnitTo
1819
1819
 
1820
+ /**
1821
+ * Converts the given unit string into its base units, their exponents, and
1822
+ * a magnitude, and returns that data.
1823
+ * @param fromUnit the unit string to be converted to base units information
1824
+ * @param fromVal the number of "from" units to be converted
1825
+ * @returns an object with the properties:
1826
+ * 'msg': an array of one or more messages, if the string is invalid or
1827
+ * an error occurred, indicating the problem, or a suggestion of a
1828
+ * substitution such as the substitution of 'G' for 'Gauss', or
1829
+ * an empty array if no messages were generated. If this is not empty,
1830
+ * no other information will be returned.
1831
+ * 'magnitude': the new value when fromVal units of fromUnits is expressed in the base units.
1832
+ * 'fromUnitIsSpecial': whether the input unit fromUnit is a "special unit"
1833
+ * as defined in UCUM. This means there is some function applied to convert
1834
+ * between fromUnit and the base units, so the returned magnitude is likely not
1835
+ * useful as a scale factor for other conversions (i.e., it only has validity
1836
+ * and usefulness for the input values that produced it).
1837
+ * 'unitToExp': a map of base units in uStr to their exponent
1838
+ */
1839
+
1840
+ }, {
1841
+ key: "convertToBaseUnits",
1842
+ value: function convertToBaseUnits(fromUnit, fromVal) {
1843
+ var inputUnitLookup = this.getSpecifiedUnit(fromUnit, 'validate');
1844
+ var retObj = {};
1845
+ var unit = inputUnitLookup.unit;
1846
+ retObj.msg = inputUnitLookup.retMsg || [];
1847
+
1848
+ if (!unit) {
1849
+ var _inputUnitLookup$retM;
1850
+
1851
+ 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);
1852
+ } else if (unit.isArbitrary_) {
1853
+ retObj.msg.push('Arbitrary units cannot be converted to base units or other units.');
1854
+ } else if (retObj.msg.length == 0) {
1855
+ var _unit$dim_, _retUnitLookup$retMsg;
1856
+
1857
+ var unitToExp = {};
1858
+ var dimVec = (_unit$dim_ = unit.dim_) === null || _unit$dim_ === void 0 ? void 0 : _unit$dim_.dimVec_;
1859
+ var baseUnitString = '1';
1860
+
1861
+ if (dimVec) {
1862
+ var dimVecIndexToBaseUnit = UnitTables.getInstance().dimVecIndexToBaseUnit_;
1863
+
1864
+ for (var i = 0, len = dimVec.length; i < len; ++i) {
1865
+ var exp = dimVec[i];
1866
+
1867
+ if (exp) {
1868
+ unitToExp[dimVecIndexToBaseUnit[i]] = exp;
1869
+ baseUnitString += '.' + dimVecIndexToBaseUnit[i] + exp;
1870
+ }
1871
+ }
1872
+ } // The unit might have a conversion function, which has to be applied; we
1873
+ // cannot just assume unit_.magnitude_ is the magnitude in base units.
1874
+
1875
+
1876
+ var retUnitLookup = this.getSpecifiedUnit(baseUnitString, 'validate'); // There should not be any error in retUnitLookup, unless there is a bug.
1877
+
1878
+ var retUnit = retUnitLookup.unit;
1879
+ if (!retUnit && ((_retUnitLookup$retMsg = retUnitLookup.retMsg) === null || _retUnitLookup$retMsg === void 0 ? void 0 : _retUnitLookup$retMsg.length) == 0) retObj.msg.push('Unable construct base unit string; tried ' + baseUnitString);else {
1880
+ try {
1881
+ retObj.magnitude = retUnit.convertFrom(fromVal, unit);
1882
+ } catch (e) {
1883
+ retObj.msg.push(e.toString());
1884
+ }
1885
+
1886
+ if (retObj.msg.length == 0) {
1887
+ retObj.unitToExp = unitToExp;
1888
+ retObj.fromUnitIsSpecial = unit.isSpecial_;
1889
+ }
1890
+ }
1891
+ }
1892
+
1893
+ return retObj;
1894
+ }
1820
1895
  /**
1821
1896
  * This method accepts a term and looks for units that include it as
1822
1897
  * a synonym - or that include the term in its name.
@@ -2419,8 +2494,8 @@ var Unit = /*#__PURE__*/function () {
2419
2494
  key: "convertFrom",
2420
2495
  value: function convertFrom(num, fromUnit) {
2421
2496
  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
2497
+ if (this.isArbitrary_) throw new Error("Attempt to convert to arbitrary unit \"".concat(this.csCode_, "\""));
2498
+ 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
2499
 
2425
2500
  if (fromUnit.dim_ && this.dim_ && !fromUnit.dim_.equals(this.dim_)) {
2426
2501
  // check first to see if a mole<->mass conversion is appropriate
@@ -2442,40 +2517,28 @@ var Unit = /*#__PURE__*/function () {
2442
2517
  }
2443
2518
 
2444
2519
  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;
2457
-
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);
2520
+ var fromMag = fromUnit.magnitude_;
2521
+ var x;
2462
2522
 
2463
- x = fromFunc.cnvFrom(num * fromUnit.cnvPfx_) * fromMag; //x = fromFunc.cnvFrom(num * fromMag) * fromUnit.cnvPfx_;
2464
- } else {
2465
- x = num * fromMag;
2466
- }
2523
+ if (fromCnv != null) {
2524
+ // turn num * fromUnit.magnitude into its ratio scale equivalent,
2525
+ // e.g., convert Celsius to Kelvin
2526
+ var fromFunc = _ucumFunctions.default.forName(fromCnv);
2467
2527
 
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_);
2528
+ x = fromFunc.cnvFrom(num * fromUnit.cnvPfx_) * fromMag; //x = fromFunc.cnvFrom(num * fromMag) * fromUnit.cnvPfx_;
2529
+ } else {
2530
+ x = num * fromMag;
2531
+ }
2472
2532
 
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
2533
+ if (this.cnv_ != null) {
2534
+ // turn mag * origUnit on ratio scale into a non-ratio unit,
2535
+ // e.g. convert Kelvin to Fahrenheit
2536
+ var toFunc = _ucumFunctions.default.forName(this.cnv_);
2478
2537
 
2538
+ newNum = toFunc.cnvTo(x / this.magnitude_) / this.cnvPfx_;
2539
+ } else {
2540
+ newNum = x / this.magnitude_;
2541
+ }
2479
2542
 
2480
2543
  return newNum;
2481
2544
  } // end convertFrom
@@ -2690,6 +2753,7 @@ var Unit = /*#__PURE__*/function () {
2690
2753
  else if (unit2.cnv_ != null) {
2691
2754
  if (!retUnit.dim_ || retUnit.dim_.isZero()) {
2692
2755
  retUnit.cnvPfx_ = unit2.cnvPfx_ * retUnit.magnitude_;
2756
+ retUnit.magnitude_ = unit2.magnitude_;
2693
2757
  retUnit.cnv_ = unit2.cnv_;
2694
2758
  } else throw new Error("Attempt to multiply non-ratio unit ".concat(unit2.name_));
2695
2759
  } // end if unit2 has a conversion function
@@ -2723,7 +2787,9 @@ var Unit = /*#__PURE__*/function () {
2723
2787
  // if (!retUnit.isMole_)
2724
2788
  // retUnit.isMole_ = unit2.isMole_ ;
2725
2789
 
2726
- if (!retUnit.isArbitrary_) retUnit.isArbitrary_ = unit2.isArbitrary_;
2790
+ if (!retUnit.isArbitrary_) retUnit.isArbitrary_ = unit2.isArbitrary_; // Likewise for special units
2791
+
2792
+ if (!retUnit.isSpecial_) retUnit.isSpecial_ = unit2.isSpecial_;
2727
2793
  return retUnit;
2728
2794
  } // end multiplyThese
2729
2795
 
@@ -3073,6 +3139,8 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+
3073
3139
 
3074
3140
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
3075
3141
 
3142
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
3143
+
3076
3144
  /**
3077
3145
  * This class handles the parsing of a unit string into a unit object
3078
3146
  */
@@ -3128,18 +3196,19 @@ var UnitString = /*#__PURE__*/function () {
3128
3196
 
3129
3197
  this.suggestions = [];
3130
3198
  } // end constructor
3131
-
3132
- /**
3133
- * Sets the emphasis strings to the HTML used in the webpage display - or
3134
- * blanks them out, depending on the use parameter.
3135
- *
3136
- * @param use flag indicating whether or not to use the html message format;
3137
- * defaults to true
3138
- */
3199
+ // The start of an error message about an invalid annotation character.
3139
3200
 
3140
3201
 
3141
3202
  _createClass(UnitString, [{
3142
3203
  key: "useHTMLInMessages",
3204
+
3205
+ /**
3206
+ * Sets the emphasis strings to the HTML used in the webpage display - or
3207
+ * blanks them out, depending on the use parameter.
3208
+ *
3209
+ * @param use flag indicating whether or not to use the html message format;
3210
+ * defaults to true
3211
+ */
3143
3212
  value: function useHTMLInMessages(use) {
3144
3213
  if (use === undefined || use) {
3145
3214
  this.openEmph_ = Ucum.openEmphHTML_;
@@ -3277,6 +3346,7 @@ var UnitString = /*#__PURE__*/function () {
3277
3346
  if (intUtils_.isIntegerUnit(finalUnit) || typeof finalUnit === 'number') {
3278
3347
  finalUnit = new Unit({
3279
3348
  'csCode_': origString,
3349
+ 'ciCode_': origString,
3280
3350
  'magnitude_': finalUnit,
3281
3351
  'name_': origString
3282
3352
  });
@@ -3435,24 +3505,35 @@ var UnitString = /*#__PURE__*/function () {
3435
3505
  var openBrace = uString.indexOf('{');
3436
3506
 
3437
3507
  while (openBrace >= 0) {
3438
- var _closeBrace = uString.indexOf('}');
3508
+ var closeBrace = uString.indexOf('}');
3439
3509
 
3440
- if (_closeBrace < 0) {
3510
+ if (closeBrace < 0) {
3441
3511
  this.retMsg_.push('Missing closing brace for annotation starting at ' + this.openEmph_ + uString.substr(openBrace) + this.closeEmph_);
3442
3512
  openBrace = -1;
3443
3513
  } else {
3444
- var braceStr = uString.substring(openBrace, _closeBrace + 1);
3445
- var aIdx = this.annotations_.length.toString();
3446
- uString = uString.replace(braceStr, this.braceFlag_ + aIdx + this.braceFlag_);
3447
- this.annotations_.push(braceStr);
3448
- openBrace = uString.indexOf('{');
3514
+ var braceStr = uString.substring(openBrace, closeBrace + 1); // Check for valid characters in the annotation.
3515
+
3516
+ if (!UnitString.VALID_ANNOTATION_REGEX.test(braceStr)) {
3517
+ this.retMsg_.push(UnitString.INVALID_ANNOTATION_CHAR_MSG + this.openEmph_ + braceStr + this.closeEmph_);
3518
+ openBrace = -1; // end search for annotations
3519
+ } else {
3520
+ var aIdx = this.annotations_.length.toString();
3521
+ uString = uString.replace(braceStr, this.braceFlag_ + aIdx + this.braceFlag_);
3522
+ this.annotations_.push(braceStr);
3523
+ openBrace = uString.indexOf('{');
3524
+ }
3449
3525
  }
3450
3526
  } // end do while we have an opening brace
3451
3527
  // check for a stray/unmatched closing brace
3452
3528
 
3453
3529
 
3454
- var closeBrace = uString.indexOf('}');
3455
- if (closeBrace >= 0) this.retMsg_.push('Missing opening brace for closing brace found at ' + this.openEmph_ + uString.substring(0, closeBrace + 1) + this.closeEmph_);
3530
+ if (this.retMsg_.length == 0) {
3531
+ // if there were no other errors above
3532
+ var _closeBrace = uString.indexOf('}');
3533
+
3534
+ if (_closeBrace >= 0) this.retMsg_.push('Missing opening brace for closing brace found at ' + this.openEmph_ + uString.substring(0, _closeBrace + 1) + this.closeEmph_);
3535
+ }
3536
+
3456
3537
  return uString;
3457
3538
  } // end _getAnnotations
3458
3539
 
@@ -4480,6 +4561,7 @@ var UnitString = /*#__PURE__*/function () {
4480
4561
  if (intUtils_.isIntegerUnit(finalUnit)) {
4481
4562
  finalUnit = new Unit({
4482
4563
  'csCode_': finalUnit,
4564
+ 'ciCode_': finalUnit,
4483
4565
  'magnitude_': Number(finalUnit),
4484
4566
  'name_': finalUnit
4485
4567
  });
@@ -4495,6 +4577,7 @@ var UnitString = /*#__PURE__*/function () {
4495
4577
  if (intUtils_.isIntegerUnit(nextUnit)) {
4496
4578
  nextUnit = new Unit({
4497
4579
  'csCode_': nextUnit,
4580
+ 'ciCode_': nextUnit,
4498
4581
  'magnitude_': Number(nextUnit),
4499
4582
  'name_': nextUnit
4500
4583
  });
@@ -4594,6 +4677,11 @@ var UnitString = /*#__PURE__*/function () {
4594
4677
 
4595
4678
  exports.UnitString = UnitString;
4596
4679
 
4680
+ _defineProperty(UnitString, "INVALID_ANNOTATION_CHAR_MSG", 'An invalid character was found in the annotation ');
4681
+
4682
+ // A regular expression for validating annotation strings.
4683
+ _defineProperty(UnitString, "VALID_ANNOTATION_REGEX", /^\{[!-z|~]*\}$/);
4684
+
4597
4685
  UnitString.getInstance = function () {
4598
4686
  return new UnitString();
4599
4687
  };
@@ -4715,6 +4803,11 @@ var UnitTablesFactory = /*#__PURE__*/function () {
4715
4803
  */
4716
4804
 
4717
4805
  this.massDimIndex_ = 0;
4806
+ /**
4807
+ * Map of indices in the dimension vector to base unit symbols.
4808
+ */
4809
+
4810
+ this.dimVecIndexToBaseUnit_ = {};
4718
4811
  }
4719
4812
  /**
4720
4813
  * Provides the number of unit objects written to the tables, using the
@@ -4755,6 +4848,17 @@ var UnitTablesFactory = /*#__PURE__*/function () {
4755
4848
  } catch (err) {// do nothing - throws error if the property is null
4756
4849
  // and that's OK here.
4757
4850
  }
4851
+
4852
+ if (theUnit.isBase_) {
4853
+ var dimVec = theUnit.dim_.dimVec_;
4854
+ var nonZeroIndex;
4855
+
4856
+ for (var i = 0, len = dimVec.length; nonZeroIndex == undefined && i < len; ++i) {
4857
+ if (dimVec[i] != 0) nonZeroIndex = i;
4858
+ }
4859
+
4860
+ this.dimVecIndexToBaseUnit_[nonZeroIndex] = theUnit.csCode_;
4861
+ }
4758
4862
  } // end addUnit
4759
4863
 
4760
4864
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lhncbc/ucum-lhc",
3
- "version": "4.1.7",
3
+ "version": "4.2.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/",
@@ -28,6 +28,7 @@
28
28
  "devDependencies": {
29
29
  "@babel/cli": "^7.19.3",
30
30
  "@babel/core": "^7.3.4",
31
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
31
32
  "@babel/plugin-transform-modules-commonjs": "^7.8.3",
32
33
  "@babel/preset-env": "^7.6.2",
33
34
  "@babel/register": "^7.6.2",
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
@@ -337,6 +337,76 @@ export class UcumLhcUtils {
337
337
  } // end convertUnitTo
338
338
 
339
339
 
340
+ /**
341
+ * Converts the given unit string into its base units, their exponents, and
342
+ * a magnitude, and returns that data.
343
+ * @param fromUnit the unit string to be converted to base units information
344
+ * @param fromVal the number of "from" units to be converted
345
+ * @returns an object with the properties:
346
+ * 'msg': an array of one or more messages, if the string is invalid or
347
+ * an error occurred, indicating the problem, or a suggestion of a
348
+ * substitution such as the substitution of 'G' for 'Gauss', or
349
+ * an empty array if no messages were generated. If this is not empty,
350
+ * no other information will be returned.
351
+ * 'magnitude': the new value when fromVal units of fromUnits is expressed in the base units.
352
+ * 'fromUnitIsSpecial': whether the input unit fromUnit is a "special unit"
353
+ * as defined in UCUM. This means there is some function applied to convert
354
+ * between fromUnit and the base units, so the returned magnitude is likely not
355
+ * useful as a scale factor for other conversions (i.e., it only has validity
356
+ * and usefulness for the input values that produced it).
357
+ * 'unitToExp': a map of base units in uStr to their exponent
358
+ */
359
+ convertToBaseUnits(fromUnit, fromVal) {
360
+ let inputUnitLookup = this.getSpecifiedUnit(fromUnit, 'validate');
361
+ let retObj = {};
362
+ let unit = inputUnitLookup.unit;
363
+ retObj.msg = inputUnitLookup.retMsg || [];
364
+ if (!unit) {
365
+ if (inputUnitLookup.retMsg?.length == 0)
366
+ retObj.msg.push('Could not find unit information for '+fromUnit);
367
+ }
368
+ else if (unit.isArbitrary_) {
369
+ retObj.msg.push('Arbitrary units cannot be converted to base units or other units.');
370
+ }
371
+ else if (retObj.msg.length == 0) {
372
+ let unitToExp = {};
373
+ let dimVec = unit.dim_?.dimVec_
374
+ let baseUnitString = '1';
375
+ if (dimVec) {
376
+ let dimVecIndexToBaseUnit = UnitTables.getInstance().dimVecIndexToBaseUnit_;
377
+ for (let i=0, len=dimVec.length; i<len; ++i) {
378
+ let exp = dimVec[i];
379
+ if (exp) {
380
+ unitToExp[dimVecIndexToBaseUnit[i]] = exp;
381
+ baseUnitString += '.' + dimVecIndexToBaseUnit[i] + exp;
382
+ }
383
+ }
384
+ }
385
+
386
+ // The unit might have a conversion function, which has to be applied; we
387
+ // cannot just assume unit_.magnitude_ is the magnitude in base units.
388
+ let retUnitLookup = this.getSpecifiedUnit(baseUnitString, 'validate');
389
+ // There should not be any error in retUnitLookup, unless there is a bug.
390
+ let retUnit = retUnitLookup.unit;
391
+ if (!retUnit && retUnitLookup.retMsg?.length == 0)
392
+ retObj.msg.push('Unable construct base unit string; tried '+baseUnitString);
393
+ else {
394
+ try {
395
+ retObj.magnitude = retUnit.convertFrom(fromVal, unit);
396
+ }
397
+ catch (e) {
398
+ retObj.msg.push(e.toString());
399
+ }
400
+ if (retObj.msg.length == 0) {
401
+ retObj.unitToExp = unitToExp;
402
+ retObj.fromUnitIsSpecial = unit.isSpecial_;
403
+ }
404
+ }
405
+ }
406
+ return retObj;
407
+ }
408
+
409
+
340
410
  /**
341
411
  * This method accepts a term and looks for units that include it as
342
412
  * a synonym - or that include the term in its name.
package/source/unit.js CHANGED
@@ -340,6 +340,7 @@ export class Unit {
340
340
  return match ;
341
341
  }// end of fullEquals
342
342
 
343
+
343
344
  /**
344
345
  * This returns the value of the property named by the parameter
345
346
  * passed in.
@@ -380,9 +381,9 @@ export class Unit {
380
381
  let newNum = 0.0 ;
381
382
 
382
383
  if (this.isArbitrary_)
383
- throw (new Error(`Attempt to convert arbitrary unit ${this.name_}`));
384
+ throw (new Error(`Attempt to convert to arbitrary unit "${this.csCode_}"`));
384
385
  if (fromUnit.isArbitrary_)
385
- throw (new Error(`Attempt to convert to arbitrary unit ${fromUnit.name_}`));
386
+ throw (new Error(`Attempt to convert arbitrary unit "${fromUnit.csCode_}"`));
386
387
 
387
388
  // reject request if both units have dimensions that are not equal
388
389
  if (fromUnit.dim_ && this.dim_ && !(fromUnit.dim_.equals(this.dim_))) {
@@ -410,39 +411,27 @@ export class Unit {
410
411
  let fromCnv = fromUnit.cnv_ ;
411
412
  let fromMag = fromUnit.magnitude_ ;
412
413
 
413
- // If the same conversion function is specified for both units, which
414
- // includes neither unit having a conversion function, multiply the
415
- // "from" unit's magnitude by the number passed in and then divide
416
- // that result by this unit's magnitude. Do this for units with
417
- // and without dimension vectors. PROBLEM with 2 non-commensurable
418
- // units with no dimension vector or function, e.g., byte to mol
419
- if (fromCnv === this.cnv_) {
420
- newNum = (num * fromMag) / this.magnitude_;
414
+ let x;
415
+ if (fromCnv != null) {
416
+ // turn num * fromUnit.magnitude into its ratio scale equivalent,
417
+ // e.g., convert Celsius to Kelvin
418
+ let fromFunc = funcs.forName(fromCnv);
419
+ x = fromFunc.cnvFrom(num * fromUnit.cnvPfx_) * fromMag;
420
+ //x = fromFunc.cnvFrom(num * fromMag) * fromUnit.cnvPfx_;
421
421
  }
422
- // else use a function to get the number to be returned
423
422
  else {
424
- let x = 0.0 ;
425
- if (fromCnv != null) {
426
- // turn num * fromUnit.magnitude into its ratio scale equivalent,
427
- // e.g., convert Celsius to Kelvin
428
- let fromFunc = funcs.forName(fromCnv);
429
- x = fromFunc.cnvFrom(num * fromUnit.cnvPfx_) * fromMag;
430
- //x = fromFunc.cnvFrom(num * fromMag) * fromUnit.cnvPfx_;
431
- }
432
- else {
433
- x = num * fromMag;
434
- }
423
+ x = num * fromMag;
424
+ }
435
425
 
436
- if (this.cnv_ != null) {
437
- // turn mag * origUnit on ratio scale into a non-ratio unit,
438
- // e.g. convert Kelvin to Fahrenheit
439
- let toFunc = funcs.forName(this.cnv_);
440
- newNum = toFunc.cnvTo(x / this.magnitude_) / this.cnvPfx_;
441
- }
442
- else {
443
- newNum = x / this.magnitude_;
444
- }
445
- } // end if either unit has a conversion function
426
+ if (this.cnv_ != null) {
427
+ // turn mag * origUnit on ratio scale into a non-ratio unit,
428
+ // e.g. convert Kelvin to Fahrenheit
429
+ let toFunc = funcs.forName(this.cnv_);
430
+ newNum = toFunc.cnvTo(x / this.magnitude_) / this.cnvPfx_;
431
+ }
432
+ else {
433
+ newNum = x / this.magnitude_;
434
+ }
446
435
 
447
436
  return newNum;
448
437
 
@@ -666,6 +655,7 @@ export class Unit {
666
655
  else if (unit2.cnv_ != null) {
667
656
  if (!retUnit.dim_ || retUnit.dim_.isZero()) {
668
657
  retUnit.cnvPfx_ = unit2.cnvPfx_ * retUnit.magnitude_;
658
+ retUnit.magnitude_ = unit2.magnitude_;
669
659
  retUnit.cnv_ = unit2.cnv_ ;
670
660
  }
671
661
  else
@@ -716,8 +706,12 @@ export class Unit {
716
706
  // via an arithmetic operation. Taint accordingly
717
707
  // if (!retUnit.isMole_)
718
708
  // retUnit.isMole_ = unit2.isMole_ ;
719
- if (!retUnit.isArbitrary_)
720
- retUnit.isArbitrary_ = unit2.isArbitrary_;
709
+ if (!retUnit.isArbitrary_)
710
+ retUnit.isArbitrary_ = unit2.isArbitrary_;
711
+
712
+ // Likewise for special units
713
+ if (!retUnit.isSpecial_)
714
+ retUnit.isSpecial_ = unit2.isSpecial_;
721
715
 
722
716
  return retUnit ;
723
717
 
@@ -59,10 +59,15 @@ export class UnitString {
59
59
 
60
60
  // suggestions for unit strings that for which no unit was found
61
61
  this.suggestions = [] ;
62
-
63
62
  } // end constructor
64
63
 
65
64
 
65
+ // The start of an error message about an invalid annotation character.
66
+ static INVALID_ANNOTATION_CHAR_MSG = 'An invalid character was found in the annotation ';
67
+
68
+ // A regular expression for validating annotation strings.
69
+ static VALID_ANNOTATION_REGEX = /^\{[!-z|~]*\}$/;
70
+
66
71
  /**
67
72
  * Sets the emphasis strings to the HTML used in the webpage display - or
68
73
  * blanks them out, depending on the use parameter.
@@ -213,6 +218,7 @@ export class UnitString {
213
218
  if (intUtils_.isIntegerUnit(finalUnit) || typeof finalUnit === 'number') {
214
219
  finalUnit = new Unit({
215
220
  'csCode_': origString,
221
+ 'ciCode_': origString,
216
222
  'magnitude_': finalUnit,
217
223
  'name_': origString
218
224
  });
@@ -383,20 +389,30 @@ export class UnitString {
383
389
  }
384
390
  else {
385
391
  let braceStr = uString.substring(openBrace, closeBrace + 1);
386
- let aIdx = this.annotations_.length.toString();
387
- uString = uString.replace(braceStr, this.braceFlag_ + aIdx +
388
- this.braceFlag_);
389
- this.annotations_.push(braceStr);
390
- openBrace = uString.indexOf('{');
392
+ // Check for valid characters in the annotation.
393
+ if (!UnitString.VALID_ANNOTATION_REGEX.test(braceStr)) {
394
+ this.retMsg_.push(UnitString.INVALID_ANNOTATION_CHAR_MSG +
395
+ this.openEmph_ + braceStr + this.closeEmph_);
396
+ openBrace = -1; // end search for annotations
397
+ }
398
+ else {
399
+ let aIdx = this.annotations_.length.toString();
400
+ uString = uString.replace(braceStr, this.braceFlag_ + aIdx +
401
+ this.braceFlag_);
402
+ this.annotations_.push(braceStr);
403
+ openBrace = uString.indexOf('{');
404
+ }
391
405
  }
392
406
  } // end do while we have an opening brace
393
407
 
394
408
  // check for a stray/unmatched closing brace
395
- let closeBrace = uString.indexOf('}');
396
- if (closeBrace >= 0)
397
- this.retMsg_.push('Missing opening brace for closing brace found at ' +
398
- this.openEmph_ + uString.substring(0, closeBrace + 1) +
399
- this.closeEmph_);
409
+ if (this.retMsg_.length == 0) { // if there were no other errors above
410
+ let closeBrace = uString.indexOf('}');
411
+ if (closeBrace >= 0)
412
+ this.retMsg_.push('Missing opening brace for closing brace found at ' +
413
+ this.openEmph_ + uString.substring(0, closeBrace + 1) +
414
+ this.closeEmph_);
415
+ }
400
416
  return uString;
401
417
  } // end _getAnnotations
402
418
 
@@ -1427,7 +1443,7 @@ export class UnitString {
1427
1443
 
1428
1444
  let finalUnit = uArray[0]['un'];
1429
1445
  if (intUtils_.isIntegerUnit(finalUnit)) {
1430
- finalUnit = new Unit({'csCode_' : finalUnit,
1446
+ finalUnit = new Unit({'csCode_' : finalUnit, 'ciCode_' : finalUnit,
1431
1447
  'magnitude_' : Number(finalUnit),
1432
1448
  'name_' : finalUnit}) ;
1433
1449
  }
@@ -1438,7 +1454,7 @@ export class UnitString {
1438
1454
  for (let u2 = 1; (u2 < uLen) && !endProcessing; u2++) {
1439
1455
  let nextUnit = uArray[u2]['un'];
1440
1456
  if (intUtils_.isIntegerUnit(nextUnit)) {
1441
- nextUnit = new Unit({'csCode_' : nextUnit ,
1457
+ nextUnit = new Unit({'csCode_' : nextUnit, 'ciCode_' : nextUnit,
1442
1458
  'magnitude_' : Number(nextUnit),
1443
1459
  'name_': nextUnit});
1444
1460
  }
@@ -97,6 +97,11 @@ class UnitTablesFactory {
97
97
  * @type integer
98
98
  */
99
99
  this.massDimIndex_ = 0;
100
+
101
+ /**
102
+ * Map of indices in the dimension vector to base unit symbols.
103
+ */
104
+ this.dimVecIndexToBaseUnit_ = {};
100
105
  }
101
106
 
102
107
 
@@ -138,6 +143,15 @@ class UnitTablesFactory {
138
143
  // and that's OK here.
139
144
  }
140
145
 
146
+ if (theUnit.isBase_) {
147
+ const dimVec = theUnit.dim_.dimVec_;
148
+ let nonZeroIndex;
149
+ for (let i=0, len=dimVec.length; nonZeroIndex==undefined && i<len; ++i) {
150
+ if (dimVec[i] != 0)
151
+ nonZeroIndex = i;
152
+ }
153
+ this.dimVecIndexToBaseUnit_[nonZeroIndex] = theUnit.csCode_;
154
+ }
141
155
  } // end addUnit
142
156
 
143
157