ChemPal Documentation - v1.6.0
    Preparing search index...
    • Turns a product slug back into something close to the store's product title.

      Slugs flatten punctuation to hyphens, so this reverses the common cases:

      • v-v / w-w / w-v(v/v), and marks the number before it as a percent
      • concentration context → percent: 10-aqueous-solution10% aqueous solution
      • leading digit runs → locants: 1-2-dichloroethane1,2-dichloroethane
      • three-digit groups → thousands: 1-000ppm1,000ppm
      • ascending pairs → ranges: 20-30-mesh20-30 mesh
      • other digit pairs → decimals: 0-100-normal0.100 normal
      • 1g-l / 10-g-l1g/L / 10 g/L
      • p-xylene → keeps the stereo/position prefix hyphen

      Measured at ~74% exact match (case- and comma-insensitive) against real product titles; the remainder is unrecoverable from a slug (commas, editorial parentheticals, degree signs, #, =, +). The real title is recovered from the product page later, so this only needs to be good enough to rank matches.

      Parameters

      • slug: string

        A product slug, e.g. sodium-metasilicate-anhydrous.

      Returns string

      The humanized product name.

      humanizeSlug('isopropyl-alcohol-70-v-v-aqueous-solution');
      // "isopropyl alcohol 70% (v/v) aqueous solution"
      export function humanizeSlug(slug: string): string {
      const parts = slug.split('-').filter(Boolean);
      const out: string[] = [];

      for (let i = 0; i < parts.length; i++) {
      const cur = parts[i];
      const next = parts[i + 1];
      const prev = out[out.length - 1];

      // "1-2-dichloroethane" -> "1,2-dichloroethane": leading digits are locants.
      if (
      i === 0 &&
      DIGITS.test(cur) &&
      next &&
      DIGITS.test(next) &&
      parts[i + 2] &&
      !DIGITS.test(parts[i + 2])
      ) {
      out.push(`${cur},${next}-${parts[i + 2]}`);
      i += 2;
      continue;
      }

      // "p-xylene", "tert-butyl": stereo/position prefixes keep their hyphen.
      if (i === 0 && next && CHEM_PREFIX.test(cur)) {
      out.push(`${cur}-${next}`);
      i++;
      continue;
      }

      // "v-v" -> "(v/v)"; a bare number in front of it is a percentage.
      if (next && RATIO.test(cur) && RATIO.test(next)) {
      if (prev && /\d$/.test(prev)) out[out.length - 1] = `${prev}%`;
      out.push(`(${cur}/${next})`);
      i++;
      continue;
      }

      // "1g-l" / "10-g-l" -> "1g/L" / "10 g/L"
      if (next === 'l' && VOLUME_UNIT.test(cur.replace(/^\d+/, ''))) {
      out.push(`${cur}/L`);
      i++;
      continue;
      }

      if (next && DIGITS.test(cur)) {
      // Leading zero is always a decimal: "0-0855" -> "0.0855"
      if (cur.startsWith('0') && DIGITS.test(next)) {
      out.push(`${cur}.${next}`);
      i++;
      continue;
      }
      // Three digits following is a thousands group: "1-000ppm" -> "1,000ppm"
      if (LEADS_3_DIGITS.test(next)) {
      out.push(`${cur},${next}`);
      i++;
      continue;
      }
      // Ascending multi-digit pair is a range: "20-30-mesh" -> "20-30 mesh"
      if (DIGITS.test(next) && cur.length >= 2 && next.length >= 2 && Number(next) > Number(cur)) {
      out.push(`${cur}-${next}`);
      i++;
      continue;
      }
      // Otherwise a decimal: "6-0-normal" -> "6.0 normal"
      if (DIGITS.test(next)) {
      out.push(`${cur}.${next}`);
      i++;
      continue;
      }
      }

      out.push(cur);
      }

      for (let i = 0; i < out.length - 1; i++) {
      // "10 aqueous solution" -> "10% aqueous solution"; also catches decimals.
      if (/^\d+([.,]\d+)?$/.test(out[i]) && CONCENTRATION_CONTEXT.has(out[i + 1])) {
      out[i] = `${out[i]}%`;
      }
      // Conductivity standards: "1,500 s solution" -> "1,500 µS solution"
      if (out[i + 1] === 's' && /^\d+,\d{3}$/.test(out[i])) out[i + 1] = 'µS';
      }

      return out.join(' ');
      }