UNPKG

@kpopfan4life/chemical-number-validification-identification

Version:

A series of functions to determine a chemical number, as well as if it is formatted and valid

495 lines (431 loc) 15.5 kB
'use-strict' import { calculateTotalDigitValueCAS, calculateTotalDigitValueEC, isModuloValid, isModuloValidExceptionEC, reverseString } from './util.js' const CAS_REGEX = /^\d{2,7}-\d{2}-\d{1}$/ // does string fit XX-YY-Z up to XXXXXXX-YY-Z const EC_REGEX = /^\d{3}-\d{3}-\d{1}$/ // does string fit XXX-YYY-Z const KE_ANNEX_ONE_REGEX = /^KE-\d{5}$/ // does string fit KE-XXXXX const KE_ANNEX_TWO_REGEX = /^\d{4}-\d{1}-\d{1,4}$/ // does string fit YYYY-X-X to YYYY-XX-ZZZZ where Y is year /** * Determines which chemical number identifier is used, and if it is valid * @param {String} stringInput the chemical number to figure out which identifier it uses * @returns {String} * @throws {TypeError} */ const identifyChemicalNumber = (stringInput) => { if (typeof stringInput !== 'string') throw new TypeError('Input must be a string') const doesStringMatchIdentifier = Object .entries(identificationObject) .map(([, value]) => {return value.valid(stringInput)} ) const isMatches = doesStringMatchIdentifier.find(value => value.success === true) if (isMatches !== undefined) return isMatches const isCloseMatch = doesStringMatchIdentifier.find(value => value.error === 'invalid') if (isCloseMatch !== undefined) return isCloseMatch return { success: false, message: 'No Chemical Identifiers were matched', originalInput: stringInput, error: 'format', } } /** * Identifies all the strings in array, determines their type and if they are valid * If invalid states "String does not match known chemical identifier" * @param {String[]} stringArray */ const identifyChemicalNumbers = (stringArray) => { if (!Array.isArray(stringArray)) throw new TypeError('Input must be an array') if(!stringArray.every(string => typeof string === 'string')) throw new TypeError('All elements of the array must be a string') const results = Object .values(stringArray) .flatMap( value => { return identifyChemicalNumber(value)}) return results } /** * Determines if a cas number is formatted correctly * @param {String} casString the number to check * @returns {{success: boolean, message: string}} * @throws {TypeError} */ const isFormattedCorrectlyCAS = (casString) => { if (typeof casString !== 'string') throw new TypeError('Input must be a string') const isValid = CAS_REGEX.test(casString) return { success: isValid, message: isValid ? `${casString} is formatted correctly` : `${casString} is not formatted correctly`, originalInput: casString, } } /** * Determines if a ec number is valid * @param {String} ecString the number to check * @returns {{success: boolean, message: string}} * @throws {TypeError} */ const isFormattedCorrectlyEC = (ecString) => { if (typeof ecString !== 'string') throw new TypeError('Input must be a string') const isValid = EC_REGEX.test(ecString) return { success: isValid, message: isValid ? `${ecString} is formatted correctly` : `${ecString} is not formatted correctly`, originalInput: ecString, } } /** * Checks if a given string fits the format KE-XXXXX * @param {String} keString the ke string to check * @returns {{success: boolean, message: string}} * @throws {TypeError} */ const isFormattedCorrectlyAnnexOneKE = (keString) => { if (typeof keString !== 'string') throw new TypeError('Input must be a string') const isValid = KE_ANNEX_ONE_REGEX.test(keString) return { success: isValid, message: isValid ? `${keString} is formatted correctly` : `${keString} is not formatted correctly`, originalInput: keString, } } /** * Checks if a given string fits the format YYYY-Z-XXXX * @param {String} keString keString the ke string to check * @returns {{success: boolean, message: string}} * @throws {TypeError} */ const isFormattedCorrectlyAnnexTwoKE = (keString) => { if (typeof keString !== 'string') throw new TypeError('Input must be a string') const isValid = KE_ANNEX_TWO_REGEX.test(keString) return { success: isValid, message: isValid ? `${keString} is formatted correctly` : `${keString} is not formatted correctly`, originalInput: keString, } } /** * Determines which annex a ke number belongs to * for true false use isFormattedCorrectlyAnnexTwoKE() or * isFormattedCorrectlyAnnexOneKE() * @param {String} keString * @returns {{ success: boolean, message: string }} { success: boolean, message: string } * @throws { TypeError } when anything but a string is passed in */ const isFormattedCorrectlyKE = (keString) => { if (typeof keString !== 'string') throw new TypeError('Input must be a string') if (KE_ANNEX_ONE_REGEX.test(keString)) return { success: true, message: `${keString} matches KE Annex 1 numbers`, originalInput: keString, } if (KE_ANNEX_TWO_REGEX.test(keString)) return { success: true, message: `${keString} matches KE Annex 2 numbers`, originalInput: keString, } return { success: false, message: `${keString} did not match either Annex numbers`, originalInput: keString } } /** * Checks that a CAS Registry Number fits the formula that governs them * Example: * CAS Number 7732-18-5 * 5 = (8×1 + 1×2 + 2×3 + 3×4 + 7×5 + 7×6) mod 10 * @param {String} casString * @returns {{success: boolean, message: string}} { success: boolean, message: string } * @throws {TypeError} */ const isValidCAS = (casString) => { if (typeof casString !== 'string') throw new TypeError('Input must be a string') const parts = casString.split('-') const checkSum = parseInt(parts[2]) const checkDigits = reverseString(parts[1]) + reverseString(parts[0]) const total = calculateTotalDigitValueCAS(checkDigits) const moduloValueCAS = 10 const isValid = isModuloValid(total, moduloValueCAS, checkSum) return { success: isValid, message: isValid ? `${casString} is a valid CAS number` : `${casString} is not a valid CAS number`, originalInput: casString, } } /** * Checks that a EC number (European Community Number) fits the formula that governs them * R = (N1 + 2N2 + 3N3 + 4N4 + 5N5 + 6N6) mod 11 * @param {String} ecString * @returns {{success: boolean, message: string}} {success: boolean, message: string} * @throws {TypeError} */ const isValidEC = (ecString) => { if (typeof ecString !== 'string') throw new TypeError('Input must be a string') const parts = ecString.split('-') const checkSum = parseInt(parts[2]) const checkDigits = parts[0] + parts[1] const total = calculateTotalDigitValueEC(checkDigits) const moduloValueEC = 11 const isValid = isModuloValid(total, moduloValueEC, checkSum) ? isModuloValid(total, moduloValueEC, checkSum) : isModuloValidExceptionEC(total, moduloValueEC, checkSum) return { success: isValid, message: isValid ? `${ecString} is a valid EC number` : `${ecString} is not a valid EC number`, originalInput: ecString, } } /** * Determines if a given string fits the Annex 1 KE list * i.e KE-12345 * @param {String} keString the string to test * @returns {{success: boolean, message: string}} {success: boolean, message: string} * @throws {TypeError} if you pass in a non string or an empty string */ const isValidAnnexOneKE = (keString) => { if (typeof keString !== 'string') throw new TypeError('Input must be a string') const isValid = keString.length === 8 return { success: isValid, message: isValid ? `${keString} is a valid Annex 1 KE number` : `${keString} is not a valid Annex 1 KE number`, originalInput: keString, } } /** * Determines if a given string fits the Annex 2 KE list * i.e KE-12345 * @param {String} keString the string to test * @returns {{success: boolean, message: string}} { success: boolean, message: string } * @throws {TypeError} if you pass in a non string or an empty string */ const isValidAnnexTwoKE = (keString) => { if (typeof keString !== 'string') throw new TypeError('Input must be a string') const parts = keString.split('-') // split on first occurance of the - const year = parseInt(parts[0]) const date = new Date().getFullYear() const AnnexTwoYearOfIntroduction = 1991 const isValid = !(year > date || year < AnnexTwoYearOfIntroduction || isNaN(year)) return { success: isValid, message: isValid ? `${keString} is a valid Annex 2 KE number` : `${keString} is not a valid Annex 2 KE number`, originalInput: keString, } } /** * Determines which Annex the number is from, and whether this is valid or not * @param {String} keString * @returns {{success: boolean, message: string}} {success: boolean, message: string} * @throws {TypeError} */ const isValidKE = (keString) => { if (typeof keString !== 'string') throw new TypeError('Input must be a string') const isValidAnnexOne = isValidAnnexOneKE(keString) if (isValidAnnexOne.success) return { success: true, message: `${keString} is a valid Annex 1 KE number`, originalInput: keString, } const isValidAnnexTwo = isValidAnnexTwoKE(keString) if (isValidAnnexTwo.success) return { success: true, message: `${keString} is a valid Annex 2 KE number`, originalInput: keString, } return { success: false, message: `${keString} is not a valid KE number for Annex 1 or Annex 2`, originalInput: keString, } } /** * * @param {String} casString * @returns {{success: boolean, message: string}} {success: boolean, message: string} * @throws {TypeError} */ const isFormattedCorrectlyAndIsValidCAS = (casString) => { const isFormattedCas = isFormattedCorrectlyCAS(casString) if (!isFormattedCas.success) return { success: isFormattedCas.success, message: isFormattedCas.message, type: 'CAS Number', numberFormat: 'XX-YY-Z - XXXXXXX-YY-Z', error: 'format', formatted: false, valid: false, originalInput: casString, } const isValid = isValidCAS(casString) if (!isValid.success) return { success: isValid.success, message: isValid.message, type: 'CAS Number', numberFormat: 'XX-YY-Z - XXXXXXX-YY-Z', error: 'invalid', formatted: true, valid: false, originalInput: casString, } return { success: true, message: `${casString} is formatted correctly and is valid`, type: 'CAS Number', numberFormat: 'XX-YY-Z - XXXXXXX-YY-Z', error: null, formatted: true, valid: true, originalInput: casString } } /**M * * @param {String} ecString * @returns {success: boolean, message: string} {success: boolean, message: string} * @throws {TypeError} */ const isFormattedCorrectlyAndIsValidEC = (ecString) => { const formattedEc = isFormattedCorrectlyEC(ecString) if (!formattedEc.success) return { success: formattedEc.success, message: formattedEc.message, type: 'EC Number', numberFormat: 'XXX-YYY-Z', error: 'format', formatted: false, valid: false, originalInput: ecString, } const validEc = isValidEC(ecString) if (!validEc.success) return { success: validEc.success, message: validEc.message, type: 'EC Number', numberFormat: 'XXX-YYY-Z', error: 'invalid', formatted: true, valid: false, originalInput: ecString, } return { success: true, message: `${ecString} is formatted correctly and is valid`, type: 'EC Number', numberFormat: 'XXX-YYY-Z', error: null, formatted: true, valid: true, originalInput: ecString, } } /** * Determines whether a given string is formatted correctly and is valid * @param {String} keString * @returns {{success: boolean, message: string}} {success: boolean, message: string} * @throws {TypeError} */ const isFormattedCorrectlyAndIsValidAnnexOneKE = (keString) => { const formattedKE = isFormattedCorrectlyAnnexOneKE(keString) if (!formattedKE.success) return { success: formattedKE.success, message: formattedKE.message, type: 'KE Annex 1', numberFormat: 'KE-XXXXX', error: 'format', formatted: false, valid: false, originalInput: keString, } const validKE = isValidAnnexOneKE(keString) if (!validKE.success) return { success: validKE.success, message: validKE.message, type: 'KE Annex 1', numberFormat: 'KE-XXXXX', error: 'invalid', formatted: true, valid: false, originalInput: keString, } return { success: true, message: `${keString} is formatted correctly and is valid`, type: 'KE Annex 1', numberFormat: 'KE-XXXXX', error: null, formatted: true, valid: true, originalInput: keString, } } /** * Determines whether a given string is formatted correctly * @param {String} keString * @returns {{success: boolean, message: string}} {success: boolean, message: string} * @throws {TypeError} */ const isFormattedCorrectlyAndIsValidAnnexTwoKE = (keString) => { const formattedKE = isFormattedCorrectlyAnnexTwoKE(keString) if (!formattedKE.success) return { success: formattedKE.success, message: formattedKE.message, type: 'KE Annex 2', numberFormat: 'YYYY-X-ZZZZ', error: 'format', formatted: false, valid: false, originalInput: keString, } const validKE = isValidAnnexTwoKE(keString) if (!validKE.success) return { success: validKE.success, message: validKE.message, type: 'KE Annex 2', numberFormat: 'YYYY-X-ZZZZ', error: 'invalid', formatted: true, valid: false, originalInput: keString, } return { success: true, message: `${keString} is formatted correctly and is valid`, type: 'KE Annex 2', numberFormat: 'YYYY-X-ZZZZ', error: null, formatted: true, valid: true, originalInput: keString, } } /** * Object to use for identifyChemicalNumber */ const identificationObject = { ['CAS Registry Number']: { valid: isFormattedCorrectlyAndIsValidCAS, }, ['EC European Community Number']: { valid: isFormattedCorrectlyAndIsValidEC, }, ['KE Annex 1']: { valid: isFormattedCorrectlyAndIsValidAnnexOneKE, }, ['KE Annex 2']: { valid: isFormattedCorrectlyAndIsValidAnnexTwoKE, }, } export default identifyChemicalNumber export { identifyChemicalNumber, identifyChemicalNumbers, isFormattedCorrectlyAndIsValidAnnexOneKE, isFormattedCorrectlyAndIsValidAnnexTwoKE, isFormattedCorrectlyAndIsValidCAS, isFormattedCorrectlyAndIsValidEC, isFormattedCorrectlyAnnexOneKE, isFormattedCorrectlyAnnexTwoKE, isFormattedCorrectlyCAS, isFormattedCorrectlyEC, isFormattedCorrectlyKE, isValidAnnexOneKE, isValidAnnexTwoKE, isValidCAS, isValidEC, isValidKE }