69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
|
|
import type { Ingredient } from '$lib/types';
|
|||
|
|
|
|||
|
|
const UNITS = new Set([
|
|||
|
|
'g',
|
|||
|
|
'kg',
|
|||
|
|
'ml',
|
|||
|
|
'l',
|
|||
|
|
'cl',
|
|||
|
|
'dl',
|
|||
|
|
'TL',
|
|||
|
|
'EL',
|
|||
|
|
'Prise',
|
|||
|
|
'Pck.',
|
|||
|
|
'Pkg',
|
|||
|
|
'Becher',
|
|||
|
|
'Stk',
|
|||
|
|
'Stück',
|
|||
|
|
'Bund',
|
|||
|
|
'Tasse',
|
|||
|
|
'Dose'
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const FRACTION_MAP: Record<string, number> = {
|
|||
|
|
'1/2': 0.5,
|
|||
|
|
'1/3': 1 / 3,
|
|||
|
|
'2/3': 2 / 3,
|
|||
|
|
'1/4': 0.25,
|
|||
|
|
'3/4': 0.75
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function parseQuantity(raw: string): number | null {
|
|||
|
|
const trimmed = raw.trim();
|
|||
|
|
if (FRACTION_MAP[trimmed] !== undefined) return FRACTION_MAP[trimmed];
|
|||
|
|
const rangeMatch = /^(\d+[.,]?\d*)\s*[-–]\s*\d+[.,]?\d*$/.exec(trimmed);
|
|||
|
|
if (rangeMatch) {
|
|||
|
|
return parseFloat(rangeMatch[1].replace(',', '.'));
|
|||
|
|
}
|
|||
|
|
const num = parseFloat(trimmed.replace(',', '.'));
|
|||
|
|
return Number.isFinite(num) ? num : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function parseIngredient(raw: string, position = 0): Ingredient {
|
|||
|
|
const rawText = raw.trim();
|
|||
|
|
let working = rawText;
|
|||
|
|
let note: string | null = null;
|
|||
|
|
const noteMatch = /\(([^)]+)\)/.exec(working);
|
|||
|
|
if (noteMatch) {
|
|||
|
|
note = noteMatch[1].trim();
|
|||
|
|
working = (
|
|||
|
|
working.slice(0, noteMatch.index) + working.slice(noteMatch.index + noteMatch[0].length)
|
|||
|
|
).trim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const qtyPattern = /^((?:\d+[.,]?\d*(?:\s*[-–]\s*\d+[.,]?\d*)?)|(?:\d+\/\d+))\s+(.+)$/;
|
|||
|
|
const qtyMatch = qtyPattern.exec(working);
|
|||
|
|
if (!qtyMatch) {
|
|||
|
|
return { position, quantity: null, unit: null, name: working, note, raw_text: rawText };
|
|||
|
|
}
|
|||
|
|
const quantity = parseQuantity(qtyMatch[1]);
|
|||
|
|
let rest = qtyMatch[2].trim();
|
|||
|
|
let unit: string | null = null;
|
|||
|
|
const firstTokenMatch = /^(\S+)\s+(.+)$/.exec(rest);
|
|||
|
|
if (firstTokenMatch && UNITS.has(firstTokenMatch[1])) {
|
|||
|
|
unit = firstTokenMatch[1];
|
|||
|
|
rest = firstTokenMatch[2].trim();
|
|||
|
|
}
|
|||
|
|
return { position, quantity, unit, name: rest, note, raw_text: rawText };
|
|||
|
|
}
|