Comparing version 3.1.1 to 3.2.0
1682
lib/index.js
@@ -1,1659 +0,27 @@ | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var chemicalElements = require('chemical-elements'); | ||
var chemicalGroups = require('chemical-groups'); | ||
var atomSorter = require('atom-sorter'); | ||
/** | ||
* Define static variable corresponding to the various Kinds of a molecular formula part. | ||
*/ | ||
const Kind = { | ||
BEGIN: 'begin', | ||
ATOM: 'atom', | ||
MULTIPLIER_RANGE: 'multiplierRange', | ||
ISOTOPE: 'isotope', | ||
ISOTOPE_RATIO: 'isotopeRatio', | ||
CHARGE: 'charge', | ||
SALT: 'salt', | ||
OPENING_PARENTHESIS: 'openingParenthesis', | ||
CLOSING_PARENTHESIS: 'closingParenthesis', | ||
PRE_MULTIPLIER: 'preMultiplier', | ||
MULTIPLIER: 'multiplier', | ||
TEXT: 'text', | ||
ANCHOR: 'anchor', | ||
COMMENT: 'comment', | ||
}; | ||
/** | ||
* Parse a string to extract the charge | ||
* The charge may be in the form --, +++, +3, -2, 4+, 2- | ||
* @param {*} charge | ||
*/ | ||
function parseCharge(charge) { | ||
charge = charge.replace(/[()]/g, ''); | ||
let chargeNumber = 0; | ||
if (charge.match(/^[+-]+$/)) { | ||
for (let i = 0; i < charge.length; i++) { | ||
if (charge.charAt(i) === '+') chargeNumber++; | ||
else chargeNumber--; | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
} else if (charge.match(/^[0-9]+[+-]$/)) { | ||
chargeNumber = Number( | ||
charge.charAt(charge.length - 1) + charge.substring(0, charge.length - 1), | ||
); | ||
} else { | ||
chargeNumber = Number(charge); | ||
} | ||
return chargeNumber; | ||
} | ||
/** | ||
* Parse a mf to an array of kind / value | ||
* @param {String} mf | ||
*/ | ||
function parse(mf) { | ||
return new MFParser().parse(mf); | ||
} | ||
class MFParser { | ||
parse(mf = '') { | ||
this.mf = mf; | ||
this.i = 0; | ||
this.result = []; | ||
let lastKind = Kind.BEGIN; | ||
while (this.i < mf.length) { | ||
if ( | ||
this.result.length > 0 && | ||
this.result[this.result.length - 1].kind !== Kind.TEXT | ||
) { | ||
lastKind = this.result[this.result.length - 1].kind; | ||
} | ||
let char = mf.charAt(this.i); | ||
let ascii = mf.charCodeAt(this.i); | ||
let nextAscii = 0; | ||
if (this.i + 1 < mf.length) nextAscii = mf.charCodeAt(this.i + 1); | ||
if ( | ||
(ascii > 47 && ascii < 58) || | ||
(char === '-' && nextAscii > 47 && nextAscii < 58) | ||
) { | ||
// a number | ||
let value = this.getNumber(ascii); | ||
if ( | ||
lastKind === Kind.SALT || | ||
lastKind === Kind.BEGIN || | ||
lastKind === Kind.OPENING_PARENTHESIS | ||
) { | ||
if (value.to) { | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'Premultiplier may not contain a -', | ||
); | ||
} | ||
this.result.push({ kind: Kind.PRE_MULTIPLIER, value: value.from }); | ||
} else if (lastKind === Kind.ANCHOR) { | ||
if (value.to) { | ||
throw new MFError(this.mf, this.i, 'Anchor ID may not contain -'); | ||
} | ||
this.result[this.result.length - 1].value = value.from; | ||
} else if (value.to) { | ||
this.result.push({ | ||
kind: Kind.MULTIPLIER_RANGE, | ||
value: { | ||
from: Math.min(value.from, value.to), | ||
to: Math.max(value.from, value.to), | ||
}, | ||
}); | ||
} else { | ||
this.result.push({ kind: Kind.MULTIPLIER, value: value.from }); | ||
} | ||
continue; | ||
} else if (char === '.') { | ||
// a point | ||
this.result.push({ kind: Kind.SALT, value: char }); | ||
// it is not in a number otherwise it would have been taken before | ||
// it must be in a salt | ||
} else if (char === '#') { | ||
// an anchor | ||
this.result.push({ kind: Kind.ANCHOR, value: 0 }); | ||
// it is not in a number otherwise it would have been taken before | ||
// it must be in a salt | ||
} else if (ascii > 64 && ascii < 91) { | ||
// an uppercase = new atom | ||
let value = this.getAtom(ascii); | ||
this.result.push({ kind: Kind.ATOM, value }); | ||
continue; | ||
} else if (ascii > 96 && ascii < 123) { | ||
// a lowercase | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'found a lowercase not following an uppercase', | ||
); | ||
} else if (char === '(') { | ||
let charge = this.getParenthesisCharge(ascii); | ||
if (charge) { | ||
this.result.push({ kind: Kind.CHARGE, value: charge }); | ||
} else { | ||
this.result.push({ kind: Kind.OPENING_PARENTHESIS, value: '(' }); | ||
} | ||
} else if (char === ')') { | ||
this.result.push({ kind: Kind.CLOSING_PARENTHESIS, value: ')' }); | ||
} else if (char === '[') { | ||
// defines an isotope | ||
let isotope = this.getIsotope(ascii); | ||
this.result.push({ kind: Kind.ISOTOPE, value: isotope }); | ||
} else if (char === ']') { | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'should never meet an closing bracket not in isotopes', | ||
); | ||
} else if (char === '{') { | ||
// can define an exotic isotopic ratio or mixtures of groups | ||
let isotopeRatio = this.getCurlyBracketIsotopeRatio(ascii); | ||
if (lastKind === Kind.ATOM) { | ||
let lastResult = this.result[this.result.length - 1]; | ||
lastResult.kind = Kind.ISOTOPE_RATIO; | ||
lastResult.value = { | ||
atom: lastResult.value, | ||
ratio: isotopeRatio, | ||
}; | ||
} else { | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'isotopic composition has to follow an atom', | ||
); | ||
} | ||
} else if (char === '}') { | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'found a unexpected closing curly bracket', | ||
); | ||
} else if (char === '+') { | ||
// charge not in parenthesis | ||
let charge = this.getNonParenthesisCharge(ascii); | ||
this.result.push({ kind: Kind.CHARGE, value: charge }); | ||
} else if (char === '-') { | ||
// charge not in parenthesis | ||
let charge = this.getNonParenthesisCharge(ascii); | ||
this.result.push({ kind: Kind.CHARGE, value: charge }); | ||
} else if (char === '$') { | ||
// it is a comment after | ||
this.result.push({ | ||
kind: Kind.COMMENT, | ||
value: this.mf.substring(this.i + 1), | ||
}); | ||
break; | ||
} else { | ||
this.result.push({ kind: Kind.TEXT, value: char }); | ||
} | ||
this.i++; | ||
} | ||
this.checkParenthesis(); | ||
return this.result; | ||
} | ||
checkParenthesis() { | ||
let counter = 0; | ||
for (let line of this.result) { | ||
if (line.kind === Kind.OPENING_PARENTHESIS) counter++; | ||
if (line.kind === Kind.CLOSING_PARENTHESIS) counter--; | ||
} | ||
if (counter !== 0) { | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'number of opening and closing parenthesis not equal', | ||
); | ||
} | ||
} | ||
getNumber(ascii) { | ||
let number = ''; | ||
let previous; | ||
do { | ||
previous = ascii; | ||
number += String.fromCharCode(ascii); | ||
this.i++; | ||
ascii = this.mf.charCodeAt(this.i); | ||
} while ( | ||
(ascii > 47 && ascii < 58) || | ||
ascii === 46 || | ||
ascii === 45 || | ||
ascii === 47 | ||
); // number . - / | ||
// we need to deal with the case there is a from / to | ||
if (previous === 46) this.i--; | ||
let indexOfDash = number.indexOf('-', 1); | ||
if (indexOfDash > -1) { | ||
return { | ||
from: parseNumberWithDivision(number.substr(0, indexOfDash)), | ||
to: parseNumberWithDivision(number.substr(indexOfDash + 1)), | ||
}; | ||
} | ||
return { from: parseNumberWithDivision(number) }; | ||
} | ||
getAtom(ascii) { | ||
let atom = ''; | ||
do { | ||
atom += String.fromCharCode(ascii); | ||
this.i++; | ||
ascii = this.mf.charCodeAt(this.i); | ||
} while (ascii > 96 && ascii < 123); | ||
return atom; | ||
} | ||
getIsotope(ascii) { | ||
// [13C] | ||
let substring = ''; | ||
do { | ||
substring += String.fromCharCode(ascii); | ||
this.i++; | ||
ascii = this.mf.charCodeAt(this.i); | ||
} while (ascii !== 93 && this.i <= this.mf.length); | ||
let atom = substring.replace(/[^a-zA-Z]/g, ''); | ||
let isotope = Number(substring.replace(/[^0-9]/g, '')); | ||
return { atom, isotope }; | ||
} | ||
getCurlyBracketIsotopeRatio(ascii) { | ||
let substring = ''; | ||
let first = true; | ||
do { | ||
if (!first) { | ||
substring += String.fromCharCode(ascii); | ||
} else { | ||
first = false; | ||
} | ||
this.i++; | ||
ascii = this.mf.charCodeAt(this.i); | ||
} while (ascii !== 125 && this.i <= this.mf.length); // closing curly bracket | ||
if (substring.match(/^[0-9,]+$/)) { | ||
return substring.split(',').map((a) => Number(a)); | ||
} | ||
throw new MFError( | ||
this.mf, | ||
this.i, | ||
'Curly brackets should contain only number and comma', | ||
); | ||
} | ||
getParenthesisCharge(ascii) { | ||
let substring = ''; | ||
let begin = this.i; | ||
do { | ||
substring += String.fromCharCode(ascii); | ||
this.i++; | ||
ascii = this.mf.charCodeAt(this.i); | ||
} while (ascii !== 41 && this.i <= this.mf.length); // closing parenthesis | ||
if (substring.match(/^\([0-9+-]+$/)) { | ||
return parseCharge(substring.substring(1)); | ||
} else { | ||
this.i = begin; | ||
return undefined; | ||
} | ||
} | ||
getNonParenthesisCharge(ascii) { | ||
let substring = ''; | ||
do { | ||
substring += String.fromCharCode(ascii); | ||
this.i++; | ||
ascii = this.mf.charCodeAt(this.i); | ||
} while (ascii === 43 || ascii === 45 || (ascii > 47 && ascii < 58)); | ||
this.i--; | ||
return parseCharge(substring); | ||
} | ||
} | ||
class MFError extends SyntaxError { | ||
constructor(mf, i, message) { | ||
let text = `${message}\n\n${mf}\n${' '.repeat(i)}^`; | ||
super(text); | ||
} | ||
} | ||
function parseNumberWithDivision(string) { | ||
if (string.includes('/')) { | ||
let parts = string.split('/'); | ||
if (parts.length !== 2) { | ||
throw new TypeError('Can not parse MF with number like: ', string); | ||
} | ||
return Number(parts[0]) / Number(parts[1]); | ||
} else { | ||
return Number(string); | ||
} | ||
} | ||
const superscript = { | ||
0: '⁰', | ||
1: '¹', | ||
2: '²', | ||
3: '³', | ||
4: '⁴', | ||
5: '⁵', | ||
6: '⁶', | ||
7: '⁷', | ||
8: '⁸', | ||
9: '⁹', | ||
'+': '⁺', | ||
'-': '⁻', | ||
'(': '⁽', | ||
')': '⁾', | ||
'{': '⁽', | ||
'}': '⁾', | ||
'.': '˙', | ||
',': '˙', | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
const subscript = { | ||
0: '₀', | ||
1: '₁', | ||
2: '₂', | ||
3: '₃', | ||
4: '₄', | ||
5: '₅', | ||
6: '₆', | ||
7: '₇', | ||
8: '₈', | ||
9: '₉', | ||
'(': '₍', | ||
')': '₎', | ||
'{': '₍', | ||
'}': '₎', | ||
'.': ' ', | ||
',': ' ', | ||
}; | ||
/** | ||
* Defines static variables corresponding to the various formatting possibilities | ||
*/ | ||
const Format = { | ||
SUBSCRIPT: 'subscript', | ||
SUPERSCRIPT: 'superscript', | ||
SUPERIMPOSE: 'superimpose', | ||
TEXT: 'text', | ||
}; | ||
function formatCharge(charge) { | ||
if (charge === 1) return '+'; | ||
if (charge > 1) return `+${charge}`; | ||
if (charge < 0) return String(charge); | ||
return ''; | ||
} | ||
/** | ||
* Converts an array of mf elements to an array of formatting information | ||
* @param {object[]} lines of the parse method | ||
*/ | ||
function toDisplay(lines) { | ||
let results = []; | ||
let result = {}; | ||
for (let line of lines) { | ||
switch (line.kind) { | ||
case Kind.MULTIPLIER: | ||
if (line.value !== 1) { | ||
result = { | ||
kind: Format.SUBSCRIPT, | ||
value: String(line.value), | ||
}; | ||
results.push(result); | ||
} | ||
break; | ||
case Kind.MULTIPLIER_RANGE: | ||
result = { | ||
kind: Format.SUBSCRIPT, | ||
value: `${String(line.value.from)}-${line.value.to}`, | ||
}; | ||
results.push(result); | ||
break; | ||
case Kind.CHARGE: | ||
if (result.kind === Format.SUBSCRIPT) { | ||
result.kind = Format.SUPERIMPOSE; | ||
result.over = formatCharge(line.value); | ||
result.under = result.value; | ||
result.value = undefined; | ||
} else { | ||
result = { | ||
kind: Format.SUPERSCRIPT, | ||
value: formatCharge(line.value), | ||
}; | ||
results.push(result); | ||
} | ||
break; | ||
case Kind.ISOTOPE: | ||
result = { | ||
kind: Format.SUPERSCRIPT, | ||
value: line.value.isotope, | ||
}; | ||
results.push(result); | ||
result = { | ||
kind: Format.TEXT, | ||
value: line.value.atom, | ||
}; | ||
results.push(result); | ||
break; | ||
case Kind.ISOTOPE_RATIO: | ||
if (result.kind === Format.TEXT) { | ||
result.value += line.value.atom; | ||
} else { | ||
result = { | ||
kind: Format.TEXT, | ||
value: line.value.atom, | ||
}; | ||
results.push(result); | ||
} | ||
result = { | ||
kind: Format.SUPERSCRIPT, | ||
value: `{${line.value.ratio.join(',')}}`, | ||
}; | ||
results.push(result); | ||
break; | ||
case Kind.SALT: | ||
if (result.kind === Format.TEXT) { | ||
result.value += ' • '; | ||
} else { | ||
result = { | ||
kind: Format.TEXT, | ||
value: ' • ', | ||
}; | ||
results.push(result); | ||
} | ||
break; | ||
default: | ||
if (result.kind === Format.TEXT) { | ||
result.value += line.value; | ||
} else { | ||
result = { | ||
kind: Format.TEXT, | ||
value: line.value, | ||
}; | ||
results.push(result); | ||
} | ||
} | ||
} | ||
return results; | ||
} | ||
function isMF(mf) { | ||
let tmpMF = mf.replace(/[^a-zA-Z]/g, ''); | ||
let parts = tmpMF.replace(/([A-Za-z])(?=[A-Z])/g, '$1 ').split(' '); | ||
for (let i = 0; i < parts.length; i++) { | ||
if (!chemicalElements.elementsObject[parts[i]] && !chemicalGroups.groupsObject[parts[i]]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
const Style = { | ||
SUPERIMPOSE: | ||
'flex-direction: column;display: inline-flex;justify-content: center;text-align: left;vertical-align: middle;', | ||
SUPERIMPOSE_SUP_SUB: 'line-height: 1; font-size: 70%', | ||
}; | ||
function toHtml(lines) { | ||
let html = []; | ||
for (let line of lines) { | ||
switch (line.kind) { | ||
case Format.SUBSCRIPT: | ||
html.push(`<sub>${line.value}</sub>`); | ||
break; | ||
case Format.SUPERSCRIPT: | ||
html.push(`<sup>${line.value}</sup>`); | ||
break; | ||
case Format.SUPERIMPOSE: | ||
html.push(`<span style="${Style.SUPERIMPOSE}">`); | ||
html.push( | ||
`<sup style="${Style.SUPERIMPOSE_SUP_SUB}">${line.over}</sup>`, | ||
); | ||
html.push( | ||
`<sub style="${Style.SUPERIMPOSE_SUP_SUB}">${line.under}</sub>`, | ||
); | ||
html.push('</span>'); | ||
break; | ||
default: | ||
html.push(line.value); | ||
} | ||
} | ||
return html.join(''); | ||
} | ||
const elements = Object.keys(chemicalElements.elementsObject).sort( | ||
(a, b) => b.length - a.length, | ||
); | ||
/** | ||
* Ensure that the mf has been entered with capital letters and not only lowercase | ||
* If there is only lowercase we try to capitalize the mf | ||
* @param {string} mf | ||
*/ | ||
function ensureCase(mf) { | ||
for (let i = 0; i < mf.length; i++) { | ||
if (mf.charCodeAt(i) > 64 && mf.charCodeAt(i) < 91) { | ||
return mf; | ||
} | ||
} | ||
let parts = mf.replace(/([a-z]*)([^a-z]*)/g, '$1 $2 ').split(/ +/); | ||
for (let i = 0; i < parts.length; i++) { | ||
if (parts[i].match(/^[a-z]$/)) { | ||
parts[i] = parts[i].toUpperCase(); | ||
} else if (parts[i].match(/^[a-z]+$/)) { | ||
let newPart = ''; | ||
for (let j = 0; j < parts[i].length; j++) { | ||
let two = parts[i].substr(j, 2); | ||
let one = parts[i].charAt(j).toUpperCase(); | ||
if ( | ||
['c', 'h', 'o', 'n'].includes(two.charAt(0)) && | ||
['h', 'o', 'n'].includes(two.charAt(1)) | ||
) { | ||
newPart += two.toUpperCase(); | ||
j++; | ||
} else { | ||
two = two.charAt(0).toUpperCase() + two.charAt(1); | ||
if (elements.includes(two)) { | ||
newPart += two; | ||
j++; | ||
} else if (elements.includes(one)) { | ||
newPart += one; | ||
} else { | ||
return mf; | ||
} | ||
} | ||
} | ||
parts[i] = newPart; | ||
} | ||
} | ||
return parts.join(''); | ||
} | ||
function flatten(parsed, options = {}) { | ||
const { groupIdentical = false, limit = 100000 } = options; | ||
if (parsed.length === 0) return ['']; | ||
let parts = []; | ||
let parenthesisLevel = 0; | ||
let currentPart; | ||
let comments = []; | ||
for (const entry of parsed) { | ||
if ( | ||
(entry.kind === 'atom' || | ||
entry.kind === 'isotope' || | ||
entry.kind === 'openingParenthesis' || | ||
!currentPart) && | ||
parenthesisLevel === 0 | ||
) { | ||
currentPart = { | ||
mf: '', | ||
min: 1, | ||
max: 1, | ||
}; | ||
parts.push(currentPart); | ||
} | ||
switch (entry.kind) { | ||
case 'atom': | ||
currentPart.mf += entry.value; | ||
break; | ||
case 'isotope': | ||
currentPart.mf += `[${entry.value.isotope}${entry.value.atom}]`; | ||
break; | ||
case 'multiplier': | ||
currentPart.mf += entry.value; | ||
break; | ||
case 'multiplierRange': | ||
if (parenthesisLevel !== 0) { | ||
throw new Error( | ||
'Range definition inside parenthesis is not allowed.', | ||
); | ||
} | ||
currentPart.min = entry.value.from; | ||
currentPart.max = entry.value.to; | ||
break; | ||
case 'openingParenthesis': | ||
parenthesisLevel++; | ||
currentPart.mf += entry.value; | ||
break; | ||
case 'charge': | ||
if (entry.value === 1) { | ||
currentPart.mf += '+'; | ||
} else if (entry.value > 1) { | ||
currentPart.mf += `(+${entry.value})`; | ||
} else if (entry.value < 0) { | ||
currentPart.mf += `(${entry.value})`; | ||
} | ||
break; | ||
case 'closingParenthesis': | ||
parenthesisLevel--; | ||
currentPart.mf += entry.value; | ||
break; | ||
case 'comment': | ||
comments.push(entry.value); | ||
break; | ||
case 'text': | ||
break; | ||
default: | ||
throw new Error( | ||
`Could not flatten the parsed MF. Unknown kind: ${entry.kind}`, | ||
); | ||
} | ||
} | ||
if (groupIdentical) { | ||
parts = optimizeRanges(parts); | ||
} | ||
const mfs = createMFs(parts, comments.join(' '), limit); | ||
return mfs; | ||
} | ||
/** | ||
* If we have many times the same mf we can combine them | ||
* This should only be applied if there are acutally some ranges | ||
*/ | ||
function optimizeRanges(parts) { | ||
let newParts = []; | ||
let mfsObject = {}; | ||
let hasRange = false; | ||
for (const mf of parts) { | ||
if (mf.min !== mf.max) { | ||
hasRange = true; | ||
break; | ||
} | ||
} | ||
if (!hasRange) return parts; | ||
for (const mf of parts) { | ||
if (!mfsObject[mf.mf]) { | ||
mfsObject[mf.mf] = { | ||
mf: mf.mf, | ||
min: mf.min, | ||
max: mf.max, | ||
}; | ||
newParts.push(mfsObject[mf.mf]); | ||
} else { | ||
mfsObject[mf.mf].min = mfsObject[mf.mf].min + mf.min; | ||
mfsObject[mf.mf].max = mfsObject[mf.mf].max + mf.max; | ||
} | ||
} | ||
return newParts; | ||
} | ||
function createMFs(parts, comment, limit) { | ||
const currents = new Array(parts.length); | ||
for (let i = 0; i < currents.length; i++) { | ||
currents[i] = parts[i].min; | ||
} | ||
const mfs = []; | ||
let position = 0; | ||
while (position < currents.length) { | ||
if (currents[position] < parts[position].max) { | ||
mfs.push(getMF(parts, currents, comment)); | ||
currents[position]++; | ||
for (let i = 0; i < position; i++) { | ||
currents[i] = parts[i].min; | ||
} | ||
position = 0; | ||
} else { | ||
position++; | ||
} | ||
if (mfs.length > limit) { | ||
throw Error(`MF.flatten generates too many fragments (over ${limit})`); | ||
} | ||
} | ||
mfs.push(getMF(parts, currents, comment)); | ||
return mfs; | ||
} | ||
function getMF(parts, currents, comment) { | ||
let mf = ''; | ||
for (let i = 0; i < parts.length; i++) { | ||
if (currents[i] === 0) { | ||
continue; | ||
} | ||
mf += parts[i].mf; | ||
if (currents[i] !== 1) { | ||
mf += currents[i]; | ||
} | ||
} | ||
if (comment) mf += `$${comment}`; | ||
return mf; | ||
} | ||
function getIsotopeRatioInfo(value) { | ||
let result = { mass: 0, monoisotopicMass: 0 }; | ||
let element = chemicalElements.elementsAndStableIsotopesObject[value.atom]; | ||
if (!element) throw new Error(`Element not found: ${value.atom}`); | ||
let isotopesArray = element.isotopes; | ||
let ratios = normalize$1(value.ratio); | ||
let max = Math.max(...ratios); | ||
if (ratios.length > isotopesArray.length) { | ||
throw new Error( | ||
`the number of specified ratios is bigger that the number of stable isotopes: ${value.atom}`, | ||
); | ||
} | ||
for (let i = 0; i < ratios.length; i++) { | ||
result.mass += ratios[i] * isotopesArray[i].mass; | ||
if (max === ratios[i] && result.monoisotopicMass === 0) { | ||
result.monoisotopicMass = isotopesArray[i].mass; | ||
} | ||
} | ||
return result; | ||
} | ||
function normalize$1(array) { | ||
let sum = array.reduce((prev, current) => prev + current, 0); | ||
return array.map((a) => a / sum); | ||
} | ||
/** | ||
* | ||
* @param {*} parts | ||
* @param {*} [options={}] | ||
*/ | ||
function getEA(parts) { | ||
let results = {}; | ||
for (let part of parts) { | ||
for (let line of part) { | ||
switch (line.kind) { | ||
case Kind.ISOTOPE: { | ||
let isotope = chemicalElements.isotopesObject[line.value.isotope + line.value.atom]; | ||
if (!isotope) { | ||
throw new Error( | ||
`Unknown isotope: ${line.value.isotope}${line.value.atom}`, | ||
); | ||
} | ||
addMass(results, line.value.atom, isotope.mass * line.multiplier); | ||
break; | ||
} | ||
case Kind.ISOTOPE_RATIO: { | ||
let isotopeRatioInfo = getIsotopeRatioInfo(line.value); | ||
addMass( | ||
results, | ||
line.value.atom, | ||
isotopeRatioInfo.mass * line.multiplier, | ||
); | ||
break; | ||
} | ||
case Kind.ATOM: { | ||
let element = chemicalElements.elementsObject[line.value]; | ||
if (!element) { | ||
element = chemicalGroups.groupsObject[line.value]; | ||
if (!element) throw Error(`Unknown element: ${line.value}`); | ||
// need to explode group ???? | ||
} | ||
addMass(results, line.value, element.mass * line.multiplier); | ||
break; | ||
} | ||
case Kind.CHARGE: | ||
break; | ||
default: | ||
throw new Error('partToMF unhandled Kind: ', line.kind); | ||
} | ||
} | ||
} | ||
let eas = []; | ||
let sum = 0; | ||
for (let key in results) { | ||
sum += results[key]; | ||
eas.push({ | ||
element: key, | ||
mass: results[key], | ||
}); | ||
} | ||
eas.forEach((ea) => { | ||
ea.ratio = ea.mass / sum; | ||
}); | ||
return eas; | ||
} | ||
function addMass(results, atom, mass) { | ||
if (!results[atom]) results[atom] = 0; | ||
results[atom] += mass; | ||
} | ||
/** | ||
* | ||
* @param {*} parts | ||
* @param {*} [options={}] | ||
*/ | ||
function getElements(parts) { | ||
const elements = []; | ||
for (const part of parts) { | ||
for (const line of part) { | ||
let number = line.multiplier; | ||
switch (line.kind) { | ||
case Kind.ATOM: { | ||
let symbol = line.value; | ||
let element = chemicalElements.elementsObject[symbol]; | ||
if (!element) { | ||
throw new Error(`element unknown: ${symbol} - ${line}`); | ||
} | ||
addElement(elements, { symbol, number }); | ||
break; | ||
} | ||
case Kind.ISOTOPE: { | ||
let element = chemicalElements.elementsAndIsotopesObject[line.value.atom]; | ||
if (!element) { | ||
throw new Error(`element unknown: ${part.value.atom} - ${line}`); | ||
} | ||
let isotope = element.isotopes.filter( | ||
(a) => a.nominal === line.value.isotope, | ||
)[0]; | ||
if (!isotope) { | ||
throw new Error(`isotope unknown: ${line.value.isotope} - ${line}`); | ||
} | ||
addElement(elements, { | ||
symbol: line.value.atom, | ||
number, | ||
isotope: line.value.isotope, | ||
}); | ||
break; | ||
} | ||
default: | ||
throw new Error(`unknown type: ${line.kind}`); | ||
} | ||
} | ||
} | ||
return elements; | ||
} | ||
function addElement(elements, newElement) { | ||
for (let element of elements) { | ||
if ( | ||
element.symbol === newElement.symbol && | ||
element.isotope === newElement.isotope | ||
) { | ||
element.number += newElement.number; | ||
return; | ||
} | ||
} | ||
elements.push(newElement); | ||
} | ||
/** | ||
* Convert a MF part to an array of atoms | ||
* This procedure will suppress the isotopes ! | ||
* This is mainly used to make queries | ||
*/ | ||
function partToAtoms(part) { | ||
let atoms = {}; | ||
for (let line of part) { | ||
switch (line.kind) { | ||
case Kind.ISOTOPE: | ||
if (!atoms[line.value.atom]) atoms[line.value.atom] = 0; | ||
atoms[line.value.atom] += line.multiplier; | ||
break; | ||
case Kind.ISOTOPE_RATIO: | ||
if (!atoms[line.value.atom]) atoms[line.value.atom] = 0; | ||
atoms[line.value.atom] += line.multiplier; | ||
break; | ||
case Kind.ATOM: | ||
if (!atoms[line.value]) atoms[line.value] = 0; | ||
atoms[line.value] += line.multiplier; | ||
break; | ||
case Kind.CHARGE: | ||
break; | ||
case Kind.ANCHOR: | ||
break; | ||
default: | ||
throw new Error('partToMF unhandled Kind: ', line.kind); | ||
} | ||
} | ||
return atoms; | ||
} | ||
function partToMF(part, options = {}) { | ||
let mf = []; | ||
for (let line of part) { | ||
switch (line.kind) { | ||
case Kind.ISOTOPE: | ||
if (line.multiplier !== 0) { | ||
mf.push( | ||
`[${line.value.isotope}${line.value.atom}]${ | ||
line.multiplier !== 1 ? line.multiplier : '' | ||
}`, | ||
); | ||
} | ||
break; | ||
case Kind.ISOTOPE_RATIO: | ||
if (line.multiplier !== 0) { | ||
mf.push( | ||
`${line.value.atom}{${line.value.ratio.join(',')}}${ | ||
line.multiplier !== 1 ? line.multiplier : '' | ||
}`, | ||
); | ||
} | ||
break; | ||
case Kind.ATOM: | ||
if (line.multiplier !== 0) { | ||
mf.push(line.value + (line.multiplier !== 1 ? line.multiplier : '')); | ||
} | ||
break; | ||
case Kind.CHARGE: | ||
if (line.value === 0 || options.neutral) break; | ||
mf.push(`(${line.value > 0 ? `+${line.value}` : line.value})`); | ||
break; | ||
} | ||
} | ||
return mf.join(''); | ||
} | ||
/** | ||
* | ||
* @param {*} parts | ||
* @param {*} [options={}] | ||
*/ | ||
function getInfo(parts, options = {}) { | ||
let { | ||
customUnsaturations = {}, | ||
emFieldName = 'monoisotopicMass', | ||
msemFieldName = 'observedMonoisotopicMass', | ||
} = options; | ||
if (parts.length === 0) return {}; | ||
if (parts.length === 1) { | ||
return getProcessedPart$1(parts[0], { | ||
customUnsaturations, | ||
emFieldName, | ||
msemFieldName, | ||
}); | ||
} | ||
let result = { parts: [] }; | ||
for (let part of parts) { | ||
result.parts.push( | ||
getProcessedPart$1(part, { | ||
customUnsaturations, | ||
emFieldName, | ||
msemFieldName, | ||
}), | ||
); | ||
} | ||
result[emFieldName] = 0; | ||
result.mass = 0; | ||
result.charge = 0; | ||
result.unsaturation = 0; | ||
result.atoms = {}; | ||
result.mf = result.parts.map((a) => a.mf).join('.'); | ||
for (const part of result.parts) { | ||
result.mass += part.mass; | ||
result[emFieldName] += part[emFieldName]; | ||
result.charge += part.charge; | ||
result.unsaturation += part.unsaturation; | ||
for (const atom in part.atoms) { | ||
if (!result.atoms[atom]) { | ||
result.atoms[atom] = 0; | ||
} | ||
result.atoms[atom] += part.atoms[atom]; | ||
} | ||
} | ||
return result; | ||
} | ||
function getProcessedPart$1(part, options) { | ||
let { customUnsaturations, emFieldName, msemFieldName } = options; | ||
let currentPart = { | ||
mass: 0, | ||
charge: 0, | ||
mf: '', | ||
atoms: partToAtoms(part), | ||
}; | ||
currentPart[emFieldName] = 0; | ||
let unsaturation = 0; | ||
let validUnsaturation = true; | ||
currentPart.mf = partToMF(part); | ||
for (let line of part) { | ||
let currentElement = ''; | ||
switch (line.kind) { | ||
case Kind.ATOM: { | ||
currentElement = line.value; | ||
let element = chemicalElements.elementsAndIsotopesObject[line.value]; | ||
// todo should we have a kind GROUP ? | ||
if (!element) { | ||
element = chemicalGroups.groupsObject[line.value]; | ||
if (!element) throw Error(`Unknown element: ${line.value}`); | ||
if (!customUnsaturations[line.value]) { | ||
customUnsaturations[line.value] = element.unsaturation; | ||
} | ||
} | ||
if (!element) throw new Error(`Unknown element: ${line.value}`); | ||
currentPart[emFieldName] += element.monoisotopicMass * line.multiplier; | ||
currentPart.mass += element.mass * line.multiplier; | ||
break; | ||
} | ||
case Kind.ISOTOPE: { | ||
currentElement = line.value.atom; | ||
let isotope = chemicalElements.isotopesObject[line.value.isotope + line.value.atom]; | ||
if (!isotope) { | ||
throw new Error( | ||
`Unknown isotope: ${line.value.isotope}${line.value.atom}`, | ||
); | ||
} | ||
currentPart[emFieldName] += isotope.mass * line.multiplier; | ||
currentPart.mass += isotope.mass * line.multiplier; | ||
break; | ||
} | ||
case Kind.ISOTOPE_RATIO: { | ||
currentElement = line.value.atom; | ||
let isotopeRatioInfo = getIsotopeRatioInfo(line.value); | ||
currentPart[emFieldName] += | ||
isotopeRatioInfo[emFieldName] * line.multiplier; | ||
currentPart.mass += isotopeRatioInfo.mass * line.multiplier; | ||
break; | ||
} | ||
case Kind.CHARGE: | ||
currentPart.charge = line.value; | ||
if (validUnsaturation) { | ||
unsaturation -= line.value; | ||
} | ||
break; | ||
default: | ||
throw new Error('Unimplemented Kind in getInfo', line.kind); | ||
} | ||
if (currentElement) { | ||
if (customUnsaturations[currentElement] !== undefined) { | ||
unsaturation += customUnsaturations[currentElement] * line.multiplier; | ||
} else if (chemicalElements.unsaturationsObject[currentElement] !== undefined) { | ||
unsaturation += chemicalElements.unsaturationsObject[currentElement] * line.multiplier; | ||
} else { | ||
validUnsaturation = false; | ||
} | ||
} | ||
} | ||
// need to calculate the observedMonoisotopicMass | ||
if (currentPart.charge) { | ||
currentPart[msemFieldName] = | ||
(currentPart[emFieldName] - currentPart.charge * chemicalElements.ELECTRON_MASS) / | ||
Math.abs(currentPart.charge); | ||
} | ||
if (validUnsaturation) { | ||
currentPart.unsaturation = unsaturation / 2 + 1; | ||
} | ||
return currentPart; | ||
} | ||
/** | ||
* | ||
* @param {*} parts | ||
* @param {*} options | ||
*/ | ||
function getIsotopesInfo(parts) { | ||
if (parts.length === 0) return []; | ||
if (parts.length > 1) { | ||
throw new Error('getIsotopesInfo can not be applied on multipart MF'); | ||
} | ||
return getProcessedPart(parts[0]); | ||
} | ||
function getProcessedPart(part) { | ||
let result = { | ||
charge: 0, | ||
isotopes: [], | ||
}; | ||
for (let line of part) { | ||
switch (line.kind) { | ||
case Kind.ISOTOPE: { | ||
let isotope = chemicalElements.isotopesObject[line.value.isotope + line.value.atom]; | ||
if (!isotope) { | ||
throw Error('unknown isotope:', line.value.atom, line.value.isotope); | ||
} | ||
result.isotopes.push({ | ||
atom: `[${line.value.isotope}${line.value.atom}]`, | ||
number: line.multiplier, | ||
distribution: [{ x: isotope.mass, y: 1 }], | ||
}); | ||
break; | ||
} | ||
case Kind.ISOTOPE_RATIO: | ||
{ | ||
let element = chemicalElements.elementsAndStableIsotopesObject[line.value.atom]; | ||
if (!element) throw new Error('unknown element:', line.value); | ||
let distribution = getDistribution( | ||
element.isotopes, | ||
line.value.ratio, | ||
); | ||
result.isotopes.push({ | ||
atom: `${line.value.atom}{${line.value.ratio.join(',')}}`, | ||
number: line.multiplier, | ||
distribution, | ||
}); | ||
} | ||
break; | ||
case Kind.ATOM: { | ||
let element = chemicalElements.elementsAndStableIsotopesObject[line.value]; | ||
if (!element) throw new Error('unknown element:', line.value); | ||
result.isotopes.push({ | ||
atom: line.value, | ||
number: line.multiplier, | ||
distribution: element.isotopes.map((e) => ({ | ||
x: e.mass, | ||
y: e.abundance, | ||
})), | ||
}); | ||
break; | ||
} | ||
case Kind.CHARGE: | ||
result.charge += line.value; | ||
break; | ||
default: | ||
throw new Error('partToMF unhandled Kind: ', line.kind); | ||
} | ||
} | ||
return result; | ||
} | ||
function getDistribution(isotopesArray, ratio) { | ||
let ratios = normalize(ratio); | ||
let result = []; | ||
if (ratios.length > isotopesArray.length) { | ||
throw new Error( | ||
`the number of specified ratios is bigger that the number of stable isotopes: ${chemicalElements.isotopesObject}`, | ||
); | ||
} | ||
for (let i = 0; i < ratios.length; i++) { | ||
result.push({ | ||
x: isotopesArray[i].mass, | ||
y: ratios[i], | ||
}); | ||
} | ||
return result; | ||
} | ||
function normalize(array) { | ||
let sum = array.reduce((prev, current) => prev + current, 0); | ||
return array.map((a) => a / sum); | ||
} | ||
/** | ||
* Converts an array of mf elements to an array of formatting information | ||
* @param {Array<Object>} result of the parse method | ||
*/ | ||
function partsToDisplay(parts) { | ||
let lines = []; | ||
for (let part of parts) { | ||
if (lines.length > 0) lines.push({ kind: Kind.SALT, value: '•' }); | ||
for (let partLine of part) { | ||
lines.push(partLine); | ||
if (partLine.multiplier) { | ||
lines.push({ | ||
kind: Kind.MULTIPLIER, | ||
value: partLine.multiplier, | ||
}); | ||
} | ||
} | ||
} | ||
return toDisplay(lines); | ||
} | ||
function partsToMF(parts, options) { | ||
let mf = []; | ||
for (let part of parts) { | ||
mf.push(partToMF(part, options)); | ||
} | ||
return mf.join(' . '); | ||
} | ||
/** | ||
* | ||
* @param {*} lines | ||
* @param {object} [options={}] | ||
* @param {boolean} [options.expand=true] - Should we expand the groupsObject | ||
*/ | ||
function toParts(lines, options = {}) { | ||
const { expand: shouldExpandgroupsObject = true } = options; | ||
let parts = []; | ||
let currentPart = createNewPart(); | ||
let previousKind = Kind.BEGIN; | ||
parts.push(currentPart); | ||
for (let line of lines) { | ||
switch (line.kind) { | ||
case Kind.ATOM: | ||
case Kind.ISOTOPE_RATIO: | ||
case Kind.ISOTOPE: | ||
case Kind.CHARGE: | ||
currentPart.lines.push({ ...line, multiplier: 1 }); | ||
break; | ||
case Kind.OPENING_PARENTHESIS: | ||
openingParenthesis(currentPart); | ||
break; | ||
case Kind.CLOSING_PARENTHESIS: | ||
closingParenthesis(currentPart); | ||
break; | ||
case Kind.PRE_MULTIPLIER: | ||
preMultiplier(currentPart, line); | ||
break; | ||
case Kind.MULTIPLIER: | ||
postMultiplier(currentPart, line.value, previousKind); | ||
break; | ||
case Kind.SALT: | ||
globalPartMultiplier(currentPart); | ||
currentPart = createNewPart(); | ||
parts.push(currentPart); | ||
break; | ||
case Kind.ANCHOR: // we ignore anchors to create the parts and canonized MF | ||
break; | ||
case Kind.COMMENT: // we ignore comments to create the parts and canonized MF | ||
break; | ||
case Kind.TEXT: | ||
break; | ||
default: | ||
throw new Error(`Can not process mf having: ${line.kind}`); | ||
} | ||
previousKind = line.kind; | ||
} | ||
globalPartMultiplier(currentPart); | ||
if (shouldExpandgroupsObject) expandgroupsObject(parts); | ||
return combineAtomsIsotopesCharges(parts); | ||
} | ||
function createNewPart() { | ||
let currentMultiplier = { value: 1, fromIndex: 0 }; | ||
return { lines: [], multipliers: [currentMultiplier], currentMultiplier }; | ||
} | ||
function openingParenthesis(currentPart) { | ||
currentPart.currentMultiplier = { | ||
value: 1, | ||
fromIndex: currentPart.lines.length, | ||
}; | ||
currentPart.multipliers.push(currentPart.currentMultiplier); | ||
} | ||
function closingParenthesis(currentPart) { | ||
currentPart.currentMultiplier = currentPart.multipliers.pop(); | ||
if (currentPart.currentMultiplier !== 1) { | ||
for ( | ||
let i = currentPart.currentMultiplier.fromIndex; | ||
i < currentPart.lines.length; | ||
i++ | ||
) { | ||
currentPart.lines[i].multiplier *= currentPart.currentMultiplier.value; | ||
} | ||
} | ||
} | ||
function preMultiplier(currentPart, line) { | ||
currentPart.currentMultiplier.value *= line.value; | ||
} | ||
function globalPartMultiplier(currentPart) { | ||
for ( | ||
let i = currentPart.multipliers[0].fromIndex; | ||
i < currentPart.lines.length; | ||
i++ | ||
) { | ||
currentPart.lines[i].multiplier *= currentPart.multipliers[0].value; | ||
} | ||
} | ||
function postMultiplier(currentPart, value, previousKind) { | ||
if (previousKind === Kind.CLOSING_PARENTHESIS) { | ||
// need to apply to everything till the previous parenthesis | ||
for ( | ||
let i = currentPart.currentMultiplier.fromIndex; | ||
i < currentPart.lines.length; | ||
i++ | ||
) { | ||
currentPart.lines[i].multiplier *= value; | ||
} | ||
} else { | ||
// just applies to the previous element | ||
currentPart.lines[currentPart.lines.length - 1].multiplier *= value; | ||
} | ||
} | ||
function expandgroupsObject(parts) { | ||
for (let part of parts) { | ||
let expanded = false; | ||
for (let i = 0; i < part.lines.length; i++) { | ||
let line = part.lines[i]; | ||
if (line.kind === Kind.ATOM) { | ||
let group = chemicalGroups.groupsObject[line.value]; | ||
if (group) { | ||
expanded = true; | ||
for (let element of group.elements) { | ||
if (element.isotope) { | ||
part.lines.push({ | ||
kind: 'isotope', | ||
value: { atom: element.symbol, isotope: element.isotope }, | ||
multiplier: line.multiplier * element.number, | ||
}); | ||
} else { | ||
part.lines.push({ | ||
kind: 'atom', | ||
value: element.symbol, | ||
multiplier: line.multiplier * element.number, | ||
}); | ||
} | ||
} | ||
part.lines[i] = undefined; | ||
} | ||
} | ||
} | ||
if (expanded) part.lines = part.lines.filter((a) => a); | ||
} | ||
} | ||
function combineAtomsIsotopesCharges(parts) { | ||
let results = []; | ||
for (let part of parts) { | ||
let result = []; | ||
results.push(result); | ||
calculateAndSortKeys(part); | ||
let currentKey = ''; | ||
for (let key of part.keys) { | ||
if (key.key === Kind.CHARGE) { | ||
if (currentKey !== key.key) { | ||
result.push({ | ||
kind: Kind.CHARGE, | ||
value: key.value.value * key.value.multiplier, | ||
}); | ||
} else { | ||
result[result.length - 1].value += | ||
key.value.value * key.value.multiplier; | ||
} | ||
} else if (currentKey !== key.key) { | ||
result.push(key.value); | ||
} else { | ||
result[result.length - 1].multiplier += key.value.multiplier; | ||
} | ||
currentKey = key.key; | ||
} | ||
result.sort((a, b) => { | ||
if (a.kind === Kind.CHARGE) return 1; | ||
if (b.kind === Kind.CHARGE) return -1; | ||
let atomA = a.kind === Kind.ATOM ? a.value : a.value.atom; | ||
let atomB = b.kind === Kind.ATOM ? b.value : b.value.atom; | ||
if (atomA !== atomB) return atomSorter.atomSorter(atomA, atomB); | ||
// same atome but some isotopes ... | ||
if (a.kind === Kind.ATOM) return -1; | ||
if (b.kind === Kind.ATOM) return 1; | ||
if (a.kind === Kind.ISOTOPE) return -1; | ||
if (b.kind === Kind.ISOTOPE) return 1; | ||
if (a.kind === Kind.ISOTOPE_RATIO) return -1; | ||
if (b.kind === Kind.ISOTOPE_RATIO) return 1; | ||
return 0; | ||
}); | ||
} | ||
return results; | ||
} | ||
function calculateAndSortKeys(part) { | ||
part.keys = []; | ||
for (let line of part.lines) { | ||
part.keys.push({ key: getKey(line), value: line }); | ||
} | ||
part.keys.sort((a, b) => stringComparator(a.key, b.key)); | ||
} | ||
function getKey(line) { | ||
let key = [line.kind]; | ||
switch (line.kind) { | ||
case Kind.CHARGE: | ||
break; | ||
default: | ||
if (typeof line.value === 'string') { | ||
key.push(line.value); | ||
} else { | ||
for (let prop of Object.keys(line.value).sort()) { | ||
key.push(line.value[prop]); | ||
} | ||
} | ||
} | ||
return key.join('-'); | ||
} | ||
function stringComparator(a, b) { | ||
if (a < b) return -1; | ||
if (a > b) return 1; | ||
return 0; | ||
} | ||
function toText(lines) { | ||
let text = []; | ||
for (let line of lines) { | ||
switch (line.kind) { | ||
case Format.SUBSCRIPT: | ||
{ | ||
const value = String(line.value); | ||
for (let i = 0; i < value.length; i++) { | ||
const char = value[i]; | ||
if (subscript[char]) { | ||
text.push(subscript[char]); | ||
} else { | ||
throw new Error(`Subscript problem with: ${char}`); | ||
} | ||
} | ||
} | ||
break; | ||
case Format.SUPERSCRIPT: { | ||
const value = String(line.value); | ||
for (let i = 0; i < value.length; i++) { | ||
const char = value[i]; | ||
if (superscript[char]) { | ||
text.push(superscript[char]); | ||
} else { | ||
throw new Error(`Superscript problem with: ${char}`); | ||
} | ||
} | ||
break; | ||
} | ||
case Format.SUPERIMPOSE: { | ||
const under = String(line.under); | ||
for (let i = 0; i < under.length; i++) { | ||
const char = under[i]; | ||
if (subscript[char]) { | ||
text.push(subscript[char]); | ||
} else { | ||
throw new Error(`Subscript problem with: ${char}`); | ||
} | ||
} | ||
const over = String(line.over); | ||
for (let i = 0; i < over.length; i++) { | ||
const char = over[i]; | ||
if (superscript[char]) { | ||
text.push(superscript[char]); | ||
} else { | ||
throw new Error(`Superscript problem with: ${char}`); | ||
} | ||
} | ||
break; | ||
} | ||
default: | ||
text.push(line.value); | ||
} | ||
} | ||
return text.join(''); | ||
} | ||
/** | ||
* Class allowing to deal with molecular formula and derived information | ||
*/ | ||
class MF { | ||
constructor(mf, options = {}) { | ||
if (options.ensureCase) { | ||
mf = ensureCase(mf); | ||
} | ||
this.parsed = parse(mf); | ||
this.cache = {}; | ||
} | ||
toDisplay() { | ||
if (!this.cache.displayed) this.cache.displayed = toDisplay(this.parsed); | ||
return this.cache.displayed; | ||
} | ||
toHtml() { | ||
if (!this.cache.html) { | ||
this.toDisplay(); | ||
this.cache.html = toHtml(this.cache.displayed); | ||
} | ||
return this.cache.html; | ||
} | ||
toText() { | ||
if (!this.cache.text) { | ||
this.toDisplay(); | ||
this.cache.text = toText(this.cache.displayed); | ||
} | ||
return this.cache.text; | ||
} | ||
toCanonicText() { | ||
if (!this.cache.canonicText) { | ||
this.cache.canonicText = new MF(this.toMF()).toText(this.cache.displayed); | ||
} | ||
return this.cache.canonicText; | ||
} | ||
toParts(options) { | ||
if (!this.cache.parts) { | ||
this.cache.parts = toParts(this.parsed, options); | ||
} | ||
return this.cache.parts; | ||
} | ||
/** | ||
* Returns an object with the global MF, global charge, monoisotopic mass and mass | ||
* as well as the same information for all the parts | ||
* @param {object} [options={}] options | ||
* @param {object} [options.customUnsaturations={}] custom unsaturations | ||
* @param {string} [options.emFieldName='monoisotopicMass'] name of the monoisotopic mass field | ||
* @param {string} [options.msemFieldName='observedMonoisotopicMass'] name of the observed monoisotopic mass field | ||
*/ | ||
getInfo(options = {}) { | ||
if (!this.cache.info) { | ||
this.toParts(); | ||
this.cache.info = getInfo(this.cache.parts, options); | ||
} | ||
return this.cache.info; | ||
} | ||
/** | ||
* Returns an object with the elemental analysis | ||
*/ | ||
getEA(options = {}) { | ||
if (!this.cache.ea) { | ||
this.toParts(); | ||
this.cache.ea = getEA(this.cache.parts); | ||
} | ||
return this.cache.ea; | ||
} | ||
/** | ||
* Get the different elements for each part | ||
* @returns an array | ||
*/ | ||
getElements() { | ||
if (!this.cache.elements) { | ||
this.toParts(); | ||
this.cache.elements = getElements(this.cache.parts); | ||
} | ||
return this.cache.elements; | ||
} | ||
/** | ||
* Returns an array with each atom and isotopic composition | ||
*/ | ||
getIsotopesInfo(options = {}) { | ||
if (!this.cache.isotopesInfo) { | ||
this.toParts(); | ||
this.cache.isotopesInfo = getIsotopesInfo(this.cache.parts); | ||
} | ||
return this.cache.isotopesInfo; | ||
} | ||
/** | ||
* Get a canonized MF | ||
*/ | ||
toMF() { | ||
if (!this.cache.mf) { | ||
this.toParts(); | ||
this.cache.mf = partsToMF(this.cache.parts); | ||
} | ||
return this.cache.mf; | ||
} | ||
/** | ||
* Get a canonized MF | ||
*/ | ||
toNeutralMF() { | ||
if (!this.cache.neutralMF) { | ||
this.toParts(); | ||
this.cache.neutralMF = partsToMF(this.cache.parts, { neutral: true }); | ||
} | ||
return this.cache.neutralMF; | ||
} | ||
canonize() { | ||
this.toParts(); | ||
this.cache.displayed = partsToDisplay(this.cache.parts); | ||
this.cache.html = undefined; | ||
} | ||
flatten(options) { | ||
return flatten(this.parsed, options); | ||
} | ||
} | ||
/** | ||
* Parse a molecular formula and converts it to an HTML code | ||
* @param {String} mf String containing the molecular formula | ||
*/ | ||
function parseToHtml(mf) { | ||
let parsed = parse(mf); | ||
let display = toDisplay(parsed); | ||
return toHtml(display); | ||
} | ||
exports.Format = Format; | ||
exports.Kind = Kind; | ||
exports.MF = MF; | ||
exports.Style = Style; | ||
exports.ensureCase = ensureCase; | ||
exports.isMF = isMF; | ||
exports.parse = parse; | ||
exports.parseToHtml = parseToHtml; | ||
exports.subscript = subscript; | ||
exports.superscript = superscript; | ||
exports.toDisplay = toDisplay; | ||
exports.toHtml = toHtml; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./parse.js"), exports); | ||
__exportStar(require("./util/subSuperscript.js"), exports); | ||
__exportStar(require("./util/toDisplay.js"), exports); | ||
__exportStar(require("./util/isMF.js"), exports); | ||
__exportStar(require("./util/toHtml.js"), exports); | ||
__exportStar(require("./Kind.js"), exports); | ||
__exportStar(require("./Format.js"), exports); | ||
__exportStar(require("./Style.js"), exports); | ||
__exportStar(require("./ensureCase.js"), exports); | ||
__exportStar(require("./MF.js"), exports); | ||
__exportStar(require("./parseToHtml.js"), exports); |
{ | ||
"name": "mf-parser", | ||
"version": "3.1.1", | ||
"version": "3.2.0", | ||
"description": "Parse a molecular formula", | ||
@@ -23,6 +23,6 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"atom-sorter": "^2.0.1", | ||
"chemical-elements": "^2.0.4", | ||
"chemical-groups": "^2.1.1", | ||
"mf-utilities": "^3.1.1" | ||
"atom-sorter": "^2.1.0", | ||
"chemical-elements": "^2.1.0", | ||
"chemical-groups": "^2.2.0", | ||
"mf-utilities": "^3.2.0" | ||
}, | ||
@@ -32,3 +32,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "b9f99ec6f05ce6b0034d35f4ae0452ae653f90c2" | ||
"gitHead": "28dae91d3b42556a23097ee08acfe4061f276ed0" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
117841
84
3621
1
Updatedatom-sorter@^2.1.0
Updatedchemical-elements@^2.1.0
Updatedchemical-groups@^2.2.0
Updatedmf-utilities@^3.2.0