Files
recipe-app/backend/dist/common/utils/recipe-parser.js
T
Nils-Johan Gynther 969dafdbc6
Test Suite / test (24.15.0) (push) Has been cancelled
Refactor code structure for improved readability and maintainability
2026-05-06 07:37:59 +02:00

124 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRecipeMarkdown = parseRecipeMarkdown;
const KNOWN_UNITS = new Set([
'g', 'kg', 'hg', 'mg', 'ml', 'dl', 'l', 'tl',
'st', 'tsk', 'msk', 'krm', 'matsled', 'tesled',
'pris', 'portion', 'port', 'burk', 'förp', 'paket', 'efter smak', 'klyfta',
]);
const H1_RE = /^#\s+/;
const H2_RE = /^##\s+/;
const INGREDIENT_HEADING_RE = /ingrediens/;
const INSTRUCTION_HEADING_RE = /instruktion|tillagning|gör så här|steg|tillväg|metod/;
const BULLET_RE = /^[-*]\s+/;
const PAREN_NOTE_RE = /\(([^)]+)\)\s*$/;
const FRACTION_RE = /^(\d+)?\s*(\d+)\s*\/\s*([\d.]+)\s+(\S+)\s+(.+)$/;
const RANGE_RE = /^(?:ca\.?\s+)?(\d+(?:[.,]\d+)?)\s*[-]\s*(?:ca\.?\s+)?(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/i;
const QTY_UNIT_NAME_RE = /^(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/;
const QTY_NAME_RE = /^(\d+(?:[.,]\d+)?)\s+(.+)$/;
const ALTERNATIVES_RE = /\s+eller\s+/i;
function parseRecipeMarkdown(markdown) {
const lines = markdown.split('\n');
let name = '';
let description = '';
let instructions = '';
const ingredients = [];
let currentSection = 'none';
const descriptionLines = [];
const instructionLines = [];
for (const line of lines) {
const trimmed = line.trim();
if (H1_RE.test(trimmed) && !trimmed.startsWith('##')) {
name = trimmed.replace(H1_RE, '').trim();
currentSection = 'description';
continue;
}
if (H2_RE.test(trimmed)) {
const heading = trimmed.replace(H2_RE, '').trim().toLowerCase();
if (INGREDIENT_HEADING_RE.test(heading)) {
currentSection = 'ingredients';
}
else if (INSTRUCTION_HEADING_RE.test(heading)) {
currentSection = 'instructions';
}
else {
currentSection = 'none';
}
continue;
}
switch (currentSection) {
case 'description':
if (trimmed.length > 0) {
descriptionLines.push(trimmed);
}
break;
case 'ingredients':
if (BULLET_RE.test(trimmed)) {
const ingredientText = trimmed.replace(BULLET_RE, '');
ingredients.push(parseIngredientLine(ingredientText));
}
break;
case 'instructions':
if (trimmed.length > 0) {
instructionLines.push(trimmed);
}
break;
}
}
description = descriptionLines.join('\n');
instructions = instructionLines.join('\n');
return { name, description, instructions, ingredients };
}
function parseIngredientLine(text) {
const trimmed = text.trim();
const toAlternatives = (rawName) => ALTERNATIVES_RE.test(rawName)
? rawName.split(ALTERNATIVES_RE).map((s) => s.trim()).filter(Boolean)
: [rawName];
let note = null;
let main = trimmed;
const parenMatch = trimmed.match(PAREN_NOTE_RE);
if (parenMatch) {
note = parenMatch[1].trim();
main = trimmed.slice(0, parenMatch.index).trim();
}
const fractionMatch = main.match(FRACTION_RE);
if (fractionMatch) {
const whole = fractionMatch[1] ? parseFloat(fractionMatch[1]) : 0;
const quantity = whole + parseFloat(fractionMatch[2]) / parseFloat(fractionMatch[3]);
const candidateUnit = fractionMatch[4].toLowerCase();
if (KNOWN_UNITS.has(candidateUnit)) {
const rawName = fractionMatch[5].trim();
return { quantity, unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) };
}
}
const rangeMatch = main.match(RANGE_RE);
if (rangeMatch) {
const candidateUnit = rangeMatch[3].toLowerCase();
if (KNOWN_UNITS.has(candidateUnit)) {
const lo = parseNumber(rangeMatch[1]);
const hi = parseNumber(rangeMatch[2]);
const rawName = rangeMatch[4].trim();
return { quantity: (lo + hi) / 2, unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) };
}
}
const fullMatch = main.match(QTY_UNIT_NAME_RE);
if (fullMatch) {
const candidateUnit = fullMatch[2].toLowerCase();
if (KNOWN_UNITS.has(candidateUnit)) {
const rawName = fullMatch[3].trim();
return { quantity: parseNumber(fullMatch[1]), unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) };
}
const rawName = fullMatch[2] + ' ' + fullMatch[3];
return { quantity: parseNumber(fullMatch[1]), unit: 'st', rawName, note, alternatives: toAlternatives(rawName) };
}
const noUnitMatch = main.match(QTY_NAME_RE);
if (noUnitMatch) {
const rawName = noUnitMatch[2].trim();
return { quantity: parseNumber(noUnitMatch[1]), unit: 'st', rawName, note, alternatives: toAlternatives(rawName) };
}
return { quantity: 0, unit: '', rawName: main, note, alternatives: toAlternatives(main) };
}
function parseNumber(s) {
return parseFloat(s.replace(',', '.'));
}
//# sourceMappingURL=recipe-parser.js.map