ochre-sdk 1.0.78 → 1.1.0

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 (84) hide show
  1. package/README.md +31 -3
  2. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  3. package/dist/categories.d.mts +249 -0
  4. package/dist/categories.mjs +259 -0
  5. package/dist/constants.d.mts +14 -0
  6. package/dist/constants.mjs +18 -1
  7. package/dist/errors.d.mts +23 -0
  8. package/dist/{utilities.mjs → errors.mjs} +33 -84
  9. package/dist/fetchers/gallery.mjs +15 -51
  10. package/dist/fetchers/item-children.mjs +20 -66
  11. package/dist/fetchers/item-links.mjs +26 -79
  12. package/dist/fetchers/item-ocr-data.d.mts +2 -2
  13. package/dist/fetchers/item-ocr-data.mjs +11 -15
  14. package/dist/fetchers/item.d.mts +0 -15
  15. package/dist/fetchers/item.mjs +20 -72
  16. package/dist/fetchers/request.d.mts +70 -0
  17. package/dist/fetchers/request.mjs +100 -0
  18. package/dist/fetchers/set/items.mjs +27 -73
  19. package/dist/fetchers/set/property-values.d.mts +2 -3
  20. package/dist/fetchers/set/property-values.mjs +96 -130
  21. package/dist/fetchers/website-metadata.mjs +35 -57
  22. package/dist/fetchers/website.d.mts +2 -3
  23. package/dist/fetchers/website.mjs +22 -31
  24. package/dist/getters.d.mts +78 -148
  25. package/dist/getters.mjs +127 -208
  26. package/dist/helpers.d.mts +0 -4
  27. package/dist/helpers.mjs +19 -6
  28. package/dist/index.d.mts +8 -6
  29. package/dist/index.mjs +6 -4
  30. package/dist/ocr.d.mts +37 -0
  31. package/dist/ocr.mjs +52 -0
  32. package/dist/parsers/helpers.d.mts +21 -1
  33. package/dist/parsers/helpers.mjs +26 -6
  34. package/dist/parsers/index.d.mts +0 -8
  35. package/dist/parsers/index.mjs +118 -259
  36. package/dist/parsers/languages.d.mts +72 -0
  37. package/dist/parsers/languages.mjs +132 -0
  38. package/dist/parsers/multilingual.d.mts +49 -74
  39. package/dist/parsers/multilingual.mjs +88 -189
  40. package/dist/parsers/property-token.d.mts +34 -0
  41. package/dist/parsers/property-token.mjs +29 -0
  42. package/dist/parsers/string.d.mts +19 -0
  43. package/dist/parsers/string.mjs +45 -25
  44. package/dist/parsers/website/bounds.d.mts +10 -0
  45. package/dist/parsers/website/bounds.mjs +28 -0
  46. package/dist/parsers/website/components.d.mts +91 -0
  47. package/dist/parsers/website/components.mjs +681 -0
  48. package/dist/parsers/website/index.d.mts +0 -7
  49. package/dist/parsers/website/index.mjs +92 -1153
  50. package/dist/parsers/website/links.d.mts +36 -0
  51. package/dist/parsers/website/links.mjs +58 -0
  52. package/dist/parsers/website/messages.d.mts +24 -0
  53. package/dist/parsers/website/messages.mjs +31 -0
  54. package/dist/parsers/website/options.d.mts +6 -0
  55. package/dist/parsers/website/options.mjs +114 -0
  56. package/dist/parsers/website/properties.d.mts +12 -0
  57. package/dist/parsers/website/properties.mjs +158 -0
  58. package/dist/parsers/website/reader.d.mts +54 -4
  59. package/dist/parsers/website/reader.mjs +65 -20
  60. package/dist/parsers/website/slug.d.mts +64 -0
  61. package/dist/parsers/website/slug.mjs +82 -0
  62. package/dist/parsers/website/styles.d.mts +28 -0
  63. package/dist/parsers/website/styles.mjs +103 -0
  64. package/dist/parsers/website/walk.d.mts +68 -0
  65. package/dist/parsers/website/walk.mjs +116 -0
  66. package/dist/query.d.mts +66 -18
  67. package/dist/query.mjs +202 -48
  68. package/dist/reflection.d.mts +64 -0
  69. package/dist/reflection.mjs +79 -0
  70. package/dist/schemas.d.mts +7 -0
  71. package/dist/schemas.mjs +12 -3
  72. package/dist/types/index.d.mts +1 -31
  73. package/dist/types/utilities.d.mts +9 -0
  74. package/dist/types/utilities.mjs +1 -0
  75. package/dist/types/website.d.mts +49 -58
  76. package/dist/xml/metadata.d.mts +16 -0
  77. package/dist/xml/metadata.mjs +32 -11
  78. package/dist/xml/schemas.d.mts +5970 -3
  79. package/dist/xml/schemas.mjs +43 -45
  80. package/dist/xml/types.d.mts +13 -30
  81. package/dist/xquery.d.mts +46 -0
  82. package/dist/xquery.mjs +66 -0
  83. package/package.json +3 -3
  84. package/dist/utilities.d.mts +0 -54
@@ -1,867 +1,13 @@
1
- import { componentSchema } from "../../schemas.mjs";
2
- import { parseXMLContent } from "../string.mjs";
3
- import { cleanObject, parseContentLike, parseLicense, parseStringContent } from "../helpers.mjs";
4
- import { parseBibliographyList, parseIdentification, parseLinks, parseMetadata, parseMetadataLanguages, parseNotes, parsePersonList, parseSimplifiedProperties, resolveDefaultLanguage, resolveLanguages } from "../index.mjs";
1
+ import { cleanObject, parseLicense } from "../helpers.mjs";
2
+ import { parseMetadataLanguages, resolveDefaultLanguage, resolveLanguages } from "../languages.mjs";
3
+ import { parseIdentification, parseLinks, parseMetadata, parsePersonList, parseSimplifiedProperties } from "../index.mjs";
4
+ import { findWebsiteLink, parseWebsiteLinkTarget, webImageFromLink } from "./links.mjs";
5
5
  import { websitePresentationReader } from "./reader.mjs";
6
- import * as v from "valibot";
6
+ import { emptyResponsiveStyles, parseResponsiveCssStyles } from "./styles.mjs";
7
+ import { collectWebsitePageSlugs, normalizeWebsiteResources, readWebsitePages } from "./walk.mjs";
8
+ import { parseWebElement, parseWebTitle } from "./components.mjs";
9
+ import { parseWebsiteProperties } from "./properties.mjs";
7
10
  //#region src/parsers/website/index.ts
8
- function isWebsiteLink(link, category) {
9
- return link.category === category;
10
- }
11
- function findWebsiteLink(links, category, isMatch) {
12
- for (const link of links) if (isWebsiteLink(link, category) && (isMatch == null || isMatch(link))) return link;
13
- return null;
14
- }
15
- function findWebsiteLinkByCategories(links, categories) {
16
- for (const link of links) for (const category of categories) if (isWebsiteLink(link, category)) return link;
17
- return null;
18
- }
19
- function getWebsiteLinks(links, category) {
20
- const matchedLinks = [];
21
- for (const link of links) if (isWebsiteLink(link, category)) matchedLinks.push(link);
22
- return matchedLinks;
23
- }
24
- function transformPermanentIdentificationUrlToItemLink(url) {
25
- return url.replace("https://pi.lib.uchicago.edu/1001/org/ochre/", "/item/");
26
- }
27
- function normalizeWebsiteResources(resources) {
28
- const normalized = [];
29
- const resourcesToNormalize = resources ?? [];
30
- for (const resource of resourcesToNormalize) {
31
- if ("identification" in resource) {
32
- normalized.push(resource);
33
- continue;
34
- }
35
- if ("resource" in resource) normalized.push(...resource.resource);
36
- }
37
- return normalized;
38
- }
39
- const SEGMENT_UNIQUE_SLUG_PREFIX_REGEX = /^\$[^-]*-/;
40
- function cleanWebsitePageSlug(slug) {
41
- return slug?.replace(SEGMENT_UNIQUE_SLUG_PREFIX_REGEX, "") ?? null;
42
- }
43
- function prefixSlug(slug, slugPrefix) {
44
- if (slugPrefix === "" || slugPrefix == null) return slug;
45
- if (slug === "") return slugPrefix;
46
- return `${slugPrefix}/${slug}`;
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
- }
62
- function collectWebsitePageSlugs(resources, options, slugPrefix, pageSlugsByUuid = /* @__PURE__ */ new Map(), segmentSlugPrefix = slugPrefix) {
63
- const slugResources = resources ?? [];
64
- for (const resource of slugResources) {
65
- if ("segments" in resource) {
66
- collectSegmentPageSlugs(resource.segments.tree, options, pageSlugsByUuid, segmentSlugPrefix);
67
- continue;
68
- }
69
- if (!("identification" in resource)) {
70
- collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
71
- continue;
72
- }
73
- const resourceProperties = parseSimplifiedProperties(resource.properties, options);
74
- if (websitePresentationReader(resourceProperties).value("presentation") === "page") {
75
- collectResourcePageSlug(resource, options, pageSlugsByUuid, slugPrefix);
76
- continue;
77
- }
78
- collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
79
- }
80
- return pageSlugsByUuid;
81
- }
82
- function parseWebsiteLinkTarget(value, context) {
83
- if (value == null) return null;
84
- if (value.href != null) return transformPermanentIdentificationUrlToItemLink(value.href);
85
- return (value.uuid == null ? void 0 : context.pageSlugsByUuid?.get(value.uuid)) ?? value.slug;
86
- }
87
- function formatXMLWebsiteResourceMetadata(resource) {
88
- const metadata = [`label “${parseStringContent(resource.identification.label)}”`, `uuid “${resource.uuid}”`];
89
- if (resource.slug != null) metadata.push(`slug “${resource.slug}”`);
90
- if (resource.identification.abbreviation != null) metadata.push(`abbreviation “${parseStringContent(resource.identification.abbreviation)}”`);
91
- return metadata.join(", ");
92
- }
93
- function formatComponentError(message, componentName, elementResource) {
94
- return `${message} for component “${componentName ?? "(unknown)"}” (${formatXMLWebsiteResourceMetadata(elementResource)})`;
95
- }
96
- /**
97
- * Extracts CSS style properties for a given presentation variant.
98
- *
99
- * @param properties - Array of properties to parse
100
- * @param cssVariant - CSS variant to parse
101
- * @returns Array of CSS styles
102
- */
103
- function parseCssStylesFromProperties(properties, cssVariant) {
104
- const label = cssVariant != null ? `css-${cssVariant}` : "css";
105
- const cssProperties = websitePresentationReader(properties).nestedByValue("presentation", label).properties;
106
- const styles = [];
107
- for (const property of cssProperties) {
108
- const value = property.values[0]?.content.toString();
109
- if (value != null) styles.push({
110
- label: property.variable.label,
111
- value
112
- });
113
- }
114
- return styles;
115
- }
116
- /**
117
- * Parses responsive CSS styles (default, tablet, mobile) from properties.
118
- *
119
- * @param properties - Array of properties to parse
120
- * @returns Object containing responsive CSS styles
121
- */
122
- function parseResponsiveCssStyles(properties) {
123
- return {
124
- default: parseCssStylesFromProperties(properties),
125
- tablet: parseCssStylesFromProperties(properties, "tablet"),
126
- mobile: parseCssStylesFromProperties(properties, "mobile")
127
- };
128
- }
129
- /**
130
- * Parses raw bounds data into a standardized bounds structure
131
- *
132
- * @param bounds - Raw bounds data in OCHRE format
133
- * @returns Parsed bounds object
134
- */
135
- function parseBounds(bounds) {
136
- const [southWest, northEast] = bounds.trimStart().startsWith("[") ? parseJsonBounds(bounds) : bounds.split(";").map((pair) => pair.split(",").map((coordinate) => Number(coordinate.trim())));
137
- if (southWest?.length !== 2 || northEast?.length !== 2 || southWest.some((coordinate) => Number.isNaN(coordinate)) || northEast.some((coordinate) => Number.isNaN(coordinate))) throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
138
- return [[southWest[0], southWest[1]], [northEast[0], northEast[1]]];
139
- }
140
- function parseJsonBounds(bounds) {
141
- let parsed;
142
- try {
143
- parsed = JSON.parse(bounds);
144
- } catch {
145
- throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
146
- }
147
- if (!isNumberPairArray(parsed)) throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
148
- return parsed;
149
- }
150
- function isNumberPairArray(value) {
151
- return Array.isArray(value) && value.every((pair) => Array.isArray(pair) && pair.every((coordinate) => typeof coordinate === "number"));
152
- }
153
- /**
154
- * Parses all context option arrays from an options object.
155
- *
156
- * @param options - Options object containing context options
157
- * @param options.flattenContexts - Flatten contexts
158
- * @param options.suppressContexts - Suppress contexts
159
- * @param options.filterContexts - Filter contexts
160
- * @param options.sortContexts - Sort contexts
161
- * @param options.detailContexts - Detail contexts
162
- * @param options.downloadContexts - Download contexts
163
- * @param options.labelContexts - Label contexts
164
- * @param options.prominentContexts - Prominent contexts
165
- * @returns Parsed context options
166
- */
167
- function parseAllOptionContexts(options, parserOptions) {
168
- function handleContexts(v) {
169
- return parseContexts(v ?? [], parserOptions);
170
- }
171
- function handleFilterContexts(v) {
172
- return parseFilterContexts(v ?? [], parserOptions);
173
- }
174
- return {
175
- flatten: handleContexts(options.flattenContexts),
176
- suppress: handleContexts(options.suppressContexts),
177
- filter: handleFilterContexts(options.filterContexts),
178
- sort: handleContexts(options.sortContexts),
179
- detail: handleContexts(options.detailContexts),
180
- download: handleContexts(options.downloadContexts),
181
- label: handleContexts(options.labelContexts),
182
- prominent: handleContexts(options.prominentContexts)
183
- };
184
- }
185
- function parseWebsiteScopes(scopes, options) {
186
- if (scopes == null) return null;
187
- return Array.from(scopes.scope, (scope) => ({
188
- uuid: scope.uuid.payload,
189
- type: scope.uuid.type,
190
- identification: parseIdentification(scope.identification, options)
191
- }));
192
- }
193
- function parseWebsiteOptions(rawOptions, options) {
194
- const parsedOptions = {
195
- scopes: parseWebsiteScopes(rawOptions?.scopes, options),
196
- contextTree: rawOptions == null ? null : parseAllOptionContexts(rawOptions, options),
197
- labels: { title: null }
198
- };
199
- const notes = parseNotes(rawOptions?.notes, options);
200
- for (const note of notes) {
201
- if (note.title?.getText() !== "Title label") continue;
202
- parsedOptions.labels.title = note.content;
203
- break;
204
- }
205
- return parsedOptions;
206
- }
207
- function parseStylesheets(styles) {
208
- const parsedStyles = [];
209
- for (const style of styles) {
210
- const defaultStyles = [];
211
- for (const [label, value] of Object.entries(style)) {
212
- if ([
213
- "variableUuid",
214
- "valueUuid",
215
- "category",
216
- "payload",
217
- "content"
218
- ].includes(label)) continue;
219
- const valueString = value?.toString();
220
- if (valueString != null) defaultStyles.push({
221
- label,
222
- value: valueString
223
- });
224
- }
225
- const stylesByViewport = {
226
- default: defaultStyles,
227
- tablet: [],
228
- mobile: []
229
- };
230
- if (style.category === "propertyValue" || style.valueUuid != null) {
231
- if (style.valueUuid == null) throw new Error(`Stylesheet property value "${style.variableUuid}" is missing a value UUID`, { cause: style });
232
- parsedStyles.push({
233
- uuid: style.valueUuid,
234
- category: "propertyValue",
235
- variableUuid: style.variableUuid,
236
- icon: style.lucideIcon ?? null,
237
- styles: stylesByViewport
238
- });
239
- continue;
240
- }
241
- parsedStyles.push({
242
- uuid: style.variableUuid,
243
- category: "propertyVariable",
244
- icon: style.lucideIcon ?? null,
245
- styles: stylesByViewport
246
- });
247
- }
248
- return parsedStyles;
249
- }
250
- /**
251
- * Default values for a collection's display properties, shared between the
252
- * "collection" component and the "query" component's collection overrides.
253
- */
254
- const COLLECTION_PROPERTY_DEFAULTS = {
255
- variant: "slide",
256
- paginationVariant: "default",
257
- loadingVariant: "skeleton",
258
- minimumColumnCount: null,
259
- maximumColumnCount: null,
260
- expectedItemCount: null,
261
- isSortDisplayed: false,
262
- isUsingQueryParams: false,
263
- isInteractive: true
264
- };
265
- /**
266
- * Default values for a collection's image display properties, merged separately
267
- * from {@link COLLECTION_PROPERTY_DEFAULTS} because overrides of the nested
268
- * "image" object are themselves partial.
269
- */
270
- const COLLECTION_IMAGE_DEFAULTS = {
271
- layout: "start",
272
- fit: "fit",
273
- alignment: null,
274
- isPlaceholderDisplayed: true
275
- };
276
- /**
277
- * Reads the collection display properties explicitly set on a reader, omitting
278
- * any that are unset. The "collection" component merges these over
279
- * {@link COLLECTION_PROPERTY_DEFAULTS} and {@link COLLECTION_IMAGE_DEFAULTS},
280
- * while the "query" component uses them as partial overrides for its embedded
281
- * collection.
282
- */
283
- function parseCollectionPropertyOverrides(reader) {
284
- const overrides = {};
285
- const imageOverrides = {};
286
- function read(key, label) {
287
- const value = reader.value(label);
288
- if (value != null) overrides[key] = value;
289
- }
290
- function readImage(key, label) {
291
- const value = reader.value(label);
292
- if (value != null) imageOverrides[key] = value;
293
- }
294
- read("variant", "variant");
295
- read("paginationVariant", "pagination-variant");
296
- read("loadingVariant", "loading-variant");
297
- read("minimumColumnCount", "minimum-column-count");
298
- read("maximumColumnCount", "maximum-column-count");
299
- read("expectedItemCount", "item-count");
300
- read("isSortDisplayed", "sort-displayed");
301
- read("isUsingQueryParams", "is-using-query-params");
302
- read("isInteractive", "is-interactive");
303
- readImage("layout", "image-layout");
304
- readImage("fit", "image-fit");
305
- readImage("alignment", "image-alignment");
306
- readImage("isPlaceholderDisplayed", "image-placeholder-displayed");
307
- return Object.keys(imageOverrides).length > 0 ? {
308
- ...overrides,
309
- image: imageOverrides
310
- } : overrides;
311
- }
312
- /**
313
- * Parses the "use-property" values defining which item properties a collection
314
- * displays, returning `null` when none are set.
315
- */
316
- function parseCollectionDisplayedProperties(reader) {
317
- const property = reader.property("use-property");
318
- if (property == null) return null;
319
- return property.values.filter((value) => value.uuid !== null).map((value) => ({
320
- uuid: value.uuid,
321
- label: value.label
322
- }));
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
- }
768
- /**
769
- * Parses raw web element properties into a standardized WebElementComponent structure
770
- *
771
- * @param componentProperty - Raw component property data in OCHRE format
772
- * @param elementResource - Raw element resource data in OCHRE format
773
- * @returns Parsed WebElementComponent object
774
- */
775
- function parseWebElementProperties(componentProperty, elementResource, options, context) {
776
- const unparsedComponentName = componentProperty.values[0].content;
777
- const componentNameResult = v.safeParse(componentSchema, unparsedComponentName);
778
- const componentName = componentNameResult.success ? componentNameResult.output : void 0;
779
- const websiteLinks = parseLinks(elementResource.links, options);
780
- const parameters = {
781
- componentProperty,
782
- componentReader: websitePresentationReader(componentProperty.properties),
783
- elementResource,
784
- websiteLinks,
785
- options,
786
- context
787
- };
788
- switch (componentName) {
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);
810
- default: throw new Error(`Invalid or non-implemented component name “${unparsedComponentName.toString()}” for the following element: “${parseStringContent(elementResource.identification.label, options)}”`);
811
- }
812
- }
813
- function parseWebTitle(properties, identification, overrides) {
814
- const title = {
815
- label: identification.label,
816
- variant: "default",
817
- properties: {
818
- isNameDisplayed: overrides?.isNameDisplayed ?? false,
819
- isDescriptionDisplayed: overrides?.isDescriptionDisplayed ?? false,
820
- isDateDisplayed: overrides?.isDateDisplayed ?? false,
821
- isCreatorsDisplayed: overrides?.isCreatorsDisplayed ?? false,
822
- isCountDisplayed: overrides?.isCountDisplayed ?? false
823
- }
824
- };
825
- const titleReader = websitePresentationReader(properties).nestedByValue("presentation", "title");
826
- if (titleReader.size > 0) {
827
- title.variant = titleReader.valueOr("variant", "default");
828
- title.properties.isNameDisplayed = titleReader.valueOr("name-displayed", false);
829
- title.properties.isDescriptionDisplayed = titleReader.valueOr("description-displayed", false);
830
- title.properties.isDateDisplayed = titleReader.valueOr("date-displayed", false);
831
- title.properties.isCreatorsDisplayed = titleReader.valueOr("creators-displayed", false);
832
- title.properties.isCountDisplayed = titleReader.valueOr("count-displayed", false);
833
- }
834
- return title;
835
- }
836
- /**
837
- * Parses raw web element data into a standardized WebElement structure
838
- *
839
- * @param elementResource - Raw element resource data in OCHRE format
840
- * @returns Parsed WebElement object
841
- */
842
- function parseWebElement(elementResource, options, context) {
843
- const identification = parseIdentification(elementResource.identification, options);
844
- const elementProperties = parseSimplifiedProperties(elementResource.properties, options);
845
- const presentationProperty = websitePresentationReader(elementProperties).requiredProperty("presentation", `Presentation property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`);
846
- const properties = parseWebElementProperties(websitePresentationReader(presentationProperty.properties).requiredProperty("component", `Component property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`), elementResource, options, context);
847
- const cssStyles = parseResponsiveCssStyles(elementProperties);
848
- const title = parseWebTitle(elementProperties, identification, {
849
- isNameDisplayed: [
850
- "annotated-image",
851
- "annotated-document",
852
- "collection"
853
- ].includes(properties.component),
854
- isCountDisplayed: properties.component === "collection"
855
- });
856
- return {
857
- uuid: elementResource.uuid,
858
- language: elementResource.lang ?? null,
859
- type: "element",
860
- title,
861
- cssStyles,
862
- ...properties
863
- };
864
- }
865
11
  function isSidebarResource(resource, options) {
866
12
  const resourceProperties = parseSimplifiedProperties(resource.properties, options);
867
13
  const resourceReader = websitePresentationReader(resourceProperties);
@@ -875,7 +21,7 @@ function parseWebBlockItems(resources, options, context) {
875
21
  if (resourceType === null) continue;
876
22
  switch (resourceType) {
877
23
  case "element":
878
- items.push(parseWebElement(resource, options, context));
24
+ items.push(parseWebElement(resource, options, context.pageSlugsByUuid));
879
25
  break;
880
26
  case "block": {
881
27
  const block = parseWebBlock(resource, options, context);
@@ -887,7 +33,7 @@ function parseWebBlockItems(resources, options, context) {
887
33
  return items;
888
34
  }
889
35
  function parseWebpageRedirect(redirectValue, context) {
890
- const redirectTarget = parseWebsiteLinkTarget(redirectValue, context);
36
+ const redirectTarget = parseWebsiteLinkTarget(redirectValue, context.pageSlugsByUuid);
891
37
  if (redirectTarget != null) {
892
38
  if (redirectValue?.href == null && redirectValue?.uuid != null) return {
893
39
  type: "page",
@@ -908,23 +54,21 @@ function parseWebpageRedirect(redirectValue, context) {
908
54
  return null;
909
55
  }
910
56
  /**
911
- * Parses raw webpage data into a standardized Webpage structure
57
+ * Parses a walked page node into a standardized Webpage structure
912
58
  *
913
- * @param webpageResource - Raw webpage resource data in OCHRE format
59
+ * @param page - The page node, with its slug and nesting already resolved
914
60
  * @returns Parsed Webpage object
915
61
  */
916
- function parseWebpage(webpageResource, options, context, slugPrefix) {
62
+ function parseWebpage(page, options, context) {
63
+ const webpageResource = page.resource;
917
64
  const webpageProperties = parseSimplifiedProperties(webpageResource.properties, options);
918
65
  const webpageReader = websitePresentationReader(webpageProperties);
919
- if (webpageReader.value("presentation") !== "page") return null;
920
66
  const identification = parseIdentification(webpageResource.identification, options);
921
- const slug = cleanWebsitePageSlug(webpageResource.slug);
922
- if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(webpageResource)})`, { cause: webpageResource });
923
67
  const returnWebpage = {
924
68
  uuid: webpageResource.uuid,
925
69
  type: "page",
926
70
  title: identification.label,
927
- slug: prefixSlug(slug, slugPrefix),
71
+ slug: page.slug,
928
72
  publicationDateTime: webpageResource.publicationDateTime ?? null,
929
73
  items: [],
930
74
  segments: [],
@@ -938,70 +82,51 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
938
82
  redirect: null,
939
83
  backgroundImage: null,
940
84
  sidebar: null,
941
- cssStyles: {
942
- default: [],
943
- tablet: [],
944
- mobile: []
945
- }
85
+ cssStyles: emptyResponsiveStyles()
946
86
  },
947
87
  webpages: []
948
88
  };
949
- const imageLink = findWebsiteLink(parseLinks(webpageResource.links, options), "resource", (link) => link.type === "image" || link.type === "IIIF");
89
+ const websiteLinks = parseLinks(webpageResource.links, options);
90
+ const imageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "image" || link.type === "IIIF");
950
91
  const webpageResources = normalizeWebsiteResources(webpageResource.resource);
951
92
  returnWebpage.items = parseWebBlockItems(webpageResources.filter((resource) => !isSidebarResource(resource, options)), options, context);
952
- returnWebpage.webpages = parseWebpages(webpageResources, options, context, slugPrefix == null ? void 0 : returnWebpage.slug);
953
- returnWebpage.segments = parseWebsiteSegments(webpageResource.resource, context, options, returnWebpage.slug);
93
+ returnWebpage.webpages = parseWebpages(page.children, options, context);
94
+ returnWebpage.segments = parseWebsiteSegments(page.segments, options, context);
954
95
  returnWebpage.properties.sidebar = parseSidebar(webpageResources, options, context);
955
96
  const pageReader = webpageReader.nestedByValue("presentation", "page");
956
97
  if (pageReader.size > 0) {
957
- returnWebpage.properties.isDisplayedInNavbar = pageReader.valueOr("displayed-in-navbar", true);
958
- returnWebpage.properties.width = pageReader.valueOr("width", "default");
959
- returnWebpage.properties.variant = pageReader.valueOr("variant", "default");
960
- returnWebpage.properties.isSidebarDisplayed = pageReader.valueOr("sidebar-displayed", true);
961
- returnWebpage.properties.isBreadcrumbsDisplayed = pageReader.valueOr("breadcrumbs-displayed", false);
962
- returnWebpage.properties.isNavbarSearchBarDisplayed = pageReader.valueOr("navbar-search-bar-displayed", true);
98
+ pageReader.readAll(returnWebpage.properties, {
99
+ isDisplayedInNavbar: "displayed-in-navbar",
100
+ width: "width",
101
+ variant: "variant",
102
+ isSidebarDisplayed: "sidebar-displayed",
103
+ isBreadcrumbsDisplayed: "breadcrumbs-displayed",
104
+ isNavbarSearchBarDisplayed: "navbar-search-bar-displayed"
105
+ });
963
106
  returnWebpage.properties.redirect = parseWebpageRedirect(pageReader.valueNode("redirect-to"), context);
964
107
  }
965
- if (imageLink != null) returnWebpage.properties.backgroundImage = {
966
- uuid: imageLink.uuid,
967
- label: imageLink.identification.label,
968
- description: imageLink.description,
969
- width: imageLink.image?.width ?? 0,
970
- height: imageLink.image?.height ?? 0,
971
- quality: "high"
972
- };
108
+ if (imageLink != null) returnWebpage.properties.backgroundImage = webImageFromLink(imageLink, "high");
973
109
  returnWebpage.properties.cssStyles = parseResponsiveCssStyles(webpageProperties);
974
110
  return returnWebpage;
975
111
  }
976
112
  /**
977
- * Parses raw webpage resources into an array of Webpage objects
113
+ * Parses walked page nodes into an array of Webpage objects
978
114
  *
979
- * @param webpageResources - Array of raw webpage resources in OCHRE format
115
+ * @param pages - The page nodes to parse
980
116
  * @returns Array of parsed Webpage objects
981
117
  */
982
- function parseWebpages(webpageResources, options, context, slugPrefix) {
983
- const returnPages = [];
984
- for (const webpageResource of webpageResources) {
985
- const webpage = parseWebpage(webpageResource, options, context, slugPrefix);
986
- if (webpage !== null) returnPages.push(webpage);
987
- }
988
- return returnPages;
118
+ function parseWebpages(pages, options, context) {
119
+ return Array.from(pages, (page) => parseWebpage(page, options, context));
989
120
  }
990
121
  function parseWebpageView(view, options, context) {
991
- return parseWebpages(view?.resource ?? [], options, context)[0] ?? null;
122
+ const pages = readWebsitePages(view?.resource, options);
123
+ return parseWebpages(pages, options, {
124
+ ...context,
125
+ pageSlugsByUuid: collectWebsitePageSlugs(pages, options)
126
+ })[0] ?? null;
992
127
  }
993
- function parseWebsiteSegments(resources, context, options, slugPrefix) {
994
- const segments = [];
995
- const segmentResources = resources ?? [];
996
- for (const resource of segmentResources) {
997
- if (!("segments" in resource)) continue;
998
- for (const tree of resource.segments.tree) {
999
- const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
1000
- if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
1001
- segments.push(parseWebsiteTree(tree, context, "segment", options, prefixSlug(segmentSlug, slugPrefix)));
1002
- }
1003
- }
1004
- return segments;
128
+ function parseWebsiteSegments(segments, options, context) {
129
+ return Array.from(segments, (segment) => parseWebsiteTree(segment.tree, options, context, "segment", segment.slugPrefix));
1005
130
  }
1006
131
  /**
1007
132
  * Parses raw sidebar data into a standardized Sidebar structure
@@ -1016,17 +141,22 @@ function parseSidebar(resources, options, context) {
1016
141
  const items = parseWebBlockItems(normalizeWebsiteResources(sidebarResource.resource), options, context);
1017
142
  if (items.length === 0) return null;
1018
143
  const sidebarReader = websitePresentationReader(sidebarBaseProperties).nestedByValue("presentation", "element").nestedByValue("component", "sidebar");
1019
- return {
144
+ const sidebar = {
1020
145
  isDisplayed: true,
1021
146
  items,
1022
147
  title: parseWebTitle(sidebarBaseProperties, parseIdentification(sidebarResource.identification, options)),
1023
- layout: sidebarReader.valueOr("layout", "start"),
1024
- mobileLayout: sidebarReader.valueOr("layout-mobile", "default"),
148
+ layout: "start",
149
+ mobileLayout: "default",
1025
150
  cssStyles: parseResponsiveCssStyles(sidebarBaseProperties)
1026
151
  };
152
+ sidebarReader.readAll(sidebar, {
153
+ layout: "layout",
154
+ mobileLayout: "layout-mobile"
155
+ });
156
+ return sidebar;
1027
157
  }
1028
158
  function parseWebAccordionItem(elementResource, childResources, options, context) {
1029
- const trigger = parseWebElement(elementResource, options, context);
159
+ const trigger = parseWebElement(elementResource, options, context.pageSlugsByUuid);
1030
160
  const items = parseWebBlockItems(childResources, options, context);
1031
161
  return {
1032
162
  uuid: trigger.uuid,
@@ -1038,19 +168,25 @@ function parseWebAccordionItem(elementResource, childResources, options, context
1038
168
  function parseBlockOverwrite(overwriteReader, isDefaultLayoutAccordion) {
1039
169
  if (overwriteReader.size === 0) return null;
1040
170
  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,
171
+ layout: void 0,
172
+ wrap: void 0,
173
+ spacing: void 0,
174
+ gap: void 0,
1045
175
  isAccordionEnabled: void 0,
1046
176
  isAccordionExpandedByDefault: void 0,
1047
177
  isAccordionSidebarDisplayed: void 0
1048
178
  };
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
- }
179
+ overwriteReader.readAll(properties, {
180
+ layout: "layout",
181
+ wrap: "wrap",
182
+ spacing: "spacing",
183
+ gap: "gap"
184
+ });
185
+ if (isDefaultLayoutAccordion || properties.layout === "accordion") overwriteReader.readAll(properties, {
186
+ isAccordionEnabled: "accordion-enabled",
187
+ isAccordionExpandedByDefault: "accordion-expanded",
188
+ isAccordionSidebarDisplayed: "accordion-sidebar-displayed"
189
+ });
1054
190
  const cleanedProperties = cleanObject(properties);
1055
191
  return Object.keys(cleanedProperties).length > 0 ? cleanedProperties : null;
1056
192
  }
@@ -1065,7 +201,7 @@ function parseAccordionAwareBlockItems(blockResources, options, context, isSuppo
1065
201
  case "element": {
1066
202
  const childResources = normalizeWebsiteResources(resource.resource);
1067
203
  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));
204
+ blockItems.push(isSupportingAccordionItems && componentType === "text" && childResources.length > 0 ? parseWebAccordionItem(resource, childResources, options, context) : parseWebElement(resource, options, context.pageSlugsByUuid));
1069
205
  break;
1070
206
  }
1071
207
  case "block": {
@@ -1101,23 +237,26 @@ function parseWebBlock(blockResource, options, context) {
1101
237
  mobile: null,
1102
238
  tablet: null
1103
239
  },
1104
- cssStyles: {
1105
- default: [],
1106
- tablet: [],
1107
- mobile: []
1108
- }
240
+ cssStyles: emptyResponsiveStyles()
1109
241
  };
1110
242
  const blockReader = websitePresentationReader(blockProperties).nestedByValue("presentation", "block");
1111
243
  if (blockReader.size > 0) {
1112
- returnBlock.properties.default.layout = blockReader.valueOr("layout", "vertical");
1113
- returnBlock.properties.default.wrap = blockReader.valueOr("wrap", "nowrap");
244
+ blockReader.readAll(returnBlock.properties.default, {
245
+ layout: "layout",
246
+ wrap: "wrap",
247
+ spacing: "spacing",
248
+ gap: "gap"
249
+ });
1114
250
  if (returnBlock.properties.default.layout === "accordion") {
1115
- returnBlock.properties.default.isAccordionEnabled = blockReader.valueOr("accordion-enabled", true);
1116
- returnBlock.properties.default.isAccordionExpandedByDefault = blockReader.valueOr("accordion-expanded", true);
1117
- returnBlock.properties.default.isAccordionSidebarDisplayed = blockReader.valueOr("accordion-sidebar-displayed", false);
251
+ returnBlock.properties.default.isAccordionEnabled = true;
252
+ returnBlock.properties.default.isAccordionExpandedByDefault = true;
253
+ returnBlock.properties.default.isAccordionSidebarDisplayed = false;
254
+ blockReader.readAll(returnBlock.properties.default, {
255
+ isAccordionEnabled: "accordion-enabled",
256
+ isAccordionExpandedByDefault: "accordion-expanded",
257
+ isAccordionSidebarDisplayed: "accordion-sidebar-displayed"
258
+ });
1118
259
  }
1119
- returnBlock.properties.default.spacing = blockReader.valueOr("spacing", null);
1120
- returnBlock.properties.default.gap = blockReader.valueOr("gap", null);
1121
260
  const isDefaultLayoutAccordion = returnBlock.properties.default.layout === "accordion";
1122
261
  returnBlock.properties.tablet = parseBlockOverwrite(blockReader.nested("overwrite-tablet"), isDefaultLayoutAccordion);
1123
262
  returnBlock.properties.mobile = parseBlockOverwrite(blockReader.nested("overwrite-mobile"), isDefaultLayoutAccordion);
@@ -1126,215 +265,12 @@ function parseWebBlock(blockResource, options, context) {
1126
265
  returnBlock.cssStyles = parseResponsiveCssStyles(blockProperties);
1127
266
  return returnBlock;
1128
267
  }
1129
- /**
1130
- * Parses raw website properties into a standardized Website properties structure
1131
- *
1132
- * @param properties - Array of raw website properties in OCHRE format
1133
- * @returns Parsed WebsiteProperties object
1134
- */
1135
- function parseWebsiteProperties(properties, websiteTree, sidebar, options, parent) {
1136
- const mainProperties = parseSimplifiedProperties({ property: properties }, options);
1137
- const websiteReader = websitePresentationReader(mainProperties).nested("presentation");
1138
- const returnProperties = {
1139
- type: websiteReader.valueOr("webUI", parent?.type ?? "traditional"),
1140
- status: websiteReader.valueOr("status", parent?.status ?? "development"),
1141
- versionLabel: websiteReader.valueOr("version-label", parent?.versionLabel ?? "release"),
1142
- privacy: websiteReader.valueOr("privacy", parent?.privacy ?? "public"),
1143
- contact: parent?.contact ?? null,
1144
- loadingVariant: "spinner",
1145
- theme: {
1146
- isThemeToggleDisplayed: true,
1147
- defaultTheme: "system"
1148
- },
1149
- icon: {
1150
- logoUuid: null,
1151
- faviconUuid: null,
1152
- appleTouchIconUuid: null
1153
- },
1154
- navbar: {
1155
- isDisplayed: true,
1156
- variant: "default",
1157
- alignment: "start",
1158
- isProjectDisplayed: true,
1159
- searchBarBoundElementUuid: null,
1160
- items: parent?.navbar.items ?? null
1161
- },
1162
- footer: {
1163
- isDisplayed: true,
1164
- logoUuid: null,
1165
- items: parent?.footer.items ?? null
1166
- },
1167
- sidebar: sidebar ?? parent?.sidebar ?? null,
1168
- itemPage: {
1169
- isMainContentDisplayed: parent?.itemPage.isMainContentDisplayed ?? true,
1170
- description: {
1171
- isDisplayed: parent?.itemPage.description.isDisplayed ?? true,
1172
- isHeaderDisplayed: parent?.itemPage.description.isHeaderDisplayed ?? true
1173
- },
1174
- document: {
1175
- isDisplayed: parent?.itemPage.document.isDisplayed ?? true,
1176
- isHeaderDisplayed: parent?.itemPage.document.isHeaderDisplayed ?? true
1177
- },
1178
- notes: {
1179
- isDisplayed: parent?.itemPage.notes.isDisplayed ?? true,
1180
- isHeaderDisplayed: parent?.itemPage.notes.isHeaderDisplayed ?? true,
1181
- variant: parent?.itemPage.notes.variant ?? "discrete"
1182
- },
1183
- events: {
1184
- isDisplayed: parent?.itemPage.events.isDisplayed ?? true,
1185
- isHeaderDisplayed: parent?.itemPage.events.isHeaderDisplayed ?? true,
1186
- variant: parent?.itemPage.events.variant ?? "tabular"
1187
- },
1188
- periods: {
1189
- isDisplayed: parent?.itemPage.periods.isDisplayed ?? true,
1190
- isHeaderDisplayed: parent?.itemPage.periods.isHeaderDisplayed ?? true
1191
- },
1192
- isPropertiesDisplayed: parent?.itemPage.isPropertiesDisplayed ?? true,
1193
- bibliography: {
1194
- isDisplayed: parent?.itemPage.bibliography.isDisplayed ?? true,
1195
- isHeaderDisplayed: parent?.itemPage.bibliography.isHeaderDisplayed ?? true
1196
- },
1197
- isPropertyValuesGrouped: parent?.itemPage.isPropertyValuesGrouped ?? true,
1198
- isPublicationDateTimeDisplayed: parent?.itemPage.isPublicationDateTimeDisplayed ?? true,
1199
- isPersistentIdentifierDisplayed: parent?.itemPage.isPersistentIdentifierDisplayed ?? true,
1200
- iiifViewer: parent?.itemPage.iiifViewer ?? "universal-viewer"
1201
- },
1202
- options: {
1203
- contextTree: parent?.options.contextTree ?? null,
1204
- scopes: parent?.options.scopes ?? null,
1205
- labels: { title: parent?.options.labels.title ?? null },
1206
- stylesheets: { properties: parent?.options.stylesheets.properties ?? [] }
1207
- }
1208
- };
1209
- const contactProperty = websiteReader.property("contact");
1210
- if (contactProperty !== null) {
1211
- const contactContent = contactProperty.values[0]?.content.toString().split(";") ?? [];
1212
- if (contactContent.length === 2) returnProperties.contact = {
1213
- name: contactContent[0],
1214
- email: contactContent[1] ?? null
1215
- };
1216
- else throw new Error(`Contact property must use “name;email”, got “${contactProperty.values[0]?.content}” (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
1217
- }
1218
- returnProperties.loadingVariant = websiteReader.valueOr("loading-variant", parent?.loadingVariant ?? "spinner");
1219
- returnProperties.theme.isThemeToggleDisplayed = websiteReader.valueOr("supports-theme-toggle", parent?.theme.isThemeToggleDisplayed ?? true);
1220
- returnProperties.theme.defaultTheme = websiteReader.valueOr("default-theme", parent?.theme.defaultTheme ?? "system");
1221
- returnProperties.icon.logoUuid = websiteReader.uuid("navbar-logo") ?? parent?.icon.logoUuid ?? null;
1222
- returnProperties.icon.faviconUuid = websiteReader.uuid("favicon-ico") ?? parent?.icon.faviconUuid ?? null;
1223
- returnProperties.icon.appleTouchIconUuid = websiteReader.uuid("favicon-img") ?? parent?.icon.appleTouchIconUuid ?? null;
1224
- returnProperties.navbar.isDisplayed = websiteReader.valueOr("navbar-displayed", parent?.navbar.isDisplayed ?? true);
1225
- returnProperties.navbar.variant = websiteReader.valueOr("navbar-variant", parent?.navbar.variant ?? "default");
1226
- returnProperties.navbar.alignment = websiteReader.valueOr("navbar-alignment", parent?.navbar.alignment ?? "start");
1227
- returnProperties.navbar.isProjectDisplayed = websiteReader.valueOr("navbar-project-displayed", parent?.navbar.isProjectDisplayed ?? true);
1228
- returnProperties.navbar.searchBarBoundElementUuid = websiteReader.uuid("bound-element-navbar-search-bar") ?? parent?.navbar.searchBarBoundElementUuid ?? null;
1229
- returnProperties.footer.isDisplayed = websiteReader.valueOr("footer-displayed", parent?.footer.isDisplayed ?? true);
1230
- returnProperties.footer.logoUuid = websiteReader.uuid("footer-logo") ?? parent?.footer.logoUuid ?? null;
1231
- const itemPageReader = websiteReader.nestedByValue("page-type", "item-page");
1232
- if (itemPageReader.size > 0) {
1233
- for (const [key, slug] of [
1234
- ["description", "description"],
1235
- ["document", "document"],
1236
- ["notes", "notes"],
1237
- ["events", "events"],
1238
- ["periods", "periods"],
1239
- ["bibliography", "bibliography"]
1240
- ]) {
1241
- const section = returnProperties.itemPage[key];
1242
- section.isDisplayed = itemPageReader.valueOr(`item-page-${slug}-displayed`, section.isDisplayed);
1243
- section.isHeaderDisplayed = itemPageReader.valueOr(`item-page-${slug}-header-displayed`, section.isHeaderDisplayed);
1244
- }
1245
- returnProperties.itemPage.notes.variant = itemPageReader.valueOr("item-page-notes-display-variant", returnProperties.itemPage.notes.variant);
1246
- returnProperties.itemPage.events.variant = itemPageReader.valueOr("item-page-events-display-variant", returnProperties.itemPage.events.variant);
1247
- returnProperties.itemPage.isPropertyValuesGrouped = itemPageReader.valueOr("item-page-property-values-grouped", returnProperties.itemPage.isPropertyValuesGrouped);
1248
- returnProperties.itemPage.isPublicationDateTimeDisplayed = itemPageReader.valueOr("item-page-publication-date-time-displayed", returnProperties.itemPage.isPublicationDateTimeDisplayed);
1249
- returnProperties.itemPage.isPersistentIdentifierDisplayed = itemPageReader.valueOr("item-page-persistent-identifier-displayed", returnProperties.itemPage.isPersistentIdentifierDisplayed);
1250
- returnProperties.itemPage.iiifViewer = itemPageReader.valueOr("item-page-iiif-viewer", returnProperties.itemPage.iiifViewer);
1251
- }
1252
- if (websiteTree.options != null) {
1253
- const parsedOptions = parseWebsiteOptions(websiteTree.options, options);
1254
- returnProperties.options.scopes = (parsedOptions.scopes != null && parsedOptions.scopes.length > 0 ? parsedOptions : returnProperties.options).scopes;
1255
- returnProperties.options.contextTree = parsedOptions.contextTree ?? returnProperties.options.contextTree;
1256
- returnProperties.options.labels = { title: parsedOptions.labels.title ?? returnProperties.options.labels.title };
1257
- }
1258
- if ("styleOptions" in websiteTree && websiteTree.styleOptions != null) {
1259
- const stylesheetProperties = parseStylesheets(websiteTree.styleOptions.style);
1260
- if (stylesheetProperties.length > 0) returnProperties.options.stylesheets.properties = stylesheetProperties;
1261
- }
1262
- return returnProperties;
1263
- }
1264
- function parseContextItem(contextItemToParse, options) {
1265
- let type = "";
1266
- const levels = [];
1267
- const levelsToParse = contextItemToParse.levels?.level ?? [];
1268
- for (const level of levelsToParse) {
1269
- const [rawVariableUuid = "", rawValueUuid] = level.payload.split(",", 2);
1270
- const valueUuid = rawValueUuid == null || rawValueUuid.trim() === "null" ? null : rawValueUuid.trim();
1271
- type = level.dataType ?? type;
1272
- levels.push({
1273
- variableUuid: rawVariableUuid.trim(),
1274
- valueUuid
1275
- });
1276
- }
1277
- return {
1278
- context: levels,
1279
- type,
1280
- identification: parseIdentification(contextItemToParse.identification, options),
1281
- description: parseContentLike(contextItemToParse.description, options)
1282
- };
1283
- }
1284
- function parseFilterContextDisplay(filterOption) {
1285
- switch (filterOption) {
1286
- case "inline-displayed": return {
1287
- isInlineDisplayed: true,
1288
- isSidebarDisplayed: false,
1289
- isSidebarOpen: false
1290
- };
1291
- case "inline-sidebar-displayed-closed": return {
1292
- isInlineDisplayed: true,
1293
- isSidebarDisplayed: true,
1294
- isSidebarOpen: false
1295
- };
1296
- case "inline-sidebar-displayed-open": return {
1297
- isInlineDisplayed: true,
1298
- isSidebarDisplayed: true,
1299
- isSidebarOpen: true
1300
- };
1301
- case "sidebar-displayed-closed": return {
1302
- isInlineDisplayed: false,
1303
- isSidebarDisplayed: true,
1304
- isSidebarOpen: false
1305
- };
1306
- case "sidebar-displayed-open": return {
1307
- isInlineDisplayed: false,
1308
- isSidebarDisplayed: true,
1309
- isSidebarOpen: true
1310
- };
1311
- default: return {
1312
- isInlineDisplayed: false,
1313
- isSidebarDisplayed: false,
1314
- isSidebarOpen: false
1315
- };
1316
- }
1317
- }
1318
- function parseContexts(contextLevels, options) {
1319
- const contextTreeLevels = [];
1320
- for (const contextLevel of contextLevels) for (const contextItemToParse of contextLevel.context) contextTreeLevels.push(parseContextItem(contextItemToParse, options));
1321
- return contextTreeLevels;
1322
- }
1323
- function parseFilterContexts(filterContextLevels, options) {
1324
- const filterContextTreeLevels = [];
1325
- for (const filterContextLevel of filterContextLevels) for (const contextItemToParse of filterContextLevel.context) filterContextTreeLevels.push({
1326
- ...parseContextItem(contextItemToParse, options),
1327
- filterType: contextItemToParse.filterType ?? "property",
1328
- filterVariant: contextItemToParse.filterVariant ?? null,
1329
- ...parseFilterContextDisplay(contextItemToParse.filterOption)
1330
- });
1331
- return filterContextTreeLevels;
1332
- }
1333
- function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
268
+ function parseWebsiteTree(websiteTree, options, context, type, slugPrefix) {
1334
269
  if (!websiteTree.properties) throw new Error(`Website properties not found (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
1335
270
  if (type === "website" && websiteTree.items?.resource == null) throw new Error(`Website pages not found (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
1336
271
  const resources = normalizeWebsiteResources(websiteTree.items?.resource);
1337
- const pageSlugsByUuid = context.pageSlugsByUuid ?? collectWebsitePageSlugs(websiteTree.items?.resource, options, slugPrefix);
272
+ const pages = readWebsitePages(websiteTree.items?.resource, options, slugPrefix);
273
+ const pageSlugsByUuid = context.pageSlugsByUuid ?? collectWebsitePageSlugs(pages, options);
1338
274
  const treeContext = {
1339
275
  ...context,
1340
276
  pageSlugsByUuid
@@ -1351,11 +287,11 @@ function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
1351
287
  identification: parseIdentification(websiteTree.identification, options),
1352
288
  creators: websiteTree.creators ? parsePersonList(websiteTree.creators.creator, options) : [],
1353
289
  license,
1354
- items: parseWebpages(resources, options, {
290
+ items: parseWebpages(pages, options, {
1355
291
  ...treeContext,
1356
292
  parentProperties: properties,
1357
293
  parentLicense: license
1358
- }, slugPrefix),
294
+ }),
1359
295
  properties
1360
296
  };
1361
297
  }
@@ -1363,17 +299,20 @@ function parseWebsite(data, options) {
1363
299
  const rawOchre = data.result.ochre;
1364
300
  const metadataLanguages = parseMetadataLanguages(rawOchre);
1365
301
  const languages = resolveLanguages(options?.languages, metadataLanguages);
1366
- const parserOptions = { languages };
1367
302
  const defaultLanguage = resolveDefaultLanguage(rawOchre, languages);
303
+ const parserOptions = {
304
+ languages,
305
+ defaultLanguage
306
+ };
1368
307
  const websiteTree = rawOchre.tree[0];
1369
308
  if (websiteTree == null) throw new Error("Website tree not found", { cause: data });
1370
- return parseWebsiteTree(websiteTree, {
309
+ return parseWebsiteTree(websiteTree, parserOptions, {
1371
310
  belongsTo: {
1372
311
  uuid: rawOchre.uuidBelongsTo,
1373
312
  abbreviation: rawOchre.belongsTo
1374
313
  },
1375
314
  metadata: parseMetadata(rawOchre, parserOptions, defaultLanguage)
1376
- }, "website", parserOptions);
315
+ }, "website");
1377
316
  }
1378
317
  //#endregion
1379
- export { parseBounds, parseWebpageView, parseWebsite };
318
+ export { parseWebpageView, parseWebsite };