104 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import recipeSchema from "@lib/recipeSchema.ts";
 | |
| import { parseIngredients } from "@lib/parseIngredient.ts";
 | |
| 
 | |
| 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
 | |
|     const ingredients = parseIngredients(
 | |
|       data?.recipeIngredient?.join("\n") || "",
 | |
|     );
 | |
| 
 | |
|     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;
 | |
| }
 |