Regex source matching one real subscript/superscript in the source format
(e.g. <su[bp]>…</su[bp]> for HTML, or the union of glyph/escape/entity/tag forms for text).
A global RegExp that matches formula candidates.
const re = buildFormulaPattern("<su[bp]>[1-9][0-9]*</su[bp]>");
[...("H<sub>2</sub>O and CO<sub>2</sub>".matchAll(re))].map((m) => m[0]);
// ["H<sub>2</sub>O", "CO<sub>2</sub>"]
export function buildFormulaPattern(subToken: string): RegExp {
const element = FORMULA_ELEMENT_PATTERN;
const subPart = `(?:${subToken}|[1-9][0-9]*)`;
const unit = `(?:(?:${element}|[()\\[\\]])+(?:${subPart})*)`;
const head = `(?:${unit})+`;
const charge = '(?:[+-](?![A-Za-z0-9]))?';
const separator = '(?:\\s*[·•‧∙⋅・・*]\\s*|\\.(?=[A-Za-z(\\[]))';
const coefficient = `(?:${subToken}|[1-9][0-9]*(?:/[1-9][0-9]*)?|[xn])`;
// Preceding char must be whitespace or start-of-string; the double-negative
// form makes start/end fall through for free and covers \r\n, \t, etc.
const leftBoundary = '(?<![^\\s>])';
// Following char must be whitespace or end-of-string.
const rightBoundary = '(?![^\\s\\.<])';
return new RegExp(
`${leftBoundary}((?![^<>]*>)${head}${charge}(?:${separator}(?:${coefficient})?${unit}+${charge})*)${rightBoundary}`,
'g',
);
}
Builds the chemical-formula matching regex, parameterized by the subscript token for the source format. Everything else is shared: element gating, an optional ionic charge, salt/hydrate separators with optional coefficients, and a "head" that must look like a formula — either ≥2 element/bracket units, or a single element carrying a real subscript.
The returned regex is global, so callers can collect every candidate (via
matchAll) and choose the most likely one with pickBestFormula.