@recipe-book/core 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +50 -7
- package/dist/chunk-D5CV3UF7.cjs +2754 -0
- package/dist/chunk-D5CV3UF7.cjs.map +1 -0
- package/dist/errors.js +76 -0
- package/dist/errors.js.map +1 -0
- package/dist/format/duration.js +36 -0
- package/dist/format/duration.js.map +1 -0
- package/dist/format/ingredient.js +49 -0
- package/dist/format/ingredient.js.map +1 -0
- package/dist/format/number.js +120 -0
- package/dist/format/number.js.map +1 -0
- package/dist/format/options.js +31 -0
- package/dist/format/options.js.map +1 -0
- package/dist/format/quantity.js +144 -0
- package/dist/format/quantity.js.map +1 -0
- package/dist/format/servings.js +68 -0
- package/dist/format/servings.js.map +1 -0
- package/dist/format/temperature.js +35 -0
- package/dist/format/temperature.js.map +1 -0
- package/dist/index.cjs +476 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +994 -0
- package/dist/index.d.ts +994 -5
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -0
- package/dist/ingredient/parse.js +412 -0
- package/dist/ingredient/parse.js.map +1 -0
- package/dist/issues.js +26 -0
- package/dist/issues.js.map +1 -0
- package/dist/quantity/amount.js +78 -0
- package/dist/quantity/amount.js.map +1 -0
- package/dist/quantity/parse.js +255 -0
- package/dist/quantity/parse.js.map +1 -0
- package/dist/quantity/scale.js +23 -0
- package/dist/quantity/scale.js.map +1 -0
- package/dist/recipe/duration.js +28 -0
- package/dist/recipe/duration.js.map +1 -0
- package/dist/recipe/ids.js +68 -0
- package/dist/recipe/ids.js.map +1 -0
- package/dist/recipe/infer.js +24 -0
- package/dist/recipe/infer.js.map +1 -0
- package/dist/recipe/nutrient-units.js +18 -0
- package/dist/recipe/nutrient-units.js.map +1 -0
- package/dist/recipe/nutrition.js +26 -0
- package/dist/recipe/nutrition.js.map +1 -0
- package/dist/recipe/parse.js +854 -0
- package/dist/recipe/parse.js.map +1 -0
- package/dist/recipe/schema.js +32 -0
- package/dist/recipe/schema.js.map +1 -0
- package/dist/recipe/url.js +9 -0
- package/dist/recipe/url.js.map +1 -0
- package/dist/resolve.js +120 -0
- package/dist/resolve.js.map +1 -0
- package/dist/schema-org/find.js +158 -0
- package/dist/schema-org/find.js.map +1 -0
- package/dist/schema-org/from-json-ld.js +647 -0
- package/dist/schema-org/from-json-ld.js.map +1 -0
- package/dist/schema-org/nutrients.js +19 -0
- package/dist/schema-org/nutrients.js.map +1 -0
- package/dist/schema-org/text.js +125 -0
- package/dist/schema-org/text.js.map +1 -0
- package/dist/schema-org/to-json-ld.js +151 -0
- package/dist/schema-org/to-json-ld.js.map +1 -0
- package/dist/schema-org.cjs +1062 -0
- package/dist/schema-org.cjs.map +1 -0
- package/dist/schema-org.d.cts +223 -0
- package/dist/schema-org.d.ts +223 -0
- package/dist/schema-org.js +10 -0
- package/dist/schema-org.js.map +1 -0
- package/dist/steps/segment.js +45 -0
- package/dist/steps/segment.js.map +1 -0
- package/dist/steps/temperature.js +174 -0
- package/dist/steps/temperature.js.map +1 -0
- package/dist/suggest.js +50 -0
- package/dist/suggest.js.map +1 -0
- package/dist/types-BknM8KOd.d.cts +628 -0
- package/dist/types-BknM8KOd.d.ts +628 -0
- package/dist/units/convert.js +79 -0
- package/dist/units/convert.js.map +1 -0
- package/dist/units/lookup.js +86 -0
- package/dist/units/lookup.js.map +1 -0
- package/dist/units/registry.js +183 -0
- package/dist/units/registry.js.map +1 -0
- package/dist/units/system.js +91 -0
- package/dist/units/system.js.map +1 -0
- package/dist/units/temperature.js +40 -0
- package/dist/units/temperature.js.map +1 -0
- package/dist/validation.js +90 -0
- package/dist/validation.js.map +1 -0
- package/package.json +53 -8
- package/schema-org/package.json +5 -0
- package/dist/chunk-22RBQQWH.mjs +0 -1
- package/dist/chunk-3CSP4UZG.mjs +0 -1
- package/dist/chunk-3FFMHRB7.mjs +0 -26
- package/dist/chunk-4EB3HG6B.mjs +0 -50
- package/dist/chunk-4EHNJLGI.mjs +0 -50
- package/dist/chunk-4IA5EEKI.mjs +0 -81
- package/dist/chunk-4TATADPT.mjs +0 -51
- package/dist/chunk-6GI5KLI4.mjs +0 -22
- package/dist/chunk-6QJZOZ6W.mjs +0 -49
- package/dist/chunk-6YYQ4RYZ.mjs +0 -26
- package/dist/chunk-7BBUDI2L.mjs +0 -0
- package/dist/chunk-7KEMNCV7.mjs +0 -26
- package/dist/chunk-7MMJZZ7Z.mjs +0 -75
- package/dist/chunk-BES4V7DM.mjs +0 -26
- package/dist/chunk-BRDXRIAH.mjs +0 -50
- package/dist/chunk-C45UD45A.mjs +0 -1
- package/dist/chunk-CBLJXA6P.mjs +0 -1
- package/dist/chunk-CFR2ZBPB.mjs +0 -77
- package/dist/chunk-DYMNVFL2.mjs +0 -78
- package/dist/chunk-F6AQKXJU.mjs +0 -51
- package/dist/chunk-FW4M2R43.mjs +0 -51
- package/dist/chunk-GCCEDUST.mjs +0 -75
- package/dist/chunk-GE7KBYZR.mjs +0 -26
- package/dist/chunk-GN7ZDGYC.mjs +0 -51
- package/dist/chunk-GOO2SCQX.mjs +0 -75
- package/dist/chunk-HAVKBQLH.mjs +0 -51
- package/dist/chunk-HCBIPANU.mjs +0 -50
- package/dist/chunk-IEBAGBS7.mjs +0 -51
- package/dist/chunk-IQ6WN2V4.mjs +0 -75
- package/dist/chunk-JB4Z2FR7.mjs +0 -77
- package/dist/chunk-JMTQOTBM.mjs +0 -75
- package/dist/chunk-JRO6JUES.mjs +0 -51
- package/dist/chunk-KH3SRXET.mjs +0 -52
- package/dist/chunk-KNZMCAQZ.mjs +0 -51
- package/dist/chunk-KXFUCO3R.mjs +0 -1
- package/dist/chunk-LEBGCVCJ.mjs +0 -75
- package/dist/chunk-MAHWFCIL.mjs +0 -51
- package/dist/chunk-MPH76SLC.mjs +0 -51
- package/dist/chunk-MQGMFBJQ.mjs +0 -26
- package/dist/chunk-NMIVG7T4.mjs +0 -6
- package/dist/chunk-Q3TT3AZ4.mjs +0 -26
- package/dist/chunk-QY5HJQIL.mjs +0 -51
- package/dist/chunk-RCPLSNKM.mjs +0 -1
- package/dist/chunk-SR2KFBT3.mjs +0 -1
- package/dist/chunk-ULDLBAEG.mjs +0 -1
- package/dist/chunk-UQ3EUYFR.mjs +0 -1
- package/dist/chunk-V26OCCTL.mjs +0 -82
- package/dist/chunk-WBQAMGXK.mjs +0 -0
- package/dist/chunk-WSW6FMUL.mjs +0 -51
- package/dist/chunk-WU55SBIV.mjs +0 -75
- package/dist/chunk-XINK45P3.mjs +0 -51
- package/dist/chunk-XNOHIWI3.mjs +0 -1
- package/dist/chunk-XV4QN4LA.mjs +0 -6
- package/dist/chunk-YHBXEVUX.mjs +0 -51
- package/dist/chunk-YSZSCEI7.mjs +0 -75
- package/dist/chunk-YY3SUQ7P.mjs +0 -16
- package/dist/chunk-YZB3IG47.mjs +0 -80
- package/dist/chunk-ZPZT4LMQ.mjs +0 -25
- package/dist/const/conversion.d.mts +0 -7
- package/dist/const/conversion.d.ts +0 -7
- package/dist/const/conversion.js +0 -1
- package/dist/const/conversion.mjs +0 -1
- package/dist/const/unit.d.mts +0 -119
- package/dist/const/unit.d.ts +0 -119
- package/dist/const/unit.js +0 -1
- package/dist/const/unit.mjs +0 -1
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -1
- package/dist/lib/assert.d.mts +0 -18
- package/dist/lib/assert.d.ts +0 -18
- package/dist/lib/assert.js +0 -1
- package/dist/lib/assert.mjs +0 -1
- package/dist/lib/ingredient.d.mts +0 -9
- package/dist/lib/ingredient.d.ts +0 -9
- package/dist/lib/ingredient.js +0 -1
- package/dist/lib/ingredient.mjs +0 -1
- package/dist/lib/recipe.d.mts +0 -12
- package/dist/lib/recipe.d.ts +0 -12
- package/dist/lib/recipe.js +0 -1
- package/dist/lib/recipe.mjs +0 -1
- package/dist/lib/runtime.d.mts +0 -3
- package/dist/lib/runtime.d.ts +0 -3
- package/dist/lib/runtime.js +0 -1
- package/dist/lib/runtime.mjs +0 -1
- package/dist/lib/unit.d.mts +0 -19
- package/dist/lib/unit.d.ts +0 -19
- package/dist/lib/unit.js +0 -1
- package/dist/lib/unit.mjs +0 -1
- package/dist/types.d.mts +0 -36
- package/dist/types.d.ts +0 -36
- package/dist/types.js +0 -1
- package/dist/types.mjs +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/schema-org/from-json-ld.ts"],"sourcesContent":["import {\n describeValue,\n quote,\n type RecipeIssue,\n type RecipeIssueCode,\n} from '../errors'\nimport { parseIngredientLine } from '../ingredient/parse'\nimport { issue, type Path, warning } from '../issues'\nimport { matchAmount } from '../quantity/parse'\nimport { parseIsoDuration } from '../recipe/duration'\nimport { parseRecipeWith } from '../recipe/parse'\nimport type { Nutrition, Recipe } from '../recipe/types'\nimport { disallowedScheme } from '../recipe/url'\nimport { asRecord, toResult } from '../validation'\nimport { entriesOf, type Located, locate, MAX_DEPTH } from './find'\nimport { NUTRIENTS } from './nutrients'\nimport { cleanLines, cleanText } from './text'\nimport type { FromJsonLdResult } from './types'\n\n/**\n * The node's fields that `fromJsonLd` reads or that core owns, so `extra`\n * leaves them out, in the order issues are reported.\n */\nconst READ_FIELDS: readonly string[] = [\n '@context',\n '@type',\n 'name',\n 'description',\n 'image',\n 'recipeYield',\n 'prepTime',\n 'cookTime',\n 'totalTime',\n 'recipeIngredient',\n 'recipeInstructions',\n 'tool',\n 'nutrition',\n]\n\nconst TIMES = [\n ['prep', 'prepTime'],\n ['cook', 'cookTime'],\n ['total', 'totalTime'],\n] as const\n\n/**\n * The longest image URL kept. Each step that refers to an image node holds\n * its own copy of the URL, so the bound keeps the recipe's JSON from growing\n * with the URL's length times the number of steps.\n */\nconst MAX_URL_LENGTH = 4096\n\nconst SERVINGS_WORD =\n /^(?:servings?|portions?|(?:serving|portion)\\(s\\)|people|persons?)\\.?$/i\n\ntype Reader = {\n /** The Recipe node, as passed in. */\n recipe: Record<string, unknown>\n ids: Map<string, Located>\n warnings: RecipeIssue[]\n /** The JSON-LD path of each value in the recipe input, keyed by its path there. */\n sources: Map<string, Path>\n /** The recipe input paths of the values a failed parse drops. */\n droppable: Set<string>\n /**\n * Each node's reads as an image: the image it holds, how many warnings\n * reading it gave, how far below the node the read went, and the path length\n * it was read at. Read directly, and read through a reference, which doesn't\n * follow another.\n */\n images: [direct: Map<unknown, Read[]>, referenced: Map<unknown, Read[]>]\n /** How many warnings reading those nodes again would have repeated. */\n repeated: number\n /**\n * The longest path the current read has gone to. Only the path's length\n * decides where `MAX_DEPTH` cuts a read off, so a read is reused at the\n * length it was read at, and one that stayed within the limit also anywhere\n * reading the node again would, so reusing a read never changes the result.\n */\n deepest: number\n}\n\ntype Image = { url: string; source: Path }\n\ntype Read = [\n image: Image | undefined,\n warnings: number,\n depth: number,\n length: number,\n]\n\n/** A value read from `recipeYield`. */\ntype Batch = {\n source: Path\n /** The value as text, to name it in a message. */\n label: string\n /** The amount, or the low end of its range. */\n amount: number\n servings?: unknown\n /** Servings with a word, such as \"4 servings\", not a bare number. */\n worded?: boolean\n yield?: unknown\n /** The yield's amount is \"a\" or \"an\", as in \"a few\", not a number. */\n article?: boolean\n}\n\n/** A list item read from JSON-LD, and where its fields came from. */\ntype Item = {\n value: unknown\n source: Path\n fields: [field: string, source: Path, droppable: boolean][]\n}\n\n/**\n * Reads a schema.org Recipe from JSON-LD, as recipe sites publish it for\n * search engines: a Recipe node, or an array, `@graph`, `mainEntity`, or\n * `ItemList` that holds one, such as a node from `findRecipeJsonLd` or\n * `JSON.parse` of a script tag. An `ItemList` is read only when no other\n * Recipe is found, as in `findRecipeJsonLd`. Never throws for bad data; an\n * error thrown by a getter or proxy in the input passes through.\n *\n * Every string has its tags removed, then its HTML entities decoded, so an\n * encoded tag such as `<b>` stays as text. Ingredient lines are read\n * with `parseIngredientLine`. Issue paths point into the JSON-LD, such as\n * `recipeIngredient[3]`.\n *\n * Reading is forgiving. A value the recipe can't hold, such as a `data:`\n * image or nutrition of \"N/A\", is left out with a warning that keeps its usual\n * code and ends \"It is ignored.\" A second Recipe node, a second number of\n * servings, or a second yield is an `ignored_value` warning. The result fails\n * only when there is no Recipe node, the recipe's name is missing or isn't\n * text, or a problem remains that leaving a value out can't fix.\n *\n * @returns The recipe or the error, and `extra`: the node's fields the recipe\n * doesn't model, such as `author` and `keywords`, as they were. A field that\n * nests more than 32 levels deep, counted from the top of `input`, is left\n * out, with a warning. Pass `extra` to `toJsonLd` to write them back. It is\n * `{}` when no Recipe node is found.\n *\n * @example\n * const result = fromJsonLd(node)\n * if (!result.success) {\n * console.error(result.error.message)\n * // Recipe has 1 problem:\n * // ✖ name Add a name for the recipe.\n * // See https://recipe-book.siraj.page/errors/invalid_recipe\n * return\n * }\n * result.data.ingredients[0].items[0] // { id: 'flour', name: 'flour', quantity: { amount: 2, unit: 'cup' }, … }\n * result.extra.author // { '@type': 'Person', name: 'Ada' }\n */\nexport function fromJsonLd(input: unknown): FromJsonLdResult {\n const { recipes, ids } = locate([input])\n const [found] = recipes\n\n if (!found) {\n return {\n ...toResult<Recipe>('invalid_recipe', undefined, [\n issue(\n 'error',\n 'invalid_type',\n [],\n `Expected a schema.org Recipe, or an array, @graph, mainEntity, or ItemList that holds one, received ${describeValue(input)}.`,\n ),\n ]),\n extra: {},\n }\n }\n\n const node = asRecord(found.node)!\n const reader: Reader = {\n recipe: found.node,\n ids,\n warnings: [],\n sources: new Map(),\n droppable: new Set(),\n images: [new Map(), new Map()],\n repeated: 0,\n deepest: 0,\n }\n const name = text(node.name)\n\n if (recipes.length > 1) {\n warning(\n reader.warnings,\n 'ignored_value',\n [],\n `Found ${recipes.length} recipes. Only the first${typeof name === 'string' ? `, ${quote(name)},` : ''} is read.`,\n )\n }\n\n const model = readRecipe(reader, node, found.path)\n const parse = () => parseRecipeWith(model, parseIngredientLine)\n let result = parse()\n const dropped: RecipeIssue[] = []\n\n if (!result.success) {\n const targets: Path[] = []\n for (const error of result.error.issues) {\n const target =\n error.severity === 'error' ? droppableAt(reader, error.path) : undefined\n\n if (target) {\n targets.push(target)\n dropped.push({\n ...error,\n severity: 'warning',\n message: `${error.message} It is ignored.`,\n })\n }\n }\n // Deepest first, so no removal walks through a hole an earlier one left,\n // where a polluted `Object.prototype` would stand in for the missing item.\n targets\n .sort((a, b) => b.length - a.length)\n .forEach((target) => remove(model, target))\n if (dropped.length > 0) {\n result = parse()\n }\n }\n\n // An object's unknown fields sort after its other issues, as in parseRecipe.\n const order = ({ code, path }: RecipeIssue): number[] => [\n path.length > found.path.length\n ? READ_FIELDS.indexOf(String(path[found.path.length]))\n : -1,\n ...path\n .slice(found.path.length + 1)\n .filter((key) => typeof key === 'number'),\n ...(code === 'unknown_field' ? [Infinity] : []),\n ]\n const issues = [\n ...reader.warnings,\n ...[\n ...dropped,\n ...(result.success ? result.warnings : result.error.issues),\n ].map((issue) => ({ ...issue, path: sourceOf(reader, issue.path) })),\n ].sort((a, b) => compareOrder(order(a), order(b)))\n\n const checked = new Map<object, number>()\n const extra = Object.fromEntries(\n Object.entries(node).filter(([key, value]) => {\n if (READ_FIELDS.includes(key)) {\n return false\n }\n const path = [...found.path, key]\n if (tooDeep(value, path.length, checked)) {\n warning(\n issues,\n 'invalid_type',\n path,\n `Expected a value to keep in extra, received ${describeValue(value)} nested more than ${MAX_DEPTH} levels deep. It is ignored.`,\n )\n return false\n }\n return true\n }),\n )\n\n return {\n ...toResult(\n 'invalid_recipe',\n result.success ? result.data : undefined,\n issues,\n typeof name === 'string' ? quote(name) : undefined,\n ),\n extra,\n }\n}\n\n/**\n * Whether a value at a JSON-LD path of `length` keys, or a value it holds, is\n * more than `MAX_DEPTH` keys deep, so `extra` stays shallow enough to\n * serialize. `checked` holds the longest path each object was found shallow\n * at, so objects shared in the input are read a bounded number of times.\n */\nfunction tooDeep(\n value: unknown,\n length: number,\n checked: Map<object, number>,\n): boolean {\n if (length > MAX_DEPTH) {\n return true\n }\n if (\n typeof value !== 'object' ||\n value === null ||\n (checked.get(value) ?? 0) >= length\n ) {\n return false\n }\n const deep = Object.values(value).some((child) =>\n tooDeep(child, length + 1, checked),\n )\n if (!deep) {\n checked.set(value, length)\n }\n return deep\n}\n\n/** Builds `parseRecipe` input from a Recipe node, and links each value to its JSON-LD path. */\nfunction readRecipe(\n reader: Reader,\n node: Record<string, unknown>,\n base: Path,\n): Record<string, unknown> {\n const at = (...path: Path): Path => [...base, ...path]\n const model: Record<string, unknown> = {\n name: text(node.name),\n description: text(node.description),\n }\n link(reader, [], base)\n link(reader, ['name'], at('name'))\n link(reader, ['description'], at('description'), true)\n\n const image = readImage(reader, node.image, at('image'))\n if (image) {\n model.image = image.url\n link(reader, ['image'], image.source, true)\n }\n\n readYield(reader, model, node.recipeYield, at('recipeYield'))\n\n const times: Record<string, number> = {}\n for (const [key, field] of TIMES) {\n const minutes = readTime(reader, node[field], at(field))\n if (minutes !== undefined) {\n times[key] = minutes\n }\n }\n model.times = times\n\n model.ingredients = readList(\n reader,\n 'ingredients',\n node.recipeIngredient,\n at('recipeIngredient'),\n readLine,\n 'an ingredient line such as \"2 cups flour\"',\n )\n model.steps = readList(\n reader,\n 'steps',\n node.recipeInstructions,\n at('recipeInstructions'),\n readStep,\n 'a step such as \"Whisk the eggs.\" or a HowToStep',\n )\n model.equipment = readTools(reader, node.tool, at('tool'))\n model.nutrition = readNutrition(reader, node.nutrition, at('nutrition'))\n return model\n}\n\nfunction readImage(\n reader: Reader,\n value: unknown,\n source: Path,\n): Image | undefined {\n if (isAbsent(value)) {\n return undefined\n }\n\n const warnings = reader.warnings.length + reader.repeated\n const image = findImage(reader, value, source)\n if (!image && reader.warnings.length + reader.repeated === warnings) {\n ignore(\n reader,\n 'invalid_type',\n source,\n `Expected an image URL, an ImageObject with a url, or a list of them, received ${describeValue(value)}.`,\n )\n }\n return image\n}\n\n/**\n * The first URL a recipe can hold in an image, a list of them, an\n * ImageObject, or a reference to one. Each URL before it that's longer than\n * `MAX_URL_LENGTH` or has another scheme, such as `data:`, each value more\n * than `MAX_DEPTH` levels deep, and a reference to the recipe itself get a\n * warning. Follows one reference, so references that cycle end. A node that\n * several images reference or hold is read and warns once, where it's first\n * read, or near `MAX_DEPTH`, where how deep a read starts decides where it's\n * cut off, once at each depth.\n */\nfunction findImage(\n reader: Reader,\n value: unknown,\n source: Path,\n referenced = false,\n): Image | undefined {\n reader.deepest = Math.max(reader.deepest, source.length)\n if (source.length > MAX_DEPTH) {\n if (!isAbsent(value)) {\n ignore(\n reader,\n 'invalid_type',\n source,\n `Expected an image URL, an ImageObject with a url, or a list of them, received ${describeValue(value)} nested more than ${MAX_DEPTH} levels deep.`,\n )\n }\n return undefined\n }\n if (typeof value === 'string') {\n const url = cleanText(value)\n const scheme = disallowedScheme(url)\n if (scheme) {\n ignore(\n reader,\n 'invalid_url',\n source,\n `Expected an http or https URL, or a relative one, received ${quote(`${scheme}…`)}.`,\n )\n return undefined\n }\n if (url.length > MAX_URL_LENGTH) {\n ignore(\n reader,\n 'invalid_url',\n source,\n `Expected a URL of at most ${MAX_URL_LENGTH} UTF-16 code units, received ${url.length}.`,\n )\n return undefined\n }\n return url ? { url, source } : undefined\n }\n\n if (Array.isArray(value)) {\n for (const [entry, entrySource] of entriesOf(value, source)) {\n const image = findImage(reader, entry, entrySource, referenced)\n if (image) {\n return image\n }\n }\n return undefined\n }\n\n const images = reader.images[referenced ? 1 : 0]\n const reads = images.get(value) ?? []\n const read = reads.find(\n ([, , depth, length]) =>\n length === source.length ||\n Math.max(length, source.length) + depth <= MAX_DEPTH,\n )\n if (read) {\n reader.repeated += read[1]\n reader.deepest = Math.max(reader.deepest, source.length + read[2])\n return read[0]\n }\n\n const record = asRecord(value)\n if (!record) {\n return undefined\n }\n\n images.set(value, reads)\n const warnings = reader.warnings.length + reader.repeated\n const deepest = reader.deepest\n reader.deepest = source.length\n const id = record['@id']\n const target =\n typeof id === 'string' && !referenced ? reader.ids.get(id) : undefined\n let image =\n findImage(reader, record.url, [...source, 'url'], referenced) ??\n findImage(reader, record.contentUrl, [...source, 'contentUrl'], referenced)\n\n if (!image && target?.node === reader.recipe) {\n ignore(\n reader,\n 'invalid_type',\n source,\n 'Expected an image URL, an ImageObject with a url, or a list of them, received a reference to the recipe itself.',\n )\n } else if (!image && target) {\n image = findImage(reader, target.node, target.path, true)\n }\n reads.push([\n image,\n reader.warnings.length + reader.repeated - warnings,\n reader.deepest - source.length,\n source.length,\n ])\n reader.deepest = Math.max(reader.deepest, deepest)\n return image\n}\n\n/**\n * Reads servings and the yield from `recipeYield`'s values, and from the\n * comma-separated values in a string, as in \"4 servings, 24 cookies\". A bare\n * number equal to the yield's amount, as in WP Recipe Maker's `[\"28\", \"28-32\n * cookies\"]`, is the yield's count, not servings. The yield is the first one\n * with an amount, so \"One 9-inch pie, 8 slices\" yields 8 slices, but one whose\n * amount is a number comes before one whose amount is \"a\" or \"an\", so\n * `[\"a few\", \"24 cookies\"]` yields 24 cookies.\n */\nfunction readYield(\n reader: Reader,\n model: Record<string, unknown>,\n value: unknown,\n source: Path,\n): void {\n const batches = entriesOf(value, source)\n .flatMap(([entry, entrySource]) =>\n (typeof entry === 'string' ? entry.split(/,\\s+/) : [entry]).map((part) =>\n readBatch(reader, part, entrySource),\n ),\n )\n .filter((batch) => batch !== undefined)\n const yields = batches.filter((batch) => batch.yield !== undefined)\n const servings = batches.filter((batch) => batch.servings !== undefined)\n const kept =\n yields.find((batch) => !batch.article) ??\n yields.find((batch) => batch.article)\n const keptServings =\n servings.find((batch) => batch.worded) ??\n servings.find((batch) => batch.amount !== kept?.amount)\n\n for (const batch of servings) {\n if (\n keptServings &&\n batch.amount !== keptServings.amount &&\n (batch.worded || batch.amount !== kept?.amount)\n ) {\n warning(\n reader.warnings,\n 'ignored_value',\n batch.source,\n `Only one number of servings is read, ${quote(keptServings.label)}, so this one is ignored.`,\n )\n }\n }\n\n for (const batch of yields) {\n if (batch !== kept) {\n warning(\n reader.warnings,\n 'ignored_value',\n batch.source,\n `Only one yield is read, ${quote(kept!.label)}, so this one is ignored.`,\n )\n }\n }\n\n if (keptServings) {\n model.servings = keptServings.servings\n link(reader, ['servings'], keptServings.source, true)\n }\n if (kept) {\n model.yield = kept.yield\n link(reader, ['yield'], kept.source, true)\n }\n}\n\nfunction readBatch(\n reader: Reader,\n value: unknown,\n source: Path,\n): Batch | undefined {\n if (typeof value === 'number') {\n return { source, label: String(value), amount: value, servings: value }\n }\n\n if (typeof value === 'string') {\n const label = cleanText(value)\n\n if (!label) {\n return undefined\n }\n\n const text = label.replace(\n /^(?:(?:serves|servings?|yields?|makes)\\s*:?\\s*)?(?:about\\s+)?/i,\n '',\n )\n const match = matchAmount(text)\n\n if (!match || 'code' in match) {\n ignore(\n reader,\n match?.code ?? 'invalid_quantity',\n source,\n match?.message ??\n `Expected a batch such as \"24 cookies\" or \"3–4 loaves\", received ${describeValue(label)}.`,\n )\n return undefined\n }\n\n const amount =\n typeof match.amount === 'number' ? match.amount : match.amount.min\n const rest = match.rest.trim()\n\n return !rest || SERVINGS_WORD.test(rest)\n ? { source, label, amount, servings: match.amountText, worded: !!rest }\n : { source, label, amount, yield: text, article: /^an?\\s/i.test(text) }\n }\n\n const record = asRecord(value)\n const amount = toNumber(record?.value ?? record?.minValue)\n\n if (record && amount !== undefined) {\n const max = toNumber(record.maxValue)\n const unit =\n typeof record.unitText === 'string' ? cleanText(record.unitText) : ''\n const range = {\n amount,\n ...(max !== undefined && max !== amount && { max }),\n }\n const label = `${amount}${max !== amount && max ? `–${max}` : ''}${unit && ` ${unit}`}`\n\n return !unit || SERVINGS_WORD.test(unit)\n ? { source, label, amount, servings: range, worded: !!unit }\n : { source, label, amount, yield: { ...range, unit } }\n }\n\n if (!isAbsent(value)) {\n ignore(\n reader,\n 'invalid_type',\n source,\n `Expected a yield such as \"4 servings\" or \"24 cookies\", received ${describeValue(value)}.`,\n )\n }\n return undefined\n}\n\n/**\n * Reads an ISO 8601 duration. Zero, as in `PT0S`, counts as absent, and a time\n * longer than `Number.MAX_SAFE_INTEGER` minutes is ignored, as `toJsonLd`\n * ignores it.\n */\nfunction readTime(\n reader: Reader,\n value: unknown,\n source: Path,\n): number | undefined {\n if (isAbsent(value)) {\n return undefined\n }\n\n const minutes =\n typeof value === 'string' ? parseIsoDuration(cleanText(value)) : undefined\n\n if (minutes === undefined || minutes > Number.MAX_SAFE_INTEGER) {\n ignore(\n reader,\n 'invalid_duration',\n source,\n `Expected an ISO 8601 duration such as \"PT30M\", received ${describeValue(value)}${minutes === undefined ? '' : ', which is too long'}.`,\n )\n return undefined\n }\n\n return minutes > 0 ? minutes : undefined\n}\n\n/**\n * Reads `recipeIngredient` or `recipeInstructions`. A named `HowToSection` or\n * `ItemList` becomes a titled section, an unnamed or nested one adds its items\n * where it is, a `ListItem` stands for its `item`, and a string adds one item\n * per line.\n */\nfunction readList(\n reader: Reader,\n key: 'ingredients' | 'steps',\n value: unknown,\n source: Path,\n readItem: (\n reader: Reader,\n value: unknown,\n source: Path,\n ) => Item[] | undefined,\n expected: string,\n): unknown[] {\n const list: unknown[] = []\n link(reader, [key], source)\n\n const add = (\n items: unknown[],\n path: Path,\n entry: unknown,\n entrySource: Path,\n inSection: boolean,\n ): void => {\n if (isAbsent(entry)) {\n return\n }\n\n if (entrySource.length > MAX_DEPTH) {\n ignore(\n reader,\n 'invalid_type',\n entrySource,\n `Expected ${expected}, received ${describeValue(entry)} nested more than ${MAX_DEPTH} levels deep.`,\n )\n return\n }\n\n if (Array.isArray(entry)) {\n const elements = entriesOf(entry, entrySource)\n const positions = elements.map(([element]) =>\n toNumber(asRecord(element)?.position),\n )\n const order = [...elements.keys()]\n if (!positions.includes(undefined)) {\n order.sort((a, b) => positions[a]! - positions[b]!)\n }\n for (const i of order) {\n const [element, elementSource] = elements[i]!\n add(items, path, element, elementSource, inSection)\n }\n return\n }\n\n const record = asRecord(entry)\n const elements = [...entrySource, 'itemListElement']\n\n if (record?.itemListElement !== undefined && record.text === undefined) {\n const title =\n typeof record.name === 'string' ? cleanText(record.name) : ''\n\n if (title && !inSection) {\n const section = { title, items: [] as unknown[] }\n const sectionPath = [key, list.length]\n list.push(section)\n link(reader, sectionPath, entrySource)\n link(reader, [...sectionPath, 'title'], [...entrySource, 'name'])\n add(\n section.items,\n [...sectionPath, 'items'],\n record.itemListElement,\n elements,\n true,\n )\n } else {\n add(items, path, record.itemListElement, elements, inSection)\n }\n return\n }\n\n if (record && !isAbsent(record.item)) {\n add(items, path, record.item, [...entrySource, 'item'], inSection)\n return\n }\n\n const read = readItem(reader, entry, entrySource)\n\n if (!read) {\n ignore(\n reader,\n 'invalid_type',\n entrySource,\n `Expected ${expected}, received ${describeValue(entry)}.`,\n )\n return\n }\n\n for (const item of read) {\n const itemPath = [...path, items.length]\n items.push(item.value)\n link(reader, itemPath, item.source, true)\n\n for (const [field, fieldSource, droppable] of item.fields) {\n link(reader, [...itemPath, field], fieldSource, droppable)\n }\n }\n }\n\n add(list, [key], value, source, false)\n return list\n}\n\n/** Reads an ingredient line: text or a `PropertyValue`. */\nfunction readLine(\n reader: Reader,\n value: unknown,\n source: Path,\n): Item[] | undefined {\n if (typeof value === 'string') {\n return cleanLines(value).map((line) => ({\n value: line,\n source,\n fields: [],\n }))\n }\n\n const record = asRecord(value)\n const parts = [record?.value, record?.unitText, record?.name ?? record?.text]\n .filter((part) => typeof part === 'string' || typeof part === 'number')\n .join(' ')\n\n return parts\n ? cleanLines(parts).map((line) => ({ value: line, source, fields: [] }))\n : undefined\n}\n\n/**\n * Reads a step: text or a `HowToStep`. A step's name is kept\n * only when it says something its text doesn't, unlike \"Step 1\" or the start\n * of the text.\n */\nfunction readStep(\n reader: Reader,\n value: unknown,\n source: Path,\n): Item[] | undefined {\n if (typeof value === 'string') {\n return cleanLines(value).map((line) => ({\n value: line,\n source,\n fields: [],\n }))\n }\n\n const record = asRecord(value)\n\n if (!record) {\n return undefined\n }\n\n const name = typeof record.name === 'string' ? cleanText(record.name) : ''\n const body = text(record.text) ?? name\n const image = readImage(reader, record.image, [...source, 'image'])\n const lead = name.replace(/(?:…|\\.\\.\\.)$/, '').trim()\n const hasName =\n typeof body === 'string' &&\n !body.startsWith(lead) &&\n !/^step\\s*\\d*[.:]?$/i.test(name)\n const fields: Item['fields'] = [\n ['text', [...source, name && body === name ? 'name' : 'text'], false],\n ]\n\n if (image) {\n fields.push(['image', image.source, true])\n }\n\n return [\n {\n value: {\n text: body,\n ...(hasName && { name }),\n ...(image && { image: image.url }),\n },\n source,\n fields,\n },\n ]\n}\n\nfunction readTools(reader: Reader, value: unknown, source: Path): unknown[] {\n const equipment: unknown[] = []\n link(reader, ['equipment'], source)\n\n for (const [entry, entrySource] of entriesOf(value, source)) {\n const record = asRecord(entry)\n const item = record\n ? {\n name: text(record.name),\n ...(record.requiredQuantity !== undefined && {\n quantity:\n toNumber(\n asRecord(record.requiredQuantity)?.value ??\n record.requiredQuantity,\n ) ?? record.requiredQuantity,\n }),\n ...(record.url !== undefined && { url: text(record.url) }),\n }\n : text(entry)\n\n if (item === undefined) {\n continue\n }\n\n const path = ['equipment', equipment.length]\n equipment.push(item)\n link(reader, path, entrySource, true)\n link(reader, [...path, 'name'], [...entrySource, 'name'])\n link(\n reader,\n [...path, 'quantity'],\n [...entrySource, 'requiredQuantity'],\n true,\n )\n link(reader, [...path, 'url'], [...entrySource, 'url'], true)\n }\n\n return equipment\n}\n\nfunction readNutrition(\n reader: Reader,\n value: unknown,\n source: Path,\n): Record<string, unknown> | undefined {\n link(reader, ['nutrition'], source)\n\n if (isAbsent(value)) {\n return undefined\n }\n\n const record = asRecord(value)\n\n if (!record) {\n ignore(\n reader,\n 'invalid_type',\n source,\n `Expected NutritionInformation, such as { \"calories\": \"250 kcal\" }, received ${describeValue(value)}.`,\n )\n return undefined\n }\n\n const nutrition: Record<string, unknown> = {}\n const keys = Object.keys(NUTRIENTS) as (keyof Nutrition)[]\n\n for (const [field, amount] of Object.entries(record)) {\n const key = keys.find((key) => NUTRIENTS[key] === field)\n\n if (key) {\n nutrition[key] = text(amount)\n link(reader, ['nutrition', key], [...source, field], true)\n } else if (!field.startsWith('@')) {\n ignore(reader, 'unknown_field', [...source, field], 'Unknown field.')\n }\n }\n\n return nutrition\n}\n\n/** Cleaned text, with empty text and `null` as absent. Other values pass through for `parseRecipe` to report. */\nfunction text(value: unknown): unknown {\n return typeof value === 'string'\n ? cleanText(value) || undefined\n : (value ?? undefined)\n}\n\nfunction toNumber(value: unknown): number | undefined {\n const number =\n typeof value === 'string' && value.trim() ? Number(value) : value\n return typeof number === 'number' && Number.isFinite(number)\n ? number\n : undefined\n}\n\nfunction isAbsent(value: unknown): boolean {\n return (\n value === undefined ||\n value === null ||\n (typeof value === 'string' && !value.trim())\n )\n}\n\nfunction ignore(\n reader: Reader,\n code: RecipeIssueCode,\n path: Path,\n message: string,\n): void {\n warning(reader.warnings, code, path, `${message} It is ignored.`)\n}\n\nfunction link(\n reader: Reader,\n path: Path,\n source: Path,\n droppable = false,\n): void {\n const key = JSON.stringify(path)\n reader.sources.set(key, source)\n if (droppable) {\n reader.droppable.add(key)\n }\n}\n\n/** The JSON-LD path of a path in the recipe input, through the longest linked prefix. */\nfunction sourceOf(reader: Reader, path: Path): Path {\n for (let n = path.length; n >= 0; n--) {\n const source = reader.sources.get(JSON.stringify(path.slice(0, n)))\n if (source) {\n return source\n }\n }\n return []\n}\n\nfunction compareOrder(a: number[], b: number[]): number {\n for (const [i, n] of a.entries()) {\n if (i === b.length) {\n return 1\n }\n const m = b[i]!\n if (n !== m) {\n return n - m\n }\n }\n return a.length - b.length\n}\n\nfunction droppableAt(reader: Reader, path: Path): Path | undefined {\n for (let n = path.length; n > 0; n--) {\n const at = path.slice(0, n)\n if (reader.droppable.has(JSON.stringify(at))) {\n return at\n }\n }\n return undefined\n}\n\n/**\n * Deletes a value from the recipe input, unless a value holding it is already\n * gone. A list keeps a hole, so later items keep their paths.\n */\nfunction remove(model: Record<string, unknown>, path: Path): void {\n const parent = path\n .slice(0, -1)\n .reduce<unknown>(\n (value, key) =>\n (value as Record<string | number, unknown> | undefined)?.[key],\n model,\n ) as Record<string | number, unknown> | undefined\n\n if (parent) {\n delete parent[path[path.length - 1]!]\n }\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA2B;AACpC,SAAS,OAAkB,eAAe;AAC1C,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAEhC,SAAS,wBAAwB;AACjC,SAAS,UAAU,gBAAgB;AACnC,SAAS,WAAyB,QAAQ,iBAAiB;AAC3D,SAAS,iBAAiB;AAC1B,SAAS,YAAY,iBAAiB;AAOtC,IAAM,cAAiC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,QAAQ;AAAA,EACZ,CAAC,QAAQ,UAAU;AAAA,EACnB,CAAC,QAAQ,UAAU;AAAA,EACnB,CAAC,SAAS,WAAW;AACvB;AAOA,IAAM,iBAAiB;AAEvB,IAAM,gBACJ;AAkGK,SAAS,WAAW,OAAkC;AAC3D,QAAM,EAAE,SAAS,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;AACvC,QAAM,CAAC,KAAK,IAAI;AAEhB,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL,GAAG,SAAiB,kBAAkB,QAAW;AAAA,QAC/C;AAAA,UACE;AAAA,UACA;AAAA,UACA,CAAC;AAAA,UACD,uGAAuG,cAAc,KAAK,CAAC;AAAA,QAC7H;AAAA,MACF,CAAC;AAAA,MACD,OAAO,CAAC;AAAA,IACV;AAAA,EACF;AAEA,QAAM,OAAO,SAAS,MAAM,IAAI;AAChC,QAAM,SAAiB;AAAA,IACrB,QAAQ,MAAM;AAAA,IACd;AAAA,IACA,UAAU,CAAC;AAAA,IACX,SAAS,oBAAI,IAAI;AAAA,IACjB,WAAW,oBAAI,IAAI;AAAA,IACnB,QAAQ,CAAC,oBAAI,IAAI,GAAG,oBAAI,IAAI,CAAC;AAAA,IAC7B,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AACA,QAAM,OAAO,KAAK,KAAK,IAAI;AAE3B,MAAI,QAAQ,SAAS,GAAG;AACtB;AAAA,MACE,OAAO;AAAA,MACP;AAAA,MACA,CAAC;AAAA,MACD,SAAS,QAAQ,MAAM,2BAA2B,OAAO,SAAS,WAAW,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE;AAAA,IACvG;AAAA,EACF;AAEA,QAAM,QAAQ,WAAW,QAAQ,MAAM,MAAM,IAAI;AACjD,QAAM,QAAQ,MAAM,gBAAgB,OAAO,mBAAmB;AAC9D,MAAI,SAAS,MAAM;AACnB,QAAM,UAAyB,CAAC;AAEhC,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,UAAkB,CAAC;AACzB,eAAW,SAAS,OAAO,MAAM,QAAQ;AACvC,YAAM,SACJ,MAAM,aAAa,UAAU,YAAY,QAAQ,MAAM,IAAI,IAAI;AAEjE,UAAI,QAAQ;AACV,gBAAQ,KAAK,MAAM;AACnB,gBAAQ,KAAK;AAAA,UACX,GAAG;AAAA,UACH,UAAU;AAAA,UACV,SAAS,GAAG,MAAM,OAAO;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAGA,YACG,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,QAAQ,CAAC,WAAW,OAAO,OAAO,MAAM,CAAC;AAC5C,QAAI,QAAQ,SAAS,GAAG;AACtB,eAAS,MAAM;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,QAAQ,CAAC,EAAE,MAAM,KAAK,MAA6B;AAAA,IACvD,KAAK,SAAS,MAAM,KAAK,SACrB,YAAY,QAAQ,OAAO,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC,IACnD;AAAA,IACJ,GAAG,KACA,MAAM,MAAM,KAAK,SAAS,CAAC,EAC3B,OAAO,CAAC,QAAQ,OAAO,QAAQ,QAAQ;AAAA,IAC1C,GAAI,SAAS,kBAAkB,CAAC,QAAQ,IAAI,CAAC;AAAA,EAC/C;AACA,QAAM,SAAS;AAAA,IACb,GAAG,OAAO;AAAA,IACV,GAAG;AAAA,MACD,GAAG;AAAA,MACH,GAAI,OAAO,UAAU,OAAO,WAAW,OAAO,MAAM;AAAA,IACtD,EAAE,IAAI,CAACA,YAAW,EAAE,GAAGA,QAAO,MAAM,SAAS,QAAQA,OAAM,IAAI,EAAE,EAAE;AAAA,EACrE,EAAE,KAAK,CAAC,GAAG,MAAM,aAAa,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;AAEjD,QAAM,UAAU,oBAAI,IAAoB;AACxC,QAAM,QAAQ,OAAO;AAAA,IACnB,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM;AAC5C,UAAI,YAAY,SAAS,GAAG,GAAG;AAC7B,eAAO;AAAA,MACT;AACA,YAAM,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG;AAChC,UAAI,QAAQ,OAAO,KAAK,QAAQ,OAAO,GAAG;AACxC;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,+CAA+C,cAAc,KAAK,CAAC,qBAAqB,SAAS;AAAA,QACnG;AACA,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,MACD;AAAA,MACA,OAAO,UAAU,OAAO,OAAO;AAAA,MAC/B;AAAA,MACA,OAAO,SAAS,WAAW,MAAM,IAAI,IAAI;AAAA,IAC3C;AAAA,IACA;AAAA,EACF;AACF;AAQA,SAAS,QACP,OACA,QACA,SACS;AACT,MAAI,SAAS,WAAW;AACtB,WAAO;AAAA,EACT;AACA,MACE,OAAO,UAAU,YACjB,UAAU,SACT,QAAQ,IAAI,KAAK,KAAK,MAAM,QAC7B;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,OAAO,KAAK,EAAE;AAAA,IAAK,CAAC,UACtC,QAAQ,OAAO,SAAS,GAAG,OAAO;AAAA,EACpC;AACA,MAAI,CAAC,MAAM;AACT,YAAQ,IAAI,OAAO,MAAM;AAAA,EAC3B;AACA,SAAO;AACT;AAGA,SAAS,WACP,QACA,MACA,MACyB;AACzB,QAAM,KAAK,IAAI,SAAqB,CAAC,GAAG,MAAM,GAAG,IAAI;AACrD,QAAM,QAAiC;AAAA,IACrC,MAAM,KAAK,KAAK,IAAI;AAAA,IACpB,aAAa,KAAK,KAAK,WAAW;AAAA,EACpC;AACA,OAAK,QAAQ,CAAC,GAAG,IAAI;AACrB,OAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC;AACjC,OAAK,QAAQ,CAAC,aAAa,GAAG,GAAG,aAAa,GAAG,IAAI;AAErD,QAAM,QAAQ,UAAU,QAAQ,KAAK,OAAO,GAAG,OAAO,CAAC;AACvD,MAAI,OAAO;AACT,UAAM,QAAQ,MAAM;AACpB,SAAK,QAAQ,CAAC,OAAO,GAAG,MAAM,QAAQ,IAAI;AAAA,EAC5C;AAEA,YAAU,QAAQ,OAAO,KAAK,aAAa,GAAG,aAAa,CAAC;AAE5D,QAAM,QAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAChC,UAAM,UAAU,SAAS,QAAQ,KAAK,KAAK,GAAG,GAAG,KAAK,CAAC;AACvD,QAAI,YAAY,QAAW;AACzB,YAAM,GAAG,IAAI;AAAA,IACf;AAAA,EACF;AACA,QAAM,QAAQ;AAEd,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,KAAK;AAAA,IACL,GAAG,kBAAkB;AAAA,IACrB;AAAA,IACA;AAAA,EACF;AACA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,KAAK;AAAA,IACL,GAAG,oBAAoB;AAAA,IACvB;AAAA,IACA;AAAA,EACF;AACA,QAAM,YAAY,UAAU,QAAQ,KAAK,MAAM,GAAG,MAAM,CAAC;AACzD,QAAM,YAAY,cAAc,QAAQ,KAAK,WAAW,GAAG,WAAW,CAAC;AACvE,SAAO;AACT;AAEA,SAAS,UACP,QACA,OACA,QACmB;AACnB,MAAI,SAAS,KAAK,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,OAAO,SAAS,SAAS,OAAO;AACjD,QAAM,QAAQ,UAAU,QAAQ,OAAO,MAAM;AAC7C,MAAI,CAAC,SAAS,OAAO,SAAS,SAAS,OAAO,aAAa,UAAU;AACnE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,iFAAiF,cAAc,KAAK,CAAC;AAAA,IACvG;AAAA,EACF;AACA,SAAO;AACT;AAYA,SAAS,UACP,QACA,OACA,QACA,aAAa,OACM;AACnB,SAAO,UAAU,KAAK,IAAI,OAAO,SAAS,OAAO,MAAM;AACvD,MAAI,OAAO,SAAS,WAAW;AAC7B,QAAI,CAAC,SAAS,KAAK,GAAG;AACpB;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,iFAAiF,cAAc,KAAK,CAAC,qBAAqB,SAAS;AAAA,MACrI;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,MAAM,UAAU,KAAK;AAC3B,UAAM,SAAS,iBAAiB,GAAG;AACnC,QAAI,QAAQ;AACV;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,8DAA8D,MAAM,GAAG,MAAM,QAAG,CAAC;AAAA,MACnF;AACA,aAAO;AAAA,IACT;AACA,QAAI,IAAI,SAAS,gBAAgB;AAC/B;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,6BAA6B,cAAc,gCAAgC,IAAI,MAAM;AAAA,MACvF;AACA,aAAO;AAAA,IACT;AACA,WAAO,MAAM,EAAE,KAAK,OAAO,IAAI;AAAA,EACjC;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,CAAC,OAAO,WAAW,KAAK,UAAU,OAAO,MAAM,GAAG;AAC3D,YAAMC,SAAQ,UAAU,QAAQ,OAAO,aAAa,UAAU;AAC9D,UAAIA,QAAO;AACT,eAAOA;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,OAAO,OAAO,aAAa,IAAI,CAAC;AAC/C,QAAM,QAAQ,OAAO,IAAI,KAAK,KAAK,CAAC;AACpC,QAAM,OAAO,MAAM;AAAA,IACjB,CAAC,CAAC,EAAE,EAAE,OAAO,MAAM,MACjB,WAAW,OAAO,UAClB,KAAK,IAAI,QAAQ,OAAO,MAAM,IAAI,SAAS;AAAA,EAC/C;AACA,MAAI,MAAM;AACR,WAAO,YAAY,KAAK,CAAC;AACzB,WAAO,UAAU,KAAK,IAAI,OAAO,SAAS,OAAO,SAAS,KAAK,CAAC,CAAC;AACjE,WAAO,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,SAAS,SAAS,KAAK;AAC7B,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,SAAO,IAAI,OAAO,KAAK;AACvB,QAAM,WAAW,OAAO,SAAS,SAAS,OAAO;AACjD,QAAM,UAAU,OAAO;AACvB,SAAO,UAAU,OAAO;AACxB,QAAM,KAAK,OAAO,KAAK;AACvB,QAAM,SACJ,OAAO,OAAO,YAAY,CAAC,aAAa,OAAO,IAAI,IAAI,EAAE,IAAI;AAC/D,MAAI,QACF,UAAU,QAAQ,OAAO,KAAK,CAAC,GAAG,QAAQ,KAAK,GAAG,UAAU,KAC5D,UAAU,QAAQ,OAAO,YAAY,CAAC,GAAG,QAAQ,YAAY,GAAG,UAAU;AAE5E,MAAI,CAAC,SAAS,QAAQ,SAAS,OAAO,QAAQ;AAC5C;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,WAAW,CAAC,SAAS,QAAQ;AAC3B,YAAQ,UAAU,QAAQ,OAAO,MAAM,OAAO,MAAM,IAAI;AAAA,EAC1D;AACA,QAAM,KAAK;AAAA,IACT;AAAA,IACA,OAAO,SAAS,SAAS,OAAO,WAAW;AAAA,IAC3C,OAAO,UAAU,OAAO;AAAA,IACxB,OAAO;AAAA,EACT,CAAC;AACD,SAAO,UAAU,KAAK,IAAI,OAAO,SAAS,OAAO;AACjD,SAAO;AACT;AAWA,SAAS,UACP,QACA,OACA,OACA,QACM;AACN,QAAM,UAAU,UAAU,OAAO,MAAM,EACpC;AAAA,IAAQ,CAAC,CAAC,OAAO,WAAW,OAC1B,OAAO,UAAU,WAAW,MAAM,MAAM,MAAM,IAAI,CAAC,KAAK,GAAG;AAAA,MAAI,CAAC,SAC/D,UAAU,QAAQ,MAAM,WAAW;AAAA,IACrC;AAAA,EACF,EACC,OAAO,CAAC,UAAU,UAAU,MAAS;AACxC,QAAM,SAAS,QAAQ,OAAO,CAAC,UAAU,MAAM,UAAU,MAAS;AAClE,QAAM,WAAW,QAAQ,OAAO,CAAC,UAAU,MAAM,aAAa,MAAS;AACvE,QAAM,OACJ,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,OAAO,KACrC,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO;AACtC,QAAM,eACJ,SAAS,KAAK,CAAC,UAAU,MAAM,MAAM,KACrC,SAAS,KAAK,CAAC,UAAU,MAAM,WAAW,MAAM,MAAM;AAExD,aAAW,SAAS,UAAU;AAC5B,QACE,gBACA,MAAM,WAAW,aAAa,WAC7B,MAAM,UAAU,MAAM,WAAW,MAAM,SACxC;AACA;AAAA,QACE,OAAO;AAAA,QACP;AAAA,QACA,MAAM;AAAA,QACN,wCAAwC,MAAM,aAAa,KAAK,CAAC;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAEA,aAAW,SAAS,QAAQ;AAC1B,QAAI,UAAU,MAAM;AAClB;AAAA,QACE,OAAO;AAAA,QACP;AAAA,QACA,MAAM;AAAA,QACN,2BAA2B,MAAM,KAAM,KAAK,CAAC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAEA,MAAI,cAAc;AAChB,UAAM,WAAW,aAAa;AAC9B,SAAK,QAAQ,CAAC,UAAU,GAAG,aAAa,QAAQ,IAAI;AAAA,EACtD;AACA,MAAI,MAAM;AACR,UAAM,QAAQ,KAAK;AACnB,SAAK,QAAQ,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI;AAAA,EAC3C;AACF;AAEA,SAAS,UACP,QACA,OACA,QACmB;AACnB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,QAAQ,OAAO,OAAO,KAAK,GAAG,QAAQ,OAAO,UAAU,MAAM;AAAA,EACxE;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,QAAQ,UAAU,KAAK;AAE7B,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAEA,UAAMC,QAAO,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,IACF;AACA,UAAM,QAAQ,YAAYA,KAAI;AAE9B,QAAI,CAAC,SAAS,UAAU,OAAO;AAC7B;AAAA,QACE;AAAA,QACA,OAAO,QAAQ;AAAA,QACf;AAAA,QACA,OAAO,WACL,wEAAmE,cAAc,KAAK,CAAC;AAAA,MAC3F;AACA,aAAO;AAAA,IACT;AAEA,UAAMC,UACJ,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,MAAM,OAAO;AACjE,UAAM,OAAO,MAAM,KAAK,KAAK;AAE7B,WAAO,CAAC,QAAQ,cAAc,KAAK,IAAI,IACnC,EAAE,QAAQ,OAAO,QAAAA,SAAQ,UAAU,MAAM,YAAY,QAAQ,CAAC,CAAC,KAAK,IACpE,EAAE,QAAQ,OAAO,QAAAA,SAAQ,OAAOD,OAAM,SAAS,UAAU,KAAKA,KAAI,EAAE;AAAA,EAC1E;AAEA,QAAM,SAAS,SAAS,KAAK;AAC7B,QAAM,SAAS,SAAS,QAAQ,SAAS,QAAQ,QAAQ;AAEzD,MAAI,UAAU,WAAW,QAAW;AAClC,UAAM,MAAM,SAAS,OAAO,QAAQ;AACpC,UAAM,OACJ,OAAO,OAAO,aAAa,WAAW,UAAU,OAAO,QAAQ,IAAI;AACrE,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,GAAI,QAAQ,UAAa,QAAQ,UAAU,EAAE,IAAI;AAAA,IACnD;AACA,UAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,UAAU,MAAM,SAAI,GAAG,KAAK,EAAE,GAAG,QAAQ,IAAI,IAAI,EAAE;AAErF,WAAO,CAAC,QAAQ,cAAc,KAAK,IAAI,IACnC,EAAE,QAAQ,OAAO,QAAQ,UAAU,OAAO,QAAQ,CAAC,CAAC,KAAK,IACzD,EAAE,QAAQ,OAAO,QAAQ,OAAO,EAAE,GAAG,OAAO,KAAK,EAAE;AAAA,EACzD;AAEA,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,mEAAmE,cAAc,KAAK,CAAC;AAAA,IACzF;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,SACP,QACA,OACA,QACoB;AACpB,MAAI,SAAS,KAAK,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,UACJ,OAAO,UAAU,WAAW,iBAAiB,UAAU,KAAK,CAAC,IAAI;AAEnE,MAAI,YAAY,UAAa,UAAU,OAAO,kBAAkB;AAC9D;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,2DAA2D,cAAc,KAAK,CAAC,GAAG,YAAY,SAAY,KAAK,qBAAqB;AAAA,IACtI;AACA,WAAO;AAAA,EACT;AAEA,SAAO,UAAU,IAAI,UAAU;AACjC;AAQA,SAAS,SACP,QACA,KACA,OACA,QACA,UAKA,UACW;AACX,QAAM,OAAkB,CAAC;AACzB,OAAK,QAAQ,CAAC,GAAG,GAAG,MAAM;AAE1B,QAAM,MAAM,CACV,OACA,MACA,OACA,aACA,cACS;AACT,QAAI,SAAS,KAAK,GAAG;AACnB;AAAA,IACF;AAEA,QAAI,YAAY,SAAS,WAAW;AAClC;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY,QAAQ,cAAc,cAAc,KAAK,CAAC,qBAAqB,SAAS;AAAA,MACtF;AACA;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,YAAME,YAAW,UAAU,OAAO,WAAW;AAC7C,YAAM,YAAYA,UAAS;AAAA,QAAI,CAAC,CAAC,OAAO,MACtC,SAAS,SAAS,OAAO,GAAG,QAAQ;AAAA,MACtC;AACA,YAAM,QAAQ,CAAC,GAAGA,UAAS,KAAK,CAAC;AACjC,UAAI,CAAC,UAAU,SAAS,MAAS,GAAG;AAClC,cAAM,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,IAAK,UAAU,CAAC,CAAE;AAAA,MACpD;AACA,iBAAW,KAAK,OAAO;AACrB,cAAM,CAAC,SAAS,aAAa,IAAIA,UAAS,CAAC;AAC3C,YAAI,OAAO,MAAM,SAAS,eAAe,SAAS;AAAA,MACpD;AACA;AAAA,IACF;AAEA,UAAM,SAAS,SAAS,KAAK;AAC7B,UAAM,WAAW,CAAC,GAAG,aAAa,iBAAiB;AAEnD,QAAI,QAAQ,oBAAoB,UAAa,OAAO,SAAS,QAAW;AACtE,YAAM,QACJ,OAAO,OAAO,SAAS,WAAW,UAAU,OAAO,IAAI,IAAI;AAE7D,UAAI,SAAS,CAAC,WAAW;AACvB,cAAM,UAAU,EAAE,OAAO,OAAO,CAAC,EAAe;AAChD,cAAM,cAAc,CAAC,KAAK,KAAK,MAAM;AACrC,aAAK,KAAK,OAAO;AACjB,aAAK,QAAQ,aAAa,WAAW;AACrC,aAAK,QAAQ,CAAC,GAAG,aAAa,OAAO,GAAG,CAAC,GAAG,aAAa,MAAM,CAAC;AAChE;AAAA,UACE,QAAQ;AAAA,UACR,CAAC,GAAG,aAAa,OAAO;AAAA,UACxB,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AACL,YAAI,OAAO,MAAM,OAAO,iBAAiB,UAAU,SAAS;AAAA,MAC9D;AACA;AAAA,IACF;AAEA,QAAI,UAAU,CAAC,SAAS,OAAO,IAAI,GAAG;AACpC,UAAI,OAAO,MAAM,OAAO,MAAM,CAAC,GAAG,aAAa,MAAM,GAAG,SAAS;AACjE;AAAA,IACF;AAEA,UAAM,OAAO,SAAS,QAAQ,OAAO,WAAW;AAEhD,QAAI,CAAC,MAAM;AACT;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY,QAAQ,cAAc,cAAc,KAAK,CAAC;AAAA,MACxD;AACA;AAAA,IACF;AAEA,eAAW,QAAQ,MAAM;AACvB,YAAM,WAAW,CAAC,GAAG,MAAM,MAAM,MAAM;AACvC,YAAM,KAAK,KAAK,KAAK;AACrB,WAAK,QAAQ,UAAU,KAAK,QAAQ,IAAI;AAExC,iBAAW,CAAC,OAAO,aAAa,SAAS,KAAK,KAAK,QAAQ;AACzD,aAAK,QAAQ,CAAC,GAAG,UAAU,KAAK,GAAG,aAAa,SAAS;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM,CAAC,GAAG,GAAG,OAAO,QAAQ,KAAK;AACrC,SAAO;AACT;AAGA,SAAS,SACP,QACA,OACA,QACoB;AACpB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,WAAW,KAAK,EAAE,IAAI,CAAC,UAAU;AAAA,MACtC,OAAO;AAAA,MACP;AAAA,MACA,QAAQ,CAAC;AAAA,IACX,EAAE;AAAA,EACJ;AAEA,QAAM,SAAS,SAAS,KAAK;AAC7B,QAAM,QAAQ,CAAC,QAAQ,OAAO,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,IAAI,EACzE,OAAO,CAAC,SAAS,OAAO,SAAS,YAAY,OAAO,SAAS,QAAQ,EACrE,KAAK,GAAG;AAEX,SAAO,QACH,WAAW,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,MAAM,QAAQ,QAAQ,CAAC,EAAE,EAAE,IACrE;AACN;AAOA,SAAS,SACP,QACA,OACA,QACoB;AACpB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,WAAW,KAAK,EAAE,IAAI,CAAC,UAAU;AAAA,MACtC,OAAO;AAAA,MACP;AAAA,MACA,QAAQ,CAAC;AAAA,IACX,EAAE;AAAA,EACJ;AAEA,QAAM,SAAS,SAAS,KAAK;AAE7B,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,OAAO,OAAO,SAAS,WAAW,UAAU,OAAO,IAAI,IAAI;AACxE,QAAM,OAAO,KAAK,OAAO,IAAI,KAAK;AAClC,QAAM,QAAQ,UAAU,QAAQ,OAAO,OAAO,CAAC,GAAG,QAAQ,OAAO,CAAC;AAClE,QAAM,OAAO,KAAK,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AACpD,QAAM,UACJ,OAAO,SAAS,YAChB,CAAC,KAAK,WAAW,IAAI,KACrB,CAAC,qBAAqB,KAAK,IAAI;AACjC,QAAM,SAAyB;AAAA,IAC7B,CAAC,QAAQ,CAAC,GAAG,QAAQ,QAAQ,SAAS,OAAO,SAAS,MAAM,GAAG,KAAK;AAAA,EACtE;AAEA,MAAI,OAAO;AACT,WAAO,KAAK,CAAC,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL;AAAA,MACE,OAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAI,WAAW,EAAE,KAAK;AAAA,QACtB,GAAI,SAAS,EAAE,OAAO,MAAM,IAAI;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,UAAU,QAAgB,OAAgB,QAAyB;AAC1E,QAAM,YAAuB,CAAC;AAC9B,OAAK,QAAQ,CAAC,WAAW,GAAG,MAAM;AAElC,aAAW,CAAC,OAAO,WAAW,KAAK,UAAU,OAAO,MAAM,GAAG;AAC3D,UAAM,SAAS,SAAS,KAAK;AAC7B,UAAM,OAAO,SACT;AAAA,MACE,MAAM,KAAK,OAAO,IAAI;AAAA,MACtB,GAAI,OAAO,qBAAqB,UAAa;AAAA,QAC3C,UACE;AAAA,UACE,SAAS,OAAO,gBAAgB,GAAG,SACjC,OAAO;AAAA,QACX,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,GAAI,OAAO,QAAQ,UAAa,EAAE,KAAK,KAAK,OAAO,GAAG,EAAE;AAAA,IAC1D,IACA,KAAK,KAAK;AAEd,QAAI,SAAS,QAAW;AACtB;AAAA,IACF;AAEA,UAAM,OAAO,CAAC,aAAa,UAAU,MAAM;AAC3C,cAAU,KAAK,IAAI;AACnB,SAAK,QAAQ,MAAM,aAAa,IAAI;AACpC,SAAK,QAAQ,CAAC,GAAG,MAAM,MAAM,GAAG,CAAC,GAAG,aAAa,MAAM,CAAC;AACxD;AAAA,MACE;AAAA,MACA,CAAC,GAAG,MAAM,UAAU;AAAA,MACpB,CAAC,GAAG,aAAa,kBAAkB;AAAA,MACnC;AAAA,IACF;AACA,SAAK,QAAQ,CAAC,GAAG,MAAM,KAAK,GAAG,CAAC,GAAG,aAAa,KAAK,GAAG,IAAI;AAAA,EAC9D;AAEA,SAAO;AACT;AAEA,SAAS,cACP,QACA,OACA,QACqC;AACrC,OAAK,QAAQ,CAAC,WAAW,GAAG,MAAM;AAElC,MAAI,SAAS,KAAK,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,SAAS,KAAK;AAE7B,MAAI,CAAC,QAAQ;AACX;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,+EAA+E,cAAc,KAAK,CAAC;AAAA,IACrG;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAqC,CAAC;AAC5C,QAAM,OAAO,OAAO,KAAK,SAAS;AAElC,aAAW,CAAC,OAAO,MAAM,KAAK,OAAO,QAAQ,MAAM,GAAG;AACpD,UAAM,MAAM,KAAK,KAAK,CAACC,SAAQ,UAAUA,IAAG,MAAM,KAAK;AAEvD,QAAI,KAAK;AACP,gBAAU,GAAG,IAAI,KAAK,MAAM;AAC5B,WAAK,QAAQ,CAAC,aAAa,GAAG,GAAG,CAAC,GAAG,QAAQ,KAAK,GAAG,IAAI;AAAA,IAC3D,WAAW,CAAC,MAAM,WAAW,GAAG,GAAG;AACjC,aAAO,QAAQ,iBAAiB,CAAC,GAAG,QAAQ,KAAK,GAAG,gBAAgB;AAAA,IACtE;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,KAAK,OAAyB;AACrC,SAAO,OAAO,UAAU,WACpB,UAAU,KAAK,KAAK,SACnB,SAAS;AAChB;AAEA,SAAS,SAAS,OAAoC;AACpD,QAAM,SACJ,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,OAAO,KAAK,IAAI;AAC9D,SAAO,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,IACvD,SACA;AACN;AAEA,SAAS,SAAS,OAAyB;AACzC,SACE,UAAU,UACV,UAAU,QACT,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK;AAE9C;AAEA,SAAS,OACP,QACA,MACA,MACA,SACM;AACN,UAAQ,OAAO,UAAU,MAAM,MAAM,GAAG,OAAO,iBAAiB;AAClE;AAEA,SAAS,KACP,QACA,MACA,QACA,YAAY,OACN;AACN,QAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,SAAO,QAAQ,IAAI,KAAK,MAAM;AAC9B,MAAI,WAAW;AACb,WAAO,UAAU,IAAI,GAAG;AAAA,EAC1B;AACF;AAGA,SAAS,SAAS,QAAgB,MAAkB;AAClD,WAAS,IAAI,KAAK,QAAQ,KAAK,GAAG,KAAK;AACrC,UAAM,SAAS,OAAO,QAAQ,IAAI,KAAK,UAAU,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC;AAClE,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAEA,SAAS,aAAa,GAAa,GAAqB;AACtD,aAAW,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG;AAChC,QAAI,MAAM,EAAE,QAAQ;AAClB,aAAO;AAAA,IACT;AACA,UAAM,IAAI,EAAE,CAAC;AACb,QAAI,MAAM,GAAG;AACX,aAAO,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO,EAAE,SAAS,EAAE;AACtB;AAEA,SAAS,YAAY,QAAgB,MAA8B;AACjE,WAAS,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK;AACpC,UAAM,KAAK,KAAK,MAAM,GAAG,CAAC;AAC1B,QAAI,OAAO,UAAU,IAAI,KAAK,UAAU,EAAE,CAAC,GAAG;AAC5C,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,OAAO,OAAgC,MAAkB;AAChE,QAAM,SAAS,KACZ,MAAM,GAAG,EAAE,EACX;AAAA,IACC,CAAC,OAAO,QACL,QAAyD,GAAG;AAAA,IAC/D;AAAA,EACF;AAEF,MAAI,QAAQ;AACV,WAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAE;AAAA,EACtC;AACF;","names":["issue","image","text","amount","elements","key"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/schema-org/nutrients.ts
|
|
2
|
+
var NUTRIENTS = {
|
|
3
|
+
servingSize: "servingSize",
|
|
4
|
+
calories: "calories",
|
|
5
|
+
fat: "fatContent",
|
|
6
|
+
saturatedFat: "saturatedFatContent",
|
|
7
|
+
transFat: "transFatContent",
|
|
8
|
+
unsaturatedFat: "unsaturatedFatContent",
|
|
9
|
+
carbohydrates: "carbohydrateContent",
|
|
10
|
+
sugar: "sugarContent",
|
|
11
|
+
fiber: "fiberContent",
|
|
12
|
+
protein: "proteinContent",
|
|
13
|
+
sodium: "sodiumContent",
|
|
14
|
+
cholesterol: "cholesterolContent"
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
NUTRIENTS
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=nutrients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/schema-org/nutrients.ts"],"sourcesContent":["import type { Nutrition } from '../recipe/types'\nimport type { SchemaOrgNutritionInformation } from './types'\n\n/** The schema.org `NutritionInformation` name for each `Nutrition` field. */\nexport const NUTRIENTS: Readonly<\n Record<keyof Nutrition, Exclude<keyof SchemaOrgNutritionInformation, '@type'>>\n> = {\n servingSize: 'servingSize',\n calories: 'calories',\n fat: 'fatContent',\n saturatedFat: 'saturatedFatContent',\n transFat: 'transFatContent',\n unsaturatedFat: 'unsaturatedFatContent',\n carbohydrates: 'carbohydrateContent',\n sugar: 'sugarContent',\n fiber: 'fiberContent',\n protein: 'proteinContent',\n sodium: 'sodiumContent',\n cholesterol: 'cholesterolContent',\n}\n"],"mappings":";AAIO,IAAM,YAET;AAAA,EACF,aAAa;AAAA,EACb,UAAU;AAAA,EACV,KAAK;AAAA,EACL,cAAc;AAAA,EACd,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,aAAa;AACf;","names":[]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/schema-org/text.ts
|
|
2
|
+
import { hasOwn } from "../issues.js";
|
|
3
|
+
var NAMED_ENTITIES = {
|
|
4
|
+
amp: "&",
|
|
5
|
+
lt: "<",
|
|
6
|
+
gt: ">",
|
|
7
|
+
quot: '"',
|
|
8
|
+
apos: "'",
|
|
9
|
+
nbsp: " ",
|
|
10
|
+
ensp: " ",
|
|
11
|
+
emsp: " ",
|
|
12
|
+
thinsp: " ",
|
|
13
|
+
shy: "",
|
|
14
|
+
zwj: "",
|
|
15
|
+
zwnj: "",
|
|
16
|
+
deg: "\xB0",
|
|
17
|
+
colon: ":",
|
|
18
|
+
Tab: " ",
|
|
19
|
+
NewLine: "\n",
|
|
20
|
+
euro: "\u20AC",
|
|
21
|
+
pound: "\xA3",
|
|
22
|
+
yen: "\xA5",
|
|
23
|
+
cent: "\xA2",
|
|
24
|
+
half: "\xBD",
|
|
25
|
+
frac12: "\xBD",
|
|
26
|
+
frac14: "\xBC",
|
|
27
|
+
frac34: "\xBE",
|
|
28
|
+
frac13: "\u2153",
|
|
29
|
+
frac23: "\u2154",
|
|
30
|
+
frac15: "\u2155",
|
|
31
|
+
frac25: "\u2156",
|
|
32
|
+
frac35: "\u2157",
|
|
33
|
+
frac45: "\u2158",
|
|
34
|
+
frac16: "\u2159",
|
|
35
|
+
frac56: "\u215A",
|
|
36
|
+
frac18: "\u215B",
|
|
37
|
+
frac38: "\u215C",
|
|
38
|
+
frac58: "\u215D",
|
|
39
|
+
frac78: "\u215E",
|
|
40
|
+
frasl: "\u2044",
|
|
41
|
+
ndash: "\u2013",
|
|
42
|
+
mdash: "\u2014",
|
|
43
|
+
minus: "\u2212",
|
|
44
|
+
hellip: "\u2026",
|
|
45
|
+
lsquo: "\u2018",
|
|
46
|
+
rsquo: "\u2019",
|
|
47
|
+
sbquo: "\u201A",
|
|
48
|
+
ldquo: "\u201C",
|
|
49
|
+
rdquo: "\u201D",
|
|
50
|
+
bdquo: "\u201E",
|
|
51
|
+
laquo: "\xAB",
|
|
52
|
+
raquo: "\xBB",
|
|
53
|
+
times: "\xD7",
|
|
54
|
+
divide: "\xF7",
|
|
55
|
+
middot: "\xB7",
|
|
56
|
+
bull: "\u2022",
|
|
57
|
+
micro: "\xB5",
|
|
58
|
+
sup1: "\xB9",
|
|
59
|
+
sup2: "\xB2",
|
|
60
|
+
sup3: "\xB3",
|
|
61
|
+
ordm: "\xBA",
|
|
62
|
+
ordf: "\xAA",
|
|
63
|
+
copy: "\xA9",
|
|
64
|
+
reg: "\xAE",
|
|
65
|
+
trade: "\u2122",
|
|
66
|
+
iexcl: "\xA1",
|
|
67
|
+
iquest: "\xBF",
|
|
68
|
+
szlig: "\xDF",
|
|
69
|
+
aelig: "\xE6",
|
|
70
|
+
AElig: "\xC6",
|
|
71
|
+
oelig: "\u0153",
|
|
72
|
+
OElig: "\u0152",
|
|
73
|
+
oslash: "\xF8",
|
|
74
|
+
Oslash: "\xD8"
|
|
75
|
+
};
|
|
76
|
+
var MARKS = {
|
|
77
|
+
grave: "\u0300",
|
|
78
|
+
acute: "\u0301",
|
|
79
|
+
circ: "\u0302",
|
|
80
|
+
tilde: "\u0303",
|
|
81
|
+
uml: "\u0308",
|
|
82
|
+
ring: "\u030A",
|
|
83
|
+
cedil: "\u0327"
|
|
84
|
+
};
|
|
85
|
+
var MARKUP = /<!--[\s\S]*?(?:-->|$)|<(script|style)(?=[\s/>])(?=((?:=\s*(?:"(?:[^"<]|<(?![a-z/!?]))*"|'(?:[^'<]|<(?![a-z/!?]))*')|[^<>])*))\2>[\s\S]*?(?:<\/\1\s*>|$)|<(?:(\/?(?:br|p|li|div|h[1-6])\b)|[/!?]?[a-z])(?=((?:=\s*(?:"(?:[^"<]|<(?![a-z/!?]))*"|'(?:[^'<]|<(?![a-z/!?]))*')|[^<>])*))\4>/gi;
|
|
86
|
+
var MAX_PASSES = 8;
|
|
87
|
+
function cleanText(text) {
|
|
88
|
+
return cleanLines(text).join(" ");
|
|
89
|
+
}
|
|
90
|
+
function cleanLines(text) {
|
|
91
|
+
return stripTags(text).replace(
|
|
92
|
+
/&(?:#(\d{1,7})|#x([\da-f]{1,6})|([a-z][a-z\d]*));/gi,
|
|
93
|
+
(entity, decimal, hex, name) => {
|
|
94
|
+
if (name === void 0) {
|
|
95
|
+
const code = decimal === void 0 ? parseInt(hex, 16) : +decimal;
|
|
96
|
+
return code > 0 && code <= 1114111 && (code < 55296 || code > 57343) ? String.fromCodePoint(code) : entity;
|
|
97
|
+
}
|
|
98
|
+
if (hasOwn(NAMED_ENTITIES, name)) {
|
|
99
|
+
return NAMED_ENTITIES[name];
|
|
100
|
+
}
|
|
101
|
+
const accent = /^([aceinouyACEINOUY])([a-z]+)$/.exec(name);
|
|
102
|
+
return accent && hasOwn(MARKS, accent[2]) ? `${accent[1]}${MARKS[accent[2]]}`.normalize("NFC") : entity;
|
|
103
|
+
}
|
|
104
|
+
).split(/[\n\r\u2028\u2029]/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
function stripTags(text) {
|
|
107
|
+
let stripped = text;
|
|
108
|
+
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
109
|
+
const next = stripped.replace(
|
|
110
|
+
MARKUP,
|
|
111
|
+
(_, _element, _attributes, block) => block ? "\n" : ""
|
|
112
|
+
);
|
|
113
|
+
if (next === stripped) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
stripped = next;
|
|
117
|
+
}
|
|
118
|
+
const end = stripped.lastIndexOf(">") + 1;
|
|
119
|
+
return stripped.slice(0, end).replace(/<+([/!?a-z]?)/gi, (run, next) => next || run) + stripped.slice(end);
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
cleanLines,
|
|
123
|
+
cleanText
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/schema-org/text.ts"],"sourcesContent":["import { hasOwn } from '../issues'\n\nconst NAMED_ENTITIES: Readonly<Record<string, string>> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n ensp: ' ',\n emsp: ' ',\n thinsp: ' ',\n shy: '',\n zwj: '',\n zwnj: '',\n deg: '°',\n colon: ':',\n Tab: '\\t',\n NewLine: '\\n',\n euro: '€',\n pound: '£',\n yen: '¥',\n cent: '¢',\n half: '½',\n frac12: '½',\n frac14: '¼',\n frac34: '¾',\n frac13: '⅓',\n frac23: '⅔',\n frac15: '⅕',\n frac25: '⅖',\n frac35: '⅗',\n frac45: '⅘',\n frac16: '⅙',\n frac56: '⅚',\n frac18: '⅛',\n frac38: '⅜',\n frac58: '⅝',\n frac78: '⅞',\n frasl: '⁄',\n ndash: '–',\n mdash: '—',\n minus: '−',\n hellip: '…',\n lsquo: '‘',\n rsquo: '’',\n sbquo: '‚',\n ldquo: '“',\n rdquo: '”',\n bdquo: '„',\n laquo: '«',\n raquo: '»',\n times: '×',\n divide: '÷',\n middot: '·',\n bull: '•',\n micro: 'µ',\n sup1: '¹',\n sup2: '²',\n sup3: '³',\n ordm: 'º',\n ordf: 'ª',\n copy: '©',\n reg: '®',\n trade: '™',\n iexcl: '¡',\n iquest: '¿',\n szlig: 'ß',\n aelig: 'æ',\n AElig: 'Æ',\n oelig: 'œ',\n OElig: 'Œ',\n oslash: 'ø',\n Oslash: 'Ø',\n}\n\n// `é` and the like decode to the letter plus a combining mark, then NFC.\nconst MARKS: Readonly<Record<string, string>> = {\n grave: '\\u0300',\n acute: '\\u0301',\n circ: '\\u0302',\n tilde: '\\u0303',\n uml: '\\u0308',\n ring: '\\u030A',\n cedil: '\\u0327',\n}\n\n// A comment, a script or style element with its content, or a tag, with its\n// name captured if it's a block tag. As in a browser, a quote starts an\n// attribute value only right after `=`. A value can hold a `>`, and a `<`\n// that can't start a tag, as in `a < b`. A tag with no `>` is tried again at\n// each `<` in it, so the attributes end at any `<` that can start a tag, which\n// keeps each try short. They're read once, in a lookahead, then matched by\n// backreference, so a failed tag doesn't retry other splits of them, which\n// would be exponential. A literal, unlike `new RegExp`, lets bundlers drop it.\nconst MARKUP =\n /<!--[\\s\\S]*?(?:-->|$)|<(script|style)(?=[\\s/>])(?=((?:=\\s*(?:\"(?:[^\"<]|<(?![a-z/!?]))*\"|'(?:[^'<]|<(?![a-z/!?]))*')|[^<>])*))\\2>[\\s\\S]*?(?:<\\/\\1\\s*>|$)|<(?:(\\/?(?:br|p|li|div|h[1-6])\\b)|[/!?]?[a-z])(?=((?:=\\s*(?:\"(?:[^\"<]|<(?![a-z/!?]))*\"|'(?:[^'<]|<(?![a-z/!?]))*')|[^<>])*))\\4>/gi\n\n// Removing a tag can join the text around it into a new one, as in\n// `<scr<b></b>ipt>`, so stripping repeats. Real markup needs a pass or two;\n// the cap keeps hostile nesting linear.\nconst MAX_PASSES = 8\n\n/**\n * JSON-LD text with its tags removed, then its HTML entities decoded, on one\n * line with single spaces. An encoded tag, such as `<b>`, stays as text.\n */\nexport function cleanText(text: string): string {\n return cleanLines(text).join(' ')\n}\n\n/** {@link cleanText} for each line, split at line breaks and block tags. Empty lines are left out. */\nexport function cleanLines(text: string): string[] {\n return stripTags(text)\n .replace(\n /&(?:#(\\d{1,7})|#x([\\da-f]{1,6})|([a-z][a-z\\d]*));/gi,\n (entity, decimal?: string, hex?: string, name?: string) => {\n if (name === undefined) {\n const code = decimal === undefined ? parseInt(hex!, 16) : +decimal\n return code > 0 &&\n code <= 0x10ffff &&\n (code < 0xd800 || code > 0xdfff)\n ? String.fromCodePoint(code)\n : entity\n }\n\n if (hasOwn(NAMED_ENTITIES, name)) {\n return NAMED_ENTITIES[name]!\n }\n\n const accent = /^([aceinouyACEINOUY])([a-z]+)$/.exec(name)\n return accent && hasOwn(MARKS, accent[2]!)\n ? `${accent[1]}${MARKS[accent[2]!]}`.normalize('NFC')\n : entity\n },\n )\n .split(/[\\n\\r\\u2028\\u2029]/)\n .map((line) => line.replace(/\\s+/g, ' ').trim())\n .filter(Boolean)\n}\n\n/**\n * Removes comments, tags, and the content of `script` and `style`, and ends a\n * line at block tags. Strips again until nothing changes, for up to\n * `MAX_PASSES` passes, so a ninth nested level stays as text such as `b>`.\n * Then removes any `<` that could still open a tag: one before a letter, `/`,\n * `!`, or `?` with a `>` after it, as in `<a <>`. A `<` with no `>` after it,\n * as in `x<y`, stays.\n */\nfunction stripTags(text: string): string {\n let stripped = text\n\n for (let pass = 0; pass < MAX_PASSES; pass++) {\n const next = stripped.replace(\n MARKUP,\n (_, _element, _attributes, block?: string) => (block ? '\\n' : ''),\n )\n if (next === stripped) {\n break\n }\n stripped = next\n }\n\n const end = stripped.lastIndexOf('>') + 1\n return (\n stripped\n .slice(0, end)\n .replace(/<+([/!?a-z]?)/gi, (run, next: string) => next || run) +\n stripped.slice(end)\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/schema-org/to-json-ld.ts
|
|
2
|
+
import { describeValue } from "../errors.js";
|
|
3
|
+
import { formatIngredient } from "../format/ingredient.js";
|
|
4
|
+
import { round } from "../format/number.js";
|
|
5
|
+
import { formatQuantity } from "../format/quantity.js";
|
|
6
|
+
import { formatServings, formatYield } from "../format/servings.js";
|
|
7
|
+
import { hasOwn, isRecord, warning } from "../issues.js";
|
|
8
|
+
import { multiply } from "../quantity/scale.js";
|
|
9
|
+
import { NUTRIENT_UNITS } from "../recipe/nutrient-units.js";
|
|
10
|
+
import { isNative, resolveRecipe } from "../resolve.js";
|
|
11
|
+
import { NUTRIENTS } from "./nutrients.js";
|
|
12
|
+
function toJsonLd(recipe, options) {
|
|
13
|
+
const { servings, system, url, extra } = options ?? {};
|
|
14
|
+
const resolved = resolveRecipe(recipe, { servings, system });
|
|
15
|
+
const page = typeof url === "string" ? url.replace(/#.*/s, "") : void 0;
|
|
16
|
+
const yields = [
|
|
17
|
+
...resolved.servings ? servingsText(resolved.servings) : [],
|
|
18
|
+
...resolved.yield ? [formatYield(resolved.yield, { fractions: false })] : []
|
|
19
|
+
];
|
|
20
|
+
const ingredients = resolved.ingredients.flatMap(
|
|
21
|
+
({ items }) => items.map((item) => ingredientLine(item, resolved.scale, resolved.system))
|
|
22
|
+
);
|
|
23
|
+
const steps = resolved.steps.flatMap(
|
|
24
|
+
({ title, items }) => {
|
|
25
|
+
const howToSteps = items.map((step) => ({
|
|
26
|
+
"@type": "HowToStep",
|
|
27
|
+
...step.name !== void 0 && { name: step.name },
|
|
28
|
+
text: step.text,
|
|
29
|
+
...step.image !== void 0 && { image: step.image },
|
|
30
|
+
...page !== void 0 && {
|
|
31
|
+
// encodeURIComponent throws on a lone surrogate. With the u flag,
|
|
32
|
+
// \p{Cs} matches only those, as a pair reads as one code point.
|
|
33
|
+
url: `${page}#${encodeURIComponent(step.id.replace(/\p{Cs}/gu, "\uFFFD"))}`
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
return title === void 0 || howToSteps.length === 0 ? howToSteps : [
|
|
37
|
+
{
|
|
38
|
+
"@type": "HowToSection",
|
|
39
|
+
name: title,
|
|
40
|
+
itemListElement: howToSteps
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
const { description, image, times, equipment, nutrition } = recipe;
|
|
46
|
+
const warnings = [];
|
|
47
|
+
const written = {
|
|
48
|
+
"@context": "https://schema.org",
|
|
49
|
+
"@type": "Recipe",
|
|
50
|
+
name: recipe.name,
|
|
51
|
+
...description !== void 0 && { description },
|
|
52
|
+
...image !== void 0 && { image },
|
|
53
|
+
...yields.length > 0 && { recipeYield: yields.map(plainSpaces) },
|
|
54
|
+
...duration(warnings, "prep", times.prep),
|
|
55
|
+
...duration(warnings, "cook", times.cook),
|
|
56
|
+
...duration(warnings, "total", times.total),
|
|
57
|
+
...ingredients.length > 0 && { recipeIngredient: ingredients },
|
|
58
|
+
...steps.length > 0 && { recipeInstructions: steps },
|
|
59
|
+
...equipment.length > 0 && { tool: equipment.map(({ name }) => name) },
|
|
60
|
+
...nutrition !== void 0 && {
|
|
61
|
+
nutrition: nutritionInformation(nutrition)
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const data = {
|
|
65
|
+
...written,
|
|
66
|
+
...Object.fromEntries(
|
|
67
|
+
Object.entries(isRecord(extra) ? extra : {}).filter(
|
|
68
|
+
([field, value]) => value !== void 0 && !hasOwn(written, field)
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
if (data.image === void 0) {
|
|
73
|
+
warning(
|
|
74
|
+
warnings,
|
|
75
|
+
"missing_field",
|
|
76
|
+
["image"],
|
|
77
|
+
"Add an image. Google requires one for recipe results."
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (data.nutrition !== void 0 && data.recipeYield === void 0) {
|
|
81
|
+
warning(
|
|
82
|
+
warnings,
|
|
83
|
+
"missing_field",
|
|
84
|
+
["recipeYield"],
|
|
85
|
+
"Add servings or a yield. Google requires one with nutrition, which is for one serving."
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return { data, warnings };
|
|
89
|
+
}
|
|
90
|
+
function servingsText(servings) {
|
|
91
|
+
const amount = formatServings(servings, { fractions: false });
|
|
92
|
+
const word = servings.amount === 1 && servings.max === void 0 ? "serving" : "servings";
|
|
93
|
+
return [amount, `${amount} ${word}`];
|
|
94
|
+
}
|
|
95
|
+
function ingredientLine(item, scale, system) {
|
|
96
|
+
const { ingredient, origin } = item;
|
|
97
|
+
const { quantity, name, note } = formatIngredient(item);
|
|
98
|
+
const authored = ingredient.quantity ? [ingredient.quantity, ...ingredient.equivalents] : [];
|
|
99
|
+
const shown = origin === "authored" ? 0 : origin === "equivalent" ? 1 + ingredient.equivalents.findIndex((q) => isNative(q, system)) : -1;
|
|
100
|
+
const alternatives = authored.filter((_, i) => i !== shown).map((q) => formatQuantity(scale === 1 ? q : multiply(q, scale))).join("; ");
|
|
101
|
+
const inParentheses = alternatives && ` (${alternatives})`;
|
|
102
|
+
const head = quantity === void 0 ? name : item.quantity?.unit === void 0 ? `${quantity} ${name}${inParentheses}` : `${quantity}${inParentheses} ${name}`;
|
|
103
|
+
return plainSpaces(
|
|
104
|
+
`${head}${note ? `, ${note}` : ""}${ingredient.optional ? " (optional)" : ""}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function duration(warnings, key, minutes) {
|
|
108
|
+
if (minutes === void 0) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
if (Number.isNaN(minutes) || minutes > Number.MAX_SAFE_INTEGER) {
|
|
112
|
+
warning(
|
|
113
|
+
warnings,
|
|
114
|
+
"invalid_duration",
|
|
115
|
+
["times", key],
|
|
116
|
+
`Expected minutes such as 30, received ${describeValue(minutes)}${Number.isNaN(minutes) ? "" : ", which is too long"}. It is ignored.`
|
|
117
|
+
);
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
120
|
+
const seconds = Math.round(minutes * 60);
|
|
121
|
+
if (seconds <= 0) {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
const parts = [
|
|
125
|
+
[Math.floor(seconds / 3600), "H"],
|
|
126
|
+
[Math.floor(seconds / 60) % 60, "M"],
|
|
127
|
+
[seconds % 60, "S"]
|
|
128
|
+
];
|
|
129
|
+
return {
|
|
130
|
+
[`${key}Time`]: `PT${parts.map(([n, unit]) => n ? `${n}${unit}` : "").join("")}`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function nutritionInformation(nutrition) {
|
|
134
|
+
const information = {
|
|
135
|
+
"@type": "NutritionInformation"
|
|
136
|
+
};
|
|
137
|
+
for (const key of Object.keys(NUTRIENTS)) {
|
|
138
|
+
const value = nutrition[key];
|
|
139
|
+
if (value !== void 0) {
|
|
140
|
+
information[NUTRIENTS[key]] = typeof value === "string" ? value : `${round(value, 3)} ${NUTRIENT_UNITS[key]}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return information;
|
|
144
|
+
}
|
|
145
|
+
function plainSpaces(text) {
|
|
146
|
+
return text.replace(/\u00A0/g, " ");
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
toJsonLd
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=to-json-ld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/schema-org/to-json-ld.ts"],"sourcesContent":["import { describeValue, type RecipeIssue } from '../errors'\nimport { formatIngredient } from '../format/ingredient'\nimport { round } from '../format/number'\nimport { formatQuantity } from '../format/quantity'\nimport { formatServings, formatYield } from '../format/servings'\nimport { hasOwn, isRecord, warning } from '../issues'\nimport { multiply } from '../quantity/scale'\nimport { NUTRIENT_UNITS, type NutrientKey } from '../recipe/nutrient-units'\nimport type { Nutrition, Recipe } from '../recipe/types'\nimport { isNative, type ResolvedIngredient, resolveRecipe } from '../resolve'\nimport type { System } from '../units/registry'\nimport { NUTRIENTS } from './nutrients'\nimport type {\n SchemaOrgHowToSection,\n SchemaOrgHowToStep,\n SchemaOrgNutritionInformation,\n SchemaOrgRecipe,\n ToJsonLdOptions,\n ToJsonLdResult,\n} from './types'\n\n/**\n * Writes a recipe as schema.org Recipe JSON-LD, for search engines' recipe\n * results. Writes it at its own servings and in `recipe.system`, else metric,\n * unless the options say otherwise; invalid options fall back to these, as in\n * `resolveRecipe`. Never throws for an unchanged recipe from `parseRecipe` or\n * `fromJsonLd`.\n *\n * `data` is plain JSON when `extra` is. Write it into a\n * `<script type=\"application/ld+json\">` tag with `<` escaped as `\\u003c`, so\n * recipe text can't end the tag. An `image` passed in `extra` is a URL; for an\n * `ImageObject` or several images, spread `data` into a new object with your\n * own `image`.\n *\n * Ingredient lines read back with `parseIngredientLine`: a quantity's\n * alternatives follow it in parentheses, as in `\"1 ½ cups (190 g) flour,\n * sifted\"` and `\"2 eggs (100 g)\"`, and an optional ingredient ends\n * `\" (optional)\"`. schema.org has no place for ingredient section titles, the\n * recipe's notes, custom times, an ingredient's `url`, or equipment beyond its\n * name, so they aren't written. Reading `data` back with `fromJsonLd` gives a\n * similar recipe, not the same one.\n *\n * `warnings` names the fields Google requires for recipe results that `data`\n * lacks, with paths that use the schema.org names: `image`, and `recipeYield`\n * when there is nutrition. A time longer than `Number.MAX_SAFE_INTEGER`\n * minutes is left out, as `fromJsonLd` leaves it out, and so is a `NaN` time,\n * each with an `invalid_duration` warning at its path in the recipe, such as\n * `['times', 'cook']`.\n *\n * @example\n * const { data } = toJsonLd(recipe, { url: 'https://example.com/pancakes' })\n * data.recipeYield // ['4', '4 servings']\n * data.recipeIngredient // ['1 ½ cups (190 g) flour, sifted', '2 eggs', 'salt, to taste']\n */\nexport function toJsonLd(\n recipe: Recipe,\n options?: ToJsonLdOptions,\n): ToJsonLdResult {\n const { servings, system, url, extra } = options ?? {}\n const resolved = resolveRecipe(recipe, { servings, system })\n // `s`, so a line break doesn't end the fragment.\n const page = typeof url === 'string' ? url.replace(/#.*/s, '') : undefined\n const yields = [\n ...(resolved.servings ? servingsText(resolved.servings) : []),\n ...(resolved.yield\n ? [formatYield(resolved.yield, { fractions: false })]\n : []),\n ]\n const ingredients = resolved.ingredients.flatMap(({ items }) =>\n items.map((item) => ingredientLine(item, resolved.scale, resolved.system)),\n )\n const steps = resolved.steps.flatMap(\n ({ title, items }): (SchemaOrgHowToStep | SchemaOrgHowToSection)[] => {\n const howToSteps = items.map((step): SchemaOrgHowToStep => ({\n '@type': 'HowToStep',\n ...(step.name !== undefined && { name: step.name }),\n text: step.text,\n ...(step.image !== undefined && { image: step.image }),\n ...(page !== undefined && {\n // encodeURIComponent throws on a lone surrogate. With the u flag,\n // \\p{Cs} matches only those, as a pair reads as one code point.\n url: `${page}#${encodeURIComponent(step.id.replace(/\\p{Cs}/gu, '\\uFFFD'))}`,\n }),\n }))\n return title === undefined || howToSteps.length === 0\n ? howToSteps\n : [\n {\n '@type': 'HowToSection',\n name: title,\n itemListElement: howToSteps,\n },\n ]\n },\n )\n const { description, image, times, equipment, nutrition } = recipe\n const warnings: RecipeIssue[] = []\n\n const written: SchemaOrgRecipe = {\n '@context': 'https://schema.org',\n '@type': 'Recipe',\n name: recipe.name,\n ...(description !== undefined && { description }),\n ...(image !== undefined && { image }),\n ...(yields.length > 0 && { recipeYield: yields.map(plainSpaces) }),\n ...duration(warnings, 'prep', times.prep),\n ...duration(warnings, 'cook', times.cook),\n ...duration(warnings, 'total', times.total),\n ...(ingredients.length > 0 && { recipeIngredient: ingredients }),\n ...(steps.length > 0 && { recipeInstructions: steps }),\n ...(equipment.length > 0 && { tool: equipment.map(({ name }) => name) }),\n ...(nutrition !== undefined && {\n nutrition: nutritionInformation(nutrition),\n }),\n }\n const data: SchemaOrgRecipe = {\n ...written,\n ...Object.fromEntries(\n Object.entries(isRecord(extra) ? extra : {}).filter(\n ([field, value]) => value !== undefined && !hasOwn(written, field),\n ),\n ),\n }\n\n if (data.image === undefined) {\n warning(\n warnings,\n 'missing_field',\n ['image'],\n 'Add an image. Google requires one for recipe results.',\n )\n }\n if (data.nutrition !== undefined && data.recipeYield === undefined) {\n warning(\n warnings,\n 'missing_field',\n ['recipeYield'],\n 'Add servings or a yield. Google requires one with nutrition, which is for one serving.',\n )\n }\n\n return { data, warnings }\n}\n\n/** `[\"4\", \"4 servings\"]`, in decimals, so the text reads back as a number. */\nfunction servingsText(servings: {\n amount: number\n max?: number | undefined\n}): string[] {\n const amount = formatServings(servings, { fractions: false })\n const word =\n servings.amount === 1 && servings.max === undefined ? 'serving' : 'servings'\n return [amount, `${amount} ${word}`]\n}\n\n/**\n * The line as `formatIngredient` writes it, with the quantity's alternatives\n * in parentheses after it, or after the name for a bare count, and\n * \" (optional)\" at the end.\n */\nfunction ingredientLine(\n item: ResolvedIngredient,\n scale: number,\n system: System,\n): string {\n const { ingredient, origin } = item\n const { quantity, name, note } = formatIngredient(item)\n const authored = ingredient.quantity\n ? [ingredient.quantity, ...ingredient.equivalents]\n : []\n const shown =\n origin === 'authored'\n ? 0\n : origin === 'equivalent'\n ? 1 + ingredient.equivalents.findIndex((q) => isNative(q, system))\n : -1\n const alternatives = authored\n .filter((_, i) => i !== shown)\n .map((q) => formatQuantity(scale === 1 ? q : multiply(q, scale)))\n .join('; ')\n const inParentheses = alternatives && ` (${alternatives})`\n const head =\n quantity === undefined\n ? name\n : item.quantity?.unit === undefined\n ? `${quantity} ${name}${inParentheses}`\n : `${quantity}${inParentheses} ${name}`\n\n return plainSpaces(\n `${head}${note ? `, ${note}` : ''}${ingredient.optional ? ' (optional)' : ''}`,\n )\n}\n\n/**\n * An ISO 8601 duration to the second, or nothing for an absent or zero time,\n * or one it can't write: `NaN`, which `parseRecipe` rejects but a recipe built\n * by hand can hold, or a time longer than `Number.MAX_SAFE_INTEGER` minutes.\n * Below that bound, the hours fit in a safe integer, so they print in plain\n * digits.\n */\nfunction duration(\n warnings: RecipeIssue[],\n key: 'prep' | 'cook' | 'total',\n minutes: number | undefined,\n): Partial<SchemaOrgRecipe> {\n if (minutes === undefined) {\n return {}\n }\n if (Number.isNaN(minutes) || minutes > Number.MAX_SAFE_INTEGER) {\n warning(\n warnings,\n 'invalid_duration',\n ['times', key],\n `Expected minutes such as 30, received ${describeValue(minutes)}${Number.isNaN(minutes) ? '' : ', which is too long'}. It is ignored.`,\n )\n return {}\n }\n\n const seconds = Math.round(minutes * 60)\n\n if (seconds <= 0) {\n return {}\n }\n\n const parts = [\n [Math.floor(seconds / 3600), 'H'],\n [Math.floor(seconds / 60) % 60, 'M'],\n [seconds % 60, 'S'],\n ] as const\n\n return {\n [`${key}Time`]: `PT${parts.map(([n, unit]) => (n ? `${n}${unit}` : '')).join('')}`,\n }\n}\n\nfunction nutritionInformation(\n nutrition: Nutrition,\n): SchemaOrgNutritionInformation {\n const information: SchemaOrgNutritionInformation = {\n '@type': 'NutritionInformation',\n }\n\n for (const key of Object.keys(NUTRIENTS) as (keyof Nutrition)[]) {\n const value = nutrition[key]\n if (value !== undefined) {\n information[NUTRIENTS[key]] =\n typeof value === 'string'\n ? value\n : `${round(value, 3)} ${NUTRIENT_UNITS[key as NutrientKey]}`\n }\n }\n\n return information\n}\n\nfunction plainSpaces(text: string): string {\n return text.replace(/\\u00A0/g, ' ')\n}\n"],"mappings":"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","names":[]}
|