@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,1062 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkD5CV3UF7_cjs = require('./chunk-D5CV3UF7.cjs');
|
|
4
|
+
|
|
5
|
+
// src/schema-org/find.ts
|
|
6
|
+
var MAX_DEPTH = 32;
|
|
7
|
+
function locate(documents) {
|
|
8
|
+
const direct = scan(documents, false);
|
|
9
|
+
return direct.recipes.length > 0 ? direct : scan(documents, true);
|
|
10
|
+
}
|
|
11
|
+
function scan(documents, lists) {
|
|
12
|
+
const recipes = [];
|
|
13
|
+
const ids = /* @__PURE__ */ new Map();
|
|
14
|
+
const recipeIds = /* @__PURE__ */ new Map();
|
|
15
|
+
const visit = (value, path, reach = "any") => {
|
|
16
|
+
if (path.length > MAX_DEPTH) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
for (const [entry, entryPath] of entriesOf(value, path)) {
|
|
21
|
+
visit(entry, entryPath, reach);
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const fields = chunkD5CV3UF7_cjs.asRecord(value);
|
|
26
|
+
if (!fields) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const isRecipe = hasType(fields, "Recipe");
|
|
30
|
+
const isList = lists && hasType(fields, "ItemList");
|
|
31
|
+
if (reach !== "any" && !isRecipe) {
|
|
32
|
+
if (isList && reach === "mainEntity") {
|
|
33
|
+
visit(fields.itemListElement, [...path, "itemListElement"], "entry");
|
|
34
|
+
} else if (lists && reach === "entry" && hasType(fields, "ListItem")) {
|
|
35
|
+
visit(fields.item, [...path, "item"], "item");
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const located = { node: value, path };
|
|
40
|
+
const id = fields["@id"] || void 0;
|
|
41
|
+
if (typeof id === "string" && !ids.has(id)) {
|
|
42
|
+
ids.set(id, located);
|
|
43
|
+
}
|
|
44
|
+
if (isRecipe) {
|
|
45
|
+
const earlier = recipeIds.get(id);
|
|
46
|
+
if (!earlier) {
|
|
47
|
+
recipes.push(located);
|
|
48
|
+
if (typeof id === "string") {
|
|
49
|
+
recipeIds.set(id, hasContent(fields) || located);
|
|
50
|
+
}
|
|
51
|
+
} else if (earlier !== true && hasContent(fields)) {
|
|
52
|
+
Object.assign(earlier, located);
|
|
53
|
+
recipeIds.set(id, true);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
visit(fields["@graph"], [...path, "@graph"]);
|
|
57
|
+
visit(fields.mainEntity, [...path, "mainEntity"], "mainEntity");
|
|
58
|
+
if (isList) {
|
|
59
|
+
visit(fields.itemListElement, [...path, "itemListElement"], "entry");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
for (const document of documents) {
|
|
64
|
+
visit(document, []);
|
|
65
|
+
}
|
|
66
|
+
return { recipes, ids };
|
|
67
|
+
}
|
|
68
|
+
var TYPES = {
|
|
69
|
+
Recipe: /(?:^|[/:#])Recipe$/,
|
|
70
|
+
ItemList: /(?:^|[/:#])ItemList$/,
|
|
71
|
+
ListItem: /(?:^|[/:#])ListItem$/
|
|
72
|
+
};
|
|
73
|
+
function hasType(node, name) {
|
|
74
|
+
return entriesOf(node["@type"], []).some(
|
|
75
|
+
([type]) => typeof type === "string" && TYPES[name].test(type)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function hasContent(node) {
|
|
79
|
+
return Object.keys(node).some((key) => !key.startsWith("@"));
|
|
80
|
+
}
|
|
81
|
+
function entriesOf(value, path) {
|
|
82
|
+
if (!Array.isArray(value)) {
|
|
83
|
+
return [[value, path]];
|
|
84
|
+
}
|
|
85
|
+
const entries = [];
|
|
86
|
+
for (let i = 0; i < value.length; i++) {
|
|
87
|
+
if (chunkD5CV3UF7_cjs.hasOwn(value, i)) {
|
|
88
|
+
entries.push([value[i], [...path, i]]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
function findRecipeJsonLd(html) {
|
|
94
|
+
if (typeof html !== "string") {
|
|
95
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
96
|
+
"invalid_argument",
|
|
97
|
+
`findRecipeJsonLd: html must be a string, such as await response.text(), received ${chunkD5CV3UF7_cjs.describeValue(html)}.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const documents = [];
|
|
101
|
+
for (const [, element, attributes = "", body = ""] of html.matchAll(
|
|
102
|
+
/<(script|style|textarea)(?=[\s/>])(?=((?:=\s*(?:"[^"]*"|'[^']*')|[^<>])*))\2>([\s\S]*?)(?:<\/\1\s*>|$)|<!--(?:-?>|[\s\S]*?(?:--!?>|$))|<\/?[a-z][^\s/>]*(?:=\s*(?:"[^"]*"|'[^']*')|[^<>])*/gi
|
|
103
|
+
)) {
|
|
104
|
+
if (element?.toLowerCase() === "script" && /[\s/]type\s*=\s*["']?application\/ld\+json\b/i.test(attributes)) {
|
|
105
|
+
const text2 = body.replace(/^\s*(?:\/\/\s*)?(?:<!--|<!\[CDATA\[)/, "").replace(/(?:\/\/\s*)?(?:-->|\]\]>)\s*$/, "");
|
|
106
|
+
const json = parseJson(text2) ?? parseJson(
|
|
107
|
+
text2.replace(
|
|
108
|
+
/"(?:[^"\\]|\\[\s\S]?)*(?:"|$)/g,
|
|
109
|
+
(string) => string.replace(/[\n\r\t]/g, (c) => JSON.stringify(c).slice(1, -1))
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
documents.push(json);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const { recipes, ids } = locate(documents);
|
|
116
|
+
return recipes.map(({ node }) => withImages(node, ids));
|
|
117
|
+
}
|
|
118
|
+
function withImages(node, ids) {
|
|
119
|
+
const follow = (value) => {
|
|
120
|
+
const id = chunkD5CV3UF7_cjs.isRecord(value) && Object.keys(value).join() === "@id" && value["@id"];
|
|
121
|
+
const target = typeof id === "string" && ids.get(id)?.node;
|
|
122
|
+
return target && target !== node ? target : value;
|
|
123
|
+
};
|
|
124
|
+
const images = (value) => Array.isArray(value) ? value.map(follow) : follow(value);
|
|
125
|
+
const copy = (value, depth, keys) => {
|
|
126
|
+
if (depth > MAX_DEPTH) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(value)) {
|
|
130
|
+
return value.map((entry) => copy(entry, depth + 1, keys));
|
|
131
|
+
}
|
|
132
|
+
const fields = chunkD5CV3UF7_cjs.asRecord(value);
|
|
133
|
+
if (!fields) {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
const result = { ...fields };
|
|
137
|
+
for (const key of keys) {
|
|
138
|
+
if (fields[key] !== void 0) {
|
|
139
|
+
result[key] = key === "image" ? images(fields[key]) : copy(fields[key], depth + 1, ["itemListElement", "item", "image"]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
};
|
|
144
|
+
return copy(node, 0, ["image", "recipeInstructions"]);
|
|
145
|
+
}
|
|
146
|
+
function parseJson(text2) {
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(text2);
|
|
149
|
+
} catch {
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/schema-org/nutrients.ts
|
|
155
|
+
var NUTRIENTS = {
|
|
156
|
+
servingSize: "servingSize",
|
|
157
|
+
calories: "calories",
|
|
158
|
+
fat: "fatContent",
|
|
159
|
+
saturatedFat: "saturatedFatContent",
|
|
160
|
+
transFat: "transFatContent",
|
|
161
|
+
unsaturatedFat: "unsaturatedFatContent",
|
|
162
|
+
carbohydrates: "carbohydrateContent",
|
|
163
|
+
sugar: "sugarContent",
|
|
164
|
+
fiber: "fiberContent",
|
|
165
|
+
protein: "proteinContent",
|
|
166
|
+
sodium: "sodiumContent",
|
|
167
|
+
cholesterol: "cholesterolContent"
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/schema-org/text.ts
|
|
171
|
+
var NAMED_ENTITIES = {
|
|
172
|
+
amp: "&",
|
|
173
|
+
lt: "<",
|
|
174
|
+
gt: ">",
|
|
175
|
+
quot: '"',
|
|
176
|
+
apos: "'",
|
|
177
|
+
nbsp: " ",
|
|
178
|
+
ensp: " ",
|
|
179
|
+
emsp: " ",
|
|
180
|
+
thinsp: " ",
|
|
181
|
+
shy: "",
|
|
182
|
+
zwj: "",
|
|
183
|
+
zwnj: "",
|
|
184
|
+
deg: "\xB0",
|
|
185
|
+
colon: ":",
|
|
186
|
+
Tab: " ",
|
|
187
|
+
NewLine: "\n",
|
|
188
|
+
euro: "\u20AC",
|
|
189
|
+
pound: "\xA3",
|
|
190
|
+
yen: "\xA5",
|
|
191
|
+
cent: "\xA2",
|
|
192
|
+
half: "\xBD",
|
|
193
|
+
frac12: "\xBD",
|
|
194
|
+
frac14: "\xBC",
|
|
195
|
+
frac34: "\xBE",
|
|
196
|
+
frac13: "\u2153",
|
|
197
|
+
frac23: "\u2154",
|
|
198
|
+
frac15: "\u2155",
|
|
199
|
+
frac25: "\u2156",
|
|
200
|
+
frac35: "\u2157",
|
|
201
|
+
frac45: "\u2158",
|
|
202
|
+
frac16: "\u2159",
|
|
203
|
+
frac56: "\u215A",
|
|
204
|
+
frac18: "\u215B",
|
|
205
|
+
frac38: "\u215C",
|
|
206
|
+
frac58: "\u215D",
|
|
207
|
+
frac78: "\u215E",
|
|
208
|
+
frasl: "\u2044",
|
|
209
|
+
ndash: "\u2013",
|
|
210
|
+
mdash: "\u2014",
|
|
211
|
+
minus: "\u2212",
|
|
212
|
+
hellip: "\u2026",
|
|
213
|
+
lsquo: "\u2018",
|
|
214
|
+
rsquo: "\u2019",
|
|
215
|
+
sbquo: "\u201A",
|
|
216
|
+
ldquo: "\u201C",
|
|
217
|
+
rdquo: "\u201D",
|
|
218
|
+
bdquo: "\u201E",
|
|
219
|
+
laquo: "\xAB",
|
|
220
|
+
raquo: "\xBB",
|
|
221
|
+
times: "\xD7",
|
|
222
|
+
divide: "\xF7",
|
|
223
|
+
middot: "\xB7",
|
|
224
|
+
bull: "\u2022",
|
|
225
|
+
micro: "\xB5",
|
|
226
|
+
sup1: "\xB9",
|
|
227
|
+
sup2: "\xB2",
|
|
228
|
+
sup3: "\xB3",
|
|
229
|
+
ordm: "\xBA",
|
|
230
|
+
ordf: "\xAA",
|
|
231
|
+
copy: "\xA9",
|
|
232
|
+
reg: "\xAE",
|
|
233
|
+
trade: "\u2122",
|
|
234
|
+
iexcl: "\xA1",
|
|
235
|
+
iquest: "\xBF",
|
|
236
|
+
szlig: "\xDF",
|
|
237
|
+
aelig: "\xE6",
|
|
238
|
+
AElig: "\xC6",
|
|
239
|
+
oelig: "\u0153",
|
|
240
|
+
OElig: "\u0152",
|
|
241
|
+
oslash: "\xF8",
|
|
242
|
+
Oslash: "\xD8"
|
|
243
|
+
};
|
|
244
|
+
var MARKS = {
|
|
245
|
+
grave: "\u0300",
|
|
246
|
+
acute: "\u0301",
|
|
247
|
+
circ: "\u0302",
|
|
248
|
+
tilde: "\u0303",
|
|
249
|
+
uml: "\u0308",
|
|
250
|
+
ring: "\u030A",
|
|
251
|
+
cedil: "\u0327"
|
|
252
|
+
};
|
|
253
|
+
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;
|
|
254
|
+
var MAX_PASSES = 8;
|
|
255
|
+
function cleanText(text2) {
|
|
256
|
+
return cleanLines(text2).join(" ");
|
|
257
|
+
}
|
|
258
|
+
function cleanLines(text2) {
|
|
259
|
+
return stripTags(text2).replace(
|
|
260
|
+
/&(?:#(\d{1,7})|#x([\da-f]{1,6})|([a-z][a-z\d]*));/gi,
|
|
261
|
+
(entity, decimal, hex, name) => {
|
|
262
|
+
if (name === void 0) {
|
|
263
|
+
const code = decimal === void 0 ? parseInt(hex, 16) : +decimal;
|
|
264
|
+
return code > 0 && code <= 1114111 && (code < 55296 || code > 57343) ? String.fromCodePoint(code) : entity;
|
|
265
|
+
}
|
|
266
|
+
if (chunkD5CV3UF7_cjs.hasOwn(NAMED_ENTITIES, name)) {
|
|
267
|
+
return NAMED_ENTITIES[name];
|
|
268
|
+
}
|
|
269
|
+
const accent = /^([aceinouyACEINOUY])([a-z]+)$/.exec(name);
|
|
270
|
+
return accent && chunkD5CV3UF7_cjs.hasOwn(MARKS, accent[2]) ? `${accent[1]}${MARKS[accent[2]]}`.normalize("NFC") : entity;
|
|
271
|
+
}
|
|
272
|
+
).split(/[\n\r\u2028\u2029]/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
273
|
+
}
|
|
274
|
+
function stripTags(text2) {
|
|
275
|
+
let stripped = text2;
|
|
276
|
+
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
277
|
+
const next = stripped.replace(
|
|
278
|
+
MARKUP,
|
|
279
|
+
(_, _element, _attributes, block) => block ? "\n" : ""
|
|
280
|
+
);
|
|
281
|
+
if (next === stripped) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
stripped = next;
|
|
285
|
+
}
|
|
286
|
+
const end = stripped.lastIndexOf(">") + 1;
|
|
287
|
+
return stripped.slice(0, end).replace(/<+([/!?a-z]?)/gi, (run, next) => next || run) + stripped.slice(end);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/schema-org/from-json-ld.ts
|
|
291
|
+
var READ_FIELDS = [
|
|
292
|
+
"@context",
|
|
293
|
+
"@type",
|
|
294
|
+
"name",
|
|
295
|
+
"description",
|
|
296
|
+
"image",
|
|
297
|
+
"recipeYield",
|
|
298
|
+
"prepTime",
|
|
299
|
+
"cookTime",
|
|
300
|
+
"totalTime",
|
|
301
|
+
"recipeIngredient",
|
|
302
|
+
"recipeInstructions",
|
|
303
|
+
"tool",
|
|
304
|
+
"nutrition"
|
|
305
|
+
];
|
|
306
|
+
var TIMES = [
|
|
307
|
+
["prep", "prepTime"],
|
|
308
|
+
["cook", "cookTime"],
|
|
309
|
+
["total", "totalTime"]
|
|
310
|
+
];
|
|
311
|
+
var MAX_URL_LENGTH = 4096;
|
|
312
|
+
var SERVINGS_WORD = /^(?:servings?|portions?|(?:serving|portion)\(s\)|people|persons?)\.?$/i;
|
|
313
|
+
function fromJsonLd(input) {
|
|
314
|
+
const { recipes, ids } = locate([input]);
|
|
315
|
+
const [found] = recipes;
|
|
316
|
+
if (!found) {
|
|
317
|
+
return {
|
|
318
|
+
...chunkD5CV3UF7_cjs.toResult("invalid_recipe", void 0, [
|
|
319
|
+
chunkD5CV3UF7_cjs.issue(
|
|
320
|
+
"error",
|
|
321
|
+
"invalid_type",
|
|
322
|
+
[],
|
|
323
|
+
`Expected a schema.org Recipe, or an array, @graph, mainEntity, or ItemList that holds one, received ${chunkD5CV3UF7_cjs.describeValue(input)}.`
|
|
324
|
+
)
|
|
325
|
+
]),
|
|
326
|
+
extra: {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const node = chunkD5CV3UF7_cjs.asRecord(found.node);
|
|
330
|
+
const reader = {
|
|
331
|
+
recipe: found.node,
|
|
332
|
+
ids,
|
|
333
|
+
warnings: [],
|
|
334
|
+
sources: /* @__PURE__ */ new Map(),
|
|
335
|
+
droppable: /* @__PURE__ */ new Set(),
|
|
336
|
+
images: [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()],
|
|
337
|
+
repeated: 0,
|
|
338
|
+
deepest: 0
|
|
339
|
+
};
|
|
340
|
+
const name = text(node.name);
|
|
341
|
+
if (recipes.length > 1) {
|
|
342
|
+
chunkD5CV3UF7_cjs.warning(
|
|
343
|
+
reader.warnings,
|
|
344
|
+
"ignored_value",
|
|
345
|
+
[],
|
|
346
|
+
`Found ${recipes.length} recipes. Only the first${typeof name === "string" ? `, ${chunkD5CV3UF7_cjs.quote(name)},` : ""} is read.`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
const model = readRecipe(reader, node, found.path);
|
|
350
|
+
const parse = () => chunkD5CV3UF7_cjs.parseRecipeWith(model, chunkD5CV3UF7_cjs.parseIngredientLine);
|
|
351
|
+
let result = parse();
|
|
352
|
+
const dropped = [];
|
|
353
|
+
if (!result.success) {
|
|
354
|
+
const targets = [];
|
|
355
|
+
for (const error of result.error.issues) {
|
|
356
|
+
const target = error.severity === "error" ? droppableAt(reader, error.path) : void 0;
|
|
357
|
+
if (target) {
|
|
358
|
+
targets.push(target);
|
|
359
|
+
dropped.push({
|
|
360
|
+
...error,
|
|
361
|
+
severity: "warning",
|
|
362
|
+
message: `${error.message} It is ignored.`
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
targets.sort((a, b) => b.length - a.length).forEach((target) => remove(model, target));
|
|
367
|
+
if (dropped.length > 0) {
|
|
368
|
+
result = parse();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const order = ({ code, path }) => [
|
|
372
|
+
path.length > found.path.length ? READ_FIELDS.indexOf(String(path[found.path.length])) : -1,
|
|
373
|
+
...path.slice(found.path.length + 1).filter((key) => typeof key === "number"),
|
|
374
|
+
...code === "unknown_field" ? [Infinity] : []
|
|
375
|
+
];
|
|
376
|
+
const issues = [
|
|
377
|
+
...reader.warnings,
|
|
378
|
+
...[
|
|
379
|
+
...dropped,
|
|
380
|
+
...result.success ? result.warnings : result.error.issues
|
|
381
|
+
].map((issue2) => ({ ...issue2, path: sourceOf(reader, issue2.path) }))
|
|
382
|
+
].sort((a, b) => compareOrder(order(a), order(b)));
|
|
383
|
+
const checked = /* @__PURE__ */ new Map();
|
|
384
|
+
const extra = Object.fromEntries(
|
|
385
|
+
Object.entries(node).filter(([key, value]) => {
|
|
386
|
+
if (READ_FIELDS.includes(key)) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
const path = [...found.path, key];
|
|
390
|
+
if (tooDeep(value, path.length, checked)) {
|
|
391
|
+
chunkD5CV3UF7_cjs.warning(
|
|
392
|
+
issues,
|
|
393
|
+
"invalid_type",
|
|
394
|
+
path,
|
|
395
|
+
`Expected a value to keep in extra, received ${chunkD5CV3UF7_cjs.describeValue(value)} nested more than ${MAX_DEPTH} levels deep. It is ignored.`
|
|
396
|
+
);
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
return true;
|
|
400
|
+
})
|
|
401
|
+
);
|
|
402
|
+
return {
|
|
403
|
+
...chunkD5CV3UF7_cjs.toResult(
|
|
404
|
+
"invalid_recipe",
|
|
405
|
+
result.success ? result.data : void 0,
|
|
406
|
+
issues,
|
|
407
|
+
typeof name === "string" ? chunkD5CV3UF7_cjs.quote(name) : void 0
|
|
408
|
+
),
|
|
409
|
+
extra
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function tooDeep(value, length, checked) {
|
|
413
|
+
if (length > MAX_DEPTH) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
if (typeof value !== "object" || value === null || (checked.get(value) ?? 0) >= length) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const deep = Object.values(value).some(
|
|
420
|
+
(child) => tooDeep(child, length + 1, checked)
|
|
421
|
+
);
|
|
422
|
+
if (!deep) {
|
|
423
|
+
checked.set(value, length);
|
|
424
|
+
}
|
|
425
|
+
return deep;
|
|
426
|
+
}
|
|
427
|
+
function readRecipe(reader, node, base) {
|
|
428
|
+
const at = (...path) => [...base, ...path];
|
|
429
|
+
const model = {
|
|
430
|
+
name: text(node.name),
|
|
431
|
+
description: text(node.description)
|
|
432
|
+
};
|
|
433
|
+
link(reader, [], base);
|
|
434
|
+
link(reader, ["name"], at("name"));
|
|
435
|
+
link(reader, ["description"], at("description"), true);
|
|
436
|
+
const image = readImage(reader, node.image, at("image"));
|
|
437
|
+
if (image) {
|
|
438
|
+
model.image = image.url;
|
|
439
|
+
link(reader, ["image"], image.source, true);
|
|
440
|
+
}
|
|
441
|
+
readYield(reader, model, node.recipeYield, at("recipeYield"));
|
|
442
|
+
const times = {};
|
|
443
|
+
for (const [key, field] of TIMES) {
|
|
444
|
+
const minutes = readTime(reader, node[field], at(field));
|
|
445
|
+
if (minutes !== void 0) {
|
|
446
|
+
times[key] = minutes;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
model.times = times;
|
|
450
|
+
model.ingredients = readList(
|
|
451
|
+
reader,
|
|
452
|
+
"ingredients",
|
|
453
|
+
node.recipeIngredient,
|
|
454
|
+
at("recipeIngredient"),
|
|
455
|
+
readLine,
|
|
456
|
+
'an ingredient line such as "2 cups flour"'
|
|
457
|
+
);
|
|
458
|
+
model.steps = readList(
|
|
459
|
+
reader,
|
|
460
|
+
"steps",
|
|
461
|
+
node.recipeInstructions,
|
|
462
|
+
at("recipeInstructions"),
|
|
463
|
+
readStep,
|
|
464
|
+
'a step such as "Whisk the eggs." or a HowToStep'
|
|
465
|
+
);
|
|
466
|
+
model.equipment = readTools(reader, node.tool, at("tool"));
|
|
467
|
+
model.nutrition = readNutrition(reader, node.nutrition, at("nutrition"));
|
|
468
|
+
return model;
|
|
469
|
+
}
|
|
470
|
+
function readImage(reader, value, source) {
|
|
471
|
+
if (isAbsent(value)) {
|
|
472
|
+
return void 0;
|
|
473
|
+
}
|
|
474
|
+
const warnings = reader.warnings.length + reader.repeated;
|
|
475
|
+
const image = findImage(reader, value, source);
|
|
476
|
+
if (!image && reader.warnings.length + reader.repeated === warnings) {
|
|
477
|
+
ignore(
|
|
478
|
+
reader,
|
|
479
|
+
"invalid_type",
|
|
480
|
+
source,
|
|
481
|
+
`Expected an image URL, an ImageObject with a url, or a list of them, received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
return image;
|
|
485
|
+
}
|
|
486
|
+
function findImage(reader, value, source, referenced = false) {
|
|
487
|
+
reader.deepest = Math.max(reader.deepest, source.length);
|
|
488
|
+
if (source.length > MAX_DEPTH) {
|
|
489
|
+
if (!isAbsent(value)) {
|
|
490
|
+
ignore(
|
|
491
|
+
reader,
|
|
492
|
+
"invalid_type",
|
|
493
|
+
source,
|
|
494
|
+
`Expected an image URL, an ImageObject with a url, or a list of them, received ${chunkD5CV3UF7_cjs.describeValue(value)} nested more than ${MAX_DEPTH} levels deep.`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return void 0;
|
|
498
|
+
}
|
|
499
|
+
if (typeof value === "string") {
|
|
500
|
+
const url = cleanText(value);
|
|
501
|
+
const scheme = chunkD5CV3UF7_cjs.disallowedScheme(url);
|
|
502
|
+
if (scheme) {
|
|
503
|
+
ignore(
|
|
504
|
+
reader,
|
|
505
|
+
"invalid_url",
|
|
506
|
+
source,
|
|
507
|
+
`Expected an http or https URL, or a relative one, received ${chunkD5CV3UF7_cjs.quote(`${scheme}\u2026`)}.`
|
|
508
|
+
);
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
if (url.length > MAX_URL_LENGTH) {
|
|
512
|
+
ignore(
|
|
513
|
+
reader,
|
|
514
|
+
"invalid_url",
|
|
515
|
+
source,
|
|
516
|
+
`Expected a URL of at most ${MAX_URL_LENGTH} UTF-16 code units, received ${url.length}.`
|
|
517
|
+
);
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
return url ? { url, source } : void 0;
|
|
521
|
+
}
|
|
522
|
+
if (Array.isArray(value)) {
|
|
523
|
+
for (const [entry, entrySource] of entriesOf(value, source)) {
|
|
524
|
+
const image2 = findImage(reader, entry, entrySource, referenced);
|
|
525
|
+
if (image2) {
|
|
526
|
+
return image2;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return void 0;
|
|
530
|
+
}
|
|
531
|
+
const images = reader.images[referenced ? 1 : 0];
|
|
532
|
+
const reads = images.get(value) ?? [];
|
|
533
|
+
const read = reads.find(
|
|
534
|
+
([, , depth, length]) => length === source.length || Math.max(length, source.length) + depth <= MAX_DEPTH
|
|
535
|
+
);
|
|
536
|
+
if (read) {
|
|
537
|
+
reader.repeated += read[1];
|
|
538
|
+
reader.deepest = Math.max(reader.deepest, source.length + read[2]);
|
|
539
|
+
return read[0];
|
|
540
|
+
}
|
|
541
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
542
|
+
if (!record) {
|
|
543
|
+
return void 0;
|
|
544
|
+
}
|
|
545
|
+
images.set(value, reads);
|
|
546
|
+
const warnings = reader.warnings.length + reader.repeated;
|
|
547
|
+
const deepest = reader.deepest;
|
|
548
|
+
reader.deepest = source.length;
|
|
549
|
+
const id = record["@id"];
|
|
550
|
+
const target = typeof id === "string" && !referenced ? reader.ids.get(id) : void 0;
|
|
551
|
+
let image = findImage(reader, record.url, [...source, "url"], referenced) ?? findImage(reader, record.contentUrl, [...source, "contentUrl"], referenced);
|
|
552
|
+
if (!image && target?.node === reader.recipe) {
|
|
553
|
+
ignore(
|
|
554
|
+
reader,
|
|
555
|
+
"invalid_type",
|
|
556
|
+
source,
|
|
557
|
+
"Expected an image URL, an ImageObject with a url, or a list of them, received a reference to the recipe itself."
|
|
558
|
+
);
|
|
559
|
+
} else if (!image && target) {
|
|
560
|
+
image = findImage(reader, target.node, target.path, true);
|
|
561
|
+
}
|
|
562
|
+
reads.push([
|
|
563
|
+
image,
|
|
564
|
+
reader.warnings.length + reader.repeated - warnings,
|
|
565
|
+
reader.deepest - source.length,
|
|
566
|
+
source.length
|
|
567
|
+
]);
|
|
568
|
+
reader.deepest = Math.max(reader.deepest, deepest);
|
|
569
|
+
return image;
|
|
570
|
+
}
|
|
571
|
+
function readYield(reader, model, value, source) {
|
|
572
|
+
const batches = entriesOf(value, source).flatMap(
|
|
573
|
+
([entry, entrySource]) => (typeof entry === "string" ? entry.split(/,\s+/) : [entry]).map(
|
|
574
|
+
(part) => readBatch(reader, part, entrySource)
|
|
575
|
+
)
|
|
576
|
+
).filter((batch) => batch !== void 0);
|
|
577
|
+
const yields = batches.filter((batch) => batch.yield !== void 0);
|
|
578
|
+
const servings = batches.filter((batch) => batch.servings !== void 0);
|
|
579
|
+
const kept = yields.find((batch) => !batch.article) ?? yields.find((batch) => batch.article);
|
|
580
|
+
const keptServings = servings.find((batch) => batch.worded) ?? servings.find((batch) => batch.amount !== kept?.amount);
|
|
581
|
+
for (const batch of servings) {
|
|
582
|
+
if (keptServings && batch.amount !== keptServings.amount && (batch.worded || batch.amount !== kept?.amount)) {
|
|
583
|
+
chunkD5CV3UF7_cjs.warning(
|
|
584
|
+
reader.warnings,
|
|
585
|
+
"ignored_value",
|
|
586
|
+
batch.source,
|
|
587
|
+
`Only one number of servings is read, ${chunkD5CV3UF7_cjs.quote(keptServings.label)}, so this one is ignored.`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const batch of yields) {
|
|
592
|
+
if (batch !== kept) {
|
|
593
|
+
chunkD5CV3UF7_cjs.warning(
|
|
594
|
+
reader.warnings,
|
|
595
|
+
"ignored_value",
|
|
596
|
+
batch.source,
|
|
597
|
+
`Only one yield is read, ${chunkD5CV3UF7_cjs.quote(kept.label)}, so this one is ignored.`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (keptServings) {
|
|
602
|
+
model.servings = keptServings.servings;
|
|
603
|
+
link(reader, ["servings"], keptServings.source, true);
|
|
604
|
+
}
|
|
605
|
+
if (kept) {
|
|
606
|
+
model.yield = kept.yield;
|
|
607
|
+
link(reader, ["yield"], kept.source, true);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function readBatch(reader, value, source) {
|
|
611
|
+
if (typeof value === "number") {
|
|
612
|
+
return { source, label: String(value), amount: value, servings: value };
|
|
613
|
+
}
|
|
614
|
+
if (typeof value === "string") {
|
|
615
|
+
const label = cleanText(value);
|
|
616
|
+
if (!label) {
|
|
617
|
+
return void 0;
|
|
618
|
+
}
|
|
619
|
+
const text2 = label.replace(
|
|
620
|
+
/^(?:(?:serves|servings?|yields?|makes)\s*:?\s*)?(?:about\s+)?/i,
|
|
621
|
+
""
|
|
622
|
+
);
|
|
623
|
+
const match = chunkD5CV3UF7_cjs.matchAmount(text2);
|
|
624
|
+
if (!match || "code" in match) {
|
|
625
|
+
ignore(
|
|
626
|
+
reader,
|
|
627
|
+
match?.code ?? "invalid_quantity",
|
|
628
|
+
source,
|
|
629
|
+
match?.message ?? `Expected a batch such as "24 cookies" or "3\u20134 loaves", received ${chunkD5CV3UF7_cjs.describeValue(label)}.`
|
|
630
|
+
);
|
|
631
|
+
return void 0;
|
|
632
|
+
}
|
|
633
|
+
const amount2 = typeof match.amount === "number" ? match.amount : match.amount.min;
|
|
634
|
+
const rest = match.rest.trim();
|
|
635
|
+
return !rest || SERVINGS_WORD.test(rest) ? { source, label, amount: amount2, servings: match.amountText, worded: !!rest } : { source, label, amount: amount2, yield: text2, article: /^an?\s/i.test(text2) };
|
|
636
|
+
}
|
|
637
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
638
|
+
const amount = toNumber(record?.value ?? record?.minValue);
|
|
639
|
+
if (record && amount !== void 0) {
|
|
640
|
+
const max = toNumber(record.maxValue);
|
|
641
|
+
const unit = typeof record.unitText === "string" ? cleanText(record.unitText) : "";
|
|
642
|
+
const range = {
|
|
643
|
+
amount,
|
|
644
|
+
...max !== void 0 && max !== amount && { max }
|
|
645
|
+
};
|
|
646
|
+
const label = `${amount}${max !== amount && max ? `\u2013${max}` : ""}${unit && ` ${unit}`}`;
|
|
647
|
+
return !unit || SERVINGS_WORD.test(unit) ? { source, label, amount, servings: range, worded: !!unit } : { source, label, amount, yield: { ...range, unit } };
|
|
648
|
+
}
|
|
649
|
+
if (!isAbsent(value)) {
|
|
650
|
+
ignore(
|
|
651
|
+
reader,
|
|
652
|
+
"invalid_type",
|
|
653
|
+
source,
|
|
654
|
+
`Expected a yield such as "4 servings" or "24 cookies", received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
return void 0;
|
|
658
|
+
}
|
|
659
|
+
function readTime(reader, value, source) {
|
|
660
|
+
if (isAbsent(value)) {
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
const minutes = typeof value === "string" ? chunkD5CV3UF7_cjs.parseIsoDuration(cleanText(value)) : void 0;
|
|
664
|
+
if (minutes === void 0 || minutes > Number.MAX_SAFE_INTEGER) {
|
|
665
|
+
ignore(
|
|
666
|
+
reader,
|
|
667
|
+
"invalid_duration",
|
|
668
|
+
source,
|
|
669
|
+
`Expected an ISO 8601 duration such as "PT30M", received ${chunkD5CV3UF7_cjs.describeValue(value)}${minutes === void 0 ? "" : ", which is too long"}.`
|
|
670
|
+
);
|
|
671
|
+
return void 0;
|
|
672
|
+
}
|
|
673
|
+
return minutes > 0 ? minutes : void 0;
|
|
674
|
+
}
|
|
675
|
+
function readList(reader, key, value, source, readItem, expected) {
|
|
676
|
+
const list = [];
|
|
677
|
+
link(reader, [key], source);
|
|
678
|
+
const add = (items, path, entry, entrySource, inSection) => {
|
|
679
|
+
if (isAbsent(entry)) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (entrySource.length > MAX_DEPTH) {
|
|
683
|
+
ignore(
|
|
684
|
+
reader,
|
|
685
|
+
"invalid_type",
|
|
686
|
+
entrySource,
|
|
687
|
+
`Expected ${expected}, received ${chunkD5CV3UF7_cjs.describeValue(entry)} nested more than ${MAX_DEPTH} levels deep.`
|
|
688
|
+
);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (Array.isArray(entry)) {
|
|
692
|
+
const elements2 = entriesOf(entry, entrySource);
|
|
693
|
+
const positions = elements2.map(
|
|
694
|
+
([element]) => toNumber(chunkD5CV3UF7_cjs.asRecord(element)?.position)
|
|
695
|
+
);
|
|
696
|
+
const order = [...elements2.keys()];
|
|
697
|
+
if (!positions.includes(void 0)) {
|
|
698
|
+
order.sort((a, b) => positions[a] - positions[b]);
|
|
699
|
+
}
|
|
700
|
+
for (const i of order) {
|
|
701
|
+
const [element, elementSource] = elements2[i];
|
|
702
|
+
add(items, path, element, elementSource, inSection);
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const record = chunkD5CV3UF7_cjs.asRecord(entry);
|
|
707
|
+
const elements = [...entrySource, "itemListElement"];
|
|
708
|
+
if (record?.itemListElement !== void 0 && record.text === void 0) {
|
|
709
|
+
const title = typeof record.name === "string" ? cleanText(record.name) : "";
|
|
710
|
+
if (title && !inSection) {
|
|
711
|
+
const section = { title, items: [] };
|
|
712
|
+
const sectionPath = [key, list.length];
|
|
713
|
+
list.push(section);
|
|
714
|
+
link(reader, sectionPath, entrySource);
|
|
715
|
+
link(reader, [...sectionPath, "title"], [...entrySource, "name"]);
|
|
716
|
+
add(
|
|
717
|
+
section.items,
|
|
718
|
+
[...sectionPath, "items"],
|
|
719
|
+
record.itemListElement,
|
|
720
|
+
elements,
|
|
721
|
+
true
|
|
722
|
+
);
|
|
723
|
+
} else {
|
|
724
|
+
add(items, path, record.itemListElement, elements, inSection);
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (record && !isAbsent(record.item)) {
|
|
729
|
+
add(items, path, record.item, [...entrySource, "item"], inSection);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const read = readItem(reader, entry, entrySource);
|
|
733
|
+
if (!read) {
|
|
734
|
+
ignore(
|
|
735
|
+
reader,
|
|
736
|
+
"invalid_type",
|
|
737
|
+
entrySource,
|
|
738
|
+
`Expected ${expected}, received ${chunkD5CV3UF7_cjs.describeValue(entry)}.`
|
|
739
|
+
);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
for (const item of read) {
|
|
743
|
+
const itemPath = [...path, items.length];
|
|
744
|
+
items.push(item.value);
|
|
745
|
+
link(reader, itemPath, item.source, true);
|
|
746
|
+
for (const [field, fieldSource, droppable] of item.fields) {
|
|
747
|
+
link(reader, [...itemPath, field], fieldSource, droppable);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
add(list, [key], value, source, false);
|
|
752
|
+
return list;
|
|
753
|
+
}
|
|
754
|
+
function readLine(reader, value, source) {
|
|
755
|
+
if (typeof value === "string") {
|
|
756
|
+
return cleanLines(value).map((line) => ({
|
|
757
|
+
value: line,
|
|
758
|
+
source,
|
|
759
|
+
fields: []
|
|
760
|
+
}));
|
|
761
|
+
}
|
|
762
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
763
|
+
const parts = [record?.value, record?.unitText, record?.name ?? record?.text].filter((part) => typeof part === "string" || typeof part === "number").join(" ");
|
|
764
|
+
return parts ? cleanLines(parts).map((line) => ({ value: line, source, fields: [] })) : void 0;
|
|
765
|
+
}
|
|
766
|
+
function readStep(reader, value, source) {
|
|
767
|
+
if (typeof value === "string") {
|
|
768
|
+
return cleanLines(value).map((line) => ({
|
|
769
|
+
value: line,
|
|
770
|
+
source,
|
|
771
|
+
fields: []
|
|
772
|
+
}));
|
|
773
|
+
}
|
|
774
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
775
|
+
if (!record) {
|
|
776
|
+
return void 0;
|
|
777
|
+
}
|
|
778
|
+
const name = typeof record.name === "string" ? cleanText(record.name) : "";
|
|
779
|
+
const body = text(record.text) ?? name;
|
|
780
|
+
const image = readImage(reader, record.image, [...source, "image"]);
|
|
781
|
+
const lead = name.replace(/(?:…|\.\.\.)$/, "").trim();
|
|
782
|
+
const hasName = typeof body === "string" && !body.startsWith(lead) && !/^step\s*\d*[.:]?$/i.test(name);
|
|
783
|
+
const fields = [
|
|
784
|
+
["text", [...source, name && body === name ? "name" : "text"], false]
|
|
785
|
+
];
|
|
786
|
+
if (image) {
|
|
787
|
+
fields.push(["image", image.source, true]);
|
|
788
|
+
}
|
|
789
|
+
return [
|
|
790
|
+
{
|
|
791
|
+
value: {
|
|
792
|
+
text: body,
|
|
793
|
+
...hasName && { name },
|
|
794
|
+
...image && { image: image.url }
|
|
795
|
+
},
|
|
796
|
+
source,
|
|
797
|
+
fields
|
|
798
|
+
}
|
|
799
|
+
];
|
|
800
|
+
}
|
|
801
|
+
function readTools(reader, value, source) {
|
|
802
|
+
const equipment = [];
|
|
803
|
+
link(reader, ["equipment"], source);
|
|
804
|
+
for (const [entry, entrySource] of entriesOf(value, source)) {
|
|
805
|
+
const record = chunkD5CV3UF7_cjs.asRecord(entry);
|
|
806
|
+
const item = record ? {
|
|
807
|
+
name: text(record.name),
|
|
808
|
+
...record.requiredQuantity !== void 0 && {
|
|
809
|
+
quantity: toNumber(
|
|
810
|
+
chunkD5CV3UF7_cjs.asRecord(record.requiredQuantity)?.value ?? record.requiredQuantity
|
|
811
|
+
) ?? record.requiredQuantity
|
|
812
|
+
},
|
|
813
|
+
...record.url !== void 0 && { url: text(record.url) }
|
|
814
|
+
} : text(entry);
|
|
815
|
+
if (item === void 0) {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const path = ["equipment", equipment.length];
|
|
819
|
+
equipment.push(item);
|
|
820
|
+
link(reader, path, entrySource, true);
|
|
821
|
+
link(reader, [...path, "name"], [...entrySource, "name"]);
|
|
822
|
+
link(
|
|
823
|
+
reader,
|
|
824
|
+
[...path, "quantity"],
|
|
825
|
+
[...entrySource, "requiredQuantity"],
|
|
826
|
+
true
|
|
827
|
+
);
|
|
828
|
+
link(reader, [...path, "url"], [...entrySource, "url"], true);
|
|
829
|
+
}
|
|
830
|
+
return equipment;
|
|
831
|
+
}
|
|
832
|
+
function readNutrition(reader, value, source) {
|
|
833
|
+
link(reader, ["nutrition"], source);
|
|
834
|
+
if (isAbsent(value)) {
|
|
835
|
+
return void 0;
|
|
836
|
+
}
|
|
837
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
838
|
+
if (!record) {
|
|
839
|
+
ignore(
|
|
840
|
+
reader,
|
|
841
|
+
"invalid_type",
|
|
842
|
+
source,
|
|
843
|
+
`Expected NutritionInformation, such as { "calories": "250 kcal" }, received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
844
|
+
);
|
|
845
|
+
return void 0;
|
|
846
|
+
}
|
|
847
|
+
const nutrition = {};
|
|
848
|
+
const keys = Object.keys(NUTRIENTS);
|
|
849
|
+
for (const [field, amount] of Object.entries(record)) {
|
|
850
|
+
const key = keys.find((key2) => NUTRIENTS[key2] === field);
|
|
851
|
+
if (key) {
|
|
852
|
+
nutrition[key] = text(amount);
|
|
853
|
+
link(reader, ["nutrition", key], [...source, field], true);
|
|
854
|
+
} else if (!field.startsWith("@")) {
|
|
855
|
+
ignore(reader, "unknown_field", [...source, field], "Unknown field.");
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return nutrition;
|
|
859
|
+
}
|
|
860
|
+
function text(value) {
|
|
861
|
+
return typeof value === "string" ? cleanText(value) || void 0 : value ?? void 0;
|
|
862
|
+
}
|
|
863
|
+
function toNumber(value) {
|
|
864
|
+
const number = typeof value === "string" && value.trim() ? Number(value) : value;
|
|
865
|
+
return typeof number === "number" && Number.isFinite(number) ? number : void 0;
|
|
866
|
+
}
|
|
867
|
+
function isAbsent(value) {
|
|
868
|
+
return value === void 0 || value === null || typeof value === "string" && !value.trim();
|
|
869
|
+
}
|
|
870
|
+
function ignore(reader, code, path, message) {
|
|
871
|
+
chunkD5CV3UF7_cjs.warning(reader.warnings, code, path, `${message} It is ignored.`);
|
|
872
|
+
}
|
|
873
|
+
function link(reader, path, source, droppable = false) {
|
|
874
|
+
const key = JSON.stringify(path);
|
|
875
|
+
reader.sources.set(key, source);
|
|
876
|
+
if (droppable) {
|
|
877
|
+
reader.droppable.add(key);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function sourceOf(reader, path) {
|
|
881
|
+
for (let n = path.length; n >= 0; n--) {
|
|
882
|
+
const source = reader.sources.get(JSON.stringify(path.slice(0, n)));
|
|
883
|
+
if (source) {
|
|
884
|
+
return source;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return [];
|
|
888
|
+
}
|
|
889
|
+
function compareOrder(a, b) {
|
|
890
|
+
for (const [i, n] of a.entries()) {
|
|
891
|
+
if (i === b.length) {
|
|
892
|
+
return 1;
|
|
893
|
+
}
|
|
894
|
+
const m = b[i];
|
|
895
|
+
if (n !== m) {
|
|
896
|
+
return n - m;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return a.length - b.length;
|
|
900
|
+
}
|
|
901
|
+
function droppableAt(reader, path) {
|
|
902
|
+
for (let n = path.length; n > 0; n--) {
|
|
903
|
+
const at = path.slice(0, n);
|
|
904
|
+
if (reader.droppable.has(JSON.stringify(at))) {
|
|
905
|
+
return at;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return void 0;
|
|
909
|
+
}
|
|
910
|
+
function remove(model, path) {
|
|
911
|
+
const parent = path.slice(0, -1).reduce(
|
|
912
|
+
(value, key) => value?.[key],
|
|
913
|
+
model
|
|
914
|
+
);
|
|
915
|
+
if (parent) {
|
|
916
|
+
delete parent[path[path.length - 1]];
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/schema-org/to-json-ld.ts
|
|
921
|
+
function toJsonLd(recipe, options) {
|
|
922
|
+
const { servings, system, url, extra } = options ?? {};
|
|
923
|
+
const resolved = chunkD5CV3UF7_cjs.resolveRecipe(recipe, { servings, system });
|
|
924
|
+
const page = typeof url === "string" ? url.replace(/#.*/s, "") : void 0;
|
|
925
|
+
const yields = [
|
|
926
|
+
...resolved.servings ? servingsText(resolved.servings) : [],
|
|
927
|
+
...resolved.yield ? [chunkD5CV3UF7_cjs.formatYield(resolved.yield, { fractions: false })] : []
|
|
928
|
+
];
|
|
929
|
+
const ingredients = resolved.ingredients.flatMap(
|
|
930
|
+
({ items }) => items.map((item) => ingredientLine(item, resolved.scale, resolved.system))
|
|
931
|
+
);
|
|
932
|
+
const steps = resolved.steps.flatMap(
|
|
933
|
+
({ title, items }) => {
|
|
934
|
+
const howToSteps = items.map((step) => ({
|
|
935
|
+
"@type": "HowToStep",
|
|
936
|
+
...step.name !== void 0 && { name: step.name },
|
|
937
|
+
text: step.text,
|
|
938
|
+
...step.image !== void 0 && { image: step.image },
|
|
939
|
+
...page !== void 0 && {
|
|
940
|
+
// encodeURIComponent throws on a lone surrogate. With the u flag,
|
|
941
|
+
// \p{Cs} matches only those, as a pair reads as one code point.
|
|
942
|
+
url: `${page}#${encodeURIComponent(step.id.replace(/\p{Cs}/gu, "\uFFFD"))}`
|
|
943
|
+
}
|
|
944
|
+
}));
|
|
945
|
+
return title === void 0 || howToSteps.length === 0 ? howToSteps : [
|
|
946
|
+
{
|
|
947
|
+
"@type": "HowToSection",
|
|
948
|
+
name: title,
|
|
949
|
+
itemListElement: howToSteps
|
|
950
|
+
}
|
|
951
|
+
];
|
|
952
|
+
}
|
|
953
|
+
);
|
|
954
|
+
const { description, image, times, equipment, nutrition } = recipe;
|
|
955
|
+
const warnings = [];
|
|
956
|
+
const written = {
|
|
957
|
+
"@context": "https://schema.org",
|
|
958
|
+
"@type": "Recipe",
|
|
959
|
+
name: recipe.name,
|
|
960
|
+
...description !== void 0 && { description },
|
|
961
|
+
...image !== void 0 && { image },
|
|
962
|
+
...yields.length > 0 && { recipeYield: yields.map(plainSpaces) },
|
|
963
|
+
...duration(warnings, "prep", times.prep),
|
|
964
|
+
...duration(warnings, "cook", times.cook),
|
|
965
|
+
...duration(warnings, "total", times.total),
|
|
966
|
+
...ingredients.length > 0 && { recipeIngredient: ingredients },
|
|
967
|
+
...steps.length > 0 && { recipeInstructions: steps },
|
|
968
|
+
...equipment.length > 0 && { tool: equipment.map(({ name }) => name) },
|
|
969
|
+
...nutrition !== void 0 && {
|
|
970
|
+
nutrition: nutritionInformation(nutrition)
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
const data = {
|
|
974
|
+
...written,
|
|
975
|
+
...Object.fromEntries(
|
|
976
|
+
Object.entries(chunkD5CV3UF7_cjs.isRecord(extra) ? extra : {}).filter(
|
|
977
|
+
([field, value]) => value !== void 0 && !chunkD5CV3UF7_cjs.hasOwn(written, field)
|
|
978
|
+
)
|
|
979
|
+
)
|
|
980
|
+
};
|
|
981
|
+
if (data.image === void 0) {
|
|
982
|
+
chunkD5CV3UF7_cjs.warning(
|
|
983
|
+
warnings,
|
|
984
|
+
"missing_field",
|
|
985
|
+
["image"],
|
|
986
|
+
"Add an image. Google requires one for recipe results."
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
if (data.nutrition !== void 0 && data.recipeYield === void 0) {
|
|
990
|
+
chunkD5CV3UF7_cjs.warning(
|
|
991
|
+
warnings,
|
|
992
|
+
"missing_field",
|
|
993
|
+
["recipeYield"],
|
|
994
|
+
"Add servings or a yield. Google requires one with nutrition, which is for one serving."
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
return { data, warnings };
|
|
998
|
+
}
|
|
999
|
+
function servingsText(servings) {
|
|
1000
|
+
const amount = chunkD5CV3UF7_cjs.formatServings(servings, { fractions: false });
|
|
1001
|
+
const word = servings.amount === 1 && servings.max === void 0 ? "serving" : "servings";
|
|
1002
|
+
return [amount, `${amount} ${word}`];
|
|
1003
|
+
}
|
|
1004
|
+
function ingredientLine(item, scale, system) {
|
|
1005
|
+
const { ingredient, origin } = item;
|
|
1006
|
+
const { quantity, name, note } = chunkD5CV3UF7_cjs.formatIngredient(item);
|
|
1007
|
+
const authored = ingredient.quantity ? [ingredient.quantity, ...ingredient.equivalents] : [];
|
|
1008
|
+
const shown = origin === "authored" ? 0 : origin === "equivalent" ? 1 + ingredient.equivalents.findIndex((q) => chunkD5CV3UF7_cjs.isNative(q, system)) : -1;
|
|
1009
|
+
const alternatives = authored.filter((_, i) => i !== shown).map((q) => chunkD5CV3UF7_cjs.formatQuantity(scale === 1 ? q : chunkD5CV3UF7_cjs.multiply(q, scale))).join("; ");
|
|
1010
|
+
const inParentheses = alternatives && ` (${alternatives})`;
|
|
1011
|
+
const head = quantity === void 0 ? name : item.quantity?.unit === void 0 ? `${quantity} ${name}${inParentheses}` : `${quantity}${inParentheses} ${name}`;
|
|
1012
|
+
return plainSpaces(
|
|
1013
|
+
`${head}${note ? `, ${note}` : ""}${ingredient.optional ? " (optional)" : ""}`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
function duration(warnings, key, minutes) {
|
|
1017
|
+
if (minutes === void 0) {
|
|
1018
|
+
return {};
|
|
1019
|
+
}
|
|
1020
|
+
if (Number.isNaN(minutes) || minutes > Number.MAX_SAFE_INTEGER) {
|
|
1021
|
+
chunkD5CV3UF7_cjs.warning(
|
|
1022
|
+
warnings,
|
|
1023
|
+
"invalid_duration",
|
|
1024
|
+
["times", key],
|
|
1025
|
+
`Expected minutes such as 30, received ${chunkD5CV3UF7_cjs.describeValue(minutes)}${Number.isNaN(minutes) ? "" : ", which is too long"}. It is ignored.`
|
|
1026
|
+
);
|
|
1027
|
+
return {};
|
|
1028
|
+
}
|
|
1029
|
+
const seconds = Math.round(minutes * 60);
|
|
1030
|
+
if (seconds <= 0) {
|
|
1031
|
+
return {};
|
|
1032
|
+
}
|
|
1033
|
+
const parts = [
|
|
1034
|
+
[Math.floor(seconds / 3600), "H"],
|
|
1035
|
+
[Math.floor(seconds / 60) % 60, "M"],
|
|
1036
|
+
[seconds % 60, "S"]
|
|
1037
|
+
];
|
|
1038
|
+
return {
|
|
1039
|
+
[`${key}Time`]: `PT${parts.map(([n, unit]) => n ? `${n}${unit}` : "").join("")}`
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function nutritionInformation(nutrition) {
|
|
1043
|
+
const information = {
|
|
1044
|
+
"@type": "NutritionInformation"
|
|
1045
|
+
};
|
|
1046
|
+
for (const key of Object.keys(NUTRIENTS)) {
|
|
1047
|
+
const value = nutrition[key];
|
|
1048
|
+
if (value !== void 0) {
|
|
1049
|
+
information[NUTRIENTS[key]] = typeof value === "string" ? value : `${chunkD5CV3UF7_cjs.round(value, 3)} ${chunkD5CV3UF7_cjs.NUTRIENT_UNITS[key]}`;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return information;
|
|
1053
|
+
}
|
|
1054
|
+
function plainSpaces(text2) {
|
|
1055
|
+
return text2.replace(/\u00A0/g, " ");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
exports.findRecipeJsonLd = findRecipeJsonLd;
|
|
1059
|
+
exports.fromJsonLd = fromJsonLd;
|
|
1060
|
+
exports.toJsonLd = toJsonLd;
|
|
1061
|
+
//# sourceMappingURL=schema-org.cjs.map
|
|
1062
|
+
//# sourceMappingURL=schema-org.cjs.map
|