@lhncbc/ucum-lhc 7.1.5 → 7.1.8

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.
@@ -990,34 +990,6 @@ class UnitString {
990
990
  retUnit.ciCode_ = retUnit.ciCode_.replace('*', '^');
991
991
  }
992
992
  }
993
- // If that didn't work, check to see if it should have brackets
994
- // around it (uCode = degF when it should be [degF]
995
- if (!retUnit) {
996
- let addBrackets = '[' + uCode + ']';
997
- retUnit = this.utabs_.getUnitByCode(addBrackets);
998
- if (retUnit) {
999
- retUnit = retUnit.clone();
1000
- origString = origString.replace(uCode, addBrackets);
1001
- this.retMsg_.push(`${uCode} is not a valid unit expression, but ` + `${addBrackets} is.\n` + this.vcMsgStart_ + `${addBrackets} (${retUnit.name_})${this.vcMsgEnd_}`);
1002
- } // end if we found the unit after adding brackets
1003
- } // end trying to add brackets
1004
-
1005
- // If we didn't find it, try it as a name
1006
- if (!retUnit) {
1007
- let retUnitAry = this.utabs_.getUnitByName(uCode);
1008
- if (retUnitAry && retUnitAry.length > 0) {
1009
- retUnit = retUnitAry[0].clone();
1010
- let mString = 'The UCUM code for ' + uCode + ' is ' + retUnit.csCode_ + '.\n' + this.vcMsgStart_ + retUnit.csCode_ + this.vcMsgEnd_;
1011
- let dupMsg = false;
1012
- for (let r = 0; r < this.retMsg_.length && !dupMsg; r++) dupMsg = this.retMsg_[r] === mString;
1013
- if (!dupMsg) this.retMsg_.push(mString);
1014
- let rStr = new RegExp('(^|[.\/({])(' + uCode + ')($|[.\/)}])');
1015
- let res = origString.match(rStr);
1016
- origString = origString.replace(rStr, res[1] + retUnit.csCode_ + res[3]);
1017
- uCode = retUnit.csCode_;
1018
- }
1019
- }
1020
-
1021
993
  // If we still don't have a unit, try assuming a modifier (prefix and/or
1022
994
  // exponent) and look for a unit without the modifier
1023
995
  if (!retUnit) {
@@ -1064,7 +1036,7 @@ class UnitString {
1064
1036
  } else {
1065
1037
  // If we still don't have a unit, separate out the prefix, if any,
1066
1038
  // and try without it.
1067
- if (!origUnit) {
1039
+ if (!origUnit && uCode.length > 1) {
1068
1040
  // Try for a single character prefix first, then for a two-digit prefix
1069
1041
  pfxCode = '';
1070
1042
  do {
@@ -1076,7 +1048,7 @@ class UnitString {
1076
1048
  // try again for the unit
1077
1049
  origUnit = this.utabs_.getUnitByCode(uCode);
1078
1050
  }
1079
- } while (!origUnit && pfxCode.length < 2);
1051
+ } while (!origUnit && pfxCode.length < 2 && uCode.length > 1);
1080
1052
 
1081
1053
  // Reject the unit we found if it might have another prefix. (??)
1082
1054
  // Such things are in our tables through the LOINC source_
@@ -1091,12 +1063,21 @@ class UnitString {
1091
1063
  // without the exponent, the unit string without a prefix,
1092
1064
  // common errors, etc. That's all we can try).
1093
1065
  if (!origUnit) {
1094
- retUnit = null;
1095
- // BUT if the user asked for suggestions, at least look for them
1096
- if (this.suggestions_) {
1097
- let suggestStat = this._getSuggestions(origCode);
1098
- } else {
1099
- this.retMsg_.push(`${origCode} is not a valid UCUM code.`);
1066
+ let bracketRet = this._getUnitAfterAddingBrackets(origCode, origString);
1067
+ retUnit = bracketRet[0];
1068
+ origString = bracketRet[1];
1069
+ if (!retUnit) {
1070
+ let nameRet = this._getUnitByName(origCode, origString);
1071
+ retUnit = nameRet[0];
1072
+ origString = nameRet[1];
1073
+ if (!retUnit) {
1074
+ // BUT if the user asked for suggestions, at least look for them
1075
+ if (this.suggestions_) {
1076
+ let suggestStat = this._getSuggestions(origCode);
1077
+ } else {
1078
+ this.retMsg_.push(`${origCode} is not a valid UCUM code.`);
1079
+ }
1080
+ }
1100
1081
  }
1101
1082
  } else {
1102
1083
  // Otherwise we found a unit object. Clone it and then apply the
@@ -1114,62 +1095,83 @@ class UnitString {
1114
1095
  // If there is an exponent for the unit, apply it to the dimension
1115
1096
  // and magnitude now
1116
1097
  if (exp) {
1117
- exp = parseInt(exp);
1118
- if (theDim) theDim = theDim.mul(exp);
1119
- retUnit.equivalentExp_ *= exp;
1120
- retUnit.moleExp_ *= exp;
1121
- theMag = Math.pow(theMag, exp);
1122
- retUnit.assignVals({
1123
- 'magnitude_': theMag
1124
- });
1098
+ // Special units cannot be raised to a power
1099
+ if (retUnit.isSpecial_) {
1100
+ this.retMsg_.push(`Special units like ${retUnit.name_} cannot be raised to a power.`);
1101
+ retUnit = null;
1102
+ } else {
1103
+ exp = parseInt(exp);
1104
+ if (theDim) theDim = theDim.mul(exp);
1105
+ retUnit.equivalentExp_ *= exp;
1106
+ retUnit.moleExp_ *= exp;
1107
+ theMag = Math.pow(theMag, exp);
1108
+ retUnit.assignVals({
1109
+ 'magnitude_': theMag
1110
+ });
1125
1111
 
1126
- // If there is also a prefix, apply the exponent to the prefix.
1127
- if (pfxObj) {
1128
- // We don't need to consider pfxObj.getExp(), because when
1129
- // present that is reflected in the pfxVal.
1130
- pfxVal = Math.pow(pfxVal, exp);
1131
- }
1112
+ // If there is also a prefix, apply the exponent to the prefix.
1113
+ if (pfxObj) {
1114
+ // We don't need to consider pfxObj.getExp(), because when
1115
+ // present that is reflected in the pfxVal. However, in some
1116
+ // cases one can avoid floating-point math inaccuracies by using
1117
+ // that exponent instead of relying on pfxVal. For example:
1118
+ // 1e-66 = Math.pow(10, -3*22) = Math.pow(0.001, 22) = 1.0000000000000005e-66
1119
+ // (This is the from the test case of the unit mg% raised to the 22nd power (mg%22).)
1120
+ // This does not help in all cases, but it does help the above
1121
+ // test case (which is in our web API service test code).
1122
+ let pfxExp = pfxObj.getExp();
1123
+ if (pfxExp) {
1124
+ // This is relying on the fact that pfxExp is null when
1125
+ // the prefix base is not 10.
1126
+ pfxVal = Math.pow(10, exp * pfxExp);
1127
+ } else {
1128
+ pfxVal = Math.pow(pfxVal, exp);
1129
+ }
1130
+ }
1131
+ } // end else - prefix and exponent handling for non-special units
1132
1132
  } // end if there's an exponent
1133
1133
 
1134
- // Now apply the prefix, if there is one, to the conversion
1135
- // prefix or the magnitude
1136
- if (pfxObj) {
1137
- if (retUnit.cnv_) {
1134
+ if (retUnit) {
1135
+ // Now apply the prefix, if there is one, to the conversion
1136
+ // prefix or the magnitude
1137
+ if (pfxObj) {
1138
+ if (retUnit.cnv_) {
1139
+ retUnit.assignVals({
1140
+ 'cnvPfx_': pfxVal
1141
+ });
1142
+ } else {
1143
+ theMag *= pfxVal;
1144
+ retUnit.assignVals({
1145
+ 'magnitude_': theMag
1146
+ });
1147
+ }
1148
+ }
1149
+ // if we have a prefix and/or an exponent, add them to the unit
1150
+ // attributes - name, csCode, ciCode and print symbol
1151
+ let theCode = retUnit.csCode_;
1152
+ if (pfxObj) {
1153
+ theName = pfxObj.getName() + theName;
1154
+ theCode = pfxCode + theCode;
1155
+ theCiCode = pfxObj.getCiCode() + theCiCode;
1156
+ thePrintSymbol = pfxObj.getPrintSymbol() + thePrintSymbol;
1138
1157
  retUnit.assignVals({
1139
- 'cnvPfx_': pfxVal
1158
+ 'name_': theName,
1159
+ 'csCode_': theCode,
1160
+ 'ciCode_': theCiCode,
1161
+ 'printSymbol_': thePrintSymbol
1140
1162
  });
1141
- } else {
1142
- theMag *= pfxVal;
1163
+ }
1164
+ if (exp) {
1165
+ let expStr = exp.toString();
1166
+ const intergerUnitExpSign = isIntegerUnitWithExp && exp > 0 ? '+' : '';
1143
1167
  retUnit.assignVals({
1144
- 'magnitude_': theMag
1168
+ 'name_': theName + '<sup>' + expStr + '</sup>',
1169
+ 'csCode_': theCode + intergerUnitExpSign + expStr,
1170
+ 'ciCode_': theCiCode + intergerUnitExpSign + expStr,
1171
+ 'printSymbol_': thePrintSymbol + '<sup>' + expStr + '</sup>'
1145
1172
  });
1146
1173
  }
1147
1174
  }
1148
- // if we have a prefix and/or an exponent, add them to the unit
1149
- // attributes - name, csCode, ciCode and print symbol
1150
- let theCode = retUnit.csCode_;
1151
- if (pfxObj) {
1152
- theName = pfxObj.getName() + theName;
1153
- theCode = pfxCode + theCode;
1154
- theCiCode = pfxObj.getCiCode() + theCiCode;
1155
- thePrintSymbol = pfxObj.getPrintSymbol() + thePrintSymbol;
1156
- retUnit.assignVals({
1157
- 'name_': theName,
1158
- 'csCode_': theCode,
1159
- 'ciCode_': theCiCode,
1160
- 'printSymbol_': thePrintSymbol
1161
- });
1162
- }
1163
- if (exp) {
1164
- let expStr = exp.toString();
1165
- const intergerUnitExpSign = isIntegerUnitWithExp && exp > 0 ? '+' : '';
1166
- retUnit.assignVals({
1167
- 'name_': theName + '<sup>' + expStr + '</sup>',
1168
- 'csCode_': theCode + intergerUnitExpSign + expStr,
1169
- 'ciCode_': theCiCode + intergerUnitExpSign + expStr,
1170
- 'printSymbol_': thePrintSymbol + '<sup>' + expStr + '</sup>'
1171
- });
1172
- }
1173
1175
  } // end if an original unit was found (without prefix and/or exponent)
1174
1176
  } // end if an invalid exponent wasn't found
1175
1177
  } // end if we didn't get a unit for the full unit code (w/out modifiers)
@@ -1177,6 +1179,75 @@ class UnitString {
1177
1179
  return [retUnit, origString];
1178
1180
  } // end _makeUnit
1179
1181
 
1182
+ /**
1183
+ * Checks whether an otherwise unresolved unit code matches a unit name.
1184
+ *
1185
+ * @param uCode the unit code or name to check
1186
+ * @param origString the original full string submitted to parseString
1187
+ * @returns an array containing the unit object found, or null, and origString
1188
+ */
1189
+ _getUnitByName(uCode, origString) {
1190
+ let retUnit = null;
1191
+ let retUnitAry = this.utabs_.getUnitByName(uCode);
1192
+ if (retUnitAry && retUnitAry.length > 0) {
1193
+ retUnit = retUnitAry[0].clone();
1194
+ let mString = 'The UCUM code for ' + uCode + ' is ' + retUnit.csCode_ + '.\n' + this.vcMsgStart_ + retUnit.csCode_ + this.vcMsgEnd_;
1195
+ let dupMsg = false;
1196
+ for (let r = 0; r < this.retMsg_.length && !dupMsg; r++) dupMsg = this.retMsg_[r] === mString;
1197
+ if (!dupMsg) this.retMsg_.push(mString);
1198
+ const escapedCode = uCode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1199
+ let rStr = new RegExp('(^|[./(])(' + escapedCode + ')($|[./)\\-\\d{])');
1200
+ const updatedOrigString = origString.replace(rStr, '$1' + retUnit.csCode_ + '$3');
1201
+ if (updatedOrigString == origString) {
1202
+ // This should not happen, if the processing has been correct. However, if it does happen, we
1203
+ // still have to change origString to signal that the input unit is invalid.
1204
+ // There is a test present to make sure this message does not appear from the top-level APIs in
1205
+ // ucumLhcUtils.js.
1206
+ // Ideally, this problem would be signalled some other way, but that would be a bigger change.
1207
+ origString += ' (Unable to update the unit expression with a suggested replacement.)';
1208
+ } else {
1209
+ origString = updatedOrigString;
1210
+ }
1211
+ }
1212
+ return [retUnit, origString];
1213
+ } // end _getUnitByName
1214
+
1215
+ /**
1216
+ * Checks whether an otherwise unresolved unit code can be found after adding
1217
+ * square brackets, e.g., degF -> [degF]. If a bracketed unit is found,
1218
+ * origString is modified to include the suggested replacement.
1219
+ *
1220
+ * @param uCode the unit code to check
1221
+ * @param origString the original full string submitted to parseString
1222
+ * @returns an array containing the unit object found, or null, and the possibly
1223
+ * modified origString
1224
+ */
1225
+ _getUnitAfterAddingBrackets(uCode, origString) {
1226
+ let retUnit = null;
1227
+ const addBrackets = '[' + uCode + ']';
1228
+ const bracketUnit = this.utabs_.getUnitByCode(addBrackets);
1229
+ if (bracketUnit) {
1230
+ retUnit = bracketUnit.clone();
1231
+ const escapedCode = uCode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1232
+ const leadingUnitBoundary = '(^|[./(])';
1233
+ const trailingUnitBoundary = '($|[./)\\-\\d{])';
1234
+ const rStr = new RegExp(leadingUnitBoundary + '(' + escapedCode + ')' + trailingUnitBoundary);
1235
+ const updatedOrigString = origString.replace(rStr, '$1' + addBrackets + '$3');
1236
+ if (updatedOrigString == origString) {
1237
+ // This should not happen, if the processing has been correct. However, if it does happen, we
1238
+ // still have to change origString to signal that the input unit is invalid.
1239
+ // There is a test present to make sure this message does not appear from the top-level APIs in
1240
+ // ucumLhcUtils.js.
1241
+ // Ideally, this problem would be signalled some other way, but that would be a bigger change.
1242
+ origString += ' (Unable to update the unit expression with a suggested replacement.)';
1243
+ } else {
1244
+ origString = updatedOrigString;
1245
+ }
1246
+ this.retMsg_.push(`${uCode} is not a valid unit expression, but ` + `${addBrackets} is.\n` + this.vcMsgStart_ + `${addBrackets} (${retUnit.name_})${this.vcMsgEnd_}`);
1247
+ }
1248
+ return [retUnit, origString];
1249
+ } // end _getUnitAfterAddingBrackets
1250
+
1180
1251
  /**
1181
1252
  * This method handles unit creation when an annotation is included
1182
1253
  * in the unit string. This basically isolates and retrieves the