2025-01-18 00:46:05 +01:00
|
|
|
import recipeSchema from "@lib/recipeSchema.ts";
|
2025-01-19 19:22:19 +01:00
|
|
|
import { parseIngredients } from "@lib/parseIngredient.ts";
|
2025-01-18 00:46:05 +01:00
|
|
|
|
|
|
|
export function parseJsonLdToRecipeSchema(jsonLdContent: string) {
|
|
|
|
try {
|
|
|
|
let data = JSON.parse(jsonLdContent);
|
|
|
|
|
|
|
|
const image = data.image;
|
|
|
|
|
|
|
|
// Handle nested data inside `mainEntity`
|
|
|
|
if (data["mainEntity"]) {
|
|
|
|
data = data["mainEntity"];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure it's a valid Recipe type
|
|
|
|
if (
|
|
|
|
typeof data !== "object" || !data["@type"] || data["@type"] !== "Recipe"
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map and parse ingredients into the new schema
|
2025-01-19 19:22:19 +01:00
|
|
|
const ingredients = parseIngredients(
|
|
|
|
data?.recipeIngredient?.join("\n") || "",
|
2025-01-18 00:46:05 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
const instructions = Array.isArray(data.recipeInstructions)
|
|
|
|
? data.recipeInstructions.map((instr) => {
|
|
|
|
if (typeof instr === "string") return instr;
|
|
|
|
if (typeof instr === "object" && instr.text) return instr.text;
|
|
|
|
return "";
|
|
|
|
}).filter((instr) => instr.trim() !== "")
|
|
|
|
: [];
|
|
|
|
|
|
|
|
// Parse servings
|
|
|
|
const servings = parseServings(data.recipeYield);
|
|
|
|
|
|
|
|
// Parse times
|
|
|
|
const prepTime = parseDuration(data.prepTime);
|
|
|
|
const cookTime = parseDuration(data.cookTime);
|
|
|
|
const totalTime = parseDuration(data.totalTime);
|
|
|
|
|
|
|
|
// Extract tags
|
|
|
|
const tags = data.keywords
|
|
|
|
? Array.isArray(data.keywords)
|
|
|
|
? data.keywords
|
|
|
|
: data.keywords.split(",").map((tag: string) => tag.trim())
|
|
|
|
: [];
|
|
|
|
|
|
|
|
// Build the recipe object
|
|
|
|
const recipe = {
|
|
|
|
title: data.name || "Unnamed Recipe",
|
|
|
|
image: pickImage(image || data.image || ""),
|
|
|
|
author: Array.isArray(data.author)
|
|
|
|
? data.author.map((a: any) => a.name).join(", ")
|
|
|
|
: data.author?.name || "",
|
|
|
|
description: data.description || "",
|
|
|
|
ingredients,
|
|
|
|
instructions,
|
|
|
|
servings,
|
|
|
|
prepTime,
|
|
|
|
cookTime,
|
|
|
|
totalTime,
|
|
|
|
tags,
|
|
|
|
notes: data.notes || [],
|
|
|
|
};
|
|
|
|
|
|
|
|
// Validate against the schema
|
|
|
|
return recipeSchema.parse(recipe);
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Invalid JSON-LD content or parsing error:", error);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function pickImage(images: string | string[]): string {
|
|
|
|
if (Array.isArray(images)) {
|
|
|
|
return images[0];
|
|
|
|
}
|
|
|
|
return images;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseServings(servingsData: any): number {
|
|
|
|
if (typeof servingsData === "string") {
|
|
|
|
const match = servingsData.match(/\d+/);
|
|
|
|
return match ? parseInt(match[0], 10) : 1;
|
|
|
|
}
|
|
|
|
if (typeof servingsData === "number") {
|
|
|
|
return servingsData;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseDuration(duration: string | undefined): number {
|
|
|
|
if (!duration) return 0;
|
|
|
|
|
|
|
|
// Matches ISO 8601 durations (e.g., "PT30M" -> 30 minutes)
|
|
|
|
const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?/);
|
|
|
|
const hours = match?.[1] ? parseInt(match[1], 10) : 0;
|
|
|
|
const minutes = match?.[2] ? parseInt(match[2], 10) : 0;
|
|
|
|
|
|
|
|
return hours * 60 + minutes;
|
|
|
|
}
|