@recipe-book/core 0.1.0 → 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 +103 -0
- package/README.md +1 -1
- package/dist/{chunk-VWVIBCTG.cjs → chunk-D5CV3UF7.cjs} +188 -173
- package/dist/chunk-D5CV3UF7.cjs.map +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/format/number.js +1 -1
- package/dist/format/number.js.map +1 -1
- package/dist/format/options.js.map +1 -1
- package/dist/index.cjs +83 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -19
- package/dist/index.d.ts +35 -19
- package/dist/ingredient/parse.js +27 -22
- package/dist/ingredient/parse.js.map +1 -1
- package/dist/issues.js.map +1 -1
- package/dist/quantity/amount.js +11 -0
- package/dist/quantity/amount.js.map +1 -1
- package/dist/quantity/parse.js +58 -81
- package/dist/quantity/parse.js.map +1 -1
- package/dist/quantity/scale.js +5 -2
- package/dist/quantity/scale.js.map +1 -1
- package/dist/recipe/parse.js +70 -55
- package/dist/recipe/parse.js.map +1 -1
- package/dist/recipe/schema.js +8 -6
- package/dist/recipe/schema.js.map +1 -1
- package/dist/resolve.js +6 -1
- package/dist/resolve.js.map +1 -1
- package/dist/schema-org/find.js +46 -13
- package/dist/schema-org/find.js.map +1 -1
- package/dist/schema-org/from-json-ld.js +127 -57
- package/dist/schema-org/from-json-ld.js.map +1 -1
- package/dist/schema-org/text.js.map +1 -1
- package/dist/schema-org/to-json-ld.js +12 -15
- package/dist/schema-org/to-json-ld.js.map +1 -1
- package/dist/schema-org.cjs +231 -132
- package/dist/schema-org.cjs.map +1 -1
- package/dist/schema-org.d.cts +32 -19
- package/dist/schema-org.d.ts +32 -19
- package/dist/steps/temperature.js +13 -13
- package/dist/steps/temperature.js.map +1 -1
- package/dist/{types-DSMf45H5.d.cts → types-BknM8KOd.d.cts} +4 -3
- package/dist/{types-DSMf45H5.d.ts → types-BknM8KOd.d.ts} +4 -3
- package/dist/units/convert.js +16 -8
- package/dist/units/convert.js.map +1 -1
- package/dist/units/temperature.js +7 -1
- package/dist/units/temperature.js.map +1 -1
- package/dist/validation.js +1 -1
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-VWVIBCTG.cjs.map +0 -1
package/dist/schema-org.cjs
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkD5CV3UF7_cjs = require('./chunk-D5CV3UF7.cjs');
|
|
4
4
|
|
|
5
5
|
// src/schema-org/find.ts
|
|
6
6
|
var MAX_DEPTH = 32;
|
|
7
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) {
|
|
8
12
|
const recipes = [];
|
|
9
13
|
const ids = /* @__PURE__ */ new Map();
|
|
10
14
|
const recipeIds = /* @__PURE__ */ new Map();
|
|
11
|
-
const visit = (value, path,
|
|
15
|
+
const visit = (value, path, reach = "any") => {
|
|
12
16
|
if (path.length > MAX_DEPTH) {
|
|
13
17
|
return;
|
|
14
18
|
}
|
|
15
19
|
if (Array.isArray(value)) {
|
|
16
|
-
value
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
for (const [entry, entryPath] of entriesOf(value, path)) {
|
|
21
|
+
visit(entry, entryPath, reach);
|
|
22
|
+
}
|
|
19
23
|
return;
|
|
20
24
|
}
|
|
21
|
-
const fields =
|
|
25
|
+
const fields = chunkD5CV3UF7_cjs.asRecord(value);
|
|
22
26
|
if (!fields) {
|
|
23
27
|
return;
|
|
24
28
|
}
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
}
|
|
30
37
|
return;
|
|
31
38
|
}
|
|
32
39
|
const located = { node: value, path };
|
|
@@ -47,7 +54,10 @@ function locate(documents) {
|
|
|
47
54
|
}
|
|
48
55
|
} else {
|
|
49
56
|
visit(fields["@graph"], [...path, "@graph"]);
|
|
50
|
-
visit(fields.mainEntity, [...path, "mainEntity"],
|
|
57
|
+
visit(fields.mainEntity, [...path, "mainEntity"], "mainEntity");
|
|
58
|
+
if (isList) {
|
|
59
|
+
visit(fields.itemListElement, [...path, "itemListElement"], "entry");
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
};
|
|
53
63
|
for (const document of documents) {
|
|
@@ -55,14 +65,36 @@ function locate(documents) {
|
|
|
55
65
|
}
|
|
56
66
|
return { recipes, ids };
|
|
57
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
|
+
}
|
|
58
78
|
function hasContent(node) {
|
|
59
|
-
return Object.keys(node).some((key) => key
|
|
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;
|
|
60
92
|
}
|
|
61
93
|
function findRecipeJsonLd(html) {
|
|
62
94
|
if (typeof html !== "string") {
|
|
63
|
-
throw new
|
|
95
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
64
96
|
"invalid_argument",
|
|
65
|
-
`findRecipeJsonLd: html must be a string, such as await response.text(), received ${
|
|
97
|
+
`findRecipeJsonLd: html must be a string, such as await response.text(), received ${chunkD5CV3UF7_cjs.describeValue(html)}.`
|
|
66
98
|
);
|
|
67
99
|
}
|
|
68
100
|
const documents = [];
|
|
@@ -85,7 +117,7 @@ function findRecipeJsonLd(html) {
|
|
|
85
117
|
}
|
|
86
118
|
function withImages(node, ids) {
|
|
87
119
|
const follow = (value) => {
|
|
88
|
-
const id =
|
|
120
|
+
const id = chunkD5CV3UF7_cjs.isRecord(value) && Object.keys(value).join() === "@id" && value["@id"];
|
|
89
121
|
const target = typeof id === "string" && ids.get(id)?.node;
|
|
90
122
|
return target && target !== node ? target : value;
|
|
91
123
|
};
|
|
@@ -97,7 +129,7 @@ function withImages(node, ids) {
|
|
|
97
129
|
if (Array.isArray(value)) {
|
|
98
130
|
return value.map((entry) => copy(entry, depth + 1, keys));
|
|
99
131
|
}
|
|
100
|
-
const fields =
|
|
132
|
+
const fields = chunkD5CV3UF7_cjs.asRecord(value);
|
|
101
133
|
if (!fields) {
|
|
102
134
|
return value;
|
|
103
135
|
}
|
|
@@ -231,11 +263,11 @@ function cleanLines(text2) {
|
|
|
231
263
|
const code = decimal === void 0 ? parseInt(hex, 16) : +decimal;
|
|
232
264
|
return code > 0 && code <= 1114111 && (code < 55296 || code > 57343) ? String.fromCodePoint(code) : entity;
|
|
233
265
|
}
|
|
234
|
-
if (
|
|
266
|
+
if (chunkD5CV3UF7_cjs.hasOwn(NAMED_ENTITIES, name)) {
|
|
235
267
|
return NAMED_ENTITIES[name];
|
|
236
268
|
}
|
|
237
269
|
const accent = /^([aceinouyACEINOUY])([a-z]+)$/.exec(name);
|
|
238
|
-
return accent &&
|
|
270
|
+
return accent && chunkD5CV3UF7_cjs.hasOwn(MARKS, accent[2]) ? `${accent[1]}${MARKS[accent[2]]}`.normalize("NFC") : entity;
|
|
239
271
|
}
|
|
240
272
|
).split(/[\n\r\u2028\u2029]/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
241
273
|
}
|
|
@@ -276,50 +308,54 @@ var TIMES = [
|
|
|
276
308
|
["cook", "cookTime"],
|
|
277
309
|
["total", "totalTime"]
|
|
278
310
|
];
|
|
311
|
+
var MAX_URL_LENGTH = 4096;
|
|
279
312
|
var SERVINGS_WORD = /^(?:servings?|portions?|(?:serving|portion)\(s\)|people|persons?)\.?$/i;
|
|
280
313
|
function fromJsonLd(input) {
|
|
281
314
|
const { recipes, ids } = locate([input]);
|
|
282
315
|
const [found] = recipes;
|
|
283
316
|
if (!found) {
|
|
284
317
|
return {
|
|
285
|
-
...
|
|
286
|
-
|
|
318
|
+
...chunkD5CV3UF7_cjs.toResult("invalid_recipe", void 0, [
|
|
319
|
+
chunkD5CV3UF7_cjs.issue(
|
|
287
320
|
"error",
|
|
288
321
|
"invalid_type",
|
|
289
322
|
[],
|
|
290
|
-
`Expected a schema.org Recipe, or an array, @graph, or
|
|
323
|
+
`Expected a schema.org Recipe, or an array, @graph, mainEntity, or ItemList that holds one, received ${chunkD5CV3UF7_cjs.describeValue(input)}.`
|
|
291
324
|
)
|
|
292
325
|
]),
|
|
293
326
|
extra: {}
|
|
294
327
|
};
|
|
295
328
|
}
|
|
296
|
-
const node =
|
|
329
|
+
const node = chunkD5CV3UF7_cjs.asRecord(found.node);
|
|
297
330
|
const reader = {
|
|
298
331
|
recipe: found.node,
|
|
299
332
|
ids,
|
|
300
333
|
warnings: [],
|
|
301
334
|
sources: /* @__PURE__ */ new Map(),
|
|
302
335
|
droppable: /* @__PURE__ */ new Set(),
|
|
303
|
-
images: /* @__PURE__ */ new Map()
|
|
336
|
+
images: [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()],
|
|
337
|
+
repeated: 0,
|
|
338
|
+
deepest: 0
|
|
304
339
|
};
|
|
305
340
|
const name = text(node.name);
|
|
306
341
|
if (recipes.length > 1) {
|
|
307
|
-
|
|
342
|
+
chunkD5CV3UF7_cjs.warning(
|
|
308
343
|
reader.warnings,
|
|
309
344
|
"ignored_value",
|
|
310
345
|
[],
|
|
311
|
-
`Found ${recipes.length} recipes. Only the first${typeof name === "string" ? `, ${
|
|
346
|
+
`Found ${recipes.length} recipes. Only the first${typeof name === "string" ? `, ${chunkD5CV3UF7_cjs.quote(name)},` : ""} is read.`
|
|
312
347
|
);
|
|
313
348
|
}
|
|
314
349
|
const model = readRecipe(reader, node, found.path);
|
|
315
|
-
const parse = () =>
|
|
350
|
+
const parse = () => chunkD5CV3UF7_cjs.parseRecipeWith(model, chunkD5CV3UF7_cjs.parseIngredientLine);
|
|
316
351
|
let result = parse();
|
|
317
352
|
const dropped = [];
|
|
318
353
|
if (!result.success) {
|
|
354
|
+
const targets = [];
|
|
319
355
|
for (const error of result.error.issues) {
|
|
320
356
|
const target = error.severity === "error" ? droppableAt(reader, error.path) : void 0;
|
|
321
357
|
if (target) {
|
|
322
|
-
|
|
358
|
+
targets.push(target);
|
|
323
359
|
dropped.push({
|
|
324
360
|
...error,
|
|
325
361
|
severity: "warning",
|
|
@@ -327,12 +363,13 @@ function fromJsonLd(input) {
|
|
|
327
363
|
});
|
|
328
364
|
}
|
|
329
365
|
}
|
|
366
|
+
targets.sort((a, b) => b.length - a.length).forEach((target) => remove(model, target));
|
|
330
367
|
if (dropped.length > 0) {
|
|
331
368
|
result = parse();
|
|
332
369
|
}
|
|
333
370
|
}
|
|
334
371
|
const order = ({ code, path }) => [
|
|
335
|
-
READ_FIELDS.indexOf(String(path[found.path.length])),
|
|
372
|
+
path.length > found.path.length ? READ_FIELDS.indexOf(String(path[found.path.length])) : -1,
|
|
336
373
|
...path.slice(found.path.length + 1).filter((key) => typeof key === "number"),
|
|
337
374
|
...code === "unknown_field" ? [Infinity] : []
|
|
338
375
|
];
|
|
@@ -343,18 +380,50 @@ function fromJsonLd(input) {
|
|
|
343
380
|
...result.success ? result.warnings : result.error.issues
|
|
344
381
|
].map((issue2) => ({ ...issue2, path: sourceOf(reader, issue2.path) }))
|
|
345
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
|
+
);
|
|
346
402
|
return {
|
|
347
|
-
...
|
|
403
|
+
...chunkD5CV3UF7_cjs.toResult(
|
|
348
404
|
"invalid_recipe",
|
|
349
405
|
result.success ? result.data : void 0,
|
|
350
406
|
issues,
|
|
351
|
-
typeof name === "string" ?
|
|
407
|
+
typeof name === "string" ? chunkD5CV3UF7_cjs.quote(name) : void 0
|
|
352
408
|
),
|
|
353
|
-
extra
|
|
354
|
-
Object.entries(node).filter(([key]) => !READ_FIELDS.includes(key))
|
|
355
|
-
)
|
|
409
|
+
extra
|
|
356
410
|
};
|
|
357
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
|
+
}
|
|
358
427
|
function readRecipe(reader, node, base) {
|
|
359
428
|
const at = (...path) => [...base, ...path];
|
|
360
429
|
const model = {
|
|
@@ -402,104 +471,130 @@ function readImage(reader, value, source) {
|
|
|
402
471
|
if (isAbsent(value)) {
|
|
403
472
|
return void 0;
|
|
404
473
|
}
|
|
405
|
-
const warnings = reader.warnings.length;
|
|
406
|
-
reader.repeated = false;
|
|
474
|
+
const warnings = reader.warnings.length + reader.repeated;
|
|
407
475
|
const image = findImage(reader, value, source);
|
|
408
|
-
if (!image &&
|
|
476
|
+
if (!image && reader.warnings.length + reader.repeated === warnings) {
|
|
409
477
|
ignore(
|
|
410
478
|
reader,
|
|
411
479
|
"invalid_type",
|
|
412
480
|
source,
|
|
413
|
-
`Expected an image URL, an ImageObject with a url, or a list of them, received ${
|
|
481
|
+
`Expected an image URL, an ImageObject with a url, or a list of them, received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
414
482
|
);
|
|
415
483
|
}
|
|
416
484
|
return image;
|
|
417
485
|
}
|
|
418
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
|
+
}
|
|
419
499
|
if (typeof value === "string") {
|
|
420
500
|
const url = cleanText(value);
|
|
421
|
-
const scheme =
|
|
501
|
+
const scheme = chunkD5CV3UF7_cjs.disallowedScheme(url);
|
|
422
502
|
if (scheme) {
|
|
423
503
|
ignore(
|
|
424
504
|
reader,
|
|
425
505
|
"invalid_url",
|
|
426
506
|
source,
|
|
427
|
-
`Expected an http or https URL, or a relative one, received ${
|
|
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}.`
|
|
428
517
|
);
|
|
429
518
|
return void 0;
|
|
430
519
|
}
|
|
431
520
|
return url ? { url, source } : void 0;
|
|
432
521
|
}
|
|
433
|
-
if (source.length > MAX_DEPTH) {
|
|
434
|
-
return void 0;
|
|
435
|
-
}
|
|
436
522
|
if (Array.isArray(value)) {
|
|
437
|
-
for (const [
|
|
438
|
-
const image2 = findImage(reader, entry,
|
|
523
|
+
for (const [entry, entrySource] of entriesOf(value, source)) {
|
|
524
|
+
const image2 = findImage(reader, entry, entrySource, referenced);
|
|
439
525
|
if (image2) {
|
|
440
526
|
return image2;
|
|
441
527
|
}
|
|
442
528
|
}
|
|
443
529
|
return void 0;
|
|
444
530
|
}
|
|
445
|
-
const
|
|
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);
|
|
446
542
|
if (!record) {
|
|
447
543
|
return void 0;
|
|
448
544
|
}
|
|
545
|
+
images.set(value, reads);
|
|
546
|
+
const warnings = reader.warnings.length + reader.repeated;
|
|
547
|
+
const deepest = reader.deepest;
|
|
548
|
+
reader.deepest = source.length;
|
|
449
549
|
const id = record["@id"];
|
|
450
550
|
const target = typeof id === "string" && !referenced ? reader.ids.get(id) : void 0;
|
|
451
|
-
|
|
452
|
-
if (image
|
|
453
|
-
return image;
|
|
454
|
-
}
|
|
455
|
-
if (target.node === reader.recipe) {
|
|
551
|
+
let image = findImage(reader, record.url, [...source, "url"], referenced) ?? findImage(reader, record.contentUrl, [...source, "contentUrl"], referenced);
|
|
552
|
+
if (!image && target?.node === reader.recipe) {
|
|
456
553
|
ignore(
|
|
457
554
|
reader,
|
|
458
555
|
"invalid_type",
|
|
459
556
|
source,
|
|
460
557
|
"Expected an image URL, an ImageObject with a url, or a list of them, received a reference to the recipe itself."
|
|
461
558
|
);
|
|
462
|
-
|
|
559
|
+
} else if (!image && target) {
|
|
560
|
+
image = findImage(reader, target.node, target.path, true);
|
|
463
561
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
return read[0];
|
|
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;
|
|
474
570
|
}
|
|
475
571
|
function readYield(reader, model, value, source) {
|
|
476
|
-
const
|
|
477
|
-
const batches = entries.flatMap(
|
|
572
|
+
const batches = entriesOf(value, source).flatMap(
|
|
478
573
|
([entry, entrySource]) => (typeof entry === "string" ? entry.split(/,\s+/) : [entry]).map(
|
|
479
574
|
(part) => readBatch(reader, part, entrySource)
|
|
480
575
|
)
|
|
481
576
|
).filter((batch) => batch !== void 0);
|
|
482
577
|
const yields = batches.filter((batch) => batch.yield !== void 0);
|
|
483
578
|
const servings = batches.filter((batch) => batch.servings !== void 0);
|
|
484
|
-
const kept = yields.find((batch) => !batch.article) ?? yields
|
|
579
|
+
const kept = yields.find((batch) => !batch.article) ?? yields.find((batch) => batch.article);
|
|
485
580
|
const keptServings = servings.find((batch) => batch.worded) ?? servings.find((batch) => batch.amount !== kept?.amount);
|
|
486
581
|
for (const batch of servings) {
|
|
487
582
|
if (keptServings && batch.amount !== keptServings.amount && (batch.worded || batch.amount !== kept?.amount)) {
|
|
488
|
-
|
|
583
|
+
chunkD5CV3UF7_cjs.warning(
|
|
489
584
|
reader.warnings,
|
|
490
585
|
"ignored_value",
|
|
491
586
|
batch.source,
|
|
492
|
-
`Only one number of servings is read, ${
|
|
587
|
+
`Only one number of servings is read, ${chunkD5CV3UF7_cjs.quote(keptServings.label)}, so this one is ignored.`
|
|
493
588
|
);
|
|
494
589
|
}
|
|
495
590
|
}
|
|
496
591
|
for (const batch of yields) {
|
|
497
592
|
if (batch !== kept) {
|
|
498
|
-
|
|
593
|
+
chunkD5CV3UF7_cjs.warning(
|
|
499
594
|
reader.warnings,
|
|
500
595
|
"ignored_value",
|
|
501
596
|
batch.source,
|
|
502
|
-
`Only one yield is read, ${
|
|
597
|
+
`Only one yield is read, ${chunkD5CV3UF7_cjs.quote(kept.label)}, so this one is ignored.`
|
|
503
598
|
);
|
|
504
599
|
}
|
|
505
600
|
}
|
|
@@ -525,13 +620,13 @@ function readBatch(reader, value, source) {
|
|
|
525
620
|
/^(?:(?:serves|servings?|yields?|makes)\s*:?\s*)?(?:about\s+)?/i,
|
|
526
621
|
""
|
|
527
622
|
);
|
|
528
|
-
const match =
|
|
623
|
+
const match = chunkD5CV3UF7_cjs.matchAmount(text2);
|
|
529
624
|
if (!match || "code" in match) {
|
|
530
625
|
ignore(
|
|
531
626
|
reader,
|
|
532
627
|
match?.code ?? "invalid_quantity",
|
|
533
628
|
source,
|
|
534
|
-
match?.message ?? `Expected a batch such as "24 cookies" or "3\u20134 loaves", received ${
|
|
629
|
+
match?.message ?? `Expected a batch such as "24 cookies" or "3\u20134 loaves", received ${chunkD5CV3UF7_cjs.describeValue(label)}.`
|
|
535
630
|
);
|
|
536
631
|
return void 0;
|
|
537
632
|
}
|
|
@@ -539,7 +634,7 @@ function readBatch(reader, value, source) {
|
|
|
539
634
|
const rest = match.rest.trim();
|
|
540
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) };
|
|
541
636
|
}
|
|
542
|
-
const record =
|
|
637
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
543
638
|
const amount = toNumber(record?.value ?? record?.minValue);
|
|
544
639
|
if (record && amount !== void 0) {
|
|
545
640
|
const max = toNumber(record.maxValue);
|
|
@@ -548,7 +643,7 @@ function readBatch(reader, value, source) {
|
|
|
548
643
|
amount,
|
|
549
644
|
...max !== void 0 && max !== amount && { max }
|
|
550
645
|
};
|
|
551
|
-
const label = `${amount}${
|
|
646
|
+
const label = `${amount}${max !== amount && max ? `\u2013${max}` : ""}${unit && ` ${unit}`}`;
|
|
552
647
|
return !unit || SERVINGS_WORD.test(unit) ? { source, label, amount, servings: range, worded: !!unit } : { source, label, amount, yield: { ...range, unit } };
|
|
553
648
|
}
|
|
554
649
|
if (!isAbsent(value)) {
|
|
@@ -556,7 +651,7 @@ function readBatch(reader, value, source) {
|
|
|
556
651
|
reader,
|
|
557
652
|
"invalid_type",
|
|
558
653
|
source,
|
|
559
|
-
`Expected a yield such as "4 servings" or "24 cookies", received ${
|
|
654
|
+
`Expected a yield such as "4 servings" or "24 cookies", received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
560
655
|
);
|
|
561
656
|
}
|
|
562
657
|
return void 0;
|
|
@@ -565,13 +660,13 @@ function readTime(reader, value, source) {
|
|
|
565
660
|
if (isAbsent(value)) {
|
|
566
661
|
return void 0;
|
|
567
662
|
}
|
|
568
|
-
const minutes = typeof value === "string" ?
|
|
663
|
+
const minutes = typeof value === "string" ? chunkD5CV3UF7_cjs.parseIsoDuration(cleanText(value)) : void 0;
|
|
569
664
|
if (minutes === void 0 || minutes > Number.MAX_SAFE_INTEGER) {
|
|
570
665
|
ignore(
|
|
571
666
|
reader,
|
|
572
667
|
"invalid_duration",
|
|
573
668
|
source,
|
|
574
|
-
`Expected an ISO 8601 duration such as "PT30M", received ${
|
|
669
|
+
`Expected an ISO 8601 duration such as "PT30M", received ${chunkD5CV3UF7_cjs.describeValue(value)}${minutes === void 0 ? "" : ", which is too long"}.`
|
|
575
670
|
);
|
|
576
671
|
return void 0;
|
|
577
672
|
}
|
|
@@ -589,24 +684,26 @@ function readList(reader, key, value, source, readItem, expected) {
|
|
|
589
684
|
reader,
|
|
590
685
|
"invalid_type",
|
|
591
686
|
entrySource,
|
|
592
|
-
`Expected ${expected}, received ${
|
|
687
|
+
`Expected ${expected}, received ${chunkD5CV3UF7_cjs.describeValue(entry)} nested more than ${MAX_DEPTH} levels deep.`
|
|
593
688
|
);
|
|
594
689
|
return;
|
|
595
690
|
}
|
|
596
691
|
if (Array.isArray(entry)) {
|
|
597
|
-
const
|
|
598
|
-
|
|
692
|
+
const elements2 = entriesOf(entry, entrySource);
|
|
693
|
+
const positions = elements2.map(
|
|
694
|
+
([element]) => toNumber(chunkD5CV3UF7_cjs.asRecord(element)?.position)
|
|
599
695
|
);
|
|
600
|
-
const order = [...
|
|
696
|
+
const order = [...elements2.keys()];
|
|
601
697
|
if (!positions.includes(void 0)) {
|
|
602
698
|
order.sort((a, b) => positions[a] - positions[b]);
|
|
603
699
|
}
|
|
604
700
|
for (const i of order) {
|
|
605
|
-
|
|
701
|
+
const [element, elementSource] = elements2[i];
|
|
702
|
+
add(items, path, element, elementSource, inSection);
|
|
606
703
|
}
|
|
607
704
|
return;
|
|
608
705
|
}
|
|
609
|
-
const record =
|
|
706
|
+
const record = chunkD5CV3UF7_cjs.asRecord(entry);
|
|
610
707
|
const elements = [...entrySource, "itemListElement"];
|
|
611
708
|
if (record?.itemListElement !== void 0 && record.text === void 0) {
|
|
612
709
|
const title = typeof record.name === "string" ? cleanText(record.name) : "";
|
|
@@ -628,13 +725,17 @@ function readList(reader, key, value, source, readItem, expected) {
|
|
|
628
725
|
}
|
|
629
726
|
return;
|
|
630
727
|
}
|
|
728
|
+
if (record && !isAbsent(record.item)) {
|
|
729
|
+
add(items, path, record.item, [...entrySource, "item"], inSection);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
631
732
|
const read = readItem(reader, entry, entrySource);
|
|
632
733
|
if (!read) {
|
|
633
734
|
ignore(
|
|
634
735
|
reader,
|
|
635
736
|
"invalid_type",
|
|
636
737
|
entrySource,
|
|
637
|
-
`Expected ${expected}, received ${
|
|
738
|
+
`Expected ${expected}, received ${chunkD5CV3UF7_cjs.describeValue(entry)}.`
|
|
638
739
|
);
|
|
639
740
|
return;
|
|
640
741
|
}
|
|
@@ -642,7 +743,7 @@ function readList(reader, key, value, source, readItem, expected) {
|
|
|
642
743
|
const itemPath = [...path, items.length];
|
|
643
744
|
items.push(item.value);
|
|
644
745
|
link(reader, itemPath, item.source, true);
|
|
645
|
-
for (const [field, fieldSource, droppable] of item.fields
|
|
746
|
+
for (const [field, fieldSource, droppable] of item.fields) {
|
|
646
747
|
link(reader, [...itemPath, field], fieldSource, droppable);
|
|
647
748
|
}
|
|
648
749
|
}
|
|
@@ -652,26 +753,28 @@ function readList(reader, key, value, source, readItem, expected) {
|
|
|
652
753
|
}
|
|
653
754
|
function readLine(reader, value, source) {
|
|
654
755
|
if (typeof value === "string") {
|
|
655
|
-
return cleanLines(value).map((line) => ({
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
756
|
+
return cleanLines(value).map((line) => ({
|
|
757
|
+
value: line,
|
|
758
|
+
source,
|
|
759
|
+
fields: []
|
|
760
|
+
}));
|
|
660
761
|
}
|
|
762
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
661
763
|
const parts = [record?.value, record?.unitText, record?.name ?? record?.text].filter((part) => typeof part === "string" || typeof part === "number").join(" ");
|
|
662
|
-
return parts ? cleanLines(parts).map((line) => ({ value: line, source })) : void 0;
|
|
764
|
+
return parts ? cleanLines(parts).map((line) => ({ value: line, source, fields: [] })) : void 0;
|
|
663
765
|
}
|
|
664
766
|
function readStep(reader, value, source) {
|
|
665
767
|
if (typeof value === "string") {
|
|
666
|
-
return cleanLines(value).map((line) => ({
|
|
768
|
+
return cleanLines(value).map((line) => ({
|
|
769
|
+
value: line,
|
|
770
|
+
source,
|
|
771
|
+
fields: []
|
|
772
|
+
}));
|
|
667
773
|
}
|
|
668
|
-
const record =
|
|
774
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
669
775
|
if (!record) {
|
|
670
776
|
return void 0;
|
|
671
777
|
}
|
|
672
|
-
if (record.item !== void 0 && source.length < MAX_DEPTH) {
|
|
673
|
-
return readStep(reader, record.item, [...source, "item"]);
|
|
674
|
-
}
|
|
675
778
|
const name = typeof record.name === "string" ? cleanText(record.name) : "";
|
|
676
779
|
const body = text(record.text) ?? name;
|
|
677
780
|
const image = readImage(reader, record.image, [...source, "image"]);
|
|
@@ -696,16 +799,15 @@ function readStep(reader, value, source) {
|
|
|
696
799
|
];
|
|
697
800
|
}
|
|
698
801
|
function readTools(reader, value, source) {
|
|
699
|
-
const entries = Array.isArray(value) ? value.map((entry, i) => [entry, [...source, i]]) : [[value, source]];
|
|
700
802
|
const equipment = [];
|
|
701
803
|
link(reader, ["equipment"], source);
|
|
702
|
-
for (const [entry, entrySource] of
|
|
703
|
-
const record =
|
|
804
|
+
for (const [entry, entrySource] of entriesOf(value, source)) {
|
|
805
|
+
const record = chunkD5CV3UF7_cjs.asRecord(entry);
|
|
704
806
|
const item = record ? {
|
|
705
807
|
name: text(record.name),
|
|
706
808
|
...record.requiredQuantity !== void 0 && {
|
|
707
809
|
quantity: toNumber(
|
|
708
|
-
|
|
810
|
+
chunkD5CV3UF7_cjs.asRecord(record.requiredQuantity)?.value ?? record.requiredQuantity
|
|
709
811
|
) ?? record.requiredQuantity
|
|
710
812
|
},
|
|
711
813
|
...record.url !== void 0 && { url: text(record.url) }
|
|
@@ -732,13 +834,13 @@ function readNutrition(reader, value, source) {
|
|
|
732
834
|
if (isAbsent(value)) {
|
|
733
835
|
return void 0;
|
|
734
836
|
}
|
|
735
|
-
const record =
|
|
837
|
+
const record = chunkD5CV3UF7_cjs.asRecord(value);
|
|
736
838
|
if (!record) {
|
|
737
839
|
ignore(
|
|
738
840
|
reader,
|
|
739
841
|
"invalid_type",
|
|
740
842
|
source,
|
|
741
|
-
`Expected NutritionInformation, such as { "calories": "250 kcal" }, received ${
|
|
843
|
+
`Expected NutritionInformation, such as { "calories": "250 kcal" }, received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
742
844
|
);
|
|
743
845
|
return void 0;
|
|
744
846
|
}
|
|
@@ -766,7 +868,7 @@ function isAbsent(value) {
|
|
|
766
868
|
return value === void 0 || value === null || typeof value === "string" && !value.trim();
|
|
767
869
|
}
|
|
768
870
|
function ignore(reader, code, path, message) {
|
|
769
|
-
|
|
871
|
+
chunkD5CV3UF7_cjs.warning(reader.warnings, code, path, `${message} It is ignored.`);
|
|
770
872
|
}
|
|
771
873
|
function link(reader, path, source, droppable = false) {
|
|
772
874
|
const key = JSON.stringify(path);
|
|
@@ -786,10 +888,10 @@ function sourceOf(reader, path) {
|
|
|
786
888
|
}
|
|
787
889
|
function compareOrder(a, b) {
|
|
788
890
|
for (const [i, n] of a.entries()) {
|
|
789
|
-
|
|
790
|
-
if (m === void 0) {
|
|
891
|
+
if (i === b.length) {
|
|
791
892
|
return 1;
|
|
792
893
|
}
|
|
894
|
+
const m = b[i];
|
|
793
895
|
if (n !== m) {
|
|
794
896
|
return n - m;
|
|
795
897
|
}
|
|
@@ -818,18 +920,28 @@ function remove(model, path) {
|
|
|
818
920
|
// src/schema-org/to-json-ld.ts
|
|
819
921
|
function toJsonLd(recipe, options) {
|
|
820
922
|
const { servings, system, url, extra } = options ?? {};
|
|
821
|
-
const resolved =
|
|
822
|
-
const page = typeof url === "string" ?
|
|
923
|
+
const resolved = chunkD5CV3UF7_cjs.resolveRecipe(recipe, { servings, system });
|
|
924
|
+
const page = typeof url === "string" ? url.replace(/#.*/s, "") : void 0;
|
|
823
925
|
const yields = [
|
|
824
926
|
...resolved.servings ? servingsText(resolved.servings) : [],
|
|
825
|
-
...resolved.yield ? [
|
|
927
|
+
...resolved.yield ? [chunkD5CV3UF7_cjs.formatYield(resolved.yield, { fractions: false })] : []
|
|
826
928
|
];
|
|
827
929
|
const ingredients = resolved.ingredients.flatMap(
|
|
828
930
|
({ items }) => items.map((item) => ingredientLine(item, resolved.scale, resolved.system))
|
|
829
931
|
);
|
|
830
932
|
const steps = resolved.steps.flatMap(
|
|
831
933
|
({ title, items }) => {
|
|
832
|
-
const howToSteps = items.map((step) =>
|
|
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
|
+
}));
|
|
833
945
|
return title === void 0 || howToSteps.length === 0 ? howToSteps : [
|
|
834
946
|
{
|
|
835
947
|
"@type": "HowToSection",
|
|
@@ -861,13 +973,13 @@ function toJsonLd(recipe, options) {
|
|
|
861
973
|
const data = {
|
|
862
974
|
...written,
|
|
863
975
|
...Object.fromEntries(
|
|
864
|
-
Object.entries(
|
|
865
|
-
([field, value]) => value !== void 0 && !
|
|
976
|
+
Object.entries(chunkD5CV3UF7_cjs.isRecord(extra) ? extra : {}).filter(
|
|
977
|
+
([field, value]) => value !== void 0 && !chunkD5CV3UF7_cjs.hasOwn(written, field)
|
|
866
978
|
)
|
|
867
979
|
)
|
|
868
980
|
};
|
|
869
981
|
if (data.image === void 0) {
|
|
870
|
-
|
|
982
|
+
chunkD5CV3UF7_cjs.warning(
|
|
871
983
|
warnings,
|
|
872
984
|
"missing_field",
|
|
873
985
|
["image"],
|
|
@@ -875,7 +987,7 @@ function toJsonLd(recipe, options) {
|
|
|
875
987
|
);
|
|
876
988
|
}
|
|
877
989
|
if (data.nutrition !== void 0 && data.recipeYield === void 0) {
|
|
878
|
-
|
|
990
|
+
chunkD5CV3UF7_cjs.warning(
|
|
879
991
|
warnings,
|
|
880
992
|
"missing_field",
|
|
881
993
|
["recipeYield"],
|
|
@@ -885,45 +997,32 @@ function toJsonLd(recipe, options) {
|
|
|
885
997
|
return { data, warnings };
|
|
886
998
|
}
|
|
887
999
|
function servingsText(servings) {
|
|
888
|
-
const amount =
|
|
1000
|
+
const amount = chunkD5CV3UF7_cjs.formatServings(servings, { fractions: false });
|
|
889
1001
|
const word = servings.amount === 1 && servings.max === void 0 ? "serving" : "servings";
|
|
890
1002
|
return [amount, `${amount} ${word}`];
|
|
891
1003
|
}
|
|
892
1004
|
function ingredientLine(item, scale, system) {
|
|
893
1005
|
const { ingredient, origin } = item;
|
|
894
|
-
const { quantity, name, note } =
|
|
1006
|
+
const { quantity, name, note } = chunkD5CV3UF7_cjs.formatIngredient(item);
|
|
895
1007
|
const authored = ingredient.quantity ? [ingredient.quantity, ...ingredient.equivalents] : [];
|
|
896
|
-
const shown = origin === "authored" ? 0 : origin === "equivalent" ? 1 + ingredient.equivalents.findIndex((q) =>
|
|
897
|
-
const alternatives = authored.filter((_, i) => i !== shown).map((q) =>
|
|
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("; ");
|
|
898
1010
|
const inParentheses = alternatives && ` (${alternatives})`;
|
|
899
1011
|
const head = quantity === void 0 ? name : item.quantity?.unit === void 0 ? `${quantity} ${name}${inParentheses}` : `${quantity}${inParentheses} ${name}`;
|
|
900
1012
|
return plainSpaces(
|
|
901
1013
|
`${head}${note ? `, ${note}` : ""}${ingredient.optional ? " (optional)" : ""}`
|
|
902
1014
|
);
|
|
903
1015
|
}
|
|
904
|
-
function howToStep(step, page) {
|
|
905
|
-
return {
|
|
906
|
-
"@type": "HowToStep",
|
|
907
|
-
...step.name !== void 0 && { name: step.name },
|
|
908
|
-
text: step.text,
|
|
909
|
-
...step.image !== void 0 && { image: step.image },
|
|
910
|
-
...page !== void 0 && {
|
|
911
|
-
// encodeURIComponent throws on a lone surrogate. With the u flag,
|
|
912
|
-
// \p{Cs} matches only those, as a pair reads as one code point.
|
|
913
|
-
url: `${page}#${encodeURIComponent(step.id.replace(/\p{Cs}/gu, "\uFFFD"))}`
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
1016
|
function duration(warnings, key, minutes) {
|
|
918
1017
|
if (minutes === void 0) {
|
|
919
1018
|
return {};
|
|
920
1019
|
}
|
|
921
1020
|
if (Number.isNaN(minutes) || minutes > Number.MAX_SAFE_INTEGER) {
|
|
922
|
-
|
|
1021
|
+
chunkD5CV3UF7_cjs.warning(
|
|
923
1022
|
warnings,
|
|
924
1023
|
"invalid_duration",
|
|
925
1024
|
["times", key],
|
|
926
|
-
`Expected minutes such as 30, received ${
|
|
1025
|
+
`Expected minutes such as 30, received ${chunkD5CV3UF7_cjs.describeValue(minutes)}${Number.isNaN(minutes) ? "" : ", which is too long"}. It is ignored.`
|
|
927
1026
|
);
|
|
928
1027
|
return {};
|
|
929
1028
|
}
|
|
@@ -947,7 +1046,7 @@ function nutritionInformation(nutrition) {
|
|
|
947
1046
|
for (const key of Object.keys(NUTRIENTS)) {
|
|
948
1047
|
const value = nutrition[key];
|
|
949
1048
|
if (value !== void 0) {
|
|
950
|
-
information[NUTRIENTS[key]] = typeof value === "string" ? value : `${
|
|
1049
|
+
information[NUTRIENTS[key]] = typeof value === "string" ? value : `${chunkD5CV3UF7_cjs.round(value, 3)} ${chunkD5CV3UF7_cjs.NUTRIENT_UNITS[key]}`;
|
|
951
1050
|
}
|
|
952
1051
|
}
|
|
953
1052
|
return information;
|