Raw product copy, possibly containing HTML markup
The chemical properties found; fields absent or invalid are omitted
parseChemicalSpecs("<p>Purity: 98%+</p><p>Formula: C5H9KO2</p><p>MW: 140.22g/mol</p>")
// { purity: 98, formula: "C5H9KO2", molecularWeight: 140.22 }
parseChemicalSpecs("<li>MW - 136.169 G/MOL</li><li>SMILES: [K+].CCCCC([O-])=O</li>")
// { molecularWeight: 136.169, smiles: "[K+].CCCCC([O-])=O" }
parseChemicalSpecs("Ships in 4-6 business days") // {}
export const parseChemicalSpecs = (html: string): ChemicalSpecs => {
if (!html || typeof html !== 'string') return {};
const text = normalizeSpecText(html);
const lines = text.split('\n').map((line) => line.trim());
const specs: ChemicalSpecs = {};
const purity = extractPurity(lines);
const grade = extractGrade(lines);
if (purity !== undefined) specs.purity = purity;
if (grade !== undefined) specs.grade = grade;
// Iterate every "…formula…" match and keep the first candidate that validates: the copy may say
// "the rough formula is C6H15NO3" (capturing the word "is") before the real "Empirical formula
// C6H15NO3", and a single match would stop at the bogus first hit. Clean single formulas (e.g.
// "NaOH") are kept verbatim; salts/adducts joined by a dot (e.g. "C6H15NO3.H3PO4") aren't valid
// isMoleForm, so fall back to the tolerant finder.
for (const formulaMatch of text.matchAll(new RegExp(FORMULA_REGEX.source, 'gi'))) {
const rawFormula = formulaMatch[1];
const formula = isMoleForm(rawFormula) ? rawFormula : findFormulaInText(rawFormula);
if (formula) {
specs.formula = formatFormula(formula);
break;
}
}
const molecularWeight = findMolarMass(text);
if (molecularWeight !== undefined) specs.molecularWeight = molecularWeight;
const smilesMatch = text.match(SMILES_LABEL_REGEX);
if (smilesMatch && looksLikeSmiles(smilesMatch[1])) specs.smiles = smilesMatch[1];
return specs;
};
Extracts structured chemical properties (purity, molecular formula, molecular weight, SMILES) from a supplier's free-form, HTML-laced product copy. Built for Wix suppliers whose specs live inside descriptions and additional-info accordions as loosely-labelled bullet lists — labels and separators vary wildly (
MW -,Molecular mass :,Formula:), so each field is matched tolerantly and validated before being returned. CAS numbers are intentionally left tofindCAS(in helpers/cas), which already searches free text robustly.