From 93837aaf2072cc39208c1de1d3b7f0003352db0b Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 22 Feb 2026 17:05:57 -0600 Subject: [PATCH] fix: fixed title parsing for special characters --- lib/measurements/matcher.js | 104 +++++++++++++++++++++++------------- recipes/recipes.11tydata.js | 4 +- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/lib/measurements/matcher.js b/lib/measurements/matcher.js index 077ab2b..1ce30ad 100644 --- a/lib/measurements/matcher.js +++ b/lib/measurements/matcher.js @@ -371,21 +371,83 @@ function findDimensions(text) { /** * Find weight measurements in text. * - * Handles: 200g, 550 g, ~250g, 180-240g, 1kg, 227g (8 oz) + * Handles: 200g, 550 g, ~250g, 180-240g, 1kg, 227g (8 oz), + * 80g (1/3 cups), 860g (800mL (3 1/3 cups)) */ function findWeights(text) { const results = []; + // Supports optional nested alternatives: + // 860g (800mL (3 1/3 cups)) → primary=860g, outer=800mL, inner=3 1/3 cups + // 80g (1/3 cups) → primary=80g, outer=1/3 cups + // 227g (8 oz) → primary=227g, outer=8 oz const weightRe = new RegExp( `(${AMOUNT})\\s*(${WEIGHT_UNIT})\\b` + - `(?:\\s*\\(\\s*(${AMOUNT})\\s*(${WEIGHT_UNIT}|${VOLUME_UNIT})\\s*\\))?`, + `(?:\\s*\\(\\s*(${AMOUNT})\\s*(${WEIGHT_UNIT}|${VOLUME_UNIT})\\b` + + `(?:\\s*\\(\\s*(${AMOUNT})\\s*(${WEIGHT_UNIT}|${VOLUME_UNIT})\\s*\\))?` + + `\\s*\\))?`, 'gi' ); let m; while ((m = weightRe.exec(text)) !== null) { - // Avoid matching dimension patterns (e.g., the "g" in "9x13 glass") - // Check if this match overlaps with any dimension + const amount = parseAmount(m[1]); + const unit = normalizeUnit(m[2]); + let alt = null; + let intermediate = null; + + if (m[5] && m[6]) { + // Nested alt: e.g. 860g (800mL (3 1/3 cups)) + // Inner alt is the display alternative, outer is the intermediate for scaling + alt = { + amount: parseAmount(m[5]), + unit: normalizeUnit(m[6]), + }; + intermediate = { + amount: parseAmount(m[3]), + unit: normalizeUnit(m[4]), + }; + } else if (m[3] && m[4]) { + // Simple alt: e.g. 227g (8 oz), 80g (1/3 cups) + alt = { + amount: parseAmount(m[3]), + unit: normalizeUnit(m[4]), + }; + } + + results.push({ + match: m[0], + index: m.index, + type: 'weight', + amount, + unit, + approximate: typeof amount.approximate === 'boolean' ? amount.approximate : false, + alt, + intermediate, + }); + } + + return results; +} + +/** + * Find volume measurements in text. + * + * Handles: 2 quarts, 1/2 cups, 1 cup, 6 tablespoons, 6 table spoons, + * 1 1/2 tablespoon, 3/4 teaspoon, 6 parts by volume, + * 800mL (3 1/3 cups) + */ +function findVolumes(text) { + const results = []; + + const volumeRe = new RegExp( + `(${AMOUNT})\\s*(${VOLUME_UNIT})\\b` + + `(?:\\s*\\(\\s*(${AMOUNT})\\s*(${VOLUME_UNIT}|${WEIGHT_UNIT})\\s*\\))?`, + 'gi' + ); + + let m; + while ((m = volumeRe.exec(text)) !== null) { const amount = parseAmount(m[1]); const unit = normalizeUnit(m[2]); let alt = null; @@ -395,38 +457,6 @@ function findWeights(text) { unit: normalizeUnit(m[4]), }; } - results.push({ - match: m[0], - index: m.index, - type: 'weight', - amount, - unit, - approximate: typeof amount.approximate === 'boolean' ? amount.approximate : false, - alt, - }); - } - - return results; -} - -/** - * Find volume measurements in text. - * - * Handles: 2 quarts, 1/2 cups, 1 cup, 6 tablespoons, 6 table spoons, - * 1 1/2 tablespoon, 3/4 teaspoon, 6 parts by volume - */ -function findVolumes(text) { - const results = []; - - const volumeRe = new RegExp( - `(${AMOUNT})\\s*(${VOLUME_UNIT})\\b`, - 'gi' - ); - - let m; - while ((m = volumeRe.exec(text)) !== null) { - const amount = parseAmount(m[1]); - const unit = normalizeUnit(m[2]); results.push({ match: m[0], index: m.index, @@ -434,7 +464,7 @@ function findVolumes(text) { amount, unit, approximate: typeof amount.approximate === 'boolean' ? amount.approximate : false, - alt: null, + alt, }); } diff --git a/recipes/recipes.11tydata.js b/recipes/recipes.11tydata.js index c82203f..0fff7e8 100644 --- a/recipes/recipes.11tydata.js +++ b/recipes/recipes.11tydata.js @@ -44,6 +44,8 @@ const getPlantBased = (filePath) => { const slugify = (text) => { return text .toLowerCase() + .normalize('NFD') // Decompose accented characters + .replace(/[\u0300-\u036f]/g, '') // Remove combining diacritical marks .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[()]/g, '') // Remove parentheses .replace(/[^\w-]+/g, '') // Remove non-word chars except hyphens @@ -224,7 +226,7 @@ module.exports = { return getSlugFromPath(data.page.inputPath); }, title: (data) => { - if (data.title && data.title !== data.page.fileSlug) { + if (data.title) { return data.title; } const slug = getSlugFromPath(data.page.inputPath);