111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
import re
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
COMMON_UNITS = [
|
|
"kg", "g", "mg", "l", "liter", "ml", "cl", "el", "tl", "esslöffel", "teelöffel",
|
|
"pck.", "pck", "päckchen", "pkt.", "pkt", "dose", "dosen", "glas", "gläser",
|
|
"prise", "prisen", "msp.", "msp", "messerspitze", "bund", "stk.", "stk",
|
|
"stück", "stücke", "zehe", "zehen", "zweig", "zweige", "blatt", "blätter",
|
|
"scheibe", "scheiben", "stange", "stangen", "kopf", "nest", "nester", "tropfen", "tr."
|
|
]
|
|
|
|
def parse_fraction(val_str: str) -> Optional[float]:
|
|
val_str = val_str.strip()
|
|
if "/" in val_str:
|
|
parts = val_str.split("/")
|
|
if len(parts) == 2:
|
|
try:
|
|
return float(parts[0]) / float(parts[1])
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
return float(val_str.replace(",", "."))
|
|
except ValueError:
|
|
return None
|
|
|
|
def parse_ingredient_line(line: str) -> Dict[str, Any]:
|
|
raw = line.strip()
|
|
if not raw:
|
|
return {"type": "empty", "raw": ""}
|
|
|
|
if raw.endswith(":") or raw.lower().startswith("für ") or raw.lower() in [
|
|
"teig", "sauce", "soße", "füllung", "belag", "creme", "glasur", "marinade", "deko",
|
|
"schichten & deko", "keksschicht", "heidelbeersoße", "zitronen-zimt-sirup"
|
|
]:
|
|
return {
|
|
"type": "header",
|
|
"text": raw.rstrip(":"),
|
|
"raw": raw
|
|
}
|
|
|
|
match = re.match(r"^(\d+\s+\d+/\d+|\d+/\d+|\d+(?:[.,]\d+)?)(?:\s*-\s*(\d+(?:[.,]\d+)?))?\s*(.*)$", raw)
|
|
if match:
|
|
amt_str = match.group(1).strip()
|
|
range_end = match.group(2)
|
|
rest = match.group(3).strip()
|
|
|
|
if " " in amt_str and "/" in amt_str:
|
|
whole, frac = amt_str.split()
|
|
val = float(whole) + (parse_fraction(frac) or 0.0)
|
|
elif "/" in amt_str:
|
|
val = parse_fraction(amt_str) or 0.0
|
|
else:
|
|
val = parse_fraction(amt_str) or 0.0
|
|
|
|
display_amt = f"{amt_str}-{range_end}" if range_end else amt_str
|
|
|
|
rest_parts = rest.split(None, 1)
|
|
if rest_parts:
|
|
first_word_clean = rest_parts[0].lower().rstrip(".,")
|
|
if first_word_clean in COMMON_UNITS or rest_parts[0].lower() in COMMON_UNITS:
|
|
unit = rest_parts[0]
|
|
ing_name = rest_parts[1] if len(rest_parts) > 1 else ""
|
|
return {
|
|
"type": "ingredient",
|
|
"amount_val": val,
|
|
"amount_str": display_amt,
|
|
"unit": unit,
|
|
"name": ing_name,
|
|
"raw": raw
|
|
}
|
|
|
|
return {
|
|
"type": "ingredient",
|
|
"amount_val": val,
|
|
"amount_str": display_amt,
|
|
"unit": "",
|
|
"name": rest,
|
|
"raw": raw
|
|
}
|
|
|
|
return {
|
|
"type": "ingredient",
|
|
"amount_val": None,
|
|
"amount_str": "",
|
|
"unit": "",
|
|
"name": raw,
|
|
"raw": raw
|
|
}
|
|
|
|
def parse_ingredients_block(text: str) -> List[Dict[str, Any]]:
|
|
lines = [l.strip() for l in (text or "").split("\n") if l.strip()]
|
|
return [parse_ingredient_line(l) for l in lines]
|
|
|
|
# Test cases
|
|
tests = [
|
|
"150 g Mehl",
|
|
"1 EL Zucker",
|
|
"1/2 TL Muskat",
|
|
"1,5 TL Salz",
|
|
"2 frische Eier",
|
|
"2-3 Karotten",
|
|
"Für den Teig:",
|
|
"Salz und Pfeffer nach Belieben",
|
|
"300 ml Milch",
|
|
"1 Pck. Vanillezucker"
|
|
]
|
|
|
|
for t in tests:
|
|
res = parse_ingredient_line(t)
|
|
print(f"'{t}' => {res['type']}: amt={res.get('amount_val')}, unit='{res.get('unit')}', name='{res.get('name') or res.get('text')}'")
|