ochre-sdk 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.mts +17 -0
- package/dist/constants.mjs +85 -0
- package/dist/fetchers/gallery.d.mts +38 -0
- package/dist/fetchers/gallery.mjs +91 -0
- package/dist/fetchers/item-links.d.mts +32 -0
- package/dist/fetchers/item-links.mjs +120 -0
- package/dist/fetchers/item.d.mts +74 -0
- package/dist/fetchers/item.mjs +146 -0
- package/dist/fetchers/set/items.d.mts +48 -0
- package/dist/fetchers/set/items.mjs +268 -0
- package/dist/fetchers/set/property-values.d.mts +46 -0
- package/dist/fetchers/set/property-values.mjs +514 -0
- package/dist/fetchers/website.d.mts +25 -0
- package/dist/fetchers/website.mjs +38 -0
- package/dist/getters.d.mts +193 -0
- package/dist/getters.mjs +341 -0
- package/dist/helpers.d.mts +18 -0
- package/dist/helpers.mjs +33 -0
- package/dist/index.d.mts +12 -1971
- package/dist/index.mjs +9 -7236
- package/dist/parsers/helpers.d.mts +27 -0
- package/dist/parsers/helpers.mjs +53 -0
- package/dist/parsers/index.d.mts +65 -0
- package/dist/parsers/index.mjs +1338 -0
- package/dist/parsers/mdx.d.mts +4 -0
- package/dist/parsers/mdx.mjs +9 -0
- package/dist/parsers/multilingual.d.mts +189 -0
- package/dist/parsers/multilingual.mjs +410 -0
- package/dist/parsers/string.d.mts +29 -0
- package/dist/parsers/string.mjs +445 -0
- package/dist/parsers/website/index.d.mts +20 -0
- package/dist/parsers/website/index.mjs +1245 -0
- package/dist/parsers/website/reader.d.mts +29 -0
- package/dist/parsers/website/reader.mjs +75 -0
- package/dist/query.d.mts +13 -0
- package/dist/query.mjs +827 -0
- package/dist/schemas.d.mts +79 -0
- package/dist/schemas.mjs +223 -0
- package/dist/types/index.d.mts +840 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/website.d.mts +501 -0
- package/dist/types/website.mjs +1 -0
- package/dist/utils.d.mts +34 -0
- package/dist/utils.mjs +172 -0
- package/dist/xml/metadata.d.mts +5 -0
- package/dist/xml/metadata.mjs +30 -0
- package/dist/xml/schemas.d.mts +13 -0
- package/dist/xml/schemas.mjs +849 -0
- package/dist/xml/types.d.mts +901 -0
- package/dist/xml/types.mjs +1 -0
- package/package.json +19 -17
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
import { componentSchema } from "../../schemas.mjs";
|
|
2
|
+
import { parseXMLContent } from "../string.mjs";
|
|
3
|
+
import { cleanObject, parseLicense, parseStringContent } from "../helpers.mjs";
|
|
4
|
+
import { parseBibliographyList, parseIdentification, parseLinks, parseMetadata, parseMetadataLanguages, parseNotes, parsePersonList, parseSimplifiedProperties, resolveDefaultLanguage, resolveLanguages } from "../index.mjs";
|
|
5
|
+
import { websitePresentationReader } from "./reader.mjs";
|
|
6
|
+
import * as v from "valibot";
|
|
7
|
+
//#region src/parsers/website/index.ts
|
|
8
|
+
function isWebsiteLink(link, category) {
|
|
9
|
+
return link.category === category;
|
|
10
|
+
}
|
|
11
|
+
function findWebsiteLink(links, category, predicate) {
|
|
12
|
+
for (const link of links) if (isWebsiteLink(link, category) && (predicate == null || predicate(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
|
+
for (const resource of resources ?? []) {
|
|
30
|
+
if ("identification" in resource) {
|
|
31
|
+
normalized.push(resource);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if ("resource" in resource) normalized.push(...resource.resource);
|
|
35
|
+
}
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
const SEGMENT_UNIQUE_SLUG_PREFIX_REGEX = /^\$[^-]*-/;
|
|
39
|
+
function prefixSlug(slug, slugPrefix) {
|
|
40
|
+
if (slugPrefix == null || slugPrefix === "") return slug;
|
|
41
|
+
if (slug === "") return slugPrefix;
|
|
42
|
+
return `${slugPrefix}/${slug}`;
|
|
43
|
+
}
|
|
44
|
+
function formatXMLWebsiteResourceMetadata(resource) {
|
|
45
|
+
const metadata = [`label “${parseStringContent(resource.identification.label)}”`, `uuid “${resource.uuid}”`];
|
|
46
|
+
if (resource.slug != null) metadata.push(`slug “${resource.slug}”`);
|
|
47
|
+
if (resource.identification.abbreviation != null) metadata.push(`abbreviation “${parseStringContent(resource.identification.abbreviation)}”`);
|
|
48
|
+
return metadata.join(", ");
|
|
49
|
+
}
|
|
50
|
+
function formatComponentError(message, componentName, elementResource) {
|
|
51
|
+
return `${message} for component “${componentName ?? "(unknown)"}” (${formatXMLWebsiteResourceMetadata(elementResource)})`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extracts CSS style properties for a given presentation variant.
|
|
55
|
+
*
|
|
56
|
+
* @param properties - Array of properties to parse
|
|
57
|
+
* @param cssVariant - CSS variant to parse
|
|
58
|
+
* @returns Array of CSS styles
|
|
59
|
+
*/
|
|
60
|
+
function parseCssStylesFromProperties(properties, cssVariant) {
|
|
61
|
+
const label = cssVariant != null ? `css-${cssVariant}` : "css";
|
|
62
|
+
const cssProperties = websitePresentationReader(properties).nestedByValue("presentation", label).properties;
|
|
63
|
+
const styles = [];
|
|
64
|
+
for (const property of cssProperties) {
|
|
65
|
+
const value = property.values[0]?.content.toString();
|
|
66
|
+
if (value != null) styles.push({
|
|
67
|
+
label: property.variable.label,
|
|
68
|
+
value
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return styles;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parses responsive CSS styles (default, tablet, mobile) from properties.
|
|
75
|
+
*
|
|
76
|
+
* @param properties - Array of properties to parse
|
|
77
|
+
* @returns Object containing responsive CSS styles
|
|
78
|
+
*/
|
|
79
|
+
function parseResponsiveCssStyles(properties) {
|
|
80
|
+
return {
|
|
81
|
+
default: parseCssStylesFromProperties(properties),
|
|
82
|
+
tablet: parseCssStylesFromProperties(properties, "tablet"),
|
|
83
|
+
mobile: parseCssStylesFromProperties(properties, "mobile")
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parses raw bounds data into a standardized bounds structure
|
|
88
|
+
*
|
|
89
|
+
* @param bounds - Raw bounds data in OCHRE format
|
|
90
|
+
* @returns Parsed bounds object
|
|
91
|
+
*/
|
|
92
|
+
function parseBounds(bounds) {
|
|
93
|
+
const [southWest, northEast] = bounds.trim().startsWith("[") ? parseJsonBounds(bounds) : bounds.split(";").map((pair) => pair.split(",").map((coordinate) => Number.parseFloat(coordinate.trim())));
|
|
94
|
+
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 });
|
|
95
|
+
return [[southWest[0], southWest[1]], [northEast[0], northEast[1]]];
|
|
96
|
+
}
|
|
97
|
+
function parseJsonBounds(bounds) {
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(bounds);
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
|
|
103
|
+
}
|
|
104
|
+
if (!isNumberPairArray(parsed)) throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
function isNumberPairArray(value) {
|
|
108
|
+
return Array.isArray(value) && value.every((pair) => Array.isArray(pair) && pair.every((coordinate) => typeof coordinate === "number"));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parses all context option arrays from an options object.
|
|
112
|
+
*
|
|
113
|
+
* @param options - Options object containing context options
|
|
114
|
+
* @param options.flattenContexts - Flatten contexts
|
|
115
|
+
* @param options.suppressContexts - Suppress contexts
|
|
116
|
+
* @param options.filterContexts - Filter contexts
|
|
117
|
+
* @param options.sortContexts - Sort contexts
|
|
118
|
+
* @param options.detailContexts - Detail contexts
|
|
119
|
+
* @param options.downloadContexts - Download contexts
|
|
120
|
+
* @param options.labelContexts - Label contexts
|
|
121
|
+
* @param options.prominentContexts - Prominent contexts
|
|
122
|
+
* @returns Parsed context options
|
|
123
|
+
*/
|
|
124
|
+
function parseAllOptionContexts(options, parserOptions) {
|
|
125
|
+
function handleContexts(v) {
|
|
126
|
+
return parseContexts(v ?? [], parserOptions);
|
|
127
|
+
}
|
|
128
|
+
function handleFilterContexts(v) {
|
|
129
|
+
return parseFilterContexts(v ?? [], parserOptions);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
flatten: handleContexts(options.flattenContexts),
|
|
133
|
+
suppress: handleContexts(options.suppressContexts),
|
|
134
|
+
filter: handleFilterContexts(options.filterContexts),
|
|
135
|
+
sort: handleContexts(options.sortContexts),
|
|
136
|
+
detail: handleContexts(options.detailContexts),
|
|
137
|
+
download: handleContexts(options.downloadContexts),
|
|
138
|
+
label: handleContexts(options.labelContexts),
|
|
139
|
+
prominent: handleContexts(options.prominentContexts)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function parseWebsiteScopes(scopes, options) {
|
|
143
|
+
if (scopes == null) return null;
|
|
144
|
+
const parsedScopes = [];
|
|
145
|
+
for (const scope of scopes.scope) parsedScopes.push({
|
|
146
|
+
uuid: scope.uuid.payload,
|
|
147
|
+
type: scope.uuid.type,
|
|
148
|
+
identification: parseIdentification(scope.identification, options)
|
|
149
|
+
});
|
|
150
|
+
return parsedScopes;
|
|
151
|
+
}
|
|
152
|
+
function parseWebsiteOptions(rawOptions, options) {
|
|
153
|
+
const parsedOptions = {
|
|
154
|
+
scopes: parseWebsiteScopes(rawOptions?.scopes, options),
|
|
155
|
+
contextTree: rawOptions == null ? null : parseAllOptionContexts(rawOptions, options),
|
|
156
|
+
labels: { title: null }
|
|
157
|
+
};
|
|
158
|
+
for (const note of parseNotes(rawOptions?.notes, options)) if (note.title?.getText() === "Title label") {
|
|
159
|
+
parsedOptions.labels.title = note.content;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
return parsedOptions;
|
|
163
|
+
}
|
|
164
|
+
function parseStylesheets(styles) {
|
|
165
|
+
const parsedStyles = [];
|
|
166
|
+
for (const style of styles) {
|
|
167
|
+
const defaultStyles = [];
|
|
168
|
+
for (const [label, value] of Object.entries(style)) {
|
|
169
|
+
if (label === "variableUuid" || label === "valueUuid" || label === "category" || label === "payload" || label === "content") continue;
|
|
170
|
+
const valueString = value?.toString();
|
|
171
|
+
if (valueString != null) defaultStyles.push({
|
|
172
|
+
label,
|
|
173
|
+
value: valueString
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const stylesByViewport = {
|
|
177
|
+
default: defaultStyles,
|
|
178
|
+
tablet: [],
|
|
179
|
+
mobile: []
|
|
180
|
+
};
|
|
181
|
+
if (style.category === "propertyValue" || style.valueUuid != null) {
|
|
182
|
+
if (style.valueUuid == null) throw new Error(`Stylesheet property value "${style.variableUuid}" is missing a value UUID`, { cause: style });
|
|
183
|
+
parsedStyles.push({
|
|
184
|
+
uuid: style.valueUuid,
|
|
185
|
+
category: "propertyValue",
|
|
186
|
+
variableUuid: style.variableUuid,
|
|
187
|
+
icon: style.lucideIcon ?? null,
|
|
188
|
+
styles: stylesByViewport
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
parsedStyles.push({
|
|
193
|
+
uuid: style.variableUuid,
|
|
194
|
+
category: "propertyVariable",
|
|
195
|
+
icon: style.lucideIcon ?? null,
|
|
196
|
+
styles: stylesByViewport
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return parsedStyles;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Parses raw web element properties into a standardized WebElementComponent structure
|
|
203
|
+
*
|
|
204
|
+
* @param componentProperty - Raw component property data in OCHRE format
|
|
205
|
+
* @param elementResource - Raw element resource data in OCHRE format
|
|
206
|
+
* @returns Parsed WebElementComponent object
|
|
207
|
+
*/
|
|
208
|
+
function parseWebElementProperties(componentProperty, elementResource, options) {
|
|
209
|
+
const unparsedComponentName = componentProperty.values[0].content;
|
|
210
|
+
const componentNameResult = v.safeParse(componentSchema, unparsedComponentName);
|
|
211
|
+
const componentName = componentNameResult.success ? componentNameResult.output : void 0;
|
|
212
|
+
let properties = null;
|
|
213
|
+
const websiteLinks = parseLinks(elementResource.links, options);
|
|
214
|
+
const componentReader = websitePresentationReader(componentProperty.properties);
|
|
215
|
+
switch (componentName) {
|
|
216
|
+
case "3d-viewer": {
|
|
217
|
+
const resourceLink = findWebsiteLink(websiteLinks, "resource", (link) => link.fileFormat === "model/obj");
|
|
218
|
+
if (resourceLink == null) throw new Error(formatComponentError("Resource link not found", componentName, elementResource), { cause: componentProperty });
|
|
219
|
+
const isInteractive = componentReader.valueOr("is-interactive", true);
|
|
220
|
+
const isControlsDisplayed = componentReader.valueOr("controls-displayed", true);
|
|
221
|
+
properties = {
|
|
222
|
+
component: "3d-viewer",
|
|
223
|
+
linkUuid: resourceLink.uuid,
|
|
224
|
+
fileSize: resourceLink.fileSize,
|
|
225
|
+
isInteractive,
|
|
226
|
+
isControlsDisplayed
|
|
227
|
+
};
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "advanced-search": {
|
|
231
|
+
const boundElementPropertyUuid = componentReader.uuid("bound-element");
|
|
232
|
+
const href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
|
|
233
|
+
if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
|
|
234
|
+
properties = {
|
|
235
|
+
component: "advanced-search",
|
|
236
|
+
boundElementUuid: boundElementPropertyUuid,
|
|
237
|
+
href
|
|
238
|
+
};
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "annotated-document": {
|
|
242
|
+
const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
|
|
243
|
+
if (documentLink == null) throw new Error(formatComponentError("Document link not found", componentName, elementResource), { cause: componentProperty });
|
|
244
|
+
properties = {
|
|
245
|
+
component: "annotated-document",
|
|
246
|
+
linkUuid: documentLink.uuid
|
|
247
|
+
};
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "annotated-image": {
|
|
251
|
+
const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
|
|
252
|
+
if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", componentName, elementResource), { cause: componentProperty });
|
|
253
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
|
|
254
|
+
const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
|
|
255
|
+
const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
|
|
256
|
+
const isAnnotationTooltipsDisplayed = componentReader.valueOr("annotation-tooltips-displayed", true);
|
|
257
|
+
properties = {
|
|
258
|
+
component: "annotated-image",
|
|
259
|
+
linkUuid: imageLinks[0].uuid,
|
|
260
|
+
isFilterInputDisplayed,
|
|
261
|
+
isOptionsDisplayed,
|
|
262
|
+
isAnnotationHighlightsDisplayed,
|
|
263
|
+
isAnnotationTooltipsDisplayed
|
|
264
|
+
};
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "audio-player": {
|
|
268
|
+
const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
|
|
269
|
+
if (audioLink == null) throw new Error(formatComponentError("Audio link not found", componentName, elementResource), { cause: componentProperty });
|
|
270
|
+
const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
|
|
271
|
+
const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
|
|
272
|
+
const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
|
|
273
|
+
properties = {
|
|
274
|
+
component: "audio-player",
|
|
275
|
+
linkUuid: audioLink.uuid,
|
|
276
|
+
isSpeedControlsDisplayed,
|
|
277
|
+
isVolumeControlsDisplayed,
|
|
278
|
+
isSeekBarDisplayed
|
|
279
|
+
};
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case "bibliography": {
|
|
283
|
+
const itemLinks = websiteLinks.filter((link) => link.category !== "bibliography");
|
|
284
|
+
const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
|
|
285
|
+
if (itemLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
|
|
286
|
+
const layout = componentReader.valueOr("layout", "long");
|
|
287
|
+
const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
|
|
288
|
+
properties = {
|
|
289
|
+
component: "bibliography",
|
|
290
|
+
linkUuids: itemLinks.map((link) => link.uuid),
|
|
291
|
+
bibliographies,
|
|
292
|
+
layout,
|
|
293
|
+
isSourceDocumentDisplayed
|
|
294
|
+
};
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "button": {
|
|
298
|
+
const variant = componentReader.valueOr("variant", "default");
|
|
299
|
+
let isExternal = false;
|
|
300
|
+
let href = componentReader.linkTarget("navigate-to", transformPermanentIdentificationUrlToItemLink);
|
|
301
|
+
if (href === null) {
|
|
302
|
+
href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
|
|
303
|
+
if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", componentName, elementResource), { cause: componentProperty });
|
|
304
|
+
else isExternal = true;
|
|
305
|
+
}
|
|
306
|
+
const startIcon = componentReader.value("start-icon");
|
|
307
|
+
const endIcon = componentReader.value("end-icon");
|
|
308
|
+
let image = null;
|
|
309
|
+
const imageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "image" || link.type === "IIIF");
|
|
310
|
+
if (imageLink != null) image = {
|
|
311
|
+
uuid: imageLink.uuid,
|
|
312
|
+
label: imageLink.identification.label,
|
|
313
|
+
width: imageLink.image?.width ?? 0,
|
|
314
|
+
height: imageLink.image?.height ?? 0,
|
|
315
|
+
description: imageLink.description,
|
|
316
|
+
quality: "high"
|
|
317
|
+
};
|
|
318
|
+
properties = {
|
|
319
|
+
component: "button",
|
|
320
|
+
variant,
|
|
321
|
+
href,
|
|
322
|
+
isExternal,
|
|
323
|
+
label: elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null,
|
|
324
|
+
startIcon,
|
|
325
|
+
endIcon,
|
|
326
|
+
image
|
|
327
|
+
};
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case "collection": {
|
|
331
|
+
const setLinks = getWebsiteLinks(websiteLinks, "set");
|
|
332
|
+
if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
|
|
333
|
+
const displayedProperties = componentReader.property("use-property");
|
|
334
|
+
const variant = componentReader.valueOr("variant", "slide");
|
|
335
|
+
const paginationVariant = componentReader.valueOr("pagination-variant", "default");
|
|
336
|
+
const loadingVariant = componentReader.valueOr("loading-variant", "skeleton");
|
|
337
|
+
const expectedItemCount = componentReader.valueOr("item-count", null);
|
|
338
|
+
const isUsingQueryParams = componentReader.valueOr("is-using-query-params", false);
|
|
339
|
+
const isFilterResultsBarDisplayed = componentReader.valueOr("filter-results-bar-displayed", false);
|
|
340
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
|
|
341
|
+
const isFilterLimitedToInputFilter = componentReader.valueOr("filter-limit-to-input-filter", false);
|
|
342
|
+
const isFilterLimitedToLeafPropertyValues = componentReader.valueOr("filter-limit-to-leaf-property-values", false);
|
|
343
|
+
const isSortDisplayed = componentReader.valueOr("sort-displayed", false);
|
|
344
|
+
const isFilterSidebarDisplayed = componentReader.valueOr("filter-sidebar-displayed", false);
|
|
345
|
+
const filterSidebarSort = componentReader.valueOr("filter-sidebar-sort", "default");
|
|
346
|
+
const imageLayout = componentReader.valueOr("image-layout", "start");
|
|
347
|
+
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
348
|
+
properties = {
|
|
349
|
+
component: "collection",
|
|
350
|
+
linkUuids: setLinks.map((link) => link.uuid),
|
|
351
|
+
displayedProperties: displayedProperties?.values.filter(({ uuid }) => uuid !== null).map((value) => ({
|
|
352
|
+
uuid: value.uuid,
|
|
353
|
+
label: value.label
|
|
354
|
+
})) ?? null,
|
|
355
|
+
variant,
|
|
356
|
+
paginationVariant,
|
|
357
|
+
loadingVariant,
|
|
358
|
+
imageLayout,
|
|
359
|
+
expectedItemCount,
|
|
360
|
+
isUsingQueryParams,
|
|
361
|
+
isSortDisplayed,
|
|
362
|
+
filter: {
|
|
363
|
+
isSidebarDisplayed: isFilterSidebarDisplayed,
|
|
364
|
+
isResultsBarDisplayed: isFilterResultsBarDisplayed,
|
|
365
|
+
isInputDisplayed: isFilterInputDisplayed,
|
|
366
|
+
isLimitedToInputFilter: isFilterLimitedToInputFilter,
|
|
367
|
+
isLimitedToLeafPropertyValues: isFilterLimitedToLeafPropertyValues,
|
|
368
|
+
sidebarSort: filterSidebarSort
|
|
369
|
+
},
|
|
370
|
+
options: componentOptions
|
|
371
|
+
};
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case "empty-space":
|
|
375
|
+
properties = {
|
|
376
|
+
component: "empty-space",
|
|
377
|
+
height: componentReader.stringValue("height"),
|
|
378
|
+
width: componentReader.stringValue("width")
|
|
379
|
+
};
|
|
380
|
+
break;
|
|
381
|
+
case "entries": {
|
|
382
|
+
const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
383
|
+
if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", componentName, elementResource), { cause: componentProperty });
|
|
384
|
+
const variant = componentReader.valueOr("variant", "entry");
|
|
385
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
|
|
386
|
+
properties = {
|
|
387
|
+
component: "entries",
|
|
388
|
+
linkUuid: entriesLink.uuid,
|
|
389
|
+
variant,
|
|
390
|
+
isFilterInputDisplayed
|
|
391
|
+
};
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case "iframe": {
|
|
395
|
+
const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
|
|
396
|
+
if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", componentName, elementResource), { cause: componentProperty });
|
|
397
|
+
properties = {
|
|
398
|
+
component: "iframe",
|
|
399
|
+
href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
|
|
400
|
+
height: componentReader.stringValue("height"),
|
|
401
|
+
width: componentReader.stringValue("width")
|
|
402
|
+
};
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
case "iiif-viewer": {
|
|
406
|
+
const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
|
|
407
|
+
if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", componentName, elementResource), { cause: componentProperty });
|
|
408
|
+
const variant = componentReader.valueOr("variant", "universal-viewer");
|
|
409
|
+
properties = {
|
|
410
|
+
component: "iiif-viewer",
|
|
411
|
+
linkUuid: manifestLink.uuid,
|
|
412
|
+
variant
|
|
413
|
+
};
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case "image": {
|
|
417
|
+
if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
|
|
418
|
+
const imageQuality = componentReader.valueOr("image-quality", "high");
|
|
419
|
+
const images = [];
|
|
420
|
+
for (const link of websiteLinks) images.push({
|
|
421
|
+
uuid: link.uuid,
|
|
422
|
+
label: link.identification.label,
|
|
423
|
+
width: "image" in link ? link.image?.width ?? 0 : 0,
|
|
424
|
+
height: "image" in link ? link.image?.height ?? 0 : 0,
|
|
425
|
+
description: link.description,
|
|
426
|
+
quality: imageQuality
|
|
427
|
+
});
|
|
428
|
+
const variant = componentReader.valueOr("variant", "default");
|
|
429
|
+
const captionLayout = componentReader.valueOr("layout-caption", "bottom");
|
|
430
|
+
let width = null;
|
|
431
|
+
const widthProperty = componentReader.value("width");
|
|
432
|
+
if (widthProperty !== null) {
|
|
433
|
+
if (typeof widthProperty === "number") width = widthProperty;
|
|
434
|
+
else if (typeof widthProperty === "string") width = Number.parseFloat(widthProperty);
|
|
435
|
+
}
|
|
436
|
+
let height = null;
|
|
437
|
+
const heightProperty = componentReader.value("height");
|
|
438
|
+
if (heightProperty !== null) {
|
|
439
|
+
if (typeof heightProperty === "number") height = heightProperty;
|
|
440
|
+
else if (typeof heightProperty === "string") height = Number.parseFloat(heightProperty);
|
|
441
|
+
}
|
|
442
|
+
const isFullWidth = componentReader.valueOr("is-full-width", true);
|
|
443
|
+
const isFullHeight = componentReader.valueOr("is-full-height", true);
|
|
444
|
+
const captionSource = componentReader.valueOr("source-caption", "name");
|
|
445
|
+
const altTextSource = componentReader.valueOr("alt-text-source", "name");
|
|
446
|
+
const isTransparentBackground = componentReader.valueOr("is-transparent", false);
|
|
447
|
+
const isCover = componentReader.valueOr("is-cover", false);
|
|
448
|
+
const variantReader = componentReader.nested("variant");
|
|
449
|
+
let carouselOptions = null;
|
|
450
|
+
if (images.length > 1) {
|
|
451
|
+
let secondsPerImage = 5;
|
|
452
|
+
if (variant === "carousel") {
|
|
453
|
+
const secondsPerImageProperty = variantReader.value("seconds-per-image");
|
|
454
|
+
if (secondsPerImageProperty !== null) {
|
|
455
|
+
if (typeof secondsPerImageProperty === "number") secondsPerImage = secondsPerImageProperty;
|
|
456
|
+
else if (typeof secondsPerImageProperty === "string") secondsPerImage = Number.parseFloat(secondsPerImageProperty);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
carouselOptions = { secondsPerImage };
|
|
460
|
+
}
|
|
461
|
+
let heroOptions = null;
|
|
462
|
+
if (variant === "hero") heroOptions = {
|
|
463
|
+
isBackgroundImageDisplayed: variantReader.valueOr("background-image-displayed", true),
|
|
464
|
+
isDocumentDisplayed: variantReader.valueOr("document-displayed", true)
|
|
465
|
+
};
|
|
466
|
+
properties = {
|
|
467
|
+
component: "image",
|
|
468
|
+
images,
|
|
469
|
+
variant,
|
|
470
|
+
width,
|
|
471
|
+
height,
|
|
472
|
+
isFullWidth,
|
|
473
|
+
isFullHeight,
|
|
474
|
+
imageQuality,
|
|
475
|
+
captionLayout,
|
|
476
|
+
captionSource,
|
|
477
|
+
altTextSource,
|
|
478
|
+
isTransparentBackground,
|
|
479
|
+
isCover,
|
|
480
|
+
carouselOptions,
|
|
481
|
+
heroOptions
|
|
482
|
+
};
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
case "image-gallery": {
|
|
486
|
+
const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
487
|
+
if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", componentName, elementResource), { cause: componentProperty });
|
|
488
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
|
|
489
|
+
properties = {
|
|
490
|
+
component: "image-gallery",
|
|
491
|
+
linkUuid: galleryLink.uuid,
|
|
492
|
+
isFilterInputDisplayed
|
|
493
|
+
};
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case "map": {
|
|
497
|
+
const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
498
|
+
if (mapLink == null) throw new Error(formatComponentError("Map link not found", componentName, elementResource), { cause: componentProperty });
|
|
499
|
+
const isInteractive = componentReader.valueOr("is-interactive", true);
|
|
500
|
+
const isClustered = componentReader.valueOr("is-clustered", false);
|
|
501
|
+
const isUsingPins = componentReader.valueOr("is-using-pins", false);
|
|
502
|
+
const customBasemap = componentReader.value("custom-basemap");
|
|
503
|
+
let initialBounds = null;
|
|
504
|
+
const initialBoundsProperty = componentReader.value("initial-bounds");
|
|
505
|
+
if (initialBoundsProperty !== null) initialBounds = parseBounds(String(initialBoundsProperty));
|
|
506
|
+
let maximumBounds = null;
|
|
507
|
+
const maximumBoundsProperty = componentReader.value("maximum-bounds");
|
|
508
|
+
if (maximumBoundsProperty !== null) maximumBounds = parseBounds(String(maximumBoundsProperty));
|
|
509
|
+
const isControlsDisplayed = componentReader.valueOr("controls-displayed", false);
|
|
510
|
+
const isFullHeight = componentReader.valueOr("is-full-height", false);
|
|
511
|
+
properties = {
|
|
512
|
+
component: "map",
|
|
513
|
+
linkUuid: mapLink.uuid,
|
|
514
|
+
customBasemap,
|
|
515
|
+
initialBounds,
|
|
516
|
+
maximumBounds,
|
|
517
|
+
isInteractive,
|
|
518
|
+
isClustered,
|
|
519
|
+
isUsingPins,
|
|
520
|
+
isControlsDisplayed,
|
|
521
|
+
isFullHeight
|
|
522
|
+
};
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
case "query": {
|
|
526
|
+
const setLinks = getWebsiteLinks(websiteLinks, "set");
|
|
527
|
+
if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
|
|
528
|
+
const items = [];
|
|
529
|
+
if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", componentName, elementResource), { cause: componentProperty });
|
|
530
|
+
for (const queryItem of componentProperty.properties) {
|
|
531
|
+
const queryReader = websitePresentationReader(queryItem.properties);
|
|
532
|
+
const label = queryReader.multilingualValue("query-prompt", options);
|
|
533
|
+
if (label === null) continue;
|
|
534
|
+
const propertyVariables = queryReader.values("use-property").filter((value) => value.uuid !== null);
|
|
535
|
+
const queryLanguage = options.languages[0];
|
|
536
|
+
if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", componentName, elementResource));
|
|
537
|
+
const queries = [];
|
|
538
|
+
for (const propertyVariable of propertyVariables) {
|
|
539
|
+
if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", componentName, elementResource), { cause: propertyVariable });
|
|
540
|
+
const dataType = propertyVariable.dataType;
|
|
541
|
+
if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", componentName, elementResource), { cause: propertyVariable });
|
|
542
|
+
queries.push({
|
|
543
|
+
target: "property",
|
|
544
|
+
propertyVariable: propertyVariable.uuid,
|
|
545
|
+
dataType,
|
|
546
|
+
matchMode: "exact",
|
|
547
|
+
isCaseSensitive: true,
|
|
548
|
+
language: queryLanguage
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const startIcon = queryReader.value("start-icon");
|
|
552
|
+
const endIcon = queryReader.value("end-icon");
|
|
553
|
+
items.push({
|
|
554
|
+
label,
|
|
555
|
+
queries,
|
|
556
|
+
startIcon,
|
|
557
|
+
endIcon
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (items.length === 0) throw new Error(formatComponentError("No queries found", componentName, elementResource), { cause: componentProperty });
|
|
561
|
+
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
562
|
+
const displayedProperties = componentReader.property("use-property");
|
|
563
|
+
const variant = componentReader.valueOr("variant", "slide");
|
|
564
|
+
const paginationVariant = componentReader.valueOr("pagination-variant", "default");
|
|
565
|
+
const loadingVariant = componentReader.valueOr("loading-variant", "skeleton");
|
|
566
|
+
const imageLayout = componentReader.valueOr("image-layout", "start");
|
|
567
|
+
properties = {
|
|
568
|
+
component: "query",
|
|
569
|
+
linkUuids: setLinks.map((link) => link.uuid),
|
|
570
|
+
items,
|
|
571
|
+
options: componentOptions,
|
|
572
|
+
collectionProperties: {
|
|
573
|
+
displayedProperties: displayedProperties?.values.filter((value) => value.uuid !== null).map((value) => ({
|
|
574
|
+
uuid: value.uuid,
|
|
575
|
+
label: value.label
|
|
576
|
+
})) ?? null,
|
|
577
|
+
variant,
|
|
578
|
+
paginationVariant,
|
|
579
|
+
loadingVariant,
|
|
580
|
+
imageLayout
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "table": {
|
|
586
|
+
const tableLink = findWebsiteLink(websiteLinks, "set");
|
|
587
|
+
if (tableLink == null) throw new Error(formatComponentError("Table link not found", componentName, elementResource), { cause: componentProperty });
|
|
588
|
+
properties = {
|
|
589
|
+
component: "table",
|
|
590
|
+
linkUuid: tableLink.uuid
|
|
591
|
+
};
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
case "search-bar": {
|
|
595
|
+
const queryVariant = componentReader.valueOr("query-variant", "submit");
|
|
596
|
+
const boundElementUuid = componentReader.uuid("bound-element");
|
|
597
|
+
const href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
|
|
598
|
+
if (!boundElementUuid && !href) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
|
|
599
|
+
properties = {
|
|
600
|
+
component: "search-bar",
|
|
601
|
+
queryVariant,
|
|
602
|
+
placeholder: componentReader.multilingualValue("placeholder-text", options),
|
|
603
|
+
baseFilterQueries: componentReader.value("base-filter-queries")?.replaceAll(String.raw`\{`, "{").replaceAll(String.raw`\}`, "}") ?? null,
|
|
604
|
+
boundElementUuid,
|
|
605
|
+
href
|
|
606
|
+
};
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
case "text": {
|
|
610
|
+
const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
|
|
611
|
+
if (content == null) throw new Error(formatComponentError("Content not found", componentName, elementResource), { cause: componentProperty });
|
|
612
|
+
let variantName = "block";
|
|
613
|
+
let variant;
|
|
614
|
+
const variantProperty = componentReader.property("variant");
|
|
615
|
+
if (variantProperty !== null) {
|
|
616
|
+
const variantReader = websitePresentationReader(variantProperty.properties);
|
|
617
|
+
variantName = variantProperty.values[0].content;
|
|
618
|
+
switch (variantName) {
|
|
619
|
+
case "paragraph":
|
|
620
|
+
variant = {
|
|
621
|
+
name: variantName,
|
|
622
|
+
size: variantReader.valueOr("size", "md")
|
|
623
|
+
};
|
|
624
|
+
break;
|
|
625
|
+
case "label":
|
|
626
|
+
variant = {
|
|
627
|
+
name: variantName,
|
|
628
|
+
size: variantReader.valueOr("size", "md")
|
|
629
|
+
};
|
|
630
|
+
break;
|
|
631
|
+
case "heading":
|
|
632
|
+
variant = {
|
|
633
|
+
name: variantName,
|
|
634
|
+
size: variantReader.valueOr("size", "md")
|
|
635
|
+
};
|
|
636
|
+
break;
|
|
637
|
+
case "display":
|
|
638
|
+
variant = {
|
|
639
|
+
name: variantName,
|
|
640
|
+
size: variantReader.valueOr("size", "md")
|
|
641
|
+
};
|
|
642
|
+
break;
|
|
643
|
+
default: variant = { name: variantName };
|
|
644
|
+
}
|
|
645
|
+
} else variant = { name: variantName };
|
|
646
|
+
const headingLevel = componentReader.value("heading-level");
|
|
647
|
+
properties = {
|
|
648
|
+
component: "text",
|
|
649
|
+
variant,
|
|
650
|
+
headingLevel,
|
|
651
|
+
content
|
|
652
|
+
};
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
case "timeline": {
|
|
656
|
+
const timelineLink = findWebsiteLink(websiteLinks, "tree");
|
|
657
|
+
if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", componentName, elementResource), { cause: componentProperty });
|
|
658
|
+
properties = {
|
|
659
|
+
component: "timeline",
|
|
660
|
+
linkUuid: timelineLink.uuid
|
|
661
|
+
};
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
case "video": {
|
|
665
|
+
const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
|
|
666
|
+
if (videoLink == null) throw new Error(formatComponentError("Video link not found", componentName, elementResource), { cause: componentProperty });
|
|
667
|
+
const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
|
|
668
|
+
properties = {
|
|
669
|
+
component: "video",
|
|
670
|
+
linkUuid: videoLink.uuid,
|
|
671
|
+
isChaptersDisplayed
|
|
672
|
+
};
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
default: throw new Error(`Invalid or non-implemented component name “${unparsedComponentName.toString()}” for the following element: “${parseStringContent(elementResource.identification.label, options)}”`);
|
|
676
|
+
}
|
|
677
|
+
return properties;
|
|
678
|
+
}
|
|
679
|
+
function parseWebTitle(properties, identification, overrides) {
|
|
680
|
+
const title = {
|
|
681
|
+
label: identification.label,
|
|
682
|
+
variant: "default",
|
|
683
|
+
properties: {
|
|
684
|
+
isNameDisplayed: overrides?.isNameDisplayed ?? false,
|
|
685
|
+
isDescriptionDisplayed: overrides?.isDescriptionDisplayed ?? false,
|
|
686
|
+
isDateDisplayed: overrides?.isDateDisplayed ?? false,
|
|
687
|
+
isCreatorsDisplayed: overrides?.isCreatorsDisplayed ?? false,
|
|
688
|
+
isCountDisplayed: overrides?.isCountDisplayed ?? false
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const titleReader = websitePresentationReader(properties).nestedByValue("presentation", "title");
|
|
692
|
+
if (titleReader.size > 0) {
|
|
693
|
+
title.variant = titleReader.valueOr("variant", "default");
|
|
694
|
+
title.properties.isNameDisplayed = titleReader.valueOr("name-displayed", false);
|
|
695
|
+
title.properties.isDescriptionDisplayed = titleReader.valueOr("description-displayed", false);
|
|
696
|
+
title.properties.isDateDisplayed = titleReader.valueOr("date-displayed", false);
|
|
697
|
+
title.properties.isCreatorsDisplayed = titleReader.valueOr("creators-displayed", false);
|
|
698
|
+
title.properties.isCountDisplayed = titleReader.valueOr("count-displayed", false);
|
|
699
|
+
}
|
|
700
|
+
return title;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Parses raw web element data into a standardized WebElement structure
|
|
704
|
+
*
|
|
705
|
+
* @param elementResource - Raw element resource data in OCHRE format
|
|
706
|
+
* @returns Parsed WebElement object
|
|
707
|
+
*/
|
|
708
|
+
function parseWebElement(elementResource, options) {
|
|
709
|
+
const identification = parseIdentification(elementResource.identification, options);
|
|
710
|
+
const elementProperties = elementResource.properties?.property ? parseSimplifiedProperties(elementResource.properties, options) : [];
|
|
711
|
+
const properties = parseWebElementProperties(websitePresentationReader(websitePresentationReader(elementProperties).requiredProperty("presentation", `Presentation property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`).properties).requiredProperty("component", `Component property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`), elementResource, options);
|
|
712
|
+
const cssStyles = parseResponsiveCssStyles(elementProperties);
|
|
713
|
+
const title = parseWebTitle(elementProperties, identification, {
|
|
714
|
+
isNameDisplayed: properties.component === "annotated-image" || properties.component === "annotated-document" || properties.component === "collection",
|
|
715
|
+
isCountDisplayed: properties.component === "collection"
|
|
716
|
+
});
|
|
717
|
+
return {
|
|
718
|
+
uuid: elementResource.uuid,
|
|
719
|
+
type: "element",
|
|
720
|
+
title,
|
|
721
|
+
cssStyles,
|
|
722
|
+
...properties
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Parses raw webpage data into a standardized Webpage structure
|
|
727
|
+
*
|
|
728
|
+
* @param webpageResource - Raw webpage resource data in OCHRE format
|
|
729
|
+
* @returns Parsed Webpage object
|
|
730
|
+
*/
|
|
731
|
+
function parseWebpage(webpageResource, options, context, slugPrefix) {
|
|
732
|
+
const webpageProperties = webpageResource.properties ? parseSimplifiedProperties(webpageResource.properties, options) : [];
|
|
733
|
+
const webpageReader = websitePresentationReader(webpageProperties);
|
|
734
|
+
if (webpageReader.value("presentation") !== "page") return null;
|
|
735
|
+
const identification = parseIdentification(webpageResource.identification, options);
|
|
736
|
+
const slug = webpageResource.slug?.replace(SEGMENT_UNIQUE_SLUG_PREFIX_REGEX, "") ?? null;
|
|
737
|
+
if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(webpageResource)})`, { cause: webpageResource });
|
|
738
|
+
const returnWebpage = {
|
|
739
|
+
uuid: webpageResource.uuid,
|
|
740
|
+
type: "page",
|
|
741
|
+
title: identification.label,
|
|
742
|
+
slug: prefixSlug(slug, slugPrefix),
|
|
743
|
+
publicationDateTime: webpageResource.publicationDateTime ?? null,
|
|
744
|
+
items: [],
|
|
745
|
+
segments: [],
|
|
746
|
+
properties: {
|
|
747
|
+
width: "default",
|
|
748
|
+
variant: "default",
|
|
749
|
+
isBreadcrumbsDisplayed: false,
|
|
750
|
+
isSidebarDisplayed: true,
|
|
751
|
+
isDisplayedInNavbar: true,
|
|
752
|
+
isNavbarSearchBarDisplayed: true,
|
|
753
|
+
backgroundImage: null,
|
|
754
|
+
cssStyles: {
|
|
755
|
+
default: [],
|
|
756
|
+
tablet: [],
|
|
757
|
+
mobile: []
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
webpages: []
|
|
761
|
+
};
|
|
762
|
+
const imageLink = findWebsiteLink(parseLinks(webpageResource.links, options), "resource", (link) => link.type === "image" || link.type === "IIIF");
|
|
763
|
+
const webpageResources = webpageResource.resource != null ? normalizeWebsiteResources(webpageResource.resource) : [];
|
|
764
|
+
const items = [];
|
|
765
|
+
for (const resource of webpageResources) {
|
|
766
|
+
const resourceType = websitePresentationReader(resource.properties != null ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation");
|
|
767
|
+
if (resourceType === null) continue;
|
|
768
|
+
switch (resourceType) {
|
|
769
|
+
case "element": {
|
|
770
|
+
const element = parseWebElement(resource, options);
|
|
771
|
+
items.push(element);
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
case "block": {
|
|
775
|
+
const block = parseWebBlock(resource, options);
|
|
776
|
+
if (block) items.push(block);
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
returnWebpage.items = items;
|
|
782
|
+
returnWebpage.webpages = parseWebpages(webpageResources, options, context, slugPrefix == null ? void 0 : returnWebpage.slug);
|
|
783
|
+
returnWebpage.segments = parseWebsiteSegments(webpageResource.resource, context, options, returnWebpage.slug);
|
|
784
|
+
const pageReader = webpageReader.nestedByValue("presentation", "page");
|
|
785
|
+
if (pageReader.size > 0) {
|
|
786
|
+
returnWebpage.properties.isDisplayedInNavbar = pageReader.valueOr("displayed-in-navbar", true);
|
|
787
|
+
returnWebpage.properties.width = pageReader.valueOr("width", "default");
|
|
788
|
+
returnWebpage.properties.variant = pageReader.valueOr("variant", "default");
|
|
789
|
+
returnWebpage.properties.isSidebarDisplayed = pageReader.valueOr("sidebar-displayed", true);
|
|
790
|
+
returnWebpage.properties.isBreadcrumbsDisplayed = pageReader.valueOr("breadcrumbs-displayed", false);
|
|
791
|
+
returnWebpage.properties.isNavbarSearchBarDisplayed = pageReader.valueOr("navbar-search-bar-displayed", true);
|
|
792
|
+
}
|
|
793
|
+
if (imageLink != null) returnWebpage.properties.backgroundImage = {
|
|
794
|
+
uuid: imageLink.uuid,
|
|
795
|
+
label: imageLink.identification.label,
|
|
796
|
+
description: imageLink.description,
|
|
797
|
+
width: imageLink.image?.width ?? 0,
|
|
798
|
+
height: imageLink.image?.height ?? 0,
|
|
799
|
+
quality: "high"
|
|
800
|
+
};
|
|
801
|
+
returnWebpage.properties.cssStyles = parseResponsiveCssStyles(webpageProperties);
|
|
802
|
+
return returnWebpage;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Parses raw webpage resources into an array of Webpage objects
|
|
806
|
+
*
|
|
807
|
+
* @param webpageResources - Array of raw webpage resources in OCHRE format
|
|
808
|
+
* @returns Array of parsed Webpage objects
|
|
809
|
+
*/
|
|
810
|
+
function parseWebpages(webpageResources, options, context, slugPrefix) {
|
|
811
|
+
const returnPages = [];
|
|
812
|
+
for (const webpageResource of webpageResources) {
|
|
813
|
+
const webpage = parseWebpage(webpageResource, options, context, slugPrefix);
|
|
814
|
+
if (webpage !== null) returnPages.push(webpage);
|
|
815
|
+
}
|
|
816
|
+
return returnPages;
|
|
817
|
+
}
|
|
818
|
+
function parseWebpageView(view, options, context) {
|
|
819
|
+
return parseWebpages(view?.resource ?? [], options, context)[0] ?? null;
|
|
820
|
+
}
|
|
821
|
+
function parseWebsiteSegments(resources, context, options, slugPrefix) {
|
|
822
|
+
const segments = [];
|
|
823
|
+
for (const resource of resources ?? []) {
|
|
824
|
+
if (!("segments" in resource)) continue;
|
|
825
|
+
for (const tree of resource.segments.tree) {
|
|
826
|
+
const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
|
|
827
|
+
if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
|
|
828
|
+
segments.push(parseWebsiteTree(tree, context, "segment", options, prefixSlug(segmentSlug, slugPrefix)));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return segments;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Parses raw sidebar data into a standardized Sidebar structure
|
|
835
|
+
*
|
|
836
|
+
* @param resources - Array of raw sidebar resources in OCHRE format
|
|
837
|
+
* @returns Parsed Sidebar object
|
|
838
|
+
*/
|
|
839
|
+
function parseSidebar(resources, options) {
|
|
840
|
+
let returnSidebar = null;
|
|
841
|
+
const items = [];
|
|
842
|
+
let title = null;
|
|
843
|
+
let layout = "start";
|
|
844
|
+
let mobileLayout = "default";
|
|
845
|
+
const cssStyles = {
|
|
846
|
+
default: [],
|
|
847
|
+
tablet: [],
|
|
848
|
+
mobile: []
|
|
849
|
+
};
|
|
850
|
+
const sidebarResource = resources.find((resource) => {
|
|
851
|
+
const resourceReader = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []);
|
|
852
|
+
return resourceReader.value("presentation") === "element" && resourceReader.nestedByValue("presentation", "element").value("component") === "sidebar";
|
|
853
|
+
});
|
|
854
|
+
if (sidebarResource != null) {
|
|
855
|
+
const sidebarBaseProperties = sidebarResource.properties ? parseSimplifiedProperties(sidebarResource.properties, options) : [];
|
|
856
|
+
title = parseWebTitle(sidebarBaseProperties, parseIdentification(sidebarResource.identification, options));
|
|
857
|
+
const sidebarReader = websitePresentationReader(sidebarBaseProperties).nestedByValue("presentation", "element").nestedByValue("component", "sidebar");
|
|
858
|
+
layout = sidebarReader.valueOr("layout", "start");
|
|
859
|
+
mobileLayout = sidebarReader.valueOr("layout-mobile", "default");
|
|
860
|
+
const parsedCssStyles = parseResponsiveCssStyles(sidebarBaseProperties);
|
|
861
|
+
cssStyles.default = parsedCssStyles.default;
|
|
862
|
+
cssStyles.tablet = parsedCssStyles.tablet;
|
|
863
|
+
cssStyles.mobile = parsedCssStyles.mobile;
|
|
864
|
+
const sidebarResources = sidebarResource.resource ? normalizeWebsiteResources(sidebarResource.resource) : [];
|
|
865
|
+
for (const resource of sidebarResources) {
|
|
866
|
+
const resourceType = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation");
|
|
867
|
+
if (resourceType === null) continue;
|
|
868
|
+
switch (resourceType) {
|
|
869
|
+
case "element": {
|
|
870
|
+
const element = parseWebElement(resource, options);
|
|
871
|
+
items.push(element);
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
case "block": {
|
|
875
|
+
const block = parseWebBlock(resource, options);
|
|
876
|
+
if (block) items.push(block);
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (items.length > 0 && title != null) returnSidebar = {
|
|
883
|
+
isDisplayed: true,
|
|
884
|
+
items,
|
|
885
|
+
title,
|
|
886
|
+
layout,
|
|
887
|
+
mobileLayout,
|
|
888
|
+
cssStyles
|
|
889
|
+
};
|
|
890
|
+
return returnSidebar;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Parses raw text element data for accordion layout with items support
|
|
894
|
+
*
|
|
895
|
+
* @param elementResource - Raw element resource data in OCHRE format
|
|
896
|
+
* @returns Parsed text WebElement with items array
|
|
897
|
+
*/
|
|
898
|
+
function parseWebElementForAccordion(elementResource, options) {
|
|
899
|
+
const textElement = parseWebElement(elementResource, options);
|
|
900
|
+
const childResources = elementResource.resource ? normalizeWebsiteResources(elementResource.resource) : [];
|
|
901
|
+
const items = [];
|
|
902
|
+
for (const resource of childResources) {
|
|
903
|
+
const resourceType = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation");
|
|
904
|
+
if (resourceType === null) continue;
|
|
905
|
+
switch (resourceType) {
|
|
906
|
+
case "element": {
|
|
907
|
+
const element = parseWebElement(resource, options);
|
|
908
|
+
items.push(element);
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
case "block": {
|
|
912
|
+
const block = parseWebBlock(resource, options);
|
|
913
|
+
if (block) items.push(block);
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
...textElement,
|
|
920
|
+
items
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Parses raw block data into a standardized WebBlock structure
|
|
925
|
+
*
|
|
926
|
+
* @param blockResource - Raw block resource data in OCHRE format
|
|
927
|
+
* @returns Parsed WebBlock object
|
|
928
|
+
*/
|
|
929
|
+
function parseWebBlock(blockResource, options) {
|
|
930
|
+
const blockProperties = blockResource.properties ? parseSimplifiedProperties(blockResource.properties, options) : [];
|
|
931
|
+
const returnBlock = {
|
|
932
|
+
uuid: blockResource.uuid,
|
|
933
|
+
type: "block",
|
|
934
|
+
title: parseWebTitle(blockProperties, parseIdentification(blockResource.identification, options)),
|
|
935
|
+
items: [],
|
|
936
|
+
properties: {
|
|
937
|
+
default: {
|
|
938
|
+
layout: "vertical",
|
|
939
|
+
wrap: "nowrap",
|
|
940
|
+
spacing: null,
|
|
941
|
+
gap: null
|
|
942
|
+
},
|
|
943
|
+
mobile: null,
|
|
944
|
+
tablet: null
|
|
945
|
+
},
|
|
946
|
+
cssStyles: {
|
|
947
|
+
default: [],
|
|
948
|
+
tablet: [],
|
|
949
|
+
mobile: []
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
const blockReader = websitePresentationReader(blockProperties).nestedByValue("presentation", "block");
|
|
953
|
+
if (blockReader.size > 0) {
|
|
954
|
+
returnBlock.properties.default.layout = blockReader.valueOr("layout", "vertical");
|
|
955
|
+
returnBlock.properties.default.wrap = blockReader.valueOr("wrap", "nowrap");
|
|
956
|
+
if (returnBlock.properties.default.layout === "accordion") {
|
|
957
|
+
returnBlock.properties.default.isAccordionEnabled = blockReader.valueOr("accordion-enabled", true);
|
|
958
|
+
returnBlock.properties.default.isAccordionExpandedByDefault = blockReader.valueOr("accordion-expanded", true);
|
|
959
|
+
returnBlock.properties.default.isAccordionSidebarDisplayed = blockReader.valueOr("accordion-sidebar-displayed", false);
|
|
960
|
+
}
|
|
961
|
+
returnBlock.properties.default.spacing = blockReader.valueOr("spacing", null);
|
|
962
|
+
returnBlock.properties.default.gap = blockReader.valueOr("gap", null);
|
|
963
|
+
const tabletOverwriteReader = blockReader.nested("overwrite-tablet");
|
|
964
|
+
if (tabletOverwriteReader.size > 0) {
|
|
965
|
+
const propertiesTablet = {
|
|
966
|
+
layout: tabletOverwriteReader.value("layout") ?? void 0,
|
|
967
|
+
wrap: tabletOverwriteReader.value("wrap") ?? void 0,
|
|
968
|
+
spacing: tabletOverwriteReader.value("spacing") ?? void 0,
|
|
969
|
+
gap: tabletOverwriteReader.value("gap") ?? void 0,
|
|
970
|
+
isAccordionEnabled: void 0,
|
|
971
|
+
isAccordionExpandedByDefault: void 0,
|
|
972
|
+
isAccordionSidebarDisplayed: void 0
|
|
973
|
+
};
|
|
974
|
+
if (propertiesTablet.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
|
|
975
|
+
propertiesTablet.isAccordionEnabled = tabletOverwriteReader.value("accordion-enabled") ?? void 0;
|
|
976
|
+
propertiesTablet.isAccordionExpandedByDefault = tabletOverwriteReader.value("accordion-expanded") ?? void 0;
|
|
977
|
+
propertiesTablet.isAccordionSidebarDisplayed = tabletOverwriteReader.value("accordion-sidebar-displayed") ?? void 0;
|
|
978
|
+
}
|
|
979
|
+
const cleanedPropertiesTablet = cleanObject(propertiesTablet);
|
|
980
|
+
if (Object.keys(cleanedPropertiesTablet).length > 0) returnBlock.properties.tablet = cleanedPropertiesTablet;
|
|
981
|
+
}
|
|
982
|
+
const mobileOverwriteReader = blockReader.nested("overwrite-mobile");
|
|
983
|
+
if (mobileOverwriteReader.size > 0) {
|
|
984
|
+
const propertiesMobile = {
|
|
985
|
+
layout: mobileOverwriteReader.value("layout") ?? void 0,
|
|
986
|
+
wrap: mobileOverwriteReader.value("wrap") ?? void 0,
|
|
987
|
+
spacing: mobileOverwriteReader.value("spacing") ?? void 0,
|
|
988
|
+
gap: mobileOverwriteReader.value("gap") ?? void 0,
|
|
989
|
+
isAccordionEnabled: void 0,
|
|
990
|
+
isAccordionExpandedByDefault: void 0,
|
|
991
|
+
isAccordionSidebarDisplayed: void 0
|
|
992
|
+
};
|
|
993
|
+
if (propertiesMobile.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
|
|
994
|
+
propertiesMobile.isAccordionEnabled = mobileOverwriteReader.value("accordion-enabled") ?? void 0;
|
|
995
|
+
propertiesMobile.isAccordionExpandedByDefault = mobileOverwriteReader.value("accordion-expanded") ?? void 0;
|
|
996
|
+
propertiesMobile.isAccordionSidebarDisplayed = mobileOverwriteReader.value("accordion-sidebar-displayed") ?? void 0;
|
|
997
|
+
}
|
|
998
|
+
const cleanedPropertiesMobile = cleanObject(propertiesMobile);
|
|
999
|
+
if (Object.keys(cleanedPropertiesMobile).length > 0) returnBlock.properties.mobile = cleanedPropertiesMobile;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const blockResources = blockResource.resource ? normalizeWebsiteResources(blockResource.resource) : [];
|
|
1003
|
+
if (returnBlock.properties.default.layout === "accordion") {
|
|
1004
|
+
const accordionItems = [];
|
|
1005
|
+
for (const resource of blockResources) {
|
|
1006
|
+
const resourceReader = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []);
|
|
1007
|
+
const resourceType = resourceReader.value("presentation");
|
|
1008
|
+
if (resourceType !== "element") throw new Error(`Accordion only accepts elements, but got “${resourceType}” (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
|
|
1009
|
+
const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
|
|
1010
|
+
if (componentType !== "text") throw new Error(`Accordion only accepts text components, but got “${componentType}” (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
|
|
1011
|
+
const element = parseWebElementForAccordion(resource, options);
|
|
1012
|
+
accordionItems.push(element);
|
|
1013
|
+
}
|
|
1014
|
+
returnBlock.items = accordionItems;
|
|
1015
|
+
} else {
|
|
1016
|
+
const blockItems = [];
|
|
1017
|
+
for (const resource of blockResources) {
|
|
1018
|
+
const resourceType = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation");
|
|
1019
|
+
if (resourceType === null) continue;
|
|
1020
|
+
switch (resourceType) {
|
|
1021
|
+
case "element": {
|
|
1022
|
+
const element = parseWebElement(resource, options);
|
|
1023
|
+
blockItems.push(element);
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
case "block": {
|
|
1027
|
+
const block = parseWebBlock(resource, options);
|
|
1028
|
+
if (block) blockItems.push(block);
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
returnBlock.items = blockItems;
|
|
1034
|
+
}
|
|
1035
|
+
returnBlock.cssStyles = parseResponsiveCssStyles(blockProperties);
|
|
1036
|
+
return returnBlock;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Parses raw website properties into a standardized Website properties structure
|
|
1040
|
+
*
|
|
1041
|
+
* @param properties - Array of raw website properties in OCHRE format
|
|
1042
|
+
* @returns Parsed WebsiteProperties object
|
|
1043
|
+
*/
|
|
1044
|
+
function parseWebsiteProperties(properties, websiteTree, sidebar, options) {
|
|
1045
|
+
const websiteReader = websitePresentationReader(parseSimplifiedProperties({ property: properties }, options)).nested("presentation");
|
|
1046
|
+
const returnProperties = {
|
|
1047
|
+
type: websiteReader.valueOr("webUI", "traditional"),
|
|
1048
|
+
status: websiteReader.valueOr("status", "development"),
|
|
1049
|
+
versionLabel: websiteReader.valueOr("version-label", "release"),
|
|
1050
|
+
privacy: websiteReader.valueOr("privacy", "public"),
|
|
1051
|
+
contact: null,
|
|
1052
|
+
loadingVariant: "spinner",
|
|
1053
|
+
theme: {
|
|
1054
|
+
isThemeToggleDisplayed: true,
|
|
1055
|
+
defaultTheme: "system"
|
|
1056
|
+
},
|
|
1057
|
+
icon: {
|
|
1058
|
+
logoUuid: null,
|
|
1059
|
+
faviconUuid: null,
|
|
1060
|
+
appleTouchIconUuid: null
|
|
1061
|
+
},
|
|
1062
|
+
navbar: {
|
|
1063
|
+
isDisplayed: true,
|
|
1064
|
+
variant: "default",
|
|
1065
|
+
alignment: "start",
|
|
1066
|
+
isProjectDisplayed: true,
|
|
1067
|
+
searchBarBoundElementUuid: null,
|
|
1068
|
+
items: null
|
|
1069
|
+
},
|
|
1070
|
+
footer: {
|
|
1071
|
+
isDisplayed: true,
|
|
1072
|
+
logoUuid: null,
|
|
1073
|
+
items: null
|
|
1074
|
+
},
|
|
1075
|
+
sidebar,
|
|
1076
|
+
itemPage: {
|
|
1077
|
+
isMainContentDisplayed: true,
|
|
1078
|
+
isDescriptionDisplayed: true,
|
|
1079
|
+
isDocumentDisplayed: true,
|
|
1080
|
+
isNotesDisplayed: true,
|
|
1081
|
+
isEventsDisplayed: true,
|
|
1082
|
+
isPeriodsDisplayed: true,
|
|
1083
|
+
isPropertiesDisplayed: true,
|
|
1084
|
+
isBibliographyDisplayed: true,
|
|
1085
|
+
isPropertyValuesGrouped: true,
|
|
1086
|
+
isPublicationDateTimeDisplayed: true,
|
|
1087
|
+
isPersistentIdentifierDisplayed: true,
|
|
1088
|
+
iiifViewer: "universal-viewer"
|
|
1089
|
+
},
|
|
1090
|
+
options: {
|
|
1091
|
+
contextTree: null,
|
|
1092
|
+
scopes: null,
|
|
1093
|
+
labels: { title: null },
|
|
1094
|
+
stylesheets: { properties: [] }
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
const contactProperty = websiteReader.property("contact");
|
|
1098
|
+
if (contactProperty !== null) {
|
|
1099
|
+
const contactContent = contactProperty.values[0]?.content.toString().split(";") ?? [];
|
|
1100
|
+
if (contactContent.length === 2) returnProperties.contact = {
|
|
1101
|
+
name: contactContent[0],
|
|
1102
|
+
email: contactContent[1] ?? null
|
|
1103
|
+
};
|
|
1104
|
+
else throw new Error(`Contact property must use “name;email”, got “${contactProperty.values[0]?.content}” (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
|
|
1105
|
+
}
|
|
1106
|
+
returnProperties.loadingVariant = websiteReader.valueOr("loading-variant", "spinner");
|
|
1107
|
+
returnProperties.theme.isThemeToggleDisplayed = websiteReader.valueOr("supports-theme-toggle", true);
|
|
1108
|
+
returnProperties.theme.defaultTheme = websiteReader.valueOr("default-theme", "system");
|
|
1109
|
+
returnProperties.icon.logoUuid = websiteReader.uuid("navbar-logo");
|
|
1110
|
+
returnProperties.icon.faviconUuid = websiteReader.uuid("favicon-ico");
|
|
1111
|
+
returnProperties.icon.appleTouchIconUuid = websiteReader.uuid("favicon-img");
|
|
1112
|
+
returnProperties.navbar.isDisplayed = websiteReader.valueOr("navbar-displayed", true);
|
|
1113
|
+
returnProperties.navbar.variant = websiteReader.valueOr("navbar-variant", "default");
|
|
1114
|
+
returnProperties.navbar.alignment = websiteReader.valueOr("navbar-alignment", "start");
|
|
1115
|
+
returnProperties.navbar.isProjectDisplayed = websiteReader.valueOr("navbar-project-displayed", true);
|
|
1116
|
+
returnProperties.navbar.searchBarBoundElementUuid = websiteReader.uuid("bound-element-navbar-search-bar");
|
|
1117
|
+
returnProperties.footer.isDisplayed = websiteReader.valueOr("footer-displayed", true);
|
|
1118
|
+
returnProperties.footer.logoUuid = websiteReader.uuid("footer-logo");
|
|
1119
|
+
const itemPageReader = websiteReader.nestedByValue("page-type", "item-page");
|
|
1120
|
+
if (itemPageReader.size > 0) {
|
|
1121
|
+
returnProperties.itemPage.isMainContentDisplayed = itemPageReader.valueOr("item-page-main-content-displayed", true);
|
|
1122
|
+
returnProperties.itemPage.isDescriptionDisplayed = itemPageReader.valueOr("item-page-description-displayed", true);
|
|
1123
|
+
returnProperties.itemPage.isDocumentDisplayed = itemPageReader.valueOr("item-page-document-displayed", true);
|
|
1124
|
+
returnProperties.itemPage.isNotesDisplayed = itemPageReader.valueOr("item-page-notes-displayed", true);
|
|
1125
|
+
returnProperties.itemPage.isEventsDisplayed = itemPageReader.valueOr("item-page-events-displayed", true);
|
|
1126
|
+
returnProperties.itemPage.isPeriodsDisplayed = itemPageReader.valueOr("item-page-periods-displayed", true);
|
|
1127
|
+
returnProperties.itemPage.isPropertiesDisplayed = itemPageReader.valueOr("item-page-properties-displayed", true);
|
|
1128
|
+
returnProperties.itemPage.isBibliographyDisplayed = itemPageReader.valueOr("item-page-bibliography-displayed", true);
|
|
1129
|
+
returnProperties.itemPage.isPropertyValuesGrouped = itemPageReader.valueOr("item-page-property-values-grouped", true);
|
|
1130
|
+
returnProperties.itemPage.isPublicationDateTimeDisplayed = itemPageReader.valueOr("item-page-publication-date-time-displayed", true);
|
|
1131
|
+
returnProperties.itemPage.isPersistentIdentifierDisplayed = itemPageReader.valueOr("item-page-persistent-identifier-displayed", true);
|
|
1132
|
+
returnProperties.itemPage.iiifViewer = itemPageReader.valueOr("item-page-iiif-viewer", "universal-viewer");
|
|
1133
|
+
}
|
|
1134
|
+
if (websiteTree.options != null) {
|
|
1135
|
+
const parsedOptions = parseWebsiteOptions(websiteTree.options, options);
|
|
1136
|
+
returnProperties.options.scopes = parsedOptions.scopes;
|
|
1137
|
+
returnProperties.options.contextTree = parsedOptions.contextTree;
|
|
1138
|
+
returnProperties.options.labels = parsedOptions.labels;
|
|
1139
|
+
}
|
|
1140
|
+
if ("styleOptions" in websiteTree && websiteTree.styleOptions != null) returnProperties.options.stylesheets.properties = parseStylesheets(websiteTree.styleOptions.style);
|
|
1141
|
+
return returnProperties;
|
|
1142
|
+
}
|
|
1143
|
+
function parseContextItem(contextItemToParse, options) {
|
|
1144
|
+
let type = "";
|
|
1145
|
+
const levels = [];
|
|
1146
|
+
for (const level of contextItemToParse.levels?.level ?? []) {
|
|
1147
|
+
const [rawVariableUuid = "", rawValueUuid] = level.payload.split(",");
|
|
1148
|
+
const valueUuid = rawValueUuid == null || rawValueUuid.trim() === "null" ? null : rawValueUuid.trim();
|
|
1149
|
+
type = level.dataType ?? type;
|
|
1150
|
+
levels.push({
|
|
1151
|
+
variableUuid: rawVariableUuid.trim(),
|
|
1152
|
+
valueUuid
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
return {
|
|
1156
|
+
context: levels,
|
|
1157
|
+
type,
|
|
1158
|
+
identification: parseIdentification(contextItemToParse.identification, options)
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function parseFilterContextDisplay(filterOption) {
|
|
1162
|
+
switch (filterOption) {
|
|
1163
|
+
case "inline-displayed": return {
|
|
1164
|
+
isInlineDisplayed: true,
|
|
1165
|
+
isSidebarDisplayed: false,
|
|
1166
|
+
isSidebarOpen: false
|
|
1167
|
+
};
|
|
1168
|
+
case "inline-sidebar-displayed-closed": return {
|
|
1169
|
+
isInlineDisplayed: true,
|
|
1170
|
+
isSidebarDisplayed: true,
|
|
1171
|
+
isSidebarOpen: false
|
|
1172
|
+
};
|
|
1173
|
+
case "inline-sidebar-displayed-open": return {
|
|
1174
|
+
isInlineDisplayed: true,
|
|
1175
|
+
isSidebarDisplayed: true,
|
|
1176
|
+
isSidebarOpen: true
|
|
1177
|
+
};
|
|
1178
|
+
case "sidebar-displayed-closed": return {
|
|
1179
|
+
isInlineDisplayed: false,
|
|
1180
|
+
isSidebarDisplayed: true,
|
|
1181
|
+
isSidebarOpen: false
|
|
1182
|
+
};
|
|
1183
|
+
case "sidebar-displayed-open": return {
|
|
1184
|
+
isInlineDisplayed: false,
|
|
1185
|
+
isSidebarDisplayed: true,
|
|
1186
|
+
isSidebarOpen: true
|
|
1187
|
+
};
|
|
1188
|
+
default: return {
|
|
1189
|
+
isInlineDisplayed: false,
|
|
1190
|
+
isSidebarDisplayed: false,
|
|
1191
|
+
isSidebarOpen: false
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
function parseContexts(contextLevels, options) {
|
|
1196
|
+
const contextTreeLevels = [];
|
|
1197
|
+
for (const contextLevel of contextLevels) for (const contextItemToParse of contextLevel.context) contextTreeLevels.push(parseContextItem(contextItemToParse, options));
|
|
1198
|
+
return contextTreeLevels;
|
|
1199
|
+
}
|
|
1200
|
+
function parseFilterContexts(filterContextLevels, options) {
|
|
1201
|
+
const filterContextTreeLevels = [];
|
|
1202
|
+
for (const filterContextLevel of filterContextLevels) for (const contextItemToParse of filterContextLevel.context) filterContextTreeLevels.push({
|
|
1203
|
+
...parseContextItem(contextItemToParse, options),
|
|
1204
|
+
filterType: contextItemToParse.filterType ?? "property",
|
|
1205
|
+
...parseFilterContextDisplay(contextItemToParse.filterOption)
|
|
1206
|
+
});
|
|
1207
|
+
return filterContextTreeLevels;
|
|
1208
|
+
}
|
|
1209
|
+
function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
|
|
1210
|
+
if (!websiteTree.properties) throw new Error(`Website properties not found (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
|
|
1211
|
+
if (type === "website" && websiteTree.items?.resource == null) throw new Error(`Website pages not found (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
|
|
1212
|
+
const resources = normalizeWebsiteResources(websiteTree.items?.resource);
|
|
1213
|
+
const sidebar = parseSidebar(resources, options);
|
|
1214
|
+
const properties = parseWebsiteProperties(websiteTree.properties.property, websiteTree, sidebar, options);
|
|
1215
|
+
return {
|
|
1216
|
+
uuid: websiteTree.uuid,
|
|
1217
|
+
type,
|
|
1218
|
+
belongsTo: context.belongsTo,
|
|
1219
|
+
metadata: context.metadata,
|
|
1220
|
+
publicationDateTime: websiteTree.publicationDateTime ?? null,
|
|
1221
|
+
identification: parseIdentification(websiteTree.identification, options),
|
|
1222
|
+
creators: websiteTree.creators ? parsePersonList(websiteTree.creators.creator, options) : [],
|
|
1223
|
+
license: parseLicense(websiteTree.availability),
|
|
1224
|
+
items: parseWebpages(resources, options, context, slugPrefix),
|
|
1225
|
+
properties
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
function parseWebsite(data, options) {
|
|
1229
|
+
const rawOchre = data.result.ochre;
|
|
1230
|
+
const metadataLanguages = parseMetadataLanguages(rawOchre);
|
|
1231
|
+
const languages = resolveLanguages(options?.languages ?? [], metadataLanguages);
|
|
1232
|
+
const parserOptions = { languages };
|
|
1233
|
+
const defaultLanguage = resolveDefaultLanguage(rawOchre, languages);
|
|
1234
|
+
const websiteTree = rawOchre.tree[0];
|
|
1235
|
+
if (websiteTree == null) throw new Error("Website tree not found", { cause: data });
|
|
1236
|
+
return parseWebsiteTree(websiteTree, {
|
|
1237
|
+
belongsTo: {
|
|
1238
|
+
uuid: rawOchre.uuidBelongsTo,
|
|
1239
|
+
abbreviation: rawOchre.belongsTo
|
|
1240
|
+
},
|
|
1241
|
+
metadata: parseMetadata(rawOchre, parserOptions, defaultLanguage)
|
|
1242
|
+
}, "website", parserOptions);
|
|
1243
|
+
}
|
|
1244
|
+
//#endregion
|
|
1245
|
+
export { parseBounds, parseWebpageView, parseWebsite };
|