ochre-sdk 1.0.76 → 1.0.78

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.
Files changed (42) hide show
  1. package/dist/constants.d.mts +13 -14
  2. package/dist/fetchers/gallery.d.mts +2 -3
  3. package/dist/fetchers/gallery.mjs +2 -1
  4. package/dist/fetchers/item-children.d.mts +3 -4
  5. package/dist/fetchers/item-children.mjs +2 -1
  6. package/dist/fetchers/item-links.d.mts +2 -3
  7. package/dist/fetchers/item-links.mjs +2 -1
  8. package/dist/fetchers/item-ocr-data.d.mts +2 -3
  9. package/dist/fetchers/item.d.mts +7 -8
  10. package/dist/fetchers/item.mjs +2 -1
  11. package/dist/fetchers/set/items.d.mts +2 -3
  12. package/dist/fetchers/set/items.mjs +2 -1
  13. package/dist/fetchers/set/property-values.d.mts +2 -3
  14. package/dist/fetchers/set/property-values.mjs +56 -49
  15. package/dist/fetchers/website-metadata.d.mts +2 -3
  16. package/dist/fetchers/website.d.mts +2 -3
  17. package/dist/getters.d.mts +40 -41
  18. package/dist/getters.mjs +6 -31
  19. package/dist/helpers.d.mts +3 -4
  20. package/dist/parsers/helpers.d.mts +16 -17
  21. package/dist/parsers/index.d.mts +19 -19
  22. package/dist/parsers/index.mjs +65 -48
  23. package/dist/parsers/mdx.d.mts +3 -4
  24. package/dist/parsers/multilingual.d.mts +9 -10
  25. package/dist/parsers/string.d.mts +5 -6
  26. package/dist/parsers/string.mjs +124 -146
  27. package/dist/parsers/website/index.d.mts +4 -5
  28. package/dist/parsers/website/index.mjs +598 -637
  29. package/dist/parsers/website/reader.d.mts +3 -4
  30. package/dist/query.d.mts +3 -4
  31. package/dist/schemas.d.mts +9 -10
  32. package/dist/types/index.d.mts +118 -119
  33. package/dist/types/website.d.mts +36 -38
  34. package/dist/utilities.d.mts +9 -10
  35. package/dist/utilities.mjs +28 -25
  36. package/dist/xml/dates.d.mts +13 -0
  37. package/dist/xml/dates.mjs +49 -0
  38. package/dist/xml/metadata.d.mts +3 -4
  39. package/dist/xml/schemas.d.mts +8 -9
  40. package/dist/xml/schemas.mjs +20 -27
  41. package/dist/xml/types.d.mts +129 -131
  42. package/package.json +13 -12
@@ -1,6 +1,6 @@
1
1
  import { componentSchema } from "../../schemas.mjs";
2
2
  import { parseXMLContent } from "../string.mjs";
3
- import { cleanObject, parseLicense, parseStringContent } from "../helpers.mjs";
3
+ import { cleanObject, parseContentLike, parseLicense, parseStringContent } from "../helpers.mjs";
4
4
  import { parseBibliographyList, parseIdentification, parseLinks, parseMetadata, parseMetadataLanguages, parseNotes, parsePersonList, parseSimplifiedProperties, resolveDefaultLanguage, resolveLanguages } from "../index.mjs";
5
5
  import { websitePresentationReader } from "./reader.mjs";
6
6
  import * as v from "valibot";
@@ -45,28 +45,34 @@ function prefixSlug(slug, slugPrefix) {
45
45
  if (slug === "") return slugPrefix;
46
46
  return `${slugPrefix}/${slug}`;
47
47
  }
48
+ function collectSegmentPageSlugs(trees, options, pageSlugsByUuid, segmentSlugPrefix) {
49
+ for (const tree of trees) {
50
+ const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
51
+ if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
52
+ collectWebsitePageSlugs(tree.items?.resource, options, prefixSlug(segmentSlug, segmentSlugPrefix), pageSlugsByUuid);
53
+ }
54
+ }
55
+ function collectResourcePageSlug(resource, options, pageSlugsByUuid, slugPrefix) {
56
+ const slug = cleanWebsitePageSlug(resource.slug);
57
+ if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
58
+ const pageSlug = prefixSlug(slug, slugPrefix);
59
+ pageSlugsByUuid.set(resource.uuid, pageSlug);
60
+ collectWebsitePageSlugs(resource.resource, options, slugPrefix == null ? void 0 : pageSlug, pageSlugsByUuid, pageSlug);
61
+ }
48
62
  function collectWebsitePageSlugs(resources, options, slugPrefix, pageSlugsByUuid = /* @__PURE__ */ new Map(), segmentSlugPrefix = slugPrefix) {
49
63
  const slugResources = resources ?? [];
50
64
  for (const resource of slugResources) {
51
65
  if ("segments" in resource) {
52
- for (const tree of resource.segments.tree) {
53
- const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
54
- if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
55
- collectWebsitePageSlugs(tree.items?.resource, options, prefixSlug(segmentSlug, segmentSlugPrefix), pageSlugsByUuid);
56
- }
66
+ collectSegmentPageSlugs(resource.segments.tree, options, pageSlugsByUuid, segmentSlugPrefix);
57
67
  continue;
58
68
  }
59
69
  if (!("identification" in resource)) {
60
70
  collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
61
71
  continue;
62
72
  }
63
- const resourceProperties = resource.properties != null ? parseSimplifiedProperties(resource.properties, options) : [];
73
+ const resourceProperties = parseSimplifiedProperties(resource.properties, options);
64
74
  if (websitePresentationReader(resourceProperties).value("presentation") === "page") {
65
- const slug = cleanWebsitePageSlug(resource.slug);
66
- if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
67
- const pageSlug = prefixSlug(slug, slugPrefix);
68
- pageSlugsByUuid.set(resource.uuid, pageSlug);
69
- collectWebsitePageSlugs(resource.resource, options, slugPrefix == null ? void 0 : pageSlug, pageSlugsByUuid, pageSlug);
75
+ collectResourcePageSlug(resource, options, pageSlugsByUuid, slugPrefix);
70
76
  continue;
71
77
  }
72
78
  collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
@@ -315,6 +321,450 @@ function parseCollectionDisplayedProperties(reader) {
315
321
  label: value.label
316
322
  }));
317
323
  }
324
+ function readStringOrNumber(reader, label) {
325
+ const value = reader.value(label);
326
+ if (typeof value === "string") return value;
327
+ if (typeof value === "number") return value.toString();
328
+ return null;
329
+ }
330
+ function parse3dViewerComponent(parameters) {
331
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
332
+ const resourceLink = findWebsiteLink(websiteLinks, "resource", (link) => link.fileFormat === "model/obj");
333
+ if (resourceLink == null) throw new Error(formatComponentError("Resource link not found", "3d-viewer", elementResource), { cause: componentProperty });
334
+ const isInteractive = componentReader.valueOr("is-interactive", true);
335
+ const isControlsDisplayed = componentReader.valueOr("controls-displayed", true);
336
+ return {
337
+ component: "3d-viewer",
338
+ linkUuid: resourceLink.uuid,
339
+ fileSize: resourceLink.fileSize,
340
+ isInteractive,
341
+ isControlsDisplayed
342
+ };
343
+ }
344
+ function parseAdvancedSearchComponent(parameters) {
345
+ const { componentProperty, componentReader, elementResource, options, context } = parameters;
346
+ const boundElementPropertyUuid = componentReader.uuid("bound-element");
347
+ const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
348
+ if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", "advanced-search", elementResource), { cause: componentProperty });
349
+ return {
350
+ component: "advanced-search",
351
+ boundElementUuid: boundElementPropertyUuid,
352
+ href,
353
+ options: parseWebsiteOptions(elementResource.options, options)
354
+ };
355
+ }
356
+ function parseAnnotatedDocumentComponent(parameters) {
357
+ const { componentProperty, elementResource, websiteLinks } = parameters;
358
+ const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
359
+ if (documentLink == null) throw new Error(formatComponentError("Document link not found", "annotated-document", elementResource), { cause: componentProperty });
360
+ return {
361
+ component: "annotated-document",
362
+ linkUuid: documentLink.uuid
363
+ };
364
+ }
365
+ function parseAnnotatedImageComponent(parameters) {
366
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
367
+ const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
368
+ if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", "annotated-image", elementResource), { cause: componentProperty });
369
+ const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
370
+ const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
371
+ const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
372
+ const isAnnotationTooltipsDisplayed = componentReader.valueOr("annotation-tooltips-displayed", true);
373
+ return {
374
+ component: "annotated-image",
375
+ linkUuid: imageLinks[0].uuid,
376
+ isFilterInputDisplayed,
377
+ isOptionsDisplayed,
378
+ isAnnotationHighlightsDisplayed,
379
+ isAnnotationTooltipsDisplayed
380
+ };
381
+ }
382
+ function parseAudioPlayerComponent(parameters) {
383
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
384
+ const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
385
+ if (audioLink == null) throw new Error(formatComponentError("Audio link not found", "audio-player", elementResource), { cause: componentProperty });
386
+ const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
387
+ const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
388
+ const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
389
+ return {
390
+ component: "audio-player",
391
+ linkUuid: audioLink.uuid,
392
+ isSpeedControlsDisplayed,
393
+ isVolumeControlsDisplayed,
394
+ isSeekBarDisplayed
395
+ };
396
+ }
397
+ function parseBibliographyComponent(parameters) {
398
+ const { componentProperty, componentReader, elementResource, websiteLinks, options } = parameters;
399
+ const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
400
+ if (websiteLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", "bibliography", elementResource), { cause: componentProperty });
401
+ const layout = componentReader.valueOr("layout", "long");
402
+ const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
403
+ return {
404
+ component: "bibliography",
405
+ linkUuids: websiteLinks.map((link) => link.uuid),
406
+ bibliographies,
407
+ layout,
408
+ isSourceDocumentDisplayed
409
+ };
410
+ }
411
+ function parseButtonComponent(parameters) {
412
+ const { componentProperty, componentReader, elementResource, websiteLinks, options, context } = parameters;
413
+ const variant = componentReader.valueOr("variant", "default");
414
+ let isExternal = false;
415
+ let isRelative = false;
416
+ let href = parseWebsiteLinkTarget(componentReader.valueNode("navigate-to"), context);
417
+ if (href === null) {
418
+ href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
419
+ if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", "button", elementResource), { cause: componentProperty });
420
+ isExternal = href.startsWith("http");
421
+ isRelative = !href.startsWith("/");
422
+ }
423
+ const startIcon = componentReader.value("start-icon");
424
+ const endIcon = componentReader.value("end-icon");
425
+ let image = null;
426
+ const imageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "image" || link.type === "IIIF");
427
+ if (imageLink != null) image = {
428
+ uuid: imageLink.uuid,
429
+ label: imageLink.identification.label,
430
+ width: imageLink.image?.width ?? 0,
431
+ height: imageLink.image?.height ?? 0,
432
+ description: imageLink.description,
433
+ quality: "high"
434
+ };
435
+ const childResources = normalizeWebsiteResources(elementResource.resource);
436
+ const elements = [];
437
+ for (const childResource of childResources) {
438
+ const childReader = websitePresentationReader(parseSimplifiedProperties(childResource.properties, options));
439
+ if (childReader.value("presentation") !== "element") continue;
440
+ if (childReader.nestedByValue("presentation", "element").value("component") === "button") continue;
441
+ elements.push(parseWebElement(childResource, options, context));
442
+ }
443
+ return {
444
+ component: "button",
445
+ variant,
446
+ href,
447
+ isExternal,
448
+ isRelative,
449
+ label: elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null,
450
+ startIcon,
451
+ endIcon,
452
+ image,
453
+ elements
454
+ };
455
+ }
456
+ function parseCollectionComponent(parameters) {
457
+ const { componentProperty, componentReader, elementResource, websiteLinks, options } = parameters;
458
+ const setLinks = getWebsiteLinks(websiteLinks, "set");
459
+ if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", "collection", elementResource), { cause: componentProperty });
460
+ const isFilterResultsBarDisplayed = componentReader.valueOr("filter-results-bar-displayed", false);
461
+ const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
462
+ const isFilterLimitedToInputFilter = componentReader.valueOr("filter-limit-to-input-filter", false);
463
+ const isFilterLimitedToLeafPropertyValues = componentReader.valueOr("filter-limit-to-leaf-property-values", false);
464
+ const isFilterSidebarDisplayed = componentReader.valueOr("filter-sidebar-displayed", false);
465
+ const filterSidebarSort = componentReader.valueOr("filter-sidebar-sort", "default");
466
+ const isFilterSidebarHelpTooltipsDisplayed = componentReader.valueOr("filter-sidebar-help-tooltips-displayed", false);
467
+ const componentOptions = parseWebsiteOptions(elementResource.options, options);
468
+ const propertyOverrides = parseCollectionPropertyOverrides(componentReader);
469
+ return {
470
+ component: "collection",
471
+ linkUuids: setLinks.map((link) => link.uuid),
472
+ displayedProperties: parseCollectionDisplayedProperties(componentReader),
473
+ ...COLLECTION_PROPERTY_DEFAULTS,
474
+ ...propertyOverrides,
475
+ image: {
476
+ ...COLLECTION_IMAGE_DEFAULTS,
477
+ ...propertyOverrides.image
478
+ },
479
+ filter: {
480
+ isSidebarDisplayed: isFilterSidebarDisplayed,
481
+ isResultsBarDisplayed: isFilterResultsBarDisplayed,
482
+ isInputDisplayed: isFilterInputDisplayed,
483
+ isLimitedToInputFilter: isFilterLimitedToInputFilter,
484
+ isLimitedToLeafPropertyValues: isFilterLimitedToLeafPropertyValues,
485
+ sidebarSort: filterSidebarSort,
486
+ isSidebarHelpTooltipsDisplayed: isFilterSidebarHelpTooltipsDisplayed
487
+ },
488
+ options: componentOptions
489
+ };
490
+ }
491
+ function parseEmptySpaceComponent(parameters) {
492
+ const { componentReader } = parameters;
493
+ return {
494
+ component: "empty-space",
495
+ height: componentReader.stringValue("height"),
496
+ width: componentReader.stringValue("width")
497
+ };
498
+ }
499
+ function parseEntriesComponent(parameters) {
500
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
501
+ const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
502
+ if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", "entries", elementResource), { cause: componentProperty });
503
+ const variant = componentReader.valueOr("variant", "entry");
504
+ const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
505
+ return {
506
+ component: "entries",
507
+ linkUuid: entriesLink.uuid,
508
+ variant,
509
+ isFilterInputDisplayed
510
+ };
511
+ }
512
+ function parseIframeComponent(parameters) {
513
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
514
+ const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
515
+ if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", "iframe", elementResource), { cause: componentProperty });
516
+ return {
517
+ component: "iframe",
518
+ href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
519
+ height: componentReader.stringValue("height"),
520
+ width: componentReader.stringValue("width")
521
+ };
522
+ }
523
+ function parseIiifViewerComponent(parameters) {
524
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
525
+ const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
526
+ if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", "iiif-viewer", elementResource), { cause: componentProperty });
527
+ const variant = componentReader.valueOr("variant", "universal-viewer");
528
+ return {
529
+ component: "iiif-viewer",
530
+ linkUuid: manifestLink.uuid,
531
+ variant
532
+ };
533
+ }
534
+ function parseImageComponent(parameters) {
535
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
536
+ if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", "image", elementResource), { cause: componentProperty });
537
+ const imageQuality = componentReader.valueOr("image-quality", "high");
538
+ const images = Array.from(websiteLinks, (link) => ({
539
+ uuid: link.uuid,
540
+ label: link.identification.label,
541
+ width: "image" in link ? link.image?.width ?? 0 : 0,
542
+ height: "image" in link ? link.image?.height ?? 0 : 0,
543
+ description: link.description,
544
+ quality: imageQuality
545
+ }));
546
+ const variant = componentReader.valueOr("variant", "default");
547
+ const captionLayout = componentReader.valueOr("layout-caption", "bottom");
548
+ const width = readStringOrNumber(componentReader, "width");
549
+ const height = readStringOrNumber(componentReader, "height");
550
+ const isFullWidth = componentReader.valueOr("is-full-width", true);
551
+ const isFullHeight = componentReader.valueOr("is-full-height", true);
552
+ const captionSource = componentReader.valueOr("source-caption", "name");
553
+ const altTextSource = componentReader.valueOr("alt-text-source", "name");
554
+ const isTransparentBackground = componentReader.valueOr("is-transparent", false);
555
+ const isCover = componentReader.valueOr("is-cover", false);
556
+ const variantReader = componentReader.nested("variant");
557
+ let carouselOptions = null;
558
+ if (images.length > 1) {
559
+ const secondsPerImage = variant === "carousel" ? readStringOrNumber(variantReader, "seconds-per-image") : null;
560
+ carouselOptions = { secondsPerImage: Number(secondsPerImage ?? 5) };
561
+ }
562
+ let heroOptions = null;
563
+ if (variant === "hero") heroOptions = {
564
+ isBackgroundImageDisplayed: variantReader.valueOr("background-image-displayed", true),
565
+ isDocumentDisplayed: variantReader.valueOr("document-displayed", true)
566
+ };
567
+ return {
568
+ component: "image",
569
+ images,
570
+ variant,
571
+ width,
572
+ height,
573
+ isFullWidth,
574
+ isFullHeight,
575
+ imageQuality,
576
+ captionLayout,
577
+ captionSource,
578
+ altTextSource,
579
+ isTransparentBackground,
580
+ isCover,
581
+ carouselOptions,
582
+ heroOptions
583
+ };
584
+ }
585
+ function parseImageGalleryComponent(parameters) {
586
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
587
+ const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
588
+ if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", "image-gallery", elementResource), { cause: componentProperty });
589
+ const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
590
+ return {
591
+ component: "image-gallery",
592
+ linkUuid: galleryLink.uuid,
593
+ isFilterInputDisplayed
594
+ };
595
+ }
596
+ function parseMapComponent(parameters) {
597
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
598
+ const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
599
+ if (mapLink == null) throw new Error(formatComponentError("Map link not found", "map", elementResource), { cause: componentProperty });
600
+ const isInteractive = componentReader.valueOr("is-interactive", true);
601
+ const isClustered = componentReader.valueOr("is-clustered", false);
602
+ const isUsingPins = componentReader.valueOr("is-using-pins", false);
603
+ const customBasemap = componentReader.value("custom-basemap");
604
+ let initialBounds = null;
605
+ const initialBoundsProperty = componentReader.value("initial-bounds");
606
+ if (initialBoundsProperty !== null) initialBounds = parseBounds(String(initialBoundsProperty));
607
+ let maximumBounds = null;
608
+ const maximumBoundsProperty = componentReader.value("maximum-bounds");
609
+ if (maximumBoundsProperty !== null) maximumBounds = parseBounds(String(maximumBoundsProperty));
610
+ const isControlsDisplayed = componentReader.valueOr("controls-displayed", false);
611
+ const isFullHeight = componentReader.valueOr("is-full-height", false);
612
+ return {
613
+ component: "map",
614
+ linkUuid: mapLink.uuid,
615
+ customBasemap,
616
+ initialBounds,
617
+ maximumBounds,
618
+ isInteractive,
619
+ isClustered,
620
+ isUsingPins,
621
+ isControlsDisplayed,
622
+ isFullHeight
623
+ };
624
+ }
625
+ function parseQueryItemQueries(propertyVariables, queryLanguage, elementResource) {
626
+ const queries = [];
627
+ for (const propertyVariable of propertyVariables) {
628
+ if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", "query", elementResource), { cause: propertyVariable });
629
+ const dataType = propertyVariable.dataType;
630
+ if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", "query", elementResource), { cause: propertyVariable });
631
+ queries.push({
632
+ target: "property",
633
+ propertyVariable: propertyVariable.uuid,
634
+ dataType,
635
+ matchMode: "exact",
636
+ isCaseSensitive: true,
637
+ language: queryLanguage
638
+ });
639
+ }
640
+ return queries;
641
+ }
642
+ function parseQueryComponent(parameters) {
643
+ const { componentProperty, componentReader, elementResource, websiteLinks, options } = parameters;
644
+ const setLinks = getWebsiteLinks(websiteLinks, "set");
645
+ if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", "query", elementResource), { cause: componentProperty });
646
+ if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", "query", elementResource), { cause: componentProperty });
647
+ const items = [];
648
+ for (const queryItem of componentProperty.properties) {
649
+ const queryReader = websitePresentationReader(queryItem.properties);
650
+ const label = queryReader.multilingualValue("query-prompt", options);
651
+ if (label === null) continue;
652
+ const propertyVariables = queryReader.values("use-property").filter((value) => value.uuid !== null);
653
+ const queryLanguage = options.languages[0];
654
+ if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", "query", elementResource));
655
+ const queries = parseQueryItemQueries(propertyVariables, queryLanguage, elementResource);
656
+ const startIcon = queryReader.value("start-icon");
657
+ const endIcon = queryReader.value("end-icon");
658
+ items.push({
659
+ label,
660
+ queries,
661
+ startIcon,
662
+ endIcon
663
+ });
664
+ }
665
+ if (items.length === 0) throw new Error(formatComponentError("No queries found", "query", elementResource), { cause: componentProperty });
666
+ const componentOptions = parseWebsiteOptions(elementResource.options, options);
667
+ const collectionProperties = parseCollectionPropertyOverrides(componentReader.nestedByValue("sub-component-override", "collection"));
668
+ const displayedProperties = parseCollectionDisplayedProperties(componentReader);
669
+ if (displayedProperties != null) collectionProperties.displayedProperties = displayedProperties;
670
+ return {
671
+ component: "query",
672
+ linkUuids: setLinks.map((link) => link.uuid),
673
+ items,
674
+ options: componentOptions,
675
+ collectionProperties
676
+ };
677
+ }
678
+ function parseTableComponent(parameters) {
679
+ const { componentProperty, elementResource, websiteLinks } = parameters;
680
+ const tableLink = findWebsiteLink(websiteLinks, "set");
681
+ if (tableLink == null) throw new Error(formatComponentError("Table link not found", "table", elementResource), { cause: componentProperty });
682
+ return {
683
+ component: "table",
684
+ linkUuid: tableLink.uuid
685
+ };
686
+ }
687
+ function parseSearchBarComponent(parameters) {
688
+ const { componentProperty, componentReader, elementResource, options, context } = parameters;
689
+ const queryVariant = componentReader.valueOr("query-variant", "submit");
690
+ const boundElementUuid = componentReader.uuid("bound-element");
691
+ const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
692
+ if (boundElementUuid === null && href === null) throw new Error(formatComponentError("Bound element or href not found", "search-bar", elementResource), { cause: componentProperty });
693
+ return {
694
+ component: "search-bar",
695
+ queryVariant,
696
+ placeholder: componentReader.multilingualValue("placeholder-text", options),
697
+ baseFilterQueries: componentReader.value("base-filter-queries")?.replaceAll(String.raw`\{`, "{").replaceAll(String.raw`\}`, "}") ?? null,
698
+ boundElementUuid,
699
+ href
700
+ };
701
+ }
702
+ function parseTextComponent(parameters) {
703
+ const { componentProperty, componentReader, elementResource, options } = parameters;
704
+ const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
705
+ if (content == null) throw new Error(formatComponentError("Content not found", "text", elementResource), { cause: componentProperty });
706
+ let variantName = "block";
707
+ let variant;
708
+ const variantProperty = componentReader.property("variant");
709
+ if (variantProperty !== null) {
710
+ const variantReader = websitePresentationReader(variantProperty.properties);
711
+ variantName = variantProperty.values[0].content;
712
+ switch (variantName) {
713
+ case "paragraph":
714
+ variant = {
715
+ name: variantName,
716
+ size: variantReader.valueOr("size", "md")
717
+ };
718
+ break;
719
+ case "label":
720
+ variant = {
721
+ name: variantName,
722
+ size: variantReader.valueOr("size", "md")
723
+ };
724
+ break;
725
+ case "heading":
726
+ variant = {
727
+ name: variantName,
728
+ size: variantReader.valueOr("size", "md")
729
+ };
730
+ break;
731
+ case "display":
732
+ variant = {
733
+ name: variantName,
734
+ size: variantReader.valueOr("size", "md")
735
+ };
736
+ break;
737
+ default: variant = { name: variantName };
738
+ }
739
+ } else variant = { name: variantName };
740
+ const headingLevel = componentReader.value("heading-level");
741
+ return {
742
+ component: "text",
743
+ variant,
744
+ headingLevel,
745
+ content
746
+ };
747
+ }
748
+ function parseTimelineComponent(parameters) {
749
+ const { componentProperty, elementResource, websiteLinks } = parameters;
750
+ const timelineLink = findWebsiteLink(websiteLinks, "tree");
751
+ if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", "timeline", elementResource), { cause: componentProperty });
752
+ return {
753
+ component: "timeline",
754
+ linkUuid: timelineLink.uuid
755
+ };
756
+ }
757
+ function parseVideoComponent(parameters) {
758
+ const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
759
+ const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
760
+ if (videoLink == null) throw new Error(formatComponentError("Video link not found", "video", elementResource), { cause: componentProperty });
761
+ const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
762
+ return {
763
+ component: "video",
764
+ linkUuid: videoLink.uuid,
765
+ isChaptersDisplayed
766
+ };
767
+ }
318
768
  /**
319
769
  * Parses raw web element properties into a standardized WebElementComponent structure
320
770
  *
@@ -326,461 +776,39 @@ function parseWebElementProperties(componentProperty, elementResource, options,
326
776
  const unparsedComponentName = componentProperty.values[0].content;
327
777
  const componentNameResult = v.safeParse(componentSchema, unparsedComponentName);
328
778
  const componentName = componentNameResult.success ? componentNameResult.output : void 0;
329
- let properties = null;
330
779
  const websiteLinks = parseLinks(elementResource.links, options);
331
- const componentReader = websitePresentationReader(componentProperty.properties);
780
+ const parameters = {
781
+ componentProperty,
782
+ componentReader: websitePresentationReader(componentProperty.properties),
783
+ elementResource,
784
+ websiteLinks,
785
+ options,
786
+ context
787
+ };
332
788
  switch (componentName) {
333
- case "3d-viewer": {
334
- const resourceLink = findWebsiteLink(websiteLinks, "resource", (link) => link.fileFormat === "model/obj");
335
- if (resourceLink == null) throw new Error(formatComponentError("Resource link not found", componentName, elementResource), { cause: componentProperty });
336
- const isInteractive = componentReader.valueOr("is-interactive", true);
337
- const isControlsDisplayed = componentReader.valueOr("controls-displayed", true);
338
- properties = {
339
- component: "3d-viewer",
340
- linkUuid: resourceLink.uuid,
341
- fileSize: resourceLink.fileSize,
342
- isInteractive,
343
- isControlsDisplayed
344
- };
345
- break;
346
- }
347
- case "advanced-search": {
348
- const boundElementPropertyUuid = componentReader.uuid("bound-element");
349
- const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
350
- if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
351
- properties = {
352
- component: "advanced-search",
353
- boundElementUuid: boundElementPropertyUuid,
354
- href,
355
- options: parseWebsiteOptions(elementResource.options, options)
356
- };
357
- break;
358
- }
359
- case "annotated-document": {
360
- const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
361
- if (documentLink == null) throw new Error(formatComponentError("Document link not found", componentName, elementResource), { cause: componentProperty });
362
- properties = {
363
- component: "annotated-document",
364
- linkUuid: documentLink.uuid
365
- };
366
- break;
367
- }
368
- case "annotated-image": {
369
- const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
370
- if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", componentName, elementResource), { cause: componentProperty });
371
- const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
372
- const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
373
- const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
374
- const isAnnotationTooltipsDisplayed = componentReader.valueOr("annotation-tooltips-displayed", true);
375
- properties = {
376
- component: "annotated-image",
377
- linkUuid: imageLinks[0].uuid,
378
- isFilterInputDisplayed,
379
- isOptionsDisplayed,
380
- isAnnotationHighlightsDisplayed,
381
- isAnnotationTooltipsDisplayed
382
- };
383
- break;
384
- }
385
- case "audio-player": {
386
- const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
387
- if (audioLink == null) throw new Error(formatComponentError("Audio link not found", componentName, elementResource), { cause: componentProperty });
388
- const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
389
- const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
390
- const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
391
- properties = {
392
- component: "audio-player",
393
- linkUuid: audioLink.uuid,
394
- isSpeedControlsDisplayed,
395
- isVolumeControlsDisplayed,
396
- isSeekBarDisplayed
397
- };
398
- break;
399
- }
400
- case "bibliography": {
401
- const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
402
- if (websiteLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
403
- const layout = componentReader.valueOr("layout", "long");
404
- const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
405
- properties = {
406
- component: "bibliography",
407
- linkUuids: websiteLinks.map((link) => link.uuid),
408
- bibliographies,
409
- layout,
410
- isSourceDocumentDisplayed
411
- };
412
- break;
413
- }
414
- case "button": {
415
- const variant = componentReader.valueOr("variant", "default");
416
- let isExternal = false;
417
- let isRelative = false;
418
- let href = parseWebsiteLinkTarget(componentReader.valueNode("navigate-to"), context);
419
- if (href === null) {
420
- href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
421
- if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", componentName, elementResource), { cause: componentProperty });
422
- isExternal = href.startsWith("http");
423
- isRelative = !href.startsWith("/");
424
- }
425
- const startIcon = componentReader.value("start-icon");
426
- const endIcon = componentReader.value("end-icon");
427
- let image = null;
428
- const imageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "image" || link.type === "IIIF");
429
- if (imageLink != null) image = {
430
- uuid: imageLink.uuid,
431
- label: imageLink.identification.label,
432
- width: imageLink.image?.width ?? 0,
433
- height: imageLink.image?.height ?? 0,
434
- description: imageLink.description,
435
- quality: "high"
436
- };
437
- const childResources = elementResource.resource ? normalizeWebsiteResources(elementResource.resource) : [];
438
- const elements = [];
439
- for (const childResource of childResources) {
440
- const childReader = websitePresentationReader(childResource.properties ? parseSimplifiedProperties(childResource.properties, options) : []);
441
- if (childReader.value("presentation") !== "element") continue;
442
- if (childReader.nestedByValue("presentation", "element").value("component") === "button") continue;
443
- elements.push(parseWebElement(childResource, options, context));
444
- }
445
- properties = {
446
- component: "button",
447
- variant,
448
- href,
449
- isExternal,
450
- isRelative,
451
- label: elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null,
452
- startIcon,
453
- endIcon,
454
- image,
455
- elements
456
- };
457
- break;
458
- }
459
- case "collection": {
460
- const setLinks = getWebsiteLinks(websiteLinks, "set");
461
- if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
462
- const isFilterResultsBarDisplayed = componentReader.valueOr("filter-results-bar-displayed", false);
463
- const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
464
- const isFilterLimitedToInputFilter = componentReader.valueOr("filter-limit-to-input-filter", false);
465
- const isFilterLimitedToLeafPropertyValues = componentReader.valueOr("filter-limit-to-leaf-property-values", false);
466
- const isFilterSidebarDisplayed = componentReader.valueOr("filter-sidebar-displayed", false);
467
- const filterSidebarSort = componentReader.valueOr("filter-sidebar-sort", "default");
468
- const componentOptions = parseWebsiteOptions(elementResource.options, options);
469
- const propertyOverrides = parseCollectionPropertyOverrides(componentReader);
470
- properties = {
471
- component: "collection",
472
- linkUuids: setLinks.map((link) => link.uuid),
473
- displayedProperties: parseCollectionDisplayedProperties(componentReader),
474
- ...COLLECTION_PROPERTY_DEFAULTS,
475
- ...propertyOverrides,
476
- image: {
477
- ...COLLECTION_IMAGE_DEFAULTS,
478
- ...propertyOverrides.image
479
- },
480
- filter: {
481
- isSidebarDisplayed: isFilterSidebarDisplayed,
482
- isResultsBarDisplayed: isFilterResultsBarDisplayed,
483
- isInputDisplayed: isFilterInputDisplayed,
484
- isLimitedToInputFilter: isFilterLimitedToInputFilter,
485
- isLimitedToLeafPropertyValues: isFilterLimitedToLeafPropertyValues,
486
- sidebarSort: filterSidebarSort
487
- },
488
- options: componentOptions
489
- };
490
- break;
491
- }
492
- case "empty-space":
493
- properties = {
494
- component: "empty-space",
495
- height: componentReader.stringValue("height"),
496
- width: componentReader.stringValue("width")
497
- };
498
- break;
499
- case "entries": {
500
- const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
501
- if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", componentName, elementResource), { cause: componentProperty });
502
- const variant = componentReader.valueOr("variant", "entry");
503
- const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
504
- properties = {
505
- component: "entries",
506
- linkUuid: entriesLink.uuid,
507
- variant,
508
- isFilterInputDisplayed
509
- };
510
- break;
511
- }
512
- case "iframe": {
513
- const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
514
- if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", componentName, elementResource), { cause: componentProperty });
515
- properties = {
516
- component: "iframe",
517
- href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
518
- height: componentReader.stringValue("height"),
519
- width: componentReader.stringValue("width")
520
- };
521
- break;
522
- }
523
- case "iiif-viewer": {
524
- const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
525
- if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", componentName, elementResource), { cause: componentProperty });
526
- const variant = componentReader.valueOr("variant", "universal-viewer");
527
- properties = {
528
- component: "iiif-viewer",
529
- linkUuid: manifestLink.uuid,
530
- variant
531
- };
532
- break;
533
- }
534
- case "image": {
535
- if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
536
- const imageQuality = componentReader.valueOr("image-quality", "high");
537
- const images = Array.from(websiteLinks, (link) => ({
538
- uuid: link.uuid,
539
- label: link.identification.label,
540
- width: "image" in link ? link.image?.width ?? 0 : 0,
541
- height: "image" in link ? link.image?.height ?? 0 : 0,
542
- description: link.description,
543
- quality: imageQuality
544
- }));
545
- const variant = componentReader.valueOr("variant", "default");
546
- const captionLayout = componentReader.valueOr("layout-caption", "bottom");
547
- let width = null;
548
- const widthProperty = componentReader.value("width");
549
- if (widthProperty !== null) {
550
- if (typeof widthProperty === "string") width = widthProperty;
551
- else if (typeof widthProperty === "number") width = widthProperty.toString();
552
- }
553
- let height = null;
554
- const heightProperty = componentReader.value("height");
555
- if (heightProperty !== null) {
556
- if (typeof heightProperty === "string") height = heightProperty;
557
- else if (typeof heightProperty === "number") height = heightProperty.toString();
558
- }
559
- const isFullWidth = componentReader.valueOr("is-full-width", true);
560
- const isFullHeight = componentReader.valueOr("is-full-height", true);
561
- const captionSource = componentReader.valueOr("source-caption", "name");
562
- const altTextSource = componentReader.valueOr("alt-text-source", "name");
563
- const isTransparentBackground = componentReader.valueOr("is-transparent", false);
564
- const isCover = componentReader.valueOr("is-cover", false);
565
- const variantReader = componentReader.nested("variant");
566
- let carouselOptions = null;
567
- if (images.length > 1) {
568
- let secondsPerImage = 5;
569
- if (variant === "carousel") {
570
- const secondsPerImageProperty = variantReader.value("seconds-per-image");
571
- if (secondsPerImageProperty !== null) {
572
- if (typeof secondsPerImageProperty === "number") secondsPerImage = secondsPerImageProperty;
573
- else if (typeof secondsPerImageProperty === "string") secondsPerImage = Number(secondsPerImageProperty);
574
- }
575
- }
576
- carouselOptions = { secondsPerImage };
577
- }
578
- let heroOptions = null;
579
- if (variant === "hero") heroOptions = {
580
- isBackgroundImageDisplayed: variantReader.valueOr("background-image-displayed", true),
581
- isDocumentDisplayed: variantReader.valueOr("document-displayed", true)
582
- };
583
- properties = {
584
- component: "image",
585
- images,
586
- variant,
587
- width,
588
- height,
589
- isFullWidth,
590
- isFullHeight,
591
- imageQuality,
592
- captionLayout,
593
- captionSource,
594
- altTextSource,
595
- isTransparentBackground,
596
- isCover,
597
- carouselOptions,
598
- heroOptions
599
- };
600
- break;
601
- }
602
- case "image-gallery": {
603
- const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
604
- if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", componentName, elementResource), { cause: componentProperty });
605
- const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
606
- properties = {
607
- component: "image-gallery",
608
- linkUuid: galleryLink.uuid,
609
- isFilterInputDisplayed
610
- };
611
- break;
612
- }
613
- case "map": {
614
- const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
615
- if (mapLink == null) throw new Error(formatComponentError("Map link not found", componentName, elementResource), { cause: componentProperty });
616
- const isInteractive = componentReader.valueOr("is-interactive", true);
617
- const isClustered = componentReader.valueOr("is-clustered", false);
618
- const isUsingPins = componentReader.valueOr("is-using-pins", false);
619
- const customBasemap = componentReader.value("custom-basemap");
620
- let initialBounds = null;
621
- const initialBoundsProperty = componentReader.value("initial-bounds");
622
- if (initialBoundsProperty !== null) initialBounds = parseBounds(String(initialBoundsProperty));
623
- let maximumBounds = null;
624
- const maximumBoundsProperty = componentReader.value("maximum-bounds");
625
- if (maximumBoundsProperty !== null) maximumBounds = parseBounds(String(maximumBoundsProperty));
626
- const isControlsDisplayed = componentReader.valueOr("controls-displayed", false);
627
- const isFullHeight = componentReader.valueOr("is-full-height", false);
628
- properties = {
629
- component: "map",
630
- linkUuid: mapLink.uuid,
631
- customBasemap,
632
- initialBounds,
633
- maximumBounds,
634
- isInteractive,
635
- isClustered,
636
- isUsingPins,
637
- isControlsDisplayed,
638
- isFullHeight
639
- };
640
- break;
641
- }
642
- case "query": {
643
- const setLinks = getWebsiteLinks(websiteLinks, "set");
644
- if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
645
- if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", componentName, elementResource), { cause: componentProperty });
646
- const items = [];
647
- for (const queryItem of componentProperty.properties) {
648
- const queryReader = websitePresentationReader(queryItem.properties);
649
- const label = queryReader.multilingualValue("query-prompt", options);
650
- if (label === null) continue;
651
- const propertyVariables = queryReader.values("use-property").filter((value) => value.uuid !== null);
652
- const queryLanguage = options.languages[0];
653
- if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", componentName, elementResource));
654
- const queries = [];
655
- for (const propertyVariable of propertyVariables) {
656
- if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", componentName, elementResource), { cause: propertyVariable });
657
- const dataType = propertyVariable.dataType;
658
- if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", componentName, elementResource), { cause: propertyVariable });
659
- queries.push({
660
- target: "property",
661
- propertyVariable: propertyVariable.uuid,
662
- dataType,
663
- matchMode: "exact",
664
- isCaseSensitive: true,
665
- language: queryLanguage
666
- });
667
- }
668
- const startIcon = queryReader.value("start-icon");
669
- const endIcon = queryReader.value("end-icon");
670
- items.push({
671
- label,
672
- queries,
673
- startIcon,
674
- endIcon
675
- });
676
- }
677
- if (items.length === 0) throw new Error(formatComponentError("No queries found", componentName, elementResource), { cause: componentProperty });
678
- const componentOptions = parseWebsiteOptions(elementResource.options, options);
679
- const collectionProperties = parseCollectionPropertyOverrides(componentReader.nestedByValue("sub-component-override", "collection"));
680
- const displayedProperties = parseCollectionDisplayedProperties(componentReader);
681
- if (displayedProperties != null) collectionProperties.displayedProperties = displayedProperties;
682
- properties = {
683
- component: "query",
684
- linkUuids: setLinks.map((link) => link.uuid),
685
- items,
686
- options: componentOptions,
687
- collectionProperties
688
- };
689
- break;
690
- }
691
- case "table": {
692
- const tableLink = findWebsiteLink(websiteLinks, "set");
693
- if (tableLink == null) throw new Error(formatComponentError("Table link not found", componentName, elementResource), { cause: componentProperty });
694
- properties = {
695
- component: "table",
696
- linkUuid: tableLink.uuid
697
- };
698
- break;
699
- }
700
- case "search-bar": {
701
- const queryVariant = componentReader.valueOr("query-variant", "submit");
702
- const boundElementUuid = componentReader.uuid("bound-element");
703
- const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
704
- if (boundElementUuid === null && href === null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
705
- properties = {
706
- component: "search-bar",
707
- queryVariant,
708
- placeholder: componentReader.multilingualValue("placeholder-text", options),
709
- baseFilterQueries: componentReader.value("base-filter-queries")?.replaceAll(String.raw`\{`, "{").replaceAll(String.raw`\}`, "}") ?? null,
710
- boundElementUuid,
711
- href
712
- };
713
- break;
714
- }
715
- case "text": {
716
- const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
717
- if (content == null) throw new Error(formatComponentError("Content not found", componentName, elementResource), { cause: componentProperty });
718
- let variantName = "block";
719
- let variant;
720
- const variantProperty = componentReader.property("variant");
721
- if (variantProperty !== null) {
722
- const variantReader = websitePresentationReader(variantProperty.properties);
723
- variantName = variantProperty.values[0].content;
724
- switch (variantName) {
725
- case "paragraph":
726
- variant = {
727
- name: variantName,
728
- size: variantReader.valueOr("size", "md")
729
- };
730
- break;
731
- case "label":
732
- variant = {
733
- name: variantName,
734
- size: variantReader.valueOr("size", "md")
735
- };
736
- break;
737
- case "heading":
738
- variant = {
739
- name: variantName,
740
- size: variantReader.valueOr("size", "md")
741
- };
742
- break;
743
- case "display":
744
- variant = {
745
- name: variantName,
746
- size: variantReader.valueOr("size", "md")
747
- };
748
- break;
749
- default: variant = { name: variantName };
750
- }
751
- } else variant = { name: variantName };
752
- const headingLevel = componentReader.value("heading-level");
753
- properties = {
754
- component: "text",
755
- variant,
756
- headingLevel,
757
- content
758
- };
759
- break;
760
- }
761
- case "timeline": {
762
- const timelineLink = findWebsiteLink(websiteLinks, "tree");
763
- if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", componentName, elementResource), { cause: componentProperty });
764
- properties = {
765
- component: "timeline",
766
- linkUuid: timelineLink.uuid
767
- };
768
- break;
769
- }
770
- case "video": {
771
- const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
772
- if (videoLink == null) throw new Error(formatComponentError("Video link not found", componentName, elementResource), { cause: componentProperty });
773
- const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
774
- properties = {
775
- component: "video",
776
- linkUuid: videoLink.uuid,
777
- isChaptersDisplayed
778
- };
779
- break;
780
- }
789
+ case "3d-viewer": return parse3dViewerComponent(parameters);
790
+ case "advanced-search": return parseAdvancedSearchComponent(parameters);
791
+ case "annotated-document": return parseAnnotatedDocumentComponent(parameters);
792
+ case "annotated-image": return parseAnnotatedImageComponent(parameters);
793
+ case "audio-player": return parseAudioPlayerComponent(parameters);
794
+ case "bibliography": return parseBibliographyComponent(parameters);
795
+ case "button": return parseButtonComponent(parameters);
796
+ case "collection": return parseCollectionComponent(parameters);
797
+ case "empty-space": return parseEmptySpaceComponent(parameters);
798
+ case "entries": return parseEntriesComponent(parameters);
799
+ case "iframe": return parseIframeComponent(parameters);
800
+ case "iiif-viewer": return parseIiifViewerComponent(parameters);
801
+ case "image": return parseImageComponent(parameters);
802
+ case "image-gallery": return parseImageGalleryComponent(parameters);
803
+ case "map": return parseMapComponent(parameters);
804
+ case "query": return parseQueryComponent(parameters);
805
+ case "table": return parseTableComponent(parameters);
806
+ case "search-bar": return parseSearchBarComponent(parameters);
807
+ case "text": return parseTextComponent(parameters);
808
+ case "timeline": return parseTimelineComponent(parameters);
809
+ case "video": return parseVideoComponent(parameters);
781
810
  default: throw new Error(`Invalid or non-implemented component name “${unparsedComponentName.toString()}” for the following element: “${parseStringContent(elementResource.identification.label, options)}”`);
782
811
  }
783
- return properties;
784
812
  }
785
813
  function parseWebTitle(properties, identification, overrides) {
786
814
  const title = {
@@ -813,7 +841,7 @@ function parseWebTitle(properties, identification, overrides) {
813
841
  */
814
842
  function parseWebElement(elementResource, options, context) {
815
843
  const identification = parseIdentification(elementResource.identification, options);
816
- const elementProperties = elementResource.properties?.property ? parseSimplifiedProperties(elementResource.properties, options) : [];
844
+ const elementProperties = parseSimplifiedProperties(elementResource.properties, options);
817
845
  const presentationProperty = websitePresentationReader(elementProperties).requiredProperty("presentation", `Presentation property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`);
818
846
  const properties = parseWebElementProperties(websitePresentationReader(presentationProperty.properties).requiredProperty("component", `Component property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`), elementResource, options, context);
819
847
  const cssStyles = parseResponsiveCssStyles(elementProperties);
@@ -834,6 +862,51 @@ function parseWebElement(elementResource, options, context) {
834
862
  ...properties
835
863
  };
836
864
  }
865
+ function isSidebarResource(resource, options) {
866
+ const resourceProperties = parseSimplifiedProperties(resource.properties, options);
867
+ const resourceReader = websitePresentationReader(resourceProperties);
868
+ return resourceReader.value("presentation") === "element" && resourceReader.nestedByValue("presentation", "element").value("component") === "sidebar";
869
+ }
870
+ function parseWebBlockItems(resources, options, context) {
871
+ const items = [];
872
+ for (const resource of resources) {
873
+ const resourceProperties = parseSimplifiedProperties(resource.properties, options);
874
+ const resourceType = websitePresentationReader(resourceProperties).value("presentation");
875
+ if (resourceType === null) continue;
876
+ switch (resourceType) {
877
+ case "element":
878
+ items.push(parseWebElement(resource, options, context));
879
+ break;
880
+ case "block": {
881
+ const block = parseWebBlock(resource, options, context);
882
+ if (block) items.push(block);
883
+ break;
884
+ }
885
+ }
886
+ }
887
+ return items;
888
+ }
889
+ function parseWebpageRedirect(redirectValue, context) {
890
+ const redirectTarget = parseWebsiteLinkTarget(redirectValue, context);
891
+ if (redirectTarget != null) {
892
+ if (redirectValue?.href == null && redirectValue?.uuid != null) return {
893
+ type: "page",
894
+ slug: redirectTarget,
895
+ uuid: redirectValue.uuid
896
+ };
897
+ return {
898
+ type: "url",
899
+ href: redirectTarget,
900
+ isExternal: redirectTarget.startsWith("http")
901
+ };
902
+ }
903
+ if (redirectValue?.uuid != null) return {
904
+ type: "item",
905
+ uuid: redirectValue.uuid,
906
+ pageType: "item"
907
+ };
908
+ return null;
909
+ }
837
910
  /**
838
911
  * Parses raw webpage data into a standardized Webpage structure
839
912
  *
@@ -841,7 +914,7 @@ function parseWebElement(elementResource, options, context) {
841
914
  * @returns Parsed Webpage object
842
915
  */
843
916
  function parseWebpage(webpageResource, options, context, slugPrefix) {
844
- const webpageProperties = webpageResource.properties ? parseSimplifiedProperties(webpageResource.properties, options) : [];
917
+ const webpageProperties = parseSimplifiedProperties(webpageResource.properties, options);
845
918
  const webpageReader = websitePresentationReader(webpageProperties);
846
919
  if (webpageReader.value("presentation") !== "page") return null;
847
920
  const identification = parseIdentification(webpageResource.identification, options);
@@ -874,27 +947,8 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
874
947
  webpages: []
875
948
  };
876
949
  const imageLink = findWebsiteLink(parseLinks(webpageResource.links, options), "resource", (link) => link.type === "image" || link.type === "IIIF");
877
- const webpageResources = webpageResource.resource != null ? normalizeWebsiteResources(webpageResource.resource) : [];
878
- const items = [];
879
- for (const resource of webpageResources) {
880
- const resourceProperties = resource.properties != null ? parseSimplifiedProperties(resource.properties, options) : [];
881
- const resourceType = websitePresentationReader(resourceProperties).value("presentation");
882
- if (resourceType === null) continue;
883
- switch (resourceType) {
884
- case "element": {
885
- if (websitePresentationReader(resourceProperties).nestedByValue("presentation", "element").value("component") === "sidebar") continue;
886
- const element = parseWebElement(resource, options, context);
887
- items.push(element);
888
- break;
889
- }
890
- case "block": {
891
- const block = parseWebBlock(resource, options, context);
892
- if (block) items.push(block);
893
- break;
894
- }
895
- }
896
- }
897
- returnWebpage.items = items;
950
+ const webpageResources = normalizeWebsiteResources(webpageResource.resource);
951
+ returnWebpage.items = parseWebBlockItems(webpageResources.filter((resource) => !isSidebarResource(resource, options)), options, context);
898
952
  returnWebpage.webpages = parseWebpages(webpageResources, options, context, slugPrefix == null ? void 0 : returnWebpage.slug);
899
953
  returnWebpage.segments = parseWebsiteSegments(webpageResource.resource, context, options, returnWebpage.slug);
900
954
  returnWebpage.properties.sidebar = parseSidebar(webpageResources, options, context);
@@ -906,24 +960,7 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
906
960
  returnWebpage.properties.isSidebarDisplayed = pageReader.valueOr("sidebar-displayed", true);
907
961
  returnWebpage.properties.isBreadcrumbsDisplayed = pageReader.valueOr("breadcrumbs-displayed", false);
908
962
  returnWebpage.properties.isNavbarSearchBarDisplayed = pageReader.valueOr("navbar-search-bar-displayed", true);
909
- const redirectValue = pageReader.valueNode("redirect-to");
910
- const redirectTarget = parseWebsiteLinkTarget(redirectValue, context);
911
- if (redirectTarget != null) {
912
- if (redirectValue?.href == null && redirectValue?.uuid != null) returnWebpage.properties.redirect = {
913
- type: "page",
914
- slug: redirectTarget,
915
- uuid: redirectValue.uuid
916
- };
917
- else returnWebpage.properties.redirect = {
918
- type: "url",
919
- href: redirectTarget,
920
- isExternal: redirectTarget.startsWith("http")
921
- };
922
- } else if (redirectValue?.uuid != null) returnWebpage.properties.redirect = {
923
- type: "item",
924
- uuid: redirectValue.uuid,
925
- pageType: "item"
926
- };
963
+ returnWebpage.properties.redirect = parseWebpageRedirect(pageReader.valueNode("redirect-to"), context);
927
964
  }
928
965
  if (imageLink != null) returnWebpage.properties.backgroundImage = {
929
966
  uuid: imageLink.uuid,
@@ -973,86 +1010,72 @@ function parseWebsiteSegments(resources, context, options, slugPrefix) {
973
1010
  * @returns Parsed Sidebar object
974
1011
  */
975
1012
  function parseSidebar(resources, options, context) {
976
- let returnSidebar = null;
977
- const items = [];
978
- let title = null;
979
- let layout = "start";
980
- let mobileLayout = "default";
981
- const cssStyles = {
982
- default: [],
983
- tablet: [],
984
- mobile: []
985
- };
986
- const sidebarResource = resources.find((resource) => {
987
- const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
988
- const resourceReader = websitePresentationReader(resourceProperties);
989
- return resourceReader.value("presentation") === "element" && resourceReader.nestedByValue("presentation", "element").value("component") === "sidebar";
990
- });
991
- if (sidebarResource != null) {
992
- const sidebarBaseProperties = sidebarResource.properties ? parseSimplifiedProperties(sidebarResource.properties, options) : [];
993
- title = parseWebTitle(sidebarBaseProperties, parseIdentification(sidebarResource.identification, options));
994
- const sidebarReader = websitePresentationReader(sidebarBaseProperties).nestedByValue("presentation", "element").nestedByValue("component", "sidebar");
995
- layout = sidebarReader.valueOr("layout", "start");
996
- mobileLayout = sidebarReader.valueOr("layout-mobile", "default");
997
- const parsedCssStyles = parseResponsiveCssStyles(sidebarBaseProperties);
998
- cssStyles.default = parsedCssStyles.default;
999
- cssStyles.tablet = parsedCssStyles.tablet;
1000
- cssStyles.mobile = parsedCssStyles.mobile;
1001
- const sidebarResources = sidebarResource.resource ? normalizeWebsiteResources(sidebarResource.resource) : [];
1002
- for (const resource of sidebarResources) {
1003
- const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
1004
- const resourceType = websitePresentationReader(resourceProperties).value("presentation");
1005
- if (resourceType === null) continue;
1006
- switch (resourceType) {
1007
- case "element": {
1008
- const element = parseWebElement(resource, options, context);
1009
- items.push(element);
1010
- break;
1011
- }
1012
- case "block": {
1013
- const block = parseWebBlock(resource, options, context);
1014
- if (block) items.push(block);
1015
- break;
1016
- }
1017
- }
1018
- }
1019
- }
1020
- if (title != null && items.length > 0) returnSidebar = {
1013
+ const sidebarResource = resources.find((resource) => isSidebarResource(resource, options));
1014
+ if (sidebarResource == null) return null;
1015
+ const sidebarBaseProperties = parseSimplifiedProperties(sidebarResource.properties, options);
1016
+ const items = parseWebBlockItems(normalizeWebsiteResources(sidebarResource.resource), options, context);
1017
+ if (items.length === 0) return null;
1018
+ const sidebarReader = websitePresentationReader(sidebarBaseProperties).nestedByValue("presentation", "element").nestedByValue("component", "sidebar");
1019
+ return {
1021
1020
  isDisplayed: true,
1022
1021
  items,
1023
- title,
1024
- layout,
1025
- mobileLayout,
1026
- cssStyles
1022
+ title: parseWebTitle(sidebarBaseProperties, parseIdentification(sidebarResource.identification, options)),
1023
+ layout: sidebarReader.valueOr("layout", "start"),
1024
+ mobileLayout: sidebarReader.valueOr("layout-mobile", "default"),
1025
+ cssStyles: parseResponsiveCssStyles(sidebarBaseProperties)
1027
1026
  };
1028
- return returnSidebar;
1029
1027
  }
1030
1028
  function parseWebAccordionItem(elementResource, childResources, options, context) {
1031
1029
  const trigger = parseWebElement(elementResource, options, context);
1032
- const items = [];
1033
- for (const resource of childResources) {
1034
- const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
1035
- const resourceType = websitePresentationReader(resourceProperties).value("presentation");
1030
+ const items = parseWebBlockItems(childResources, options, context);
1031
+ return {
1032
+ uuid: trigger.uuid,
1033
+ type: "accordion-item",
1034
+ trigger,
1035
+ items
1036
+ };
1037
+ }
1038
+ function parseBlockOverwrite(overwriteReader, isDefaultLayoutAccordion) {
1039
+ if (overwriteReader.size === 0) return null;
1040
+ const properties = {
1041
+ layout: overwriteReader.value("layout") ?? void 0,
1042
+ wrap: overwriteReader.value("wrap") ?? void 0,
1043
+ spacing: overwriteReader.value("spacing") ?? void 0,
1044
+ gap: overwriteReader.value("gap") ?? void 0,
1045
+ isAccordionEnabled: void 0,
1046
+ isAccordionExpandedByDefault: void 0,
1047
+ isAccordionSidebarDisplayed: void 0
1048
+ };
1049
+ if (isDefaultLayoutAccordion || properties.layout === "accordion") {
1050
+ properties.isAccordionEnabled = overwriteReader.value("accordion-enabled") ?? void 0;
1051
+ properties.isAccordionExpandedByDefault = overwriteReader.value("accordion-expanded") ?? void 0;
1052
+ properties.isAccordionSidebarDisplayed = overwriteReader.value("accordion-sidebar-displayed") ?? void 0;
1053
+ }
1054
+ const cleanedProperties = cleanObject(properties);
1055
+ return Object.keys(cleanedProperties).length > 0 ? cleanedProperties : null;
1056
+ }
1057
+ function parseAccordionAwareBlockItems(blockResources, options, context, isSupportingAccordionItems) {
1058
+ const blockItems = [];
1059
+ for (const resource of blockResources) {
1060
+ const resourceProperties = parseSimplifiedProperties(resource.properties, options);
1061
+ const resourceReader = websitePresentationReader(resourceProperties);
1062
+ const resourceType = resourceReader.value("presentation");
1036
1063
  if (resourceType === null) continue;
1037
1064
  switch (resourceType) {
1038
1065
  case "element": {
1039
- const element = parseWebElement(resource, options, context);
1040
- items.push(element);
1066
+ const childResources = normalizeWebsiteResources(resource.resource);
1067
+ const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
1068
+ blockItems.push(isSupportingAccordionItems && componentType === "text" && childResources.length > 0 ? parseWebAccordionItem(resource, childResources, options, context) : parseWebElement(resource, options, context));
1041
1069
  break;
1042
1070
  }
1043
1071
  case "block": {
1044
1072
  const block = parseWebBlock(resource, options, context);
1045
- if (block) items.push(block);
1073
+ if (block) blockItems.push(block);
1046
1074
  break;
1047
1075
  }
1048
1076
  }
1049
1077
  }
1050
- return {
1051
- uuid: trigger.uuid,
1052
- type: "accordion-item",
1053
- trigger,
1054
- items
1055
- };
1078
+ return blockItems;
1056
1079
  }
1057
1080
  /**
1058
1081
  * Parses raw block data into a standardized WebBlock structure
@@ -1061,7 +1084,7 @@ function parseWebAccordionItem(elementResource, childResources, options, context
1061
1084
  * @returns Parsed WebBlock object
1062
1085
  */
1063
1086
  function parseWebBlock(blockResource, options, context) {
1064
- const blockProperties = blockResource.properties ? parseSimplifiedProperties(blockResource.properties, options) : [];
1087
+ const blockProperties = parseSimplifiedProperties(blockResource.properties, options);
1065
1088
  const returnBlock = {
1066
1089
  uuid: blockResource.uuid,
1067
1090
  language: blockResource.lang ?? null,
@@ -1095,74 +1118,11 @@ function parseWebBlock(blockResource, options, context) {
1095
1118
  }
1096
1119
  returnBlock.properties.default.spacing = blockReader.valueOr("spacing", null);
1097
1120
  returnBlock.properties.default.gap = blockReader.valueOr("gap", null);
1098
- const tabletOverwriteReader = blockReader.nested("overwrite-tablet");
1099
- if (tabletOverwriteReader.size > 0) {
1100
- const propertiesTablet = {
1101
- layout: tabletOverwriteReader.value("layout") ?? void 0,
1102
- wrap: tabletOverwriteReader.value("wrap") ?? void 0,
1103
- spacing: tabletOverwriteReader.value("spacing") ?? void 0,
1104
- gap: tabletOverwriteReader.value("gap") ?? void 0,
1105
- isAccordionEnabled: void 0,
1106
- isAccordionExpandedByDefault: void 0,
1107
- isAccordionSidebarDisplayed: void 0
1108
- };
1109
- if (propertiesTablet.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
1110
- propertiesTablet.isAccordionEnabled = tabletOverwriteReader.value("accordion-enabled") ?? void 0;
1111
- propertiesTablet.isAccordionExpandedByDefault = tabletOverwriteReader.value("accordion-expanded") ?? void 0;
1112
- propertiesTablet.isAccordionSidebarDisplayed = tabletOverwriteReader.value("accordion-sidebar-displayed") ?? void 0;
1113
- }
1114
- const cleanedPropertiesTablet = cleanObject(propertiesTablet);
1115
- if (Object.keys(cleanedPropertiesTablet).length > 0) returnBlock.properties.tablet = cleanedPropertiesTablet;
1116
- }
1117
- const mobileOverwriteReader = blockReader.nested("overwrite-mobile");
1118
- if (mobileOverwriteReader.size > 0) {
1119
- const propertiesMobile = {
1120
- layout: mobileOverwriteReader.value("layout") ?? void 0,
1121
- wrap: mobileOverwriteReader.value("wrap") ?? void 0,
1122
- spacing: mobileOverwriteReader.value("spacing") ?? void 0,
1123
- gap: mobileOverwriteReader.value("gap") ?? void 0,
1124
- isAccordionEnabled: void 0,
1125
- isAccordionExpandedByDefault: void 0,
1126
- isAccordionSidebarDisplayed: void 0
1127
- };
1128
- if (propertiesMobile.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
1129
- propertiesMobile.isAccordionEnabled = mobileOverwriteReader.value("accordion-enabled") ?? void 0;
1130
- propertiesMobile.isAccordionExpandedByDefault = mobileOverwriteReader.value("accordion-expanded") ?? void 0;
1131
- propertiesMobile.isAccordionSidebarDisplayed = mobileOverwriteReader.value("accordion-sidebar-displayed") ?? void 0;
1132
- }
1133
- const cleanedPropertiesMobile = cleanObject(propertiesMobile);
1134
- if (Object.keys(cleanedPropertiesMobile).length > 0) returnBlock.properties.mobile = cleanedPropertiesMobile;
1135
- }
1136
- }
1137
- const blockResources = blockResource.resource ? normalizeWebsiteResources(blockResource.resource) : [];
1138
- const isSupportingAccordionItems = returnBlock.properties.default.layout === "accordion" || returnBlock.properties.tablet?.layout === "accordion" || returnBlock.properties.mobile?.layout === "accordion";
1139
- const blockItems = [];
1140
- for (const resource of blockResources) {
1141
- const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
1142
- const resourceReader = websitePresentationReader(resourceProperties);
1143
- const resourceType = resourceReader.value("presentation");
1144
- if (resourceType === null) continue;
1145
- switch (resourceType) {
1146
- case "element": {
1147
- const childResources = resource.resource ? normalizeWebsiteResources(resource.resource) : [];
1148
- const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
1149
- if (isSupportingAccordionItems && componentType === "text" && childResources.length > 0) {
1150
- const item = parseWebAccordionItem(resource, childResources, options, context);
1151
- blockItems.push(item);
1152
- break;
1153
- }
1154
- const element = parseWebElement(resource, options, context);
1155
- blockItems.push(element);
1156
- break;
1157
- }
1158
- case "block": {
1159
- const block = parseWebBlock(resource, options, context);
1160
- if (block) blockItems.push(block);
1161
- break;
1162
- }
1163
- }
1121
+ const isDefaultLayoutAccordion = returnBlock.properties.default.layout === "accordion";
1122
+ returnBlock.properties.tablet = parseBlockOverwrite(blockReader.nested("overwrite-tablet"), isDefaultLayoutAccordion);
1123
+ returnBlock.properties.mobile = parseBlockOverwrite(blockReader.nested("overwrite-mobile"), isDefaultLayoutAccordion);
1164
1124
  }
1165
- returnBlock.items = blockItems;
1125
+ returnBlock.items = parseAccordionAwareBlockItems(normalizeWebsiteResources(blockResource.resource), options, context, returnBlock.properties.default.layout === "accordion" || returnBlock.properties.tablet?.layout === "accordion" || returnBlock.properties.mobile?.layout === "accordion");
1166
1126
  returnBlock.cssStyles = parseResponsiveCssStyles(blockProperties);
1167
1127
  return returnBlock;
1168
1128
  }
@@ -1317,7 +1277,8 @@ function parseContextItem(contextItemToParse, options) {
1317
1277
  return {
1318
1278
  context: levels,
1319
1279
  type,
1320
- identification: parseIdentification(contextItemToParse.identification, options)
1280
+ identification: parseIdentification(contextItemToParse.identification, options),
1281
+ description: parseContentLike(contextItemToParse.description, options)
1321
1282
  };
1322
1283
  }
1323
1284
  function parseFilterContextDisplay(filterOption) {
@@ -1401,7 +1362,7 @@ function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
1401
1362
  function parseWebsite(data, options) {
1402
1363
  const rawOchre = data.result.ochre;
1403
1364
  const metadataLanguages = parseMetadataLanguages(rawOchre);
1404
- const languages = resolveLanguages(options?.languages ?? [], metadataLanguages);
1365
+ const languages = resolveLanguages(options?.languages, metadataLanguages);
1405
1366
  const parserOptions = { languages };
1406
1367
  const defaultLanguage = resolveDefaultLanguage(rawOchre, languages);
1407
1368
  const websiteTree = rawOchre.tree[0];