@lhncbc/ucum-lhc 4.2.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +50 -12
  2. package/browser-dist/ucum-lhc.js +841 -1325
  3. package/data/ucumDefs.min.json +1 -1
  4. package/package.json +1 -1
  5. package/source/ucumLhcUtils.js +100 -66
  6. package/source/ucumXmlDocument.js +5 -2
  7. package/source/unit.js +2 -1
  8. package/source/unitString.js +159 -140
  9. package/source-cjs/config.js +1 -12
  10. package/source-cjs/config.js.map +1 -1
  11. package/source-cjs/dimension.js +20 -63
  12. package/source-cjs/dimension.js.map +1 -1
  13. package/source-cjs/jsonArrayPack.js +7 -25
  14. package/source-cjs/jsonArrayPack.js.map +1 -1
  15. package/source-cjs/prefix.js +12 -26
  16. package/source-cjs/prefix.js.map +1 -1
  17. package/source-cjs/prefixTables.js +10 -24
  18. package/source-cjs/prefixTables.js.map +1 -1
  19. package/source-cjs/ucumFunctions.js +35 -32
  20. package/source-cjs/ucumFunctions.js.map +1 -1
  21. package/source-cjs/ucumInternalUtils.js +5 -13
  22. package/source-cjs/ucumInternalUtils.js.map +1 -1
  23. package/source-cjs/ucumJsonDefs.js +1 -16
  24. package/source-cjs/ucumJsonDefs.js.map +1 -1
  25. package/source-cjs/ucumLhcUtils.js +117 -138
  26. package/source-cjs/ucumLhcUtils.js.map +1 -1
  27. package/source-cjs/ucumPkg.js +1 -6
  28. package/source-cjs/ucumPkg.js.map +1 -1
  29. package/source-cjs/ucumXmlDocument.js +162 -184
  30. package/source-cjs/ucumXmlDocument.js.map +1 -1
  31. package/source-cjs/unit.js +97 -181
  32. package/source-cjs/unit.js.map +1 -1
  33. package/source-cjs/unitString.js +537 -618
  34. package/source-cjs/unitString.js.map +1 -1
  35. package/source-cjs/unitTables.js +33 -139
  36. package/source-cjs/unitTables.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lhncbc/ucum-lhc",
3
- "version": "4.2.0",
3
+ "version": "5.0.2",
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/",
@@ -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
  }
@@ -343,63 +331,77 @@ export class UcumLhcUtils {
343
331
  * @param fromUnit the unit string to be converted to base units information
344
332
  * @param fromVal the number of "from" units to be converted
345
333
  * @returns an object with the properties:
346
- * 'msg': an array of one or more messages, if the string is invalid or
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 messages (possibly empty) if the string is invalid or
347
341
  * an error occurred, indicating the problem, or a suggestion of a
348
342
  * 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.
343
+ * an empty array if no messages were generated. There can also be a
344
+ * message that is just informational or warning.
351
345
  * 'magnitude': the new value when fromVal units of fromUnits is expressed in the base units.
352
346
  * 'fromUnitIsSpecial': whether the input unit fromUnit is a "special unit"
353
347
  * as defined in UCUM. This means there is some function applied to convert
354
348
  * between fromUnit and the base units, so the returned magnitude is likely not
355
349
  * useful as a scale factor for other conversions (i.e., it only has validity
356
350
  * and usefulness for the input values that produced it).
357
- * 'unitToExp': a map of base units in uStr to their exponent
351
+ * 'unitToExp': a map of base units in fromUnit to their exponent
358
352
  */
359
353
  convertToBaseUnits(fromUnit, fromVal) {
360
- let inputUnitLookup = this.getSpecifiedUnit(fromUnit, 'validate');
361
354
  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;
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
+ }
382
381
  }
383
382
  }
384
- }
385
383
 
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());
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';
399
392
  }
400
- if (retObj.msg.length == 0) {
401
- retObj.unitToExp = unitToExp;
402
- retObj.fromUnitIsSpecial = unit.isSpecial_;
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
+ }
403
405
  }
404
406
  }
405
407
  }
@@ -407,6 +409,26 @@ export class UcumLhcUtils {
407
409
  }
408
410
 
409
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
+
410
432
  /**
411
433
  * This method accepts a term and looks for units that include it as
412
434
  * a synonym - or that include the term in its name.
@@ -450,13 +472,18 @@ export class UcumLhcUtils {
450
472
  * true indicates suggestions are wanted; false indicates they are not,
451
473
  * and is the default if the parameter is not specified;
452
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);
453
480
  * 'unit' the unit object (or null if there were problems creating the
454
481
  * unit);
455
482
  * 'origString' the possibly updated unit string passed in;
456
483
  * 'retMsg' an array of user messages (informational, error or warning) if
457
484
  * any were generated (IF any were generated, otherwise will be an
458
485
  * empty array); and
459
- * '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
460
487
  * contains three elements:
461
488
  * 'msg' which is a message indicating what unit expression the
462
489
  * suggestions are for;
@@ -511,6 +538,17 @@ export class UcumLhcUtils {
511
538
  } // end if the unit was not found as a unit name
512
539
  } // end if a unit expression was specified
513
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
+
514
552
  return retObj;
515
553
 
516
554
  } // end getSpecifiedUnit
@@ -582,7 +620,3 @@ export class UcumLhcUtils {
582
620
  UcumLhcUtils.getInstance = function(){
583
621
  return new UcumLhcUtils();
584
622
  } ;
585
-
586
-
587
-
588
-
@@ -116,7 +116,10 @@ export class UcumXmlDocument {
116
116
  attrs["exp_"] = pValNode.childNamed('sup');
117
117
  if (attrs["exp_"] != null) {
118
118
  attrs["exp_"] = attrs["exp_"].val;
119
- attrs["value_"] = Math.pow(10, attrs["exp_"]);
119
+ // Use parseFloat('1eSOMETHING') instead of Math.pow() to avoid
120
+ // small number changes like 1.0000000000000001e-21. See LF-2830.
121
+ // attrs["value_"] = Math.pow(10, attrs["exp_"]);
122
+ attrs["value_"] = parseFloat(`1e${attrs["exp_"]}`);
120
123
  }
121
124
  else {
122
125
  attrs["value_"] = pValNode.val;
@@ -519,4 +522,4 @@ UcumXmlDocument.getInstance = function(){
519
522
 
520
523
  // Perform the first request for the document object, to get the
521
524
  // getInstance method set.
522
- UcumXmlDocument.getInstance();
525
+ UcumXmlDocument.getInstance();
package/source/unit.js CHANGED
@@ -839,7 +839,8 @@ export class Unit {
839
839
  invertString(theString) {
840
840
 
841
841
  if (theString.length > 0) {
842
- let stringRep = theString.replace('/', "!").replace('.', '/').replace("!", '.');
842
+ // replace('<!', '</') is here to make sure closing html tags like </sup> are intact. See LF-2830.
843
+ let stringRep = theString.replace('/', "!").replace('.', '/').replace('<!', '</').replace("!", '.');
843
844
  switch(stringRep.charAt(0)) {
844
845
  case '.' : theString = stringRep.substr(1); break;
845
846
  case '/' : theString = stringRep; break;
@@ -878,11 +878,16 @@ export class UnitString {
878
878
  else {
879
879
 
880
880
  if (intUtils_.isNumericString(aftText)) {
881
- pStr += aftText;
882
- retUnit = retUnit.power(Number(aftText));
883
- this.retMsg_.push(`An exponent (${aftText}) following a parenthesis ` +
884
- `is invalid as of revision 1.9 of the UCUM Specification.\n ` +
885
- this.vcMsgStart_ + pStr + this.vcMsgEnd_);
881
+ retUnit = null;
882
+ let msg = `An exponent (${aftText}) following a parenthesis ` +
883
+ `is invalid as of revision 1.9 of the UCUM Specification.`;
884
+ // Add the suggestion only if the string in the parenthesis don't end with a number.
885
+ if (!pStr.match(/\d$/)) {
886
+ pStr += aftText;
887
+ msg += '\n ' + this.vcMsgStart_ + pStr + this.vcMsgEnd_;
888
+ }
889
+ this.retMsg_.push(msg);
890
+ endProcessing = true;
886
891
  }
887
892
  // else the text after the parentheses is neither a number nor
888
893
  // an annotation. If suggestions were NOT requested, record an
@@ -1149,143 +1154,150 @@ export class UnitString {
1149
1154
  origUnit = this.utabs_.getUnitByCode(uCode);
1150
1155
  }
1151
1156
 
1157
+ // If an exponent is found but it's not a valid number, e.g. "2-1",
1158
+ // mark the unit invalid. Otherwise, the "-1" part will be ignored
1159
+ // because parseInt("2-1") results in 2. See LF-2870.
1160
+ if (exp && isNaN(exp)) {
1161
+ retUnit = null;
1162
+ this.retMsg_.push(`${origCode} is not a valid UCUM code.`);
1163
+ }
1164
+ else {
1165
+ // If we still don't have a unit, separate out the prefix, if any,
1166
+ // and try without it.
1167
+ if (!origUnit) {
1168
+ // Try for a single character prefix first.
1169
+ pfxCode = uCode.charAt(0);
1170
+ pfxObj = this.pfxTabs_.getPrefixByCode(pfxCode);
1171
+
1172
+ // if we got a prefix, get its info and remove it from the unit code
1173
+ if (pfxObj) {
1174
+ pfxVal = pfxObj.getValue();
1175
+ pfxExp = pfxObj.getExp();
1176
+ let pCodeLen = pfxCode.length;
1177
+ uCode = uCode.substr(pCodeLen);
1152
1178
 
1153
- // If we still don't have a unit, separate out the prefix, if any,
1154
- // and try without it.
1155
- if (!origUnit) {
1156
- // Try for a single character prefix first.
1157
- pfxCode = uCode.charAt(0);
1158
- pfxObj = this.pfxTabs_.getPrefixByCode(pfxCode);
1159
-
1160
- // if we got a prefix, get its info and remove it from the unit code
1161
- if (pfxObj) {
1162
- pfxVal = pfxObj.getValue();
1163
- pfxExp = pfxObj.getExp();
1164
- let pCodeLen = pfxCode.length;
1165
- uCode = uCode.substr(pCodeLen);
1179
+ // try again for the unit
1180
+ origUnit = this.utabs_.getUnitByCode(uCode);
1166
1181
 
1167
- // try again for the unit
1168
- origUnit = this.utabs_.getUnitByCode(uCode);
1182
+ // If we still don't have a unit, see if the prefix could be the
1183
+ // two character "da" (deka) prefix. That's the only prefix with
1184
+ // two characters, and without this check it's interpreted as "d"
1185
+ // (deci) and the "a" is considered part of the unit code.
1169
1186
 
1170
- // If we still don't have a unit, see if the prefix could be the
1171
- // two character "da" (deka) prefix. That's the only prefix with
1172
- // two characters, and without this check it's interpreted as "d"
1173
- // (deci) and the "a" is considered part of the unit code.
1187
+ if (!origUnit && pfxCode == 'd' && uCode.substr(0, 1) == 'a') {
1188
+ pfxCode = 'da';
1189
+ pfxObj = this.pfxTabs_.getPrefixByCode(pfxCode);
1190
+ pfxVal = pfxObj.getValue();
1191
+ uCode = uCode.substr(1);
1174
1192
 
1175
- if (!origUnit && pfxCode == 'd' && uCode.substr(0, 1) == 'a') {
1176
- pfxCode = 'da';
1177
- pfxObj = this.pfxTabs_.getPrefixByCode(pfxCode);
1178
- pfxVal = pfxObj.getValue();
1179
- uCode = uCode.substr(1);
1193
+ // try one more time for the unit
1194
+ origUnit = this.utabs_.getUnitByCode(uCode);
1195
+ }
1180
1196
 
1181
- // try one more time for the unit
1182
- origUnit = this.utabs_.getUnitByCode(uCode);
1197
+ // Reject the unit we found if it might have another prefix.
1198
+ // Such things are in our tables through the LOINC source_
1199
+ // (ucum.csv) which has guidance and synonyms. I think it should be
1200
+ // safe to exclude anything whose source is LOINC from having a
1201
+ // prefix.
1202
+ if (origUnit && origUnit.source_ == 'LOINC')
1203
+ origUnit = null;
1204
+ } // end if we found a prefix
1205
+ } // end if we didn't get a unit after removing an exponent
1206
+
1207
+ // If we still haven't found anything, we're done looking.
1208
+ // (We tried with the full unit string, with the unit string
1209
+ // without the exponent, the unit string without a prefix,
1210
+ // common errors, etc. That's all we can try).
1211
+ if (!origUnit) {
1212
+ retUnit = null ;
1213
+ // BUT if the user asked for suggestions, at least look for them
1214
+ if (this.suggestions_) {
1215
+ let suggestStat = this._getSuggestions(origCode);
1216
+ }
1217
+ else {
1218
+ this.retMsg_.push(`${origCode} is not a valid UCUM code.`);
1183
1219
  }
1184
-
1185
- // Reject the unit we found if it might have another prefix.
1186
- // Such things are in our tables through the LOINC source_
1187
- // (ucum.csv) which has guidance and synonyms. I think it should be
1188
- // safe to exclude anything whose source is LOINC from having a
1189
- // prefix.
1190
- if (origUnit && origUnit.source_ == 'LOINC')
1191
- origUnit = null;
1192
- } // end if we found a prefix
1193
- } // end if we didn't get a unit after removing an exponent
1194
-
1195
- // If we still haven't found anything, we're done looking.
1196
- // (We tried with the full unit string, with the unit string
1197
- // without the exponent, the unit string without a prefix,
1198
- // common errors, etc. That's all we can try).
1199
- if (!origUnit) {
1200
- retUnit = null ;
1201
- // BUT if the user asked for suggestions, at least look for them
1202
- if (this.suggestions_) {
1203
- let suggestStat = this._getSuggestions(origCode);
1204
1220
  }
1205
1221
  else {
1206
- this.retMsg_.push(`${origCode} is not a valid UCUM code.`);
1207
- }
1208
- }
1209
- else {
1210
- // Otherwise we found a unit object. Clone it and then apply the
1211
- // prefix and exponent, if any, to it. And remove the guidance.
1212
- retUnit = origUnit.clone();
1213
- // If we are here, this is only part of the full unit string, so it is
1214
- // not a base unit, and the synonyms will mostly likely not be correct for the full
1215
- // string.
1216
- retUnit.resetFieldsForDerivedUnit();
1217
- let theDim = retUnit.getProperty('dim_');
1218
- let theMag = retUnit.getProperty('magnitude_');
1219
- let theName = retUnit.getProperty('name_');
1220
- let theCiCode = retUnit.getProperty('ciCode_');
1221
- let thePrintSymbol = retUnit.getProperty('printSymbol_');
1222
- // If there is an exponent for the unit, apply it to the dimension
1223
- // and magnitude now
1224
- if (exp) {
1225
- exp = parseInt(exp);
1226
- let expMul = exp;
1227
- if (theDim)
1228
- theDim = theDim.mul(exp);
1229
- theMag = Math.pow(theMag, exp);
1230
- retUnit.assignVals({'magnitude_': theMag});
1231
-
1232
- // If there is also a prefix, apply the exponent to the prefix.
1222
+ // Otherwise we found a unit object. Clone it and then apply the
1223
+ // prefix and exponent, if any, to it. And remove the guidance.
1224
+ retUnit = origUnit.clone();
1225
+ // If we are here, this is only part of the full unit string, so it is
1226
+ // not a base unit, and the synonyms will mostly likely not be correct for the full
1227
+ // string.
1228
+ retUnit.resetFieldsForDerivedUnit();
1229
+ let theDim = retUnit.getProperty('dim_');
1230
+ let theMag = retUnit.getProperty('magnitude_');
1231
+ let theName = retUnit.getProperty('name_');
1232
+ let theCiCode = retUnit.getProperty('ciCode_');
1233
+ let thePrintSymbol = retUnit.getProperty('printSymbol_');
1234
+ // If there is an exponent for the unit, apply it to the dimension
1235
+ // and magnitude now
1236
+ if (exp) {
1237
+ exp = parseInt(exp);
1238
+ let expMul = exp;
1239
+ if (theDim)
1240
+ theDim = theDim.mul(exp);
1241
+ theMag = Math.pow(theMag, exp);
1242
+ retUnit.assignVals({'magnitude_': theMag});
1243
+
1244
+ // If there is also a prefix, apply the exponent to the prefix.
1245
+ if (pfxObj) {
1246
+
1247
+ // if the prefix base is 10 it will have an exponent. Multiply
1248
+ // the current prefix exponent by the exponent for the unit
1249
+ // we're working with. Then raise the prefix value to the level
1250
+ // defined by the exponent.
1251
+ if (pfxExp) {
1252
+ expMul *= pfxObj.getExp();
1253
+ pfxVal = Math.pow(10, expMul);
1254
+ }
1255
+ // If the prefix base is not 10, it won't have an exponent.
1256
+ // At the moment I don't see any units using the prefixes
1257
+ // that aren't base 10. But if we get one the prefix value
1258
+ // will be applied to the magnitude (below) if the unit does
1259
+ // not have a conversion function, and to the conversion prefix
1260
+ // if it does.
1261
+ } // end if there's a prefix as well as the exponent
1262
+ } // end if there's an exponent
1263
+
1264
+ // Now apply the prefix, if there is one, to the conversion
1265
+ // prefix or the magnitude
1233
1266
  if (pfxObj) {
1234
-
1235
- // if the prefix base is 10 it will have an exponent. Multiply
1236
- // the current prefix exponent by the exponent for the unit
1237
- // we're working with. Then raise the prefix value to the level
1238
- // defined by the exponent.
1239
- if (pfxExp) {
1240
- expMul *= pfxObj.getExp();
1241
- pfxVal = Math.pow(10, expMul);
1267
+ if (retUnit.cnv_) {
1268
+ retUnit.assignVals({'cnvPfx_': pfxVal});
1269
+ }
1270
+ else {
1271
+ theMag *= pfxVal;
1272
+ retUnit.assignVals({'magnitude_': theMag})
1242
1273
  }
1243
- // If the prefix base is not 10, it won't have an exponent.
1244
- // At the moment I don't see any units using the prefixes
1245
- // that aren't base 10. But if we get one the prefix value
1246
- // will be applied to the magnitude (below) if the unit does
1247
- // not have a conversion function, and to the conversion prefix
1248
- // if it does.
1249
- } // end if there's a prefix as well as the exponent
1250
- } // end if there's an exponent
1251
-
1252
- // Now apply the prefix, if there is one, to the conversion
1253
- // prefix or the magnitude
1254
- if (pfxObj) {
1255
- if (retUnit.cnv_) {
1256
- retUnit.assignVals({'cnvPfx_': pfxVal});
1257
1274
  }
1258
- else {
1259
- theMag *= pfxVal;
1260
- retUnit.assignVals({'magnitude_': theMag})
1275
+ // if we have a prefix and/or an exponent, add them to the unit
1276
+ // attributes - name, csCode, ciCode and print symbol
1277
+ let theCode = retUnit.csCode_;
1278
+ if (pfxObj) {
1279
+ theName = pfxObj.getName() + theName;
1280
+ theCode = pfxCode + theCode;
1281
+ theCiCode = pfxObj.getCiCode() + theCiCode;
1282
+ thePrintSymbol = pfxObj.getPrintSymbol() + thePrintSymbol;
1283
+ retUnit.assignVals({
1284
+ 'name_': theName,
1285
+ 'csCode_': theCode,
1286
+ 'ciCode_': theCiCode,
1287
+ 'printSymbol_': thePrintSymbol
1288
+ });
1261
1289
  }
1262
- }
1263
- // if we have a prefix and/or an exponent, add them to the unit
1264
- // attributes - name, csCode, ciCode and print symbol
1265
- let theCode = retUnit.csCode_;
1266
- if (pfxObj) {
1267
- theName = pfxObj.getName() + theName;
1268
- theCode = pfxCode + theCode;
1269
- theCiCode = pfxObj.getCiCode() + theCiCode;
1270
- thePrintSymbol = pfxObj.getPrintSymbol() + thePrintSymbol;
1271
- retUnit.assignVals({
1272
- 'name_': theName,
1273
- 'csCode_': theCode,
1274
- 'ciCode_': theCiCode,
1275
- 'printSymbol_': thePrintSymbol
1276
- });
1277
- }
1278
- if (exp) {
1279
- let expStr = exp.toString();
1280
- retUnit.assignVals({
1281
- 'name_': theName + '<sup>' + expStr + '</sup>',
1282
- 'csCode_': theCode + expStr,
1283
- 'ciCode_': theCiCode + expStr,
1284
- 'printSymbol_': thePrintSymbol + '<sup>' + expStr + '</sup>'
1285
- });
1286
- }
1287
- } // end if an original unit was found (without prefix and/or exponent)
1288
-
1290
+ if (exp) {
1291
+ let expStr = exp.toString();
1292
+ retUnit.assignVals({
1293
+ 'name_': theName + '<sup>' + expStr + '</sup>',
1294
+ 'csCode_': theCode + expStr,
1295
+ 'ciCode_': theCiCode + expStr,
1296
+ 'printSymbol_': thePrintSymbol + '<sup>' + expStr + '</sup>'
1297
+ });
1298
+ }
1299
+ } // end if an original unit was found (without prefix and/or exponent)
1300
+ } // end if an invalid exponent wasn't found
1289
1301
  } // end if we didn't get a unit for the full unit code (w/out modifiers)
1290
1302
  } // end if we didn't find the unit on the first try, before parsing
1291
1303
  return [retUnit, origString];
@@ -1330,24 +1342,31 @@ export class UnitString {
1330
1342
  let tryBrackets = '[' + annoText.substring(1, annoText.length - 1) + ']';
1331
1343
  let mkUnitRet = this._makeUnit(tryBrackets, origString);
1332
1344
 
1333
- // If we got back a unit, assign it to the returned unit, and add
1334
- // a message to advise the user that brackets should enclose the code
1345
+ // Nearly anything inside braces is valid, so we don't want to change the
1346
+ // unit, but we can put the found unit in the message as a sort of
1347
+ // warning.
1335
1348
  if (mkUnitRet[0]) {
1336
- retUnit = mkUnitRet[0];
1337
- origString = origString.replace(annoText, tryBrackets);
1338
- this.retMsg_.push(`${annoText} is not a valid unit expression, but ` +
1339
- `${tryBrackets} is.\n` + this.vcMsgStart_ +
1340
- `${tryBrackets} (${retUnit.name_})${this.vcMsgEnd_}`);
1349
+ retUnit = uCode;
1350
+ this.retMsg_.push(`${annoText} is a valid unit expression, but ` +
1351
+ `did you mean ${tryBrackets} (${mkUnitRet[0].name_})?`);
1341
1352
  }
1342
- // Otherwise assume that this should be interpreted as a 1
1343
1353
  else {
1344
1354
  // remove error message generated for trybrackets
1345
1355
  if (this.retMsg_.length > msgLen) {
1346
1356
  this.retMsg_.pop();
1347
1357
  }
1348
- uCode = 1;
1349
- retUnit = 1;
1350
1358
  }
1359
+
1360
+ // This is the case where the string is only this annotation.
1361
+ // Create and return a unit object, as we do for numeric units in
1362
+ // parseString.
1363
+ retUnit = new Unit({
1364
+ 'csCode_': annoText,
1365
+ 'ciCode_': annoText,
1366
+ 'magnitude_': 1,
1367
+ 'name_': annoText
1368
+ });
1369
+
1351
1370
  } // end if it's only an annotation
1352
1371
 
1353
1372
  else {