@lhncbc/ucum-lhc 5.0.4 → 6.0.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lhncbc/ucum-lhc",
3
- "version": "5.0.4",
3
+ "version": "6.0.1",
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
@@ -90,6 +90,24 @@ export var Ucum = {
90
90
  'molecular weight of the substance represented by the ' +
91
91
  'units is required to perform the conversion.',
92
92
 
93
+ /**
94
+ * Message that is returned when a mass<->eq conversion is requested
95
+ * (which requires a molecular weight to calculate), but no molecular
96
+ * weight was provided by the user.
97
+ */
98
+ needEqWeightMsg_ : 'Did you wish to convert with equivalents? The ' +
99
+ 'molecular weight of the substance is required to perform ' +
100
+ 'the conversion.',
101
+
102
+ /**
103
+ * Message that is returned when a mass<->eq or a mol<->eq conversion
104
+ * is requested (which requires a charge to calculate), but no charge
105
+ * was provided by the user.
106
+ */
107
+ needEqChargeMsg_ : 'Did you wish to convert with equivalents? The ' +
108
+ 'charge of the substance is required to perform ' +
109
+ 'the conversion.',
110
+
93
111
  /**
94
112
  * Hash that matches unit column names to names used in the csv file
95
113
  * that is submitted to the data updater.
@@ -145,71 +145,143 @@ export class UcumLhcUtils {
145
145
  } // end validateUnitString
146
146
 
147
147
 
148
+
149
+ /**
150
+ * @typedef {
151
+ * 'normal',
152
+ * 'mol->mass',
153
+ * 'mass->mol',
154
+ * 'eq->mass',
155
+ * 'mass->eq',
156
+ * 'eq->mol',
157
+ * 'mol->eq',
158
+ * } ConversionType
159
+ */
160
+
161
+ /**
162
+ * Detects the type of conversion between two units.
163
+ *
164
+ * @param {Object} fromUnit - The unit to convert from.
165
+ * @param {Object} toUnit - The unit to convert to.
166
+ * @returns {ConversionType} conversionType - The type of conversion as a string.
167
+ */
168
+ detectConversionType(fromUnit, toUnit) {
169
+ /** @type {ConversionType} */
170
+ let conversionType = 'normal';
171
+
172
+ // detect mol <-> mass conversions and eq <-> eq conversions,
173
+ // and non-mol <-> eq <-> mass conversions
174
+ if ((fromUnit.isMolarUnit() && toUnit.isMolarUnit()) ||
175
+ (fromUnit.isEquivalentUnit() && toUnit.isEquivalentUnit()) ||
176
+ (!(fromUnit.isMolarUnit() || toUnit.isMolarUnit()) &&
177
+ !(fromUnit.isEquivalentUnit() || toUnit.isEquivalentUnit()))) {
178
+ conversionType = 'normal';
179
+ }
180
+ // handle eq <-> mol/mass conversions
181
+ else if (fromUnit.isEquivalentUnit()) {
182
+ conversionType = toUnit.isMolarUnit() ? 'eq->mol' : 'eq->mass';
183
+ } else if (toUnit.isEquivalentUnit()) {
184
+ conversionType = fromUnit.isMolarUnit() ? 'mol->eq' : 'mass->eq';
185
+ }
186
+ // handle mol <-> mass conversions
187
+ else if (fromUnit.isMolarUnit()) {
188
+ conversionType = 'mol->mass';
189
+ } else if (toUnit.isMolarUnit()) {
190
+ conversionType = 'mass->mol';
191
+ }
192
+
193
+ return conversionType;
194
+ } // end detectConversionType
195
+
196
+ /**
197
+ * @typedef {{
198
+ * status: 'succeeded' | 'failed' | 'error',
199
+ * toVal: number | null,
200
+ * msg: string[],
201
+ * suggestions: {
202
+ * from: {
203
+ * msg: string,
204
+ * invalidUnit: string,
205
+ * units: string[]
206
+ * },
207
+ * to: {
208
+ * msg: string,
209
+ * invalidUnit: string,
210
+ * units: string[]
211
+ * }
212
+ * },
213
+ * fromUnit: string,
214
+ * toUnit: string
215
+ * }} ConvertUnitResult
216
+ */
217
+
218
+
148
219
  /**
149
220
  * This method converts one unit to another
150
221
  *
151
- * @param fromUnitCode the unit code/expression/string of the unit to be converted
152
- * @param fromVal the number of "from" units to be converted to "to" units
153
- * @param toUnitCode the unit code/expression/string of the unit that the from
154
- * field is to be converted to
155
- * @param suggest a boolean to indicate whether or not suggestions are
156
- * requested for a string that cannot be resolved to a valid unit;
157
- * true indicates suggestions are wanted; false indicates they are not,
158
- * and is the default if the parameter is not specified;
159
- * @param molecularWeight the molecular weight of the substance in question
160
- * when a conversion is being requested from mass to moles and vice versa.
161
- * This is required when one of the units represents a value in moles. It is
162
- * ignored if neither unit includes a measurement in moles.
163
- * @returns a hash with six elements:
164
- * 'status' that will be: 'succeeded' if the conversion was successfully
222
+ * @param {string} fromUnitCode - the unit code/expression/string of the unit to be converted
223
+ * @param {number | string} fromVal - the number of "from" units to be converted to "to" units
224
+ * @param {string} toUnitCode - the unit code/expression/string of the unit that the from field is to be converted to
225
+ * @param {{
226
+ * suggest?: boolean,
227
+ * molecularWeight?: number
228
+ * charge?: number
229
+ * }} options
230
+ * - suggest: a boolean to indicate whether or not suggestions are requested for a string that cannot be resolved to a valid unit;
231
+ * true indicates suggestions are wanted; false indicates they are not, and is the default if the parameter is not specified;
232
+ * - molecularWeight: the molecular weight of the substance in question when a conversion is being requested from mass to moles and vice versa.
233
+ * This is required when one of the units represents a value in moles. It is ignored if neither unit includes a measurement in moles.
234
+ * - charge: the absolute value of the charge of the substance in question when a conversion is being requested from mass/moles to
235
+ * equivalents and vice versa. It is required when one of the units represents a value in equivalents and the other in mass or moles.
236
+ * It is ignored if neither unit includes an equivalent unit.
237
+ * @returns {ConvertUnitResult}
238
+ * - a hash with six elements:
239
+ * - 'status' that will be: 'succeeded' if the conversion was successfully
165
240
  * calculated; 'failed' if the conversion could not be made, e.g., if
166
241
  * the units are not commensurable; or 'error' if an error occurred;
167
- * 'toVal' the numeric value indicating the conversion amount, or null
242
+ * - 'toVal' the numeric value indicating the conversion amount, or null
168
243
  * if the conversion failed (e.g., if the units are not commensurable);
169
- * 'msg' is an array message, if the string is invalid or an error occurred,
170
- * indicating the problem, or an explanation of a substitution such as
171
- * the substitution of 'G' for 'Gauss', or an empty array if no
172
- * messages were generated;
173
- * 'suggestions' if suggestions were requested and found, this is a hash
244
+ * - 'msg' is an array message, if the string is invalid or an error occurred,
245
+ * indicating the problem, or an explanation of a substitution such as
246
+ * the substitution of 'G' for 'Gauss', or an empty array if no
247
+ * messages were generated;
248
+ * - 'suggestions' if suggestions were requested and found, this is a hash
174
249
  * that contains at most two elements:
175
- * 'from' which, if the fromUnitCode input parameter or one or more of
250
+ * - 'from' which, if the fromUnitCode input parameter or one or more of
176
251
  * its components could not be found, is an array one or more hash
177
252
  * objects. Each hash contains three elements:
178
- * 'msg' which is a message indicating what unit expression the
179
- * suggestions are for;
180
- * 'invalidUnit' which is the unit expression the suggestions
181
- * are for; and
182
- * 'units' which is an array of data for each suggested unit found.
183
- * Each array will contain the unit code, the unit name and the
184
- * unit guidance (if any).
253
+ * - 'msg' which is a message indicating what unit expression the
254
+ * suggestions are for;
255
+ * - 'invalidUnit' which is the unit expression the suggestions
256
+ * are for; and
257
+ * - 'units' which is an array of data for each suggested unit found.
258
+ * Each array will contain the unit code, the unit name and the
259
+ * unit guidance (if any).
185
260
  * If no suggestions were found for the fromUnitCode this element
186
261
  * will not be included.
187
- * 'to' which, if the "to" unit expression or one or more of its
262
+ * - 'to' which, if the "to" unit expression or one or more of its
188
263
  * components could not be found, is an array one or more hash objects. Each hash
189
264
  * contains three elements:
190
- * 'msg' which is a message indicating what toUnitCode input
191
- * parameter the suggestions are for;
192
- * 'invalidUnit' which is the unit expression the suggestions
193
- * are for; and
194
- * 'units' which is an array of data for each suggested unit found.
195
- * Each array will contain the unit code, the unit name and the
196
- * unit guidance (if any).
265
+ * - 'msg' which is a message indicating what toUnitCode input
266
+ * parameter the suggestions are for;
267
+ * - 'invalidUnit' which is the unit expression the suggestions
268
+ * are for; and
269
+ * - 'units' which is an array of data for each suggested unit found.
270
+ * Each array will contain the unit code, the unit name and the
271
+ * unit guidance (if any).
197
272
  * If no suggestions were found for the toUnitCode this element
198
273
  * will not be included.
199
- * No 'suggestions' element will be included in the returned hash
200
- * object if none were found, whether or not they were requested.
201
- * 'fromUnit' the unit object for the fromUnitCode passed in; returned
274
+ * No 'suggestions' element will be included in the returned hash
275
+ * object if none were found, whether or not they were requested.
276
+ * - 'fromUnit' the unit object for the fromUnitCode passed in; returned
202
277
  * in case it's needed for additional data from the object; and
203
- * 'toUnit' the unit object for the toUnitCode passed in; returned
278
+ * - 'toUnit' the unit object for the toUnitCode passed in; returned
204
279
  * in case it's needed for additional data from the object.
205
280
  */
206
- convertUnitTo(fromUnitCode, fromVal, toUnitCode, suggest, molecularWeight) {
207
- if (suggest === undefined)
208
- suggest = false ;
209
-
210
- if (molecularWeight === undefined)
211
- molecularWeight = null ;
281
+ convertUnitTo(fromUnitCode, fromVal, toUnitCode, options = {}) {
282
+ let { suggest = false, molecularWeight = null, charge = null } = options;
212
283
 
284
+ /** @type {ConvertUnitResult} */
213
285
  let returnObj = {'status' : 'failed',
214
286
  'toVal' : null,
215
287
  'msg' : []} ;
@@ -263,40 +335,53 @@ export class UcumLhcUtils {
263
335
 
264
336
  if (fromUnit && toUnit) {
265
337
  try {
266
- // if no molecular weight was specified perform a normal conversion
267
- if (!molecularWeight) {
268
- returnObj['toVal'] = toUnit.convertFrom(fromVal, fromUnit);
269
- }
270
- else {
271
- if (fromUnit.moleExp_ !== 0 && toUnit.moleExp_ !== 0) {
272
- throw(new Error('A molecular weight was specified ' +
273
- 'but a mass <-> mole conversion cannot be executed for two ' +
274
- 'mole-based units. No conversion was attempted.'));
275
- }
276
- if (fromUnit.moleExp_ === 0 && toUnit.moleExp_ === 0) {
277
- throw(new Error('A molecular weight was specified ' +
278
- 'but a mass <-> mole conversion cannot be executed when ' +
279
- 'neither unit is mole-based. No conversion was attempted.'));
280
- }
281
- if (!fromUnit.isMoleMassCommensurable(toUnit)) {
282
- throw(new Error(`Sorry. ${fromUnitCode} cannot be ` +
283
- `converted to ${toUnitCode}.`));
284
- }
285
-
286
- // if the "from" unit is a mole-based unit, assume a mole to mass
287
- // request
288
- if (fromUnit.moleExp_ !== 0) {
289
- returnObj['toVal'] =
290
- fromUnit.convertMolToMass(fromVal, toUnit, molecularWeight);
291
- }
292
- // else the "to" unit must be the mole-based unit, so assume a
293
- // mass to mole request
294
- else {
295
- returnObj['toVal'] =
338
+ const convertType = this.detectConversionType(fromUnit, toUnit);
339
+
340
+ switch (convertType) {
341
+ case 'normal':
342
+ returnObj['toVal'] = toUnit.convertFrom(fromVal, fromUnit);
343
+ break;
344
+ case 'mol->mass':
345
+ case 'mass->mol':
346
+ if (!molecularWeight) {
347
+ throw new Error(Ucum.needMoleWeightMsg_);
348
+ }
349
+ if (!fromUnit.isMoleMassCommensurable(toUnit)) {
350
+ throw new Error(`Sorry. ${fromUnitCode} cannot be ` +
351
+ `converted to ${toUnitCode}.`);
352
+ }
353
+ returnObj['toVal'] = convertType === "mol->mass" ?
354
+ fromUnit.convertMolToMass(fromVal, toUnit, molecularWeight) :
296
355
  fromUnit.convertMassToMol(fromVal, toUnit, molecularWeight);
297
- }
298
- } // end if a molecular weight was specified
299
-
356
+ break;
357
+ case 'eq->mass':
358
+ case 'mass->eq':
359
+ if (!molecularWeight) {
360
+ throw new Error(Ucum.needEqWeightMsg_);
361
+ }
362
+ if (!charge) {
363
+ throw new Error(Ucum.needEqChargeMsg_);
364
+ }
365
+ if (!fromUnit.isEqMassCommensurable(toUnit)) {
366
+ throw new Error(`Sorry. ${fromUnitCode} cannot be ` +
367
+ `converted to ${toUnitCode}.`);
368
+ }
369
+ returnObj['toVal'] = convertType === "eq->mass" ?
370
+ fromUnit.convertEqToMass(fromVal, toUnit, molecularWeight, charge) :
371
+ fromUnit.convertMassToEq(fromVal, toUnit, molecularWeight, charge);
372
+ break;
373
+ case 'eq->mol':
374
+ case 'mol->eq':
375
+ if (!charge) {
376
+ throw new Error(Ucum.needEqChargeMsg_);
377
+ }
378
+ returnObj['toVal'] = convertType === "eq->mol" ?
379
+ fromUnit.convertEqToMol(fromVal, toUnit, charge) :
380
+ fromUnit.convertMolToEq(fromVal, toUnit, charge);
381
+ break;
382
+ default:
383
+ throw new Error("Unknown conversion type. No conversion was attempted.");
384
+ }
300
385
  // if an error hasn't been thrown - either from convertFrom or here,
301
386
  // set the return object to show success
302
387
  returnObj['status'] = 'succeeded';
@@ -307,10 +392,8 @@ export class UcumLhcUtils {
307
392
  returnObj['status'] = 'failed';
308
393
  returnObj['msg'].push(err.message);
309
394
  }
310
-
311
-
312
- } // end if we have the from and to units
313
- }
395
+ } // end if we have the from and to units
396
+ }
314
397
  catch (err) {
315
398
  if (err.message == Ucum.needMoleWeightMsg_)
316
399
  returnObj['status'] = 'failed';
@@ -45,7 +45,11 @@ export class UcumXmlDocument {
45
45
  // Creation of unit objects after this file is processed will pick up
46
46
  // the moleExp_ value from the base mole unit, but the ones defined in
47
47
  // this file will not necessarily do that.
48
- this.moleCodes_ = ['mol', 'eq', 'osm', 'kat', 'U' ];
48
+ this.moleCodes_ = ['mol', 'osm', 'kat', 'U' ];
49
+ // Works similarly to the moleCodes_ array, but for units that represents
50
+ // equivalent units. For unit codes in the equivCodes_ array, an equivalentExp_
51
+ // attribute flag will be set to 1.
52
+ this.equivCodes_ = ['eq'];
49
53
 
50
54
  // Make this a singleton. See UnitTables constructor for details.
51
55
 
@@ -231,10 +235,20 @@ export class UcumXmlDocument {
231
235
  attrs['class_'] = curUA.attr.class;
232
236
  }
233
237
  let valNode = curUA.childNamed('value');
238
+ // Note: This currently works as a boolean flag,
239
+ // but it should be used to represent the dimensionality
240
+ // of the unit as an integer instead.
234
241
  if (this.moleCodes_.indexOf(curUA.attr.Code) !== -1)
235
242
  attrs['moleExp_'] = 1;
236
243
  else
237
244
  attrs['moleExp_'] = 0;
245
+ // Adds a flag similar to how moleExp_ works, but for units
246
+ // that are equivalent. Note that ideally this should also
247
+ // take values other than 1 or 0, but for now it is a boolean
248
+ // flag.
249
+ if (this.equivCodes_.indexOf(curUA.attr.Code) !== -1) {
250
+ attrs['equivalentExp_'] = 1;
251
+ }
238
252
 
239
253
 
240
254
  // Process special units
package/source/unit.js CHANGED
@@ -151,6 +151,11 @@ export class Unit {
151
151
  */
152
152
  this.moleExp_ = attrs['moleExp_'] || 0;
153
153
 
154
+ /**
155
+ * Flag indicating whether or not this is a equivalent mole unit
156
+ */
157
+ this.equivalentExp_ = attrs['equivalentExp_'] || 0;
158
+
154
159
  /*
155
160
  * Added when added LOINC list of units
156
161
  * synonyms are used by the autocompleter to enhance lookup capabilities
@@ -546,7 +551,7 @@ export class Unit {
546
551
  // return the molAmt divided by the molesFactor as the number of moles
547
552
  // for the molUnit
548
553
  return molAmt/molesFactor ;
549
- }
554
+ } // end convertMassToMol
550
555
 
551
556
  /**
552
557
  * Calculates the number of units that would result from converting a unit
@@ -559,7 +564,7 @@ export class Unit {
559
564
  * @param amt the quantity of this unit to be converted
560
565
  * @param massUnit the target/to unit for which the converted # is wanted
561
566
  * @param molecularWeight the molecular weight of the substance for which the
562
- * conversion is being made
567
+ * conversion is being made
563
568
  * @return the equivalent amount in massUnit
564
569
  */
565
570
  convertMolToMass(amt, massUnit, molecularWeight) {
@@ -581,9 +586,106 @@ export class Unit {
581
586
  // divided by any effects of prefixes applied to the "to" unit, which
582
587
  // is assumed to be some form of a gram unit
583
588
  return massAmt / massUnit.magnitude_ ;
584
- }
589
+ } // end convertMolToMass
590
+
591
+ /**
592
+ * Converts equivalents to mass.
593
+ *
594
+ * @param {number} equivalents - The amount in equivalents to be converted.
595
+ * @param {object} targetUnit - The target/to unit for which the converted number is wanted.
596
+ * @param {number} molecularWeight - The molecular weight of the substance for which the conversion is being made.
597
+ * @param {number} charge - The absolute value of the charge of the substance for which the conversion is being made.
598
+ * @returns {number} - The equivalent mass in the specified mass unit.
599
+ */
600
+ convertEqToMass(equivalents, targetUnit, molecularWeight, charge) {
601
+ let standardMoleUnit = this._getUnitTables().getUnitByCode('mol');
602
+ const molAmount = this.convertEqToMol(equivalents, standardMoleUnit, charge);
603
+ return this.convertMolToMass(molAmount, targetUnit, molecularWeight);
604
+ } // end convertEqToMass
605
+
606
+ /**
607
+ * Converts mass to equivalents.
608
+ *
609
+ * @param {number} mass - The mass to be converted.
610
+ * @param {object} eqUnit - The target/to unit for which the converted number is wanted.
611
+ * @param {number} molecularWeight - The molecular weight of the substance for which the conversion is being made.
612
+ * @param {number} charge - The absolute value of the charge of the substance for which the conversion is being made.
613
+ * @returns {number} - The equivalent amount in the specified equivalent unit.
614
+ */
615
+ convertMassToEq(mass, eqUnit, molecularWeight, charge) {
616
+ // Calculate equivalent mass by dividing molecular weight by charge
617
+ let equivalentMass = molecularWeight / charge;
618
+ // Calculate equivalents by dividing mass by equivalent mass
619
+ let equivalents = mass / equivalentMass;
620
+ // Get Avogadro's number from the unit tables
621
+ let avogadroNumber = this._getUnitTables().getUnitByCode('mol').magnitude_ ;
622
+ // Calculate mole factor by dividing the magnitude of the equivalent unit by Avogadro's number
623
+ // eqUnit may have a prefix (e.g. meq) and we need to adjust for that
624
+ let moleFactor = eqUnit.magnitude_ / avogadroNumber ;
625
+ // Adjust equivalents by dividing by the mole factor
626
+ let adjustedEquivalents = equivalents / moleFactor;
627
+ // Return the adjusted equivalents
628
+ return adjustedEquivalents;
629
+ } // end convertMassToEq
585
630
 
631
+ /**
632
+ * Checks if the given unit is an equivalent unit.
633
+ *
634
+ * Note: equivalent units are also be molar units, so a unit can return true for
635
+ * both isEquivalentUnit and isMolarUnit.
636
+ *
637
+ * @returns {boolean} - Returns true if the unit is an equivalent unit, false otherwise.
638
+ */
639
+ isEquivalentUnit() {
640
+ return this.equivalentExp_ !== 0;
641
+ } // end isEquivalentUnit
586
642
 
643
+ /**
644
+ * Checks if the given unit is a molar unit.
645
+ *
646
+ * @returns {boolean} - Returns true if the unit is a molar unit, false otherwise.
647
+ */
648
+ isMolarUnit() {
649
+ return this.moleExp_ !== 0;
650
+ } // end isMolarUnit
651
+
652
+
653
+ /**
654
+ * This function converts an equivalent amount to moles using the charge of the substance.
655
+ *
656
+ * @param {number} eqFromVal - The equivalent amount for which the conversion is being made.
657
+ * @param {object} molToUnit - The target unit for which the converted number is wanted.
658
+ * @param {number} charge - The absolute value of the charge of the substance for which the conversion is being made.
659
+ * @return {number} - The amount in moles.
660
+ */
661
+ convertEqToMol(eqFromVal, molToUnit, charge){
662
+ // Check if molToUnit is a molar unit and eqFromVal is a eq unit
663
+ if (!molToUnit.isMolarUnit() || !this.isEquivalentUnit()){
664
+ throw new Error("Invalid units for conversion of Eq to Mol. Please provide an equivalent and a molar unit.");
665
+ }
666
+ // The conversion from equivalents to moles is based on the principle that one equivalent is equal to 1/valencyFactor moles.
667
+ // The relative magnitude is accounted for via the current unit's magnitude (this.magnitude_) and the target unit's magnitude (molToUnit.magnitude_)
668
+ return eqFromVal * (this.magnitude_ / molToUnit.magnitude_) / charge;
669
+ } // end convertEqToMol
670
+
671
+ /**
672
+ * This function converts moles to equivalent amount using the charge of the substance.
673
+ *
674
+ * @param {number} molFromVal - The mole amount for which the conversion is being made
675
+ * @param {object} eqToUnit - The target unit for which the converted number is wanted
676
+ * @param {number} charge - The absolute value of the charge of the substance for which the conversion is being made
677
+ * @return {number} - The amount in equivalent
678
+ */
679
+ convertMolToEq(molFromVal, eqToUnit, charge){
680
+ // Check if eqToUnit is an equivalent unit and molFromVal is a molar unit
681
+ if (!eqToUnit.isEquivalentUnit() || !this.isMolarUnit()){
682
+ throw new Error("Invalid units for conversion of Mol to Eq. Please provide a molar and an equivalent unit.");
683
+ }
684
+ // The conversion from moles to equivalents is based on the principle that one equivalent is equal to 1/valencyFactor moles.
685
+ // The relative magnitude is accounted for via the current unit's magnitude (this.magnitude_) and the target unit's magnitude (eqToUnit.magnitude_)
686
+ return molFromVal * charge * (this.magnitude_ / eqToUnit.magnitude_);
687
+ } // end convertMolToEq
688
+
587
689
  /**
588
690
  * Mutates this unit into a unit on a ratio scale and converts a specified
589
691
  * number of units to an appropriate value for this converted unit
@@ -1016,6 +1118,41 @@ export class Unit {
1016
1118
  return commensurable ;
1017
1119
  }
1018
1120
 
1121
+ /**
1122
+ * This function tests this unit against the unit passed in to see if the
1123
+ * two are eq to mass commensurable. It assumes that one of the units
1124
+ * is a eq-based unit and the other is a mass-based unit. It also assumes
1125
+ * that the eq-based unit has a single eq unit in the numerator and that
1126
+ * the mass-based unit has a single mass unit in the numerator. It does NOT
1127
+ * check to validate those assumptions.
1128
+ *
1129
+ * The check is made by setting the dimension vector element corresponding
1130
+ * to the base mass unit (gram) in the eq unit, and then comparing the
1131
+ * two dimension vectors. If they match, the units are commensurable.
1132
+ * Otherwise they are not.
1133
+ *
1134
+ * @param {Unit} unit2 the unit to be compared to this one
1135
+ * @returns {boolean} boolean indicating commensurability
1136
+ */
1137
+ isEqMassCommensurable(unit2) {
1138
+ let tabs = this._getUnitTables();
1139
+ let d = tabs.getMassDimensionIndex();
1140
+ let commensurable = false ;
1141
+ if (this.equivalentExp_ === 1 && unit2.equivalentExp_ === 0) {
1142
+ let testDim = this.dim_.clone();
1143
+ let curVal = testDim.getElementAt(d);
1144
+ testDim.setElementAt(d, (curVal + this.equivalentExp_));
1145
+ commensurable = (testDim.equals(unit2.dim_));
1146
+ }
1147
+ else if (unit2.equivalentExp_ === 1 && this.equivalentExp_ === 0) {
1148
+ let testDim = unit2.dim_.clone();
1149
+ let curVal = testDim.getElementAt(d);
1150
+ testDim.setElementAt(d, (curVal + unit2.equivalentExp_));
1151
+ commensurable = (testDim.equals(this.dim_));
1152
+ }
1153
+ return commensurable;
1154
+ }
1155
+
1019
1156
 
1020
1157
  /**
1021
1158
  * This returns the UnitTables singleton object. Including the require
@@ -1153,10 +1153,17 @@ export class UnitString {
1153
1153
  // Look first for an exponent. If we got one, separate it out and
1154
1154
  // try to get the unit again
1155
1155
  let codeAndExp = this._isCodeWithExponent(uCode);
1156
+ let isIntegerUnitWithExp = false;
1156
1157
  if (codeAndExp) {
1157
1158
  uCode = codeAndExp[0];
1158
1159
  exp = codeAndExp[1];
1159
- origUnit = this.utabs_.getUnitByCode(uCode);
1160
+ isIntegerUnitWithExp = intUtils_.isIntegerUnit(uCode);
1161
+ origUnit = isIntegerUnitWithExp ?
1162
+ new Unit({'csCode_' : uCode,
1163
+ 'ciCode_' : uCode,
1164
+ 'magnitude_' : Number(uCode),
1165
+ 'name_' : uCode}) :
1166
+ this.utabs_.getUnitByCode(uCode);
1160
1167
  }
1161
1168
 
1162
1169
  // If an exponent is found but it's not a valid number, e.g. "2-1",
@@ -1294,10 +1301,11 @@ export class UnitString {
1294
1301
  }
1295
1302
  if (exp) {
1296
1303
  let expStr = exp.toString();
1304
+ const intergerUnitExpSign = isIntegerUnitWithExp && exp > 0 ? '+' : '';
1297
1305
  retUnit.assignVals({
1298
1306
  'name_': theName + '<sup>' + expStr + '</sup>',
1299
- 'csCode_': theCode + expStr,
1300
- 'ciCode_': theCiCode + expStr,
1307
+ 'csCode_': theCode + intergerUnitExpSign + expStr,
1308
+ 'ciCode_': theCiCode + intergerUnitExpSign + expStr,
1301
1309
  'printSymbol_': thePrintSymbol + '<sup>' + expStr + '</sup>'
1302
1310
  });
1303
1311
  }
@@ -78,6 +78,18 @@ var Ucum = {
78
78
  * but no molecular weight was specified.
79
79
  */
80
80
  needMoleWeightMsg_: 'Did you wish to convert between mass and moles? The ' + 'molecular weight of the substance represented by the ' + 'units is required to perform the conversion.',
81
+ /**
82
+ * Message that is returned when a mass<->eq conversion is requested
83
+ * (which requires a molecular weight to calculate), but no molecular
84
+ * weight was provided by the user.
85
+ */
86
+ needEqWeightMsg_: 'Did you wish to convert with equivalents? The ' + 'molecular weight of the substance is required to perform ' + 'the conversion.',
87
+ /**
88
+ * Message that is returned when a mass<->eq or a mol<->eq conversion
89
+ * is requested (which requires a charge to calculate), but no charge
90
+ * was provided by the user.
91
+ */
92
+ needEqChargeMsg_: 'Did you wish to convert with equivalents? The ' + 'charge of the substance is required to perform ' + 'the conversion.',
81
93
  /**
82
94
  * Hash that matches unit column names to names used in the csv file
83
95
  * that is submitted to the data updater.
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":["Ucum","dimLen_","validOps_","codeSep_","valMsgStart_","valMsgEnd_","cnvMsgStart_","cnvMsgEnd_","openEmph_","closeEmph_","openEmphHTML_","closeEmphHTML_","bracesMsg_","needMoleWeightMsg_","csvCols_","inputKey_","specUnits_","exports"],"sources":["../source/config.js"],"sourcesContent":["/*\n * This defines the namespace for the UCUM classes and provides\n * a place for the definition of global variables and constants.\n *\n * The javascript for this UCUM implementation uses syntax as\n * defined by the ECMAScript 6 standard\n */\n\nexport var Ucum = {\n\n /**\n * Flag indicating whether or not we're using case sensitive labels\n * I don't think we need this. I think we're just going with\n * case sensitive, per Clem. Gunther's code has this flag, but I\n * am removing it, at least for now. lm, 6/2016\n */\n //caseSensitive_: true ,\n\n /**\n * The number of elements in a Dimension array. Currently this\n * is set as a configuration variable, but when we get to the point\n * of loading the unit definitions from a file, this value will be\n * set from that.\n */\n dimLen_: 7,\n\n\n /**\n * The characters used as valid operators in a UCUM unit expression,\n * where '.' is for multiplication and '/' is for division.\n */\n validOps_: ['.', '/'],\n\n\n /**\n * The string used to separate a unit code and unit name when they\n * are displayed together\n */\n codeSep_ : ': ',\n\n // Message text variations for validation methods and conversion methods\n valMsgStart_ : 'Did you mean ',\n valMsgEnd_ : '?' ,\n cnvMsgStart_ : 'We assumed you meant ',\n cnvMsgEnd_ : '.',\n\n\n/**\n * Default opening string used to emphasize portions of error messages.\n * Used when NOT displaying messages on a web site, i.e., for output\n * from the library methods or to a file.\n */\n openEmph_ : ' ->',\n\n /**\n * Default closing string used to emphasize portions of error messages.\n * Used when NOT displaying messages on a web site, i.e., for output\n * from the library methods or to a file.\n */\n closeEmph_ : '<- ' ,\n\n /**\n * Opening HTML used to emphasize portions of error messages. Used when\n * displaying messages on a web site; should be blank when output is\n * to a file.\n */\n openEmphHTML_ : ' <span class=\"emphSpan\">',\n\n /**\n * Closing HTML used to emphasize portions of error messages. Used when\n * displaying messages on a web site; should be blank when output is\n * to a file.\n */\n closeEmphHTML_ : '</span> ' ,\n\n /**\n * Message that is displayed when annotations are included in a unit\n * string, to let the user know how they are interpreted.\n */\n bracesMsg_ : 'FYI - annotations (text in curly braces {}) are ignored, ' +\n 'except that an annotation without a leading symbol implies ' +\n 'the default unit 1 (the unity).',\n\n /**\n * Message that is displayed or returned when a conversion is requested\n * for two units where (only) a mass<->moles conversion is appropriate\n * but no molecular weight was specified.\n */\n needMoleWeightMsg_ : 'Did you wish to convert between mass and moles? The ' +\n 'molecular weight of the substance represented by the ' +\n 'units is required to perform the conversion.',\n\n /**\n * Hash that matches unit column names to names used in the csv file\n * that is submitted to the data updater.\n */\n csvCols_ : {\n 'case-sensitive code' : 'csCode_',\n 'LOINC property' : 'loincProperty_',\n 'name (display)' : 'name_',\n 'synonyms' : 'synonyms_',\n 'source' : 'source_',\n 'category' : 'category_',\n 'Guidance' : 'guidance_'\n } ,\n\n /**\n * Name of the column in the csv file that serves as the key\n */\n inputKey_ : 'case-sensitive code' ,\n\n /**\n * Special codes that contain operators within brackets. The operator\n * within these codes causes them to parse incorrectly if they are preceded\n * by a prefix, because the parsing algorithm splits them up on the operator.\n * So we use this object to identify them and substitute placeholders to\n * avoid that.\n */\n specUnits_ : { 'B[10.nV]' : 'specialUnitOne',\n '[m/s2/Hz^(1/2)]' : 'specialUnitTwo'}\n} ;\n\n\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO,IAAIA,IAAI,GAAG;EAEhB;AACF;AACA;AACA;AACA;AACA;EACE;;EAEA;AACF;AACA;AACA;AACA;AACA;EACEC,OAAO,EAAE,CAAC;EAGV;AACF;AACA;AACA;EACEC,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;EAGrB;AACF;AACA;AACA;EACEC,QAAQ,EAAG,IAAI;EAEf;EACAC,YAAY,EAAG,eAAe;EAC9BC,UAAU,EAAG,GAAG;EAChBC,YAAY,EAAG,uBAAuB;EACtCC,UAAU,EAAG,GAAG;EAGlB;AACA;AACA;AACA;AACA;EACEC,SAAS,EAAG,KAAK;EAEjB;AACF;AACA;AACA;AACA;EACEC,UAAU,EAAG,KAAK;EAElB;AACF;AACA;AACA;AACA;EACEC,aAAa,EAAG,0BAA0B;EAE1C;AACF;AACA;AACA;AACA;EACEC,cAAc,EAAG,UAAU;EAE3B;AACF;AACA;AACA;EACEC,UAAU,EAAG,2DAA2D,GAC3D,6DAA6D,GAC7D,iCAAiC;EAE9C;AACF;AACA;AACA;AACA;EACEC,kBAAkB,EAAG,uDAAuD,GACvD,uDAAuD,GACvD,8CAA8C;EAEnE;AACF;AACA;AACA;EACEC,QAAQ,EAAG;IACT,qBAAqB,EAAG,SAAS;IACjC,gBAAgB,EAAG,gBAAgB;IACnC,gBAAgB,EAAG,OAAO;IAC1B,UAAU,EAAG,WAAW;IACxB,QAAQ,EAAG,SAAS;IACpB,UAAU,EAAG,WAAW;IACxB,UAAU,EAAG;EACf,CAAC;EAED;AACF;AACA;EACEC,SAAS,EAAG,qBAAqB;EAEjC;AACF;AACA;AACA;AACA;AACA;AACA;EACGC,UAAU,EAAG;IAAE,UAAU,EAAG,gBAAgB;IAC7B,iBAAiB,EAAG;EAAgB;AACtD,CAAC;AAAEC,OAAA,CAAAjB,IAAA,GAAAA,IAAA"}
1
+ {"version":3,"file":"config.js","names":["Ucum","dimLen_","validOps_","codeSep_","valMsgStart_","valMsgEnd_","cnvMsgStart_","cnvMsgEnd_","openEmph_","closeEmph_","openEmphHTML_","closeEmphHTML_","bracesMsg_","needMoleWeightMsg_","needEqWeightMsg_","needEqChargeMsg_","csvCols_","inputKey_","specUnits_","exports"],"sources":["../source/config.js"],"sourcesContent":["/*\n * This defines the namespace for the UCUM classes and provides\n * a place for the definition of global variables and constants.\n *\n * The javascript for this UCUM implementation uses syntax as\n * defined by the ECMAScript 6 standard\n */\n\nexport var Ucum = {\n\n /**\n * Flag indicating whether or not we're using case sensitive labels\n * I don't think we need this. I think we're just going with\n * case sensitive, per Clem. Gunther's code has this flag, but I\n * am removing it, at least for now. lm, 6/2016\n */\n //caseSensitive_: true ,\n\n /**\n * The number of elements in a Dimension array. Currently this\n * is set as a configuration variable, but when we get to the point\n * of loading the unit definitions from a file, this value will be\n * set from that.\n */\n dimLen_: 7,\n\n\n /**\n * The characters used as valid operators in a UCUM unit expression,\n * where '.' is for multiplication and '/' is for division.\n */\n validOps_: ['.', '/'],\n\n\n /**\n * The string used to separate a unit code and unit name when they\n * are displayed together\n */\n codeSep_ : ': ',\n\n // Message text variations for validation methods and conversion methods\n valMsgStart_ : 'Did you mean ',\n valMsgEnd_ : '?' ,\n cnvMsgStart_ : 'We assumed you meant ',\n cnvMsgEnd_ : '.',\n\n\n/**\n * Default opening string used to emphasize portions of error messages.\n * Used when NOT displaying messages on a web site, i.e., for output\n * from the library methods or to a file.\n */\n openEmph_ : ' ->',\n\n /**\n * Default closing string used to emphasize portions of error messages.\n * Used when NOT displaying messages on a web site, i.e., for output\n * from the library methods or to a file.\n */\n closeEmph_ : '<- ' ,\n\n /**\n * Opening HTML used to emphasize portions of error messages. Used when\n * displaying messages on a web site; should be blank when output is\n * to a file.\n */\n openEmphHTML_ : ' <span class=\"emphSpan\">',\n\n /**\n * Closing HTML used to emphasize portions of error messages. Used when\n * displaying messages on a web site; should be blank when output is\n * to a file.\n */\n closeEmphHTML_ : '</span> ' ,\n\n /**\n * Message that is displayed when annotations are included in a unit\n * string, to let the user know how they are interpreted.\n */\n bracesMsg_ : 'FYI - annotations (text in curly braces {}) are ignored, ' +\n 'except that an annotation without a leading symbol implies ' +\n 'the default unit 1 (the unity).',\n\n /**\n * Message that is displayed or returned when a conversion is requested\n * for two units where (only) a mass<->moles conversion is appropriate\n * but no molecular weight was specified.\n */\n needMoleWeightMsg_ : 'Did you wish to convert between mass and moles? The ' +\n 'molecular weight of the substance represented by the ' +\n 'units is required to perform the conversion.',\n\n /**\n * Message that is returned when a mass<->eq conversion is requested \n * (which requires a molecular weight to calculate), but no molecular \n * weight was provided by the user.\n */ \n needEqWeightMsg_ : 'Did you wish to convert with equivalents? The ' +\n 'molecular weight of the substance is required to perform ' + \n 'the conversion.', \n\n /**\n * Message that is returned when a mass<->eq or a mol<->eq conversion \n * is requested (which requires a charge to calculate), but no charge\n * was provided by the user.\n */\n needEqChargeMsg_ : 'Did you wish to convert with equivalents? The ' +\n 'charge of the substance is required to perform ' + \n 'the conversion.', \n\n /**\n * Hash that matches unit column names to names used in the csv file\n * that is submitted to the data updater.\n */\n csvCols_ : {\n 'case-sensitive code' : 'csCode_',\n 'LOINC property' : 'loincProperty_',\n 'name (display)' : 'name_',\n 'synonyms' : 'synonyms_',\n 'source' : 'source_',\n 'category' : 'category_',\n 'Guidance' : 'guidance_'\n } ,\n\n /**\n * Name of the column in the csv file that serves as the key\n */\n inputKey_ : 'case-sensitive code' ,\n\n /**\n * Special codes that contain operators within brackets. The operator\n * within these codes causes them to parse incorrectly if they are preceded\n * by a prefix, because the parsing algorithm splits them up on the operator.\n * So we use this object to identify them and substitute placeholders to\n * avoid that.\n */\n specUnits_ : { 'B[10.nV]' : 'specialUnitOne',\n '[m/s2/Hz^(1/2)]' : 'specialUnitTwo'}\n} ;\n\n\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO,IAAIA,IAAI,GAAG;EAEhB;AACF;AACA;AACA;AACA;AACA;EACE;;EAEA;AACF;AACA;AACA;AACA;AACA;EACEC,OAAO,EAAE,CAAC;EAGV;AACF;AACA;AACA;EACEC,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;EAGrB;AACF;AACA;AACA;EACEC,QAAQ,EAAG,IAAI;EAEf;EACAC,YAAY,EAAG,eAAe;EAC9BC,UAAU,EAAG,GAAG;EAChBC,YAAY,EAAG,uBAAuB;EACtCC,UAAU,EAAG,GAAG;EAGlB;AACA;AACA;AACA;AACA;EACEC,SAAS,EAAG,KAAK;EAEjB;AACF;AACA;AACA;AACA;EACEC,UAAU,EAAG,KAAK;EAElB;AACF;AACA;AACA;AACA;EACEC,aAAa,EAAG,0BAA0B;EAE1C;AACF;AACA;AACA;AACA;EACEC,cAAc,EAAG,UAAU;EAE3B;AACF;AACA;AACA;EACEC,UAAU,EAAG,2DAA2D,GAC3D,6DAA6D,GAC7D,iCAAiC;EAE9C;AACF;AACA;AACA;AACA;EACEC,kBAAkB,EAAG,uDAAuD,GACvD,uDAAuD,GACvD,8CAA8C;EAEnE;AACF;AACA;AACA;AACA;EACEC,gBAAgB,EAAG,iDAAiD,GAChD,2DAA2D,GAC3D,iBAAiB;EAErC;AACF;AACA;AACA;AACA;EACEC,gBAAgB,EAAG,iDAAiD,GAChD,iDAAiD,GACjD,iBAAiB;EAErC;AACF;AACA;AACA;EACEC,QAAQ,EAAG;IACT,qBAAqB,EAAG,SAAS;IACjC,gBAAgB,EAAG,gBAAgB;IACnC,gBAAgB,EAAG,OAAO;IAC1B,UAAU,EAAG,WAAW;IACxB,QAAQ,EAAG,SAAS;IACpB,UAAU,EAAG,WAAW;IACxB,UAAU,EAAG;EACf,CAAC;EAED;AACF;AACA;EACEC,SAAS,EAAG,qBAAqB;EAEjC;AACF;AACA;AACA;AACA;AACA;AACA;EACGC,UAAU,EAAG;IAAE,UAAU,EAAG,gBAAgB;IAC7B,iBAAiB,EAAG;EAAgB;AACtD,CAAC;AAAEC,OAAA,CAAAnB,IAAA,GAAAA,IAAA"}